@dreb/coding-agent 1.18.0 → 2.0.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 (76) hide show
  1. package/dist/core/tools/search.d.ts.map +1 -1
  2. package/dist/core/tools/search.js +14 -36
  3. package/dist/core/tools/search.js.map +1 -1
  4. package/package.json +2 -1
  5. package/dist/core/search/chunker.d.ts +0 -21
  6. package/dist/core/search/chunker.d.ts.map +0 -1
  7. package/dist/core/search/chunker.js +0 -51
  8. package/dist/core/search/chunker.js.map +0 -1
  9. package/dist/core/search/db.d.ts +0 -89
  10. package/dist/core/search/db.d.ts.map +0 -1
  11. package/dist/core/search/db.js +0 -406
  12. package/dist/core/search/db.js.map +0 -1
  13. package/dist/core/search/embedder.d.ts +0 -52
  14. package/dist/core/search/embedder.d.ts.map +0 -1
  15. package/dist/core/search/embedder.js +0 -158
  16. package/dist/core/search/embedder.js.map +0 -1
  17. package/dist/core/search/index-manager.d.ts +0 -55
  18. package/dist/core/search/index-manager.d.ts.map +0 -1
  19. package/dist/core/search/index-manager.js +0 -311
  20. package/dist/core/search/index-manager.js.map +0 -1
  21. package/dist/core/search/metrics/bm25.d.ts +0 -10
  22. package/dist/core/search/metrics/bm25.d.ts.map +0 -1
  23. package/dist/core/search/metrics/bm25.js +0 -32
  24. package/dist/core/search/metrics/bm25.js.map +0 -1
  25. package/dist/core/search/metrics/git-recency.d.ts +0 -14
  26. package/dist/core/search/metrics/git-recency.d.ts.map +0 -1
  27. package/dist/core/search/metrics/git-recency.js +0 -123
  28. package/dist/core/search/metrics/git-recency.js.map +0 -1
  29. package/dist/core/search/metrics/import-graph.d.ts +0 -15
  30. package/dist/core/search/metrics/import-graph.d.ts.map +0 -1
  31. package/dist/core/search/metrics/import-graph.js +0 -115
  32. package/dist/core/search/metrics/import-graph.js.map +0 -1
  33. package/dist/core/search/metrics/path-match.d.ts +0 -13
  34. package/dist/core/search/metrics/path-match.d.ts.map +0 -1
  35. package/dist/core/search/metrics/path-match.js +0 -54
  36. package/dist/core/search/metrics/path-match.js.map +0 -1
  37. package/dist/core/search/metrics/symbol-match.d.ts +0 -12
  38. package/dist/core/search/metrics/symbol-match.d.ts.map +0 -1
  39. package/dist/core/search/metrics/symbol-match.js +0 -62
  40. package/dist/core/search/metrics/symbol-match.js.map +0 -1
  41. package/dist/core/search/metrics/tokenize.d.ts +0 -12
  42. package/dist/core/search/metrics/tokenize.d.ts.map +0 -1
  43. package/dist/core/search/metrics/tokenize.js +0 -29
  44. package/dist/core/search/metrics/tokenize.js.map +0 -1
  45. package/dist/core/search/poem.d.ts +0 -38
  46. package/dist/core/search/poem.d.ts.map +0 -1
  47. package/dist/core/search/poem.js +0 -214
  48. package/dist/core/search/poem.js.map +0 -1
  49. package/dist/core/search/query-classifier.d.ts +0 -17
  50. package/dist/core/search/query-classifier.d.ts.map +0 -1
  51. package/dist/core/search/query-classifier.js +0 -54
  52. package/dist/core/search/query-classifier.js.map +0 -1
  53. package/dist/core/search/scanner.d.ts +0 -30
  54. package/dist/core/search/scanner.d.ts.map +0 -1
  55. package/dist/core/search/scanner.js +0 -344
  56. package/dist/core/search/scanner.js.map +0 -1
  57. package/dist/core/search/search.d.ts +0 -51
  58. package/dist/core/search/search.d.ts.map +0 -1
  59. package/dist/core/search/search.js +0 -381
  60. package/dist/core/search/search.js.map +0 -1
  61. package/dist/core/search/text-chunker.d.ts +0 -15
  62. package/dist/core/search/text-chunker.d.ts.map +0 -1
  63. package/dist/core/search/text-chunker.js +0 -580
  64. package/dist/core/search/text-chunker.js.map +0 -1
  65. package/dist/core/search/tree-sitter-chunker.d.ts +0 -25
  66. package/dist/core/search/tree-sitter-chunker.d.ts.map +0 -1
  67. package/dist/core/search/tree-sitter-chunker.js +0 -357
  68. package/dist/core/search/tree-sitter-chunker.js.map +0 -1
  69. package/dist/core/search/types.d.ts +0 -96
  70. package/dist/core/search/types.d.ts.map +0 -1
  71. package/dist/core/search/types.js +0 -6
  72. package/dist/core/search/types.js.map +0 -1
  73. package/dist/core/search/vector-store.d.ts +0 -43
  74. package/dist/core/search/vector-store.d.ts.map +0 -1
  75. package/dist/core/search/vector-store.js +0 -73
  76. package/dist/core/search/vector-store.js.map +0 -1
@@ -1,32 +0,0 @@
1
- /**
2
- * BM25 metric — full-text search scoring via FTS5.
3
- */
4
- /**
5
- * Compute BM25 scores for a query using FTS5.
6
- * Returns a Map of chunkId → normalized score (0-1, higher = more relevant).
7
- */
8
- export function computeBm25Scores(db, query, limit) {
9
- const scores = new Map();
10
- try {
11
- const results = db.ftsSearch(query, limit);
12
- if (results.length === 0)
13
- return scores;
14
- // Find the maximum score for normalization
15
- let maxScore = 0;
16
- for (const r of results) {
17
- if (r.score > maxScore)
18
- maxScore = r.score;
19
- }
20
- // Normalize: top result → 1.0, others proportional
21
- if (maxScore > 0) {
22
- for (const r of results) {
23
- scores.set(r.chunkId, r.score / maxScore);
24
- }
25
- }
26
- }
27
- catch {
28
- // If FTS query fails, return empty map
29
- }
30
- return scores;
31
- }
32
- //# sourceMappingURL=bm25.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bm25.js","sourceRoot":"","sources":["../../../../src/core/search/metrics/bm25.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,EAAkB,EAAE,KAAa,EAAE,KAAa,EAAuB;IACxG,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEzC,IAAI,CAAC;QACJ,MAAM,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC3C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,MAAM,CAAC;QAExC,2CAA2C;QAC3C,IAAI,QAAQ,GAAG,CAAC,CAAC;QACjB,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACzB,IAAI,CAAC,CAAC,KAAK,GAAG,QAAQ;gBAAE,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC;QAC5C,CAAC;QAED,qDAAmD;QACnD,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;YAClB,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;gBACzB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,GAAG,QAAQ,CAAC,CAAC;YAC3C,CAAC;QACF,CAAC;IACF,CAAC;IAAC,MAAM,CAAC;QACR,uCAAuC;IACxC,CAAC;IAED,OAAO,MAAM,CAAC;AAAA,CACd","sourcesContent":["/**\n * BM25 metric — full-text search scoring via FTS5.\n */\n\nimport type { SearchDatabase } from \"../db.js\";\n\n/**\n * Compute BM25 scores for a query using FTS5.\n * Returns a Map of chunkId → normalized score (0-1, higher = more relevant).\n */\nexport function computeBm25Scores(db: SearchDatabase, query: string, limit: number): Map<number, number> {\n\tconst scores = new Map<number, number>();\n\n\ttry {\n\t\tconst results = db.ftsSearch(query, limit);\n\t\tif (results.length === 0) return scores;\n\n\t\t// Find the maximum score for normalization\n\t\tlet maxScore = 0;\n\t\tfor (const r of results) {\n\t\t\tif (r.score > maxScore) maxScore = r.score;\n\t\t}\n\n\t\t// Normalize: top result → 1.0, others proportional\n\t\tif (maxScore > 0) {\n\t\t\tfor (const r of results) {\n\t\t\t\tscores.set(r.chunkId, r.score / maxScore);\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// If FTS query fails, return empty map\n\t}\n\n\treturn scores;\n}\n"]}
@@ -1,14 +0,0 @@
1
- /**
2
- * Git recency metric — recently modified files score higher.
3
- *
4
- * Runs a single `git log` command to get last-modified timestamps for all
5
- * files, then applies linear decay scoring.
6
- */
7
- import type { StoredChunk } from "../types.js";
8
- /**
9
- * Compute git recency scores based on when files were last modified.
10
- * More recently modified files score higher.
11
- * Falls back gracefully if git is unavailable.
12
- */
13
- export declare function computeGitRecencyScores(projectRoot: string, chunks: StoredChunk[]): Promise<Map<number, number>>;
14
- //# sourceMappingURL=git-recency.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"git-recency.d.ts","sourceRoot":"","sources":["../../../../src/core/search/metrics/git-recency.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAa/C;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC5C,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,WAAW,EAAE,GACnB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAyD9B","sourcesContent":["/**\n * Git recency metric — recently modified files score higher.\n *\n * Runs a single `git log` command to get last-modified timestamps for all\n * files, then applies linear decay scoring.\n */\n\nimport { execFile as execFileCb } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport type { StoredChunk } from \"../types.js\";\n\nconst execFile = promisify(execFileCb);\n\n/** Timeout for the git command in milliseconds. */\nconst GIT_TIMEOUT_MS = 15000;\n\n/** Max buffer for git output (10 MB — sufficient for large repos). */\nconst GIT_MAX_BUFFER = 10 * 1024 * 1024;\n\n/** Default score for files where git info is unavailable. */\nconst NEUTRAL_SCORE = 0.5;\n\n/**\n * Compute git recency scores based on when files were last modified.\n * More recently modified files score higher.\n * Falls back gracefully if git is unavailable.\n */\nexport async function computeGitRecencyScores(\n\tprojectRoot: string,\n\tchunks: StoredChunk[],\n): Promise<Map<number, number>> {\n\tconst scores = new Map<number, number>();\n\n\ttry {\n\t\tif (chunks.length === 0) return scores;\n\n\t\t// Collect unique file paths\n\t\tconst uniquePaths = new Set<string>();\n\t\tfor (const chunk of chunks) {\n\t\t\tuniquePaths.add(chunk.filePath);\n\t\t}\n\n\t\t// Get last-modified timestamps in a single git call.\n\t\t// Output format: \"COMMIT <timestamp>\" lines followed by changed file names.\n\t\t// We take the first (most recent) timestamp seen for each file.\n\t\tconst fileTimestamps = await getFileTimestamps(projectRoot, uniquePaths);\n\n\t\t// If no timestamps found, assign neutral scores\n\t\tif (fileTimestamps.size === 0) {\n\t\t\tfor (const chunk of chunks) {\n\t\t\t\tscores.set(chunk.id, NEUTRAL_SCORE);\n\t\t\t}\n\t\t\treturn scores;\n\t\t}\n\n\t\t// Find oldest and newest timestamps\n\t\tlet oldest = Infinity;\n\t\tlet newest = -Infinity;\n\t\tfor (const ts of fileTimestamps.values()) {\n\t\t\tif (ts < oldest) oldest = ts;\n\t\t\tif (ts > newest) newest = ts;\n\t\t}\n\n\t\tconst range = newest - oldest;\n\n\t\t// Assign scores to chunks\n\t\tfor (const chunk of chunks) {\n\t\t\tconst ts = fileTimestamps.get(chunk.filePath);\n\t\t\tif (ts === undefined) {\n\t\t\t\t// Not tracked by git → neutral score\n\t\t\t\tscores.set(chunk.id, NEUTRAL_SCORE);\n\t\t\t} else if (range === 0) {\n\t\t\t\t// All files have the same timestamp\n\t\t\t\tscores.set(chunk.id, 1);\n\t\t\t} else {\n\t\t\t\t// Linear decay: newest → 1.0, oldest → 0.0\n\t\t\t\tscores.set(chunk.id, (ts - oldest) / range);\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Git unavailable entirely — assign neutral scores\n\t\tfor (const chunk of chunks) {\n\t\t\tscores.set(chunk.id, NEUTRAL_SCORE);\n\t\t}\n\t}\n\n\treturn scores;\n}\n\n/**\n * Get last-modified timestamps for files using a single `git log` invocation.\n * Returns a Map of filePath → unix timestamp (seconds).\n */\nasync function getFileTimestamps(projectRoot: string, targetPaths: Set<string>): Promise<Map<string, number>> {\n\tconst fileTimestamps = new Map<string, number>();\n\n\ttry {\n\t\t// Single git call: list all commits with their timestamps and changed files.\n\t\t// --diff-filter=AMCR: only additions, modifications, copies, renames.\n\t\t// --name-only: list file names after each commit.\n\t\t// --format=\"COMMIT %at\": prefix each commit with its unix timestamp.\n\t\tconst { stdout: output } = await execFile(\n\t\t\t\"git\",\n\t\t\t[\"log\", \"--max-count=10000\", \"--format=COMMIT %at\", \"--name-only\", \"--diff-filter=AMCR\"],\n\t\t\t{\n\t\t\t\tcwd: projectRoot,\n\t\t\t\ttimeout: GIT_TIMEOUT_MS,\n\t\t\t\tencoding: \"utf-8\",\n\t\t\t\tmaxBuffer: GIT_MAX_BUFFER,\n\t\t\t},\n\t\t);\n\n\t\tlet currentTimestamp = 0;\n\t\tlet foundAll = false;\n\n\t\tfor (const line of output.split(\"\\n\")) {\n\t\t\tif (foundAll) break;\n\n\t\t\tif (line.startsWith(\"COMMIT \")) {\n\t\t\t\tcurrentTimestamp = Number.parseInt(line.slice(7), 10);\n\t\t\t\tif (Number.isNaN(currentTimestamp) || currentTimestamp <= 0) {\n\t\t\t\t\tcurrentTimestamp = 0;\n\t\t\t\t}\n\t\t\t} else if (line.trim() && currentTimestamp > 0) {\n\t\t\t\tconst filePath = line.trim();\n\t\t\t\t// Only record the first (most recent) timestamp per file\n\t\t\t\tif (targetPaths.has(filePath) && !fileTimestamps.has(filePath)) {\n\t\t\t\t\tfileTimestamps.set(filePath, currentTimestamp);\n\t\t\t\t\t// Early exit once we've found all target files\n\t\t\t\t\tif (fileTimestamps.size === targetPaths.size) {\n\t\t\t\t\t\tfoundAll = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Git unavailable or failed — return empty map\n\t}\n\n\treturn fileTimestamps;\n}\n"]}
@@ -1,123 +0,0 @@
1
- /**
2
- * Git recency metric — recently modified files score higher.
3
- *
4
- * Runs a single `git log` command to get last-modified timestamps for all
5
- * files, then applies linear decay scoring.
6
- */
7
- import { execFile as execFileCb } from "node:child_process";
8
- import { promisify } from "node:util";
9
- const execFile = promisify(execFileCb);
10
- /** Timeout for the git command in milliseconds. */
11
- const GIT_TIMEOUT_MS = 15000;
12
- /** Max buffer for git output (10 MB — sufficient for large repos). */
13
- const GIT_MAX_BUFFER = 10 * 1024 * 1024;
14
- /** Default score for files where git info is unavailable. */
15
- const NEUTRAL_SCORE = 0.5;
16
- /**
17
- * Compute git recency scores based on when files were last modified.
18
- * More recently modified files score higher.
19
- * Falls back gracefully if git is unavailable.
20
- */
21
- export async function computeGitRecencyScores(projectRoot, chunks) {
22
- const scores = new Map();
23
- try {
24
- if (chunks.length === 0)
25
- return scores;
26
- // Collect unique file paths
27
- const uniquePaths = new Set();
28
- for (const chunk of chunks) {
29
- uniquePaths.add(chunk.filePath);
30
- }
31
- // Get last-modified timestamps in a single git call.
32
- // Output format: "COMMIT <timestamp>" lines followed by changed file names.
33
- // We take the first (most recent) timestamp seen for each file.
34
- const fileTimestamps = await getFileTimestamps(projectRoot, uniquePaths);
35
- // If no timestamps found, assign neutral scores
36
- if (fileTimestamps.size === 0) {
37
- for (const chunk of chunks) {
38
- scores.set(chunk.id, NEUTRAL_SCORE);
39
- }
40
- return scores;
41
- }
42
- // Find oldest and newest timestamps
43
- let oldest = Infinity;
44
- let newest = -Infinity;
45
- for (const ts of fileTimestamps.values()) {
46
- if (ts < oldest)
47
- oldest = ts;
48
- if (ts > newest)
49
- newest = ts;
50
- }
51
- const range = newest - oldest;
52
- // Assign scores to chunks
53
- for (const chunk of chunks) {
54
- const ts = fileTimestamps.get(chunk.filePath);
55
- if (ts === undefined) {
56
- // Not tracked by git → neutral score
57
- scores.set(chunk.id, NEUTRAL_SCORE);
58
- }
59
- else if (range === 0) {
60
- // All files have the same timestamp
61
- scores.set(chunk.id, 1);
62
- }
63
- else {
64
- // Linear decay: newest → 1.0, oldest → 0.0
65
- scores.set(chunk.id, (ts - oldest) / range);
66
- }
67
- }
68
- }
69
- catch {
70
- // Git unavailable entirely — assign neutral scores
71
- for (const chunk of chunks) {
72
- scores.set(chunk.id, NEUTRAL_SCORE);
73
- }
74
- }
75
- return scores;
76
- }
77
- /**
78
- * Get last-modified timestamps for files using a single `git log` invocation.
79
- * Returns a Map of filePath → unix timestamp (seconds).
80
- */
81
- async function getFileTimestamps(projectRoot, targetPaths) {
82
- const fileTimestamps = new Map();
83
- try {
84
- // Single git call: list all commits with their timestamps and changed files.
85
- // --diff-filter=AMCR: only additions, modifications, copies, renames.
86
- // --name-only: list file names after each commit.
87
- // --format="COMMIT %at": prefix each commit with its unix timestamp.
88
- const { stdout: output } = await execFile("git", ["log", "--max-count=10000", "--format=COMMIT %at", "--name-only", "--diff-filter=AMCR"], {
89
- cwd: projectRoot,
90
- timeout: GIT_TIMEOUT_MS,
91
- encoding: "utf-8",
92
- maxBuffer: GIT_MAX_BUFFER,
93
- });
94
- let currentTimestamp = 0;
95
- let foundAll = false;
96
- for (const line of output.split("\n")) {
97
- if (foundAll)
98
- break;
99
- if (line.startsWith("COMMIT ")) {
100
- currentTimestamp = Number.parseInt(line.slice(7), 10);
101
- if (Number.isNaN(currentTimestamp) || currentTimestamp <= 0) {
102
- currentTimestamp = 0;
103
- }
104
- }
105
- else if (line.trim() && currentTimestamp > 0) {
106
- const filePath = line.trim();
107
- // Only record the first (most recent) timestamp per file
108
- if (targetPaths.has(filePath) && !fileTimestamps.has(filePath)) {
109
- fileTimestamps.set(filePath, currentTimestamp);
110
- // Early exit once we've found all target files
111
- if (fileTimestamps.size === targetPaths.size) {
112
- foundAll = true;
113
- }
114
- }
115
- }
116
- }
117
- }
118
- catch {
119
- // Git unavailable or failed — return empty map
120
- }
121
- return fileTimestamps;
122
- }
123
- //# sourceMappingURL=git-recency.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"git-recency.js","sourceRoot":"","sources":["../../../../src/core/search/metrics/git-recency.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Git recency metric — recently modified files score higher.\n *\n * Runs a single `git log` command to get last-modified timestamps for all\n * files, then applies linear decay scoring.\n */\n\nimport { execFile as execFileCb } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport type { StoredChunk } from \"../types.js\";\n\nconst execFile = promisify(execFileCb);\n\n/** Timeout for the git command in milliseconds. */\nconst GIT_TIMEOUT_MS = 15000;\n\n/** Max buffer for git output (10 MB — sufficient for large repos). */\nconst GIT_MAX_BUFFER = 10 * 1024 * 1024;\n\n/** Default score for files where git info is unavailable. */\nconst NEUTRAL_SCORE = 0.5;\n\n/**\n * Compute git recency scores based on when files were last modified.\n * More recently modified files score higher.\n * Falls back gracefully if git is unavailable.\n */\nexport async function computeGitRecencyScores(\n\tprojectRoot: string,\n\tchunks: StoredChunk[],\n): Promise<Map<number, number>> {\n\tconst scores = new Map<number, number>();\n\n\ttry {\n\t\tif (chunks.length === 0) return scores;\n\n\t\t// Collect unique file paths\n\t\tconst uniquePaths = new Set<string>();\n\t\tfor (const chunk of chunks) {\n\t\t\tuniquePaths.add(chunk.filePath);\n\t\t}\n\n\t\t// Get last-modified timestamps in a single git call.\n\t\t// Output format: \"COMMIT <timestamp>\" lines followed by changed file names.\n\t\t// We take the first (most recent) timestamp seen for each file.\n\t\tconst fileTimestamps = await getFileTimestamps(projectRoot, uniquePaths);\n\n\t\t// If no timestamps found, assign neutral scores\n\t\tif (fileTimestamps.size === 0) {\n\t\t\tfor (const chunk of chunks) {\n\t\t\t\tscores.set(chunk.id, NEUTRAL_SCORE);\n\t\t\t}\n\t\t\treturn scores;\n\t\t}\n\n\t\t// Find oldest and newest timestamps\n\t\tlet oldest = Infinity;\n\t\tlet newest = -Infinity;\n\t\tfor (const ts of fileTimestamps.values()) {\n\t\t\tif (ts < oldest) oldest = ts;\n\t\t\tif (ts > newest) newest = ts;\n\t\t}\n\n\t\tconst range = newest - oldest;\n\n\t\t// Assign scores to chunks\n\t\tfor (const chunk of chunks) {\n\t\t\tconst ts = fileTimestamps.get(chunk.filePath);\n\t\t\tif (ts === undefined) {\n\t\t\t\t// Not tracked by git → neutral score\n\t\t\t\tscores.set(chunk.id, NEUTRAL_SCORE);\n\t\t\t} else if (range === 0) {\n\t\t\t\t// All files have the same timestamp\n\t\t\t\tscores.set(chunk.id, 1);\n\t\t\t} else {\n\t\t\t\t// Linear decay: newest → 1.0, oldest → 0.0\n\t\t\t\tscores.set(chunk.id, (ts - oldest) / range);\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Git unavailable entirely — assign neutral scores\n\t\tfor (const chunk of chunks) {\n\t\t\tscores.set(chunk.id, NEUTRAL_SCORE);\n\t\t}\n\t}\n\n\treturn scores;\n}\n\n/**\n * Get last-modified timestamps for files using a single `git log` invocation.\n * Returns a Map of filePath → unix timestamp (seconds).\n */\nasync function getFileTimestamps(projectRoot: string, targetPaths: Set<string>): Promise<Map<string, number>> {\n\tconst fileTimestamps = new Map<string, number>();\n\n\ttry {\n\t\t// Single git call: list all commits with their timestamps and changed files.\n\t\t// --diff-filter=AMCR: only additions, modifications, copies, renames.\n\t\t// --name-only: list file names after each commit.\n\t\t// --format=\"COMMIT %at\": prefix each commit with its unix timestamp.\n\t\tconst { stdout: output } = await execFile(\n\t\t\t\"git\",\n\t\t\t[\"log\", \"--max-count=10000\", \"--format=COMMIT %at\", \"--name-only\", \"--diff-filter=AMCR\"],\n\t\t\t{\n\t\t\t\tcwd: projectRoot,\n\t\t\t\ttimeout: GIT_TIMEOUT_MS,\n\t\t\t\tencoding: \"utf-8\",\n\t\t\t\tmaxBuffer: GIT_MAX_BUFFER,\n\t\t\t},\n\t\t);\n\n\t\tlet currentTimestamp = 0;\n\t\tlet foundAll = false;\n\n\t\tfor (const line of output.split(\"\\n\")) {\n\t\t\tif (foundAll) break;\n\n\t\t\tif (line.startsWith(\"COMMIT \")) {\n\t\t\t\tcurrentTimestamp = Number.parseInt(line.slice(7), 10);\n\t\t\t\tif (Number.isNaN(currentTimestamp) || currentTimestamp <= 0) {\n\t\t\t\t\tcurrentTimestamp = 0;\n\t\t\t\t}\n\t\t\t} else if (line.trim() && currentTimestamp > 0) {\n\t\t\t\tconst filePath = line.trim();\n\t\t\t\t// Only record the first (most recent) timestamp per file\n\t\t\t\tif (targetPaths.has(filePath) && !fileTimestamps.has(filePath)) {\n\t\t\t\t\tfileTimestamps.set(filePath, currentTimestamp);\n\t\t\t\t\t// Early exit once we've found all target files\n\t\t\t\t\tif (fileTimestamps.size === targetPaths.size) {\n\t\t\t\t\t\tfoundAll = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Git unavailable or failed — return empty map\n\t}\n\n\treturn fileTimestamps;\n}\n"]}
@@ -1,15 +0,0 @@
1
- /**
2
- * Import graph proximity metric.
3
- *
4
- * Files that import or are imported by high-scoring files get a boost.
5
- * Uses a simple 1-hop propagation from seed scores.
6
- */
7
- import type { SearchDatabase } from "../db.js";
8
- /**
9
- * Compute import graph proximity scores.
10
- * Files that import/are imported by high-scoring files get a boost.
11
- * Seed files also get a connectivity bonus based on how many of their
12
- * neighbors are in the seed set.
13
- */
14
- export declare function computeImportGraphScores(db: SearchDatabase, seedScores: Map<number, number>, fileIdToChunkIds: Map<number, number[]>): Map<number, number>;
15
- //# sourceMappingURL=import-graph.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"import-graph.d.ts","sourceRoot":"","sources":["../../../../src/core/search/metrics/import-graph.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAQ/C;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACvC,EAAE,EAAE,cAAc,EAClB,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GACrC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAqGrB","sourcesContent":["/**\n * Import graph proximity metric.\n *\n * Files that import or are imported by high-scoring files get a boost.\n * Uses a simple 1-hop propagation from seed scores.\n */\n\nimport type { SearchDatabase } from \"../db.js\";\n\n/** Fraction of seed score propagated to neighbors. */\nconst PROPAGATION_FACTOR = 0.5;\n\n/** Fraction of propagated score given back to seed files with many connections. */\nconst SELF_BOOST_FACTOR = 0.25;\n\n/**\n * Compute import graph proximity scores.\n * Files that import/are imported by high-scoring files get a boost.\n * Seed files also get a connectivity bonus based on how many of their\n * neighbors are in the seed set.\n */\nexport function computeImportGraphScores(\n\tdb: SearchDatabase,\n\tseedScores: Map<number, number>,\n\tfileIdToChunkIds: Map<number, number[]>,\n): Map<number, number> {\n\tconst scores = new Map<number, number>();\n\n\ttry {\n\t\tif (seedScores.size === 0) return scores;\n\n\t\t// Build fileId → filePath lookup and extension-stripped path index\n\t\tconst allFiles = db.getAllFiles();\n\t\tconst fileIdToPath = new Map<number, string>();\n\t\tconst pathToFileId = new Map<string, number>();\n\t\tconst strippedToFileId = new Map<string, number>();\n\n\t\tfor (const f of allFiles) {\n\t\t\tfileIdToPath.set(f.id, f.filePath);\n\t\t\tpathToFileId.set(f.filePath, f.id);\n\t\t\t// Also index without extension so import paths (which strip .js/.ts)\n\t\t\t// can match stored file paths (which keep the extension)\n\t\t\tconst stripped = stripExtension(f.filePath);\n\t\t\tif (!strippedToFileId.has(stripped)) {\n\t\t\t\tstrippedToFileId.set(stripped, f.id);\n\t\t\t}\n\t\t}\n\n\t\t/** Resolve an import target path to a fileId. Tries exact match first, then extension-stripped. */\n\t\tfunction resolveTarget(targetPath: string): number | undefined {\n\t\t\treturn pathToFileId.get(targetPath) ?? strippedToFileId.get(targetPath);\n\t\t}\n\n\t\t// Propagate scores to neighbors\n\t\tconst propagated = new Map<number, number>(); // fileId → accumulated propagated score\n\n\t\tfor (const [fileId, seedScore] of seedScores) {\n\t\t\tconst propagatedScore = seedScore * PROPAGATION_FACTOR;\n\t\t\tif (propagatedScore <= 0) continue;\n\n\t\t\tlet connectedSeedCount = 0;\n\n\t\t\t// Files this file imports\n\t\t\tconst importedPaths = db.getImportsFrom(fileId);\n\t\t\tfor (const targetPath of importedPaths) {\n\t\t\t\tconst targetFileId = resolveTarget(targetPath);\n\t\t\t\tif (targetFileId === undefined) continue;\n\n\t\t\t\tif (seedScores.has(targetFileId)) {\n\t\t\t\t\tconnectedSeedCount++;\n\t\t\t\t} else {\n\t\t\t\t\tpropagated.set(targetFileId, (propagated.get(targetFileId) ?? 0) + propagatedScore);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Files that import this file\n\t\t\tconst filePath = fileIdToPath.get(fileId);\n\t\t\tif (filePath) {\n\t\t\t\tconst importerIds = db.getImportersOf(filePath);\n\t\t\t\t// Also check extension-stripped variant\n\t\t\t\tconst stripped = stripExtension(filePath);\n\t\t\t\tconst strippedImporterIds = stripped !== filePath ? db.getImportersOf(stripped) : [];\n\t\t\t\tconst allImporterIds = new Set([...importerIds, ...strippedImporterIds]);\n\n\t\t\t\tfor (const importerFileId of allImporterIds) {\n\t\t\t\t\tif (seedScores.has(importerFileId)) {\n\t\t\t\t\t\tconnectedSeedCount++;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tpropagated.set(importerFileId, (propagated.get(importerFileId) ?? 0) + propagatedScore);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Self-boost: seed files with many connections to other seeds get a bonus\n\t\t\tif (connectedSeedCount > 0) {\n\t\t\t\tconst selfBoost = seedScore * SELF_BOOST_FACTOR * Math.min(connectedSeedCount / 3, 1);\n\t\t\t\tpropagated.set(fileId, (propagated.get(fileId) ?? 0) + selfBoost);\n\t\t\t}\n\t\t}\n\n\t\tif (propagated.size === 0) return scores;\n\n\t\t// Find max for normalization\n\t\tlet maxScore = 0;\n\t\tfor (const score of propagated.values()) {\n\t\t\tif (score > maxScore) maxScore = score;\n\t\t}\n\n\t\tif (maxScore <= 0) return scores;\n\n\t\t// Distribute to chunks and normalize\n\t\tfor (const [fileId, fileScore] of propagated) {\n\t\t\tconst chunkIds = fileIdToChunkIds.get(fileId);\n\t\t\tif (!chunkIds || chunkIds.length === 0) continue;\n\n\t\t\t// Distribute equally among chunks in this file\n\t\t\tconst perChunkScore = fileScore / maxScore / chunkIds.length;\n\t\t\tfor (const chunkId of chunkIds) {\n\t\t\t\tscores.set(chunkId, perChunkScore);\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// If anything fails, return empty map\n\t}\n\n\treturn scores;\n}\n\n/** Strip common source file extensions for path matching. */\nfunction stripExtension(filePath: string): string {\n\treturn filePath.replace(/\\.[jt]sx?$|\\.py$|\\.go$|\\.rs$|\\.java$|\\.c$|\\.h$|\\.cpp$|\\.hpp$|\\.cc$|\\.cxx$/, \"\");\n}\n"]}
@@ -1,115 +0,0 @@
1
- /**
2
- * Import graph proximity metric.
3
- *
4
- * Files that import or are imported by high-scoring files get a boost.
5
- * Uses a simple 1-hop propagation from seed scores.
6
- */
7
- /** Fraction of seed score propagated to neighbors. */
8
- const PROPAGATION_FACTOR = 0.5;
9
- /** Fraction of propagated score given back to seed files with many connections. */
10
- const SELF_BOOST_FACTOR = 0.25;
11
- /**
12
- * Compute import graph proximity scores.
13
- * Files that import/are imported by high-scoring files get a boost.
14
- * Seed files also get a connectivity bonus based on how many of their
15
- * neighbors are in the seed set.
16
- */
17
- export function computeImportGraphScores(db, seedScores, fileIdToChunkIds) {
18
- const scores = new Map();
19
- try {
20
- if (seedScores.size === 0)
21
- return scores;
22
- // Build fileId → filePath lookup and extension-stripped path index
23
- const allFiles = db.getAllFiles();
24
- const fileIdToPath = new Map();
25
- const pathToFileId = new Map();
26
- const strippedToFileId = new Map();
27
- for (const f of allFiles) {
28
- fileIdToPath.set(f.id, f.filePath);
29
- pathToFileId.set(f.filePath, f.id);
30
- // Also index without extension so import paths (which strip .js/.ts)
31
- // can match stored file paths (which keep the extension)
32
- const stripped = stripExtension(f.filePath);
33
- if (!strippedToFileId.has(stripped)) {
34
- strippedToFileId.set(stripped, f.id);
35
- }
36
- }
37
- /** Resolve an import target path to a fileId. Tries exact match first, then extension-stripped. */
38
- function resolveTarget(targetPath) {
39
- return pathToFileId.get(targetPath) ?? strippedToFileId.get(targetPath);
40
- }
41
- // Propagate scores to neighbors
42
- const propagated = new Map(); // fileId → accumulated propagated score
43
- for (const [fileId, seedScore] of seedScores) {
44
- const propagatedScore = seedScore * PROPAGATION_FACTOR;
45
- if (propagatedScore <= 0)
46
- continue;
47
- let connectedSeedCount = 0;
48
- // Files this file imports
49
- const importedPaths = db.getImportsFrom(fileId);
50
- for (const targetPath of importedPaths) {
51
- const targetFileId = resolveTarget(targetPath);
52
- if (targetFileId === undefined)
53
- continue;
54
- if (seedScores.has(targetFileId)) {
55
- connectedSeedCount++;
56
- }
57
- else {
58
- propagated.set(targetFileId, (propagated.get(targetFileId) ?? 0) + propagatedScore);
59
- }
60
- }
61
- // Files that import this file
62
- const filePath = fileIdToPath.get(fileId);
63
- if (filePath) {
64
- const importerIds = db.getImportersOf(filePath);
65
- // Also check extension-stripped variant
66
- const stripped = stripExtension(filePath);
67
- const strippedImporterIds = stripped !== filePath ? db.getImportersOf(stripped) : [];
68
- const allImporterIds = new Set([...importerIds, ...strippedImporterIds]);
69
- for (const importerFileId of allImporterIds) {
70
- if (seedScores.has(importerFileId)) {
71
- connectedSeedCount++;
72
- }
73
- else {
74
- propagated.set(importerFileId, (propagated.get(importerFileId) ?? 0) + propagatedScore);
75
- }
76
- }
77
- }
78
- // Self-boost: seed files with many connections to other seeds get a bonus
79
- if (connectedSeedCount > 0) {
80
- const selfBoost = seedScore * SELF_BOOST_FACTOR * Math.min(connectedSeedCount / 3, 1);
81
- propagated.set(fileId, (propagated.get(fileId) ?? 0) + selfBoost);
82
- }
83
- }
84
- if (propagated.size === 0)
85
- return scores;
86
- // Find max for normalization
87
- let maxScore = 0;
88
- for (const score of propagated.values()) {
89
- if (score > maxScore)
90
- maxScore = score;
91
- }
92
- if (maxScore <= 0)
93
- return scores;
94
- // Distribute to chunks and normalize
95
- for (const [fileId, fileScore] of propagated) {
96
- const chunkIds = fileIdToChunkIds.get(fileId);
97
- if (!chunkIds || chunkIds.length === 0)
98
- continue;
99
- // Distribute equally among chunks in this file
100
- const perChunkScore = fileScore / maxScore / chunkIds.length;
101
- for (const chunkId of chunkIds) {
102
- scores.set(chunkId, perChunkScore);
103
- }
104
- }
105
- }
106
- catch {
107
- // If anything fails, return empty map
108
- }
109
- return scores;
110
- }
111
- /** Strip common source file extensions for path matching. */
112
- function stripExtension(filePath) {
113
- return filePath.replace(/\.[jt]sx?$|\.py$|\.go$|\.rs$|\.java$|\.c$|\.h$|\.cpp$|\.hpp$|\.cc$|\.cxx$/, "");
114
- }
115
- //# sourceMappingURL=import-graph.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"import-graph.js","sourceRoot":"","sources":["../../../../src/core/search/metrics/import-graph.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Import graph proximity metric.\n *\n * Files that import or are imported by high-scoring files get a boost.\n * Uses a simple 1-hop propagation from seed scores.\n */\n\nimport type { SearchDatabase } from \"../db.js\";\n\n/** Fraction of seed score propagated to neighbors. */\nconst PROPAGATION_FACTOR = 0.5;\n\n/** Fraction of propagated score given back to seed files with many connections. */\nconst SELF_BOOST_FACTOR = 0.25;\n\n/**\n * Compute import graph proximity scores.\n * Files that import/are imported by high-scoring files get a boost.\n * Seed files also get a connectivity bonus based on how many of their\n * neighbors are in the seed set.\n */\nexport function computeImportGraphScores(\n\tdb: SearchDatabase,\n\tseedScores: Map<number, number>,\n\tfileIdToChunkIds: Map<number, number[]>,\n): Map<number, number> {\n\tconst scores = new Map<number, number>();\n\n\ttry {\n\t\tif (seedScores.size === 0) return scores;\n\n\t\t// Build fileId → filePath lookup and extension-stripped path index\n\t\tconst allFiles = db.getAllFiles();\n\t\tconst fileIdToPath = new Map<number, string>();\n\t\tconst pathToFileId = new Map<string, number>();\n\t\tconst strippedToFileId = new Map<string, number>();\n\n\t\tfor (const f of allFiles) {\n\t\t\tfileIdToPath.set(f.id, f.filePath);\n\t\t\tpathToFileId.set(f.filePath, f.id);\n\t\t\t// Also index without extension so import paths (which strip .js/.ts)\n\t\t\t// can match stored file paths (which keep the extension)\n\t\t\tconst stripped = stripExtension(f.filePath);\n\t\t\tif (!strippedToFileId.has(stripped)) {\n\t\t\t\tstrippedToFileId.set(stripped, f.id);\n\t\t\t}\n\t\t}\n\n\t\t/** Resolve an import target path to a fileId. Tries exact match first, then extension-stripped. */\n\t\tfunction resolveTarget(targetPath: string): number | undefined {\n\t\t\treturn pathToFileId.get(targetPath) ?? strippedToFileId.get(targetPath);\n\t\t}\n\n\t\t// Propagate scores to neighbors\n\t\tconst propagated = new Map<number, number>(); // fileId → accumulated propagated score\n\n\t\tfor (const [fileId, seedScore] of seedScores) {\n\t\t\tconst propagatedScore = seedScore * PROPAGATION_FACTOR;\n\t\t\tif (propagatedScore <= 0) continue;\n\n\t\t\tlet connectedSeedCount = 0;\n\n\t\t\t// Files this file imports\n\t\t\tconst importedPaths = db.getImportsFrom(fileId);\n\t\t\tfor (const targetPath of importedPaths) {\n\t\t\t\tconst targetFileId = resolveTarget(targetPath);\n\t\t\t\tif (targetFileId === undefined) continue;\n\n\t\t\t\tif (seedScores.has(targetFileId)) {\n\t\t\t\t\tconnectedSeedCount++;\n\t\t\t\t} else {\n\t\t\t\t\tpropagated.set(targetFileId, (propagated.get(targetFileId) ?? 0) + propagatedScore);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Files that import this file\n\t\t\tconst filePath = fileIdToPath.get(fileId);\n\t\t\tif (filePath) {\n\t\t\t\tconst importerIds = db.getImportersOf(filePath);\n\t\t\t\t// Also check extension-stripped variant\n\t\t\t\tconst stripped = stripExtension(filePath);\n\t\t\t\tconst strippedImporterIds = stripped !== filePath ? db.getImportersOf(stripped) : [];\n\t\t\t\tconst allImporterIds = new Set([...importerIds, ...strippedImporterIds]);\n\n\t\t\t\tfor (const importerFileId of allImporterIds) {\n\t\t\t\t\tif (seedScores.has(importerFileId)) {\n\t\t\t\t\t\tconnectedSeedCount++;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tpropagated.set(importerFileId, (propagated.get(importerFileId) ?? 0) + propagatedScore);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Self-boost: seed files with many connections to other seeds get a bonus\n\t\t\tif (connectedSeedCount > 0) {\n\t\t\t\tconst selfBoost = seedScore * SELF_BOOST_FACTOR * Math.min(connectedSeedCount / 3, 1);\n\t\t\t\tpropagated.set(fileId, (propagated.get(fileId) ?? 0) + selfBoost);\n\t\t\t}\n\t\t}\n\n\t\tif (propagated.size === 0) return scores;\n\n\t\t// Find max for normalization\n\t\tlet maxScore = 0;\n\t\tfor (const score of propagated.values()) {\n\t\t\tif (score > maxScore) maxScore = score;\n\t\t}\n\n\t\tif (maxScore <= 0) return scores;\n\n\t\t// Distribute to chunks and normalize\n\t\tfor (const [fileId, fileScore] of propagated) {\n\t\t\tconst chunkIds = fileIdToChunkIds.get(fileId);\n\t\t\tif (!chunkIds || chunkIds.length === 0) continue;\n\n\t\t\t// Distribute equally among chunks in this file\n\t\t\tconst perChunkScore = fileScore / maxScore / chunkIds.length;\n\t\t\tfor (const chunkId of chunkIds) {\n\t\t\t\tscores.set(chunkId, perChunkScore);\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// If anything fails, return empty map\n\t}\n\n\treturn scores;\n}\n\n/** Strip common source file extensions for path matching. */\nfunction stripExtension(filePath: string): string {\n\treturn filePath.replace(/\\.[jt]sx?$|\\.py$|\\.go$|\\.rs$|\\.java$|\\.c$|\\.h$|\\.cpp$|\\.hpp$|\\.cc$|\\.cxx$/, \"\");\n}\n"]}
@@ -1,13 +0,0 @@
1
- /**
2
- * Path/filename similarity metric.
3
- *
4
- * Tokenizes query and file paths, computes a recall-oriented overlap score
5
- * (what fraction of query tokens appear in the path).
6
- */
7
- import type { StoredChunk } from "../types.js";
8
- /**
9
- * Compute path/filename similarity scores.
10
- * Tokenizes query and file paths, returns Jaccard-like overlap score.
11
- */
12
- export declare function computePathMatchScores(query: string, chunks: StoredChunk[]): Map<number, number>;
13
- //# sourceMappingURL=path-match.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"path-match.d.ts","sourceRoot":"","sources":["../../../../src/core/search/metrics/path-match.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CA+ChG","sourcesContent":["/**\n * Path/filename similarity metric.\n *\n * Tokenizes query and file paths, computes a recall-oriented overlap score\n * (what fraction of query tokens appear in the path).\n */\n\nimport type { StoredChunk } from \"../types.js\";\nimport { tokenize } from \"./tokenize.js\";\n\n/**\n * Compute path/filename similarity scores.\n * Tokenizes query and file paths, returns Jaccard-like overlap score.\n */\nexport function computePathMatchScores(query: string, chunks: StoredChunk[]): Map<number, number> {\n\tconst scores = new Map<number, number>();\n\n\ttry {\n\t\tconst queryTokens = tokenize(query);\n\t\tif (queryTokens.length === 0) return scores;\n\n\t\tconst querySet = new Set(queryTokens);\n\n\t\t// Cache path tokens per file path (many chunks share the same file)\n\t\tconst pathTokenCache = new Map<string, Set<string>>();\n\n\t\tlet maxScore = 0;\n\n\t\tfor (const chunk of chunks) {\n\t\t\tlet pathTokenSet = pathTokenCache.get(chunk.filePath);\n\t\t\tif (!pathTokenSet) {\n\t\t\t\tpathTokenSet = new Set(tokenize(chunk.filePath));\n\t\t\t\tpathTokenCache.set(chunk.filePath, pathTokenSet);\n\t\t\t}\n\n\t\t\t// Score = |intersection| / |query tokens| (recall-oriented)\n\t\t\tlet intersection = 0;\n\t\t\tfor (const qt of querySet) {\n\t\t\t\tif (pathTokenSet.has(qt)) {\n\t\t\t\t\tintersection++;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst score = intersection / queryTokens.length;\n\t\t\tif (score > 0) {\n\t\t\t\tscores.set(chunk.id, score);\n\t\t\t\tif (score > maxScore) maxScore = score;\n\t\t\t}\n\t\t}\n\n\t\t// Normalize to 0-1 (divide by max if not already bounded)\n\t\tif (maxScore > 0 && maxScore !== 1) {\n\t\t\tfor (const [id, score] of scores) {\n\t\t\t\tscores.set(id, score / maxScore);\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// If anything fails, return empty map\n\t}\n\n\treturn scores;\n}\n"]}
@@ -1,54 +0,0 @@
1
- /**
2
- * Path/filename similarity metric.
3
- *
4
- * Tokenizes query and file paths, computes a recall-oriented overlap score
5
- * (what fraction of query tokens appear in the path).
6
- */
7
- import { tokenize } from "./tokenize.js";
8
- /**
9
- * Compute path/filename similarity scores.
10
- * Tokenizes query and file paths, returns Jaccard-like overlap score.
11
- */
12
- export function computePathMatchScores(query, chunks) {
13
- const scores = new Map();
14
- try {
15
- const queryTokens = tokenize(query);
16
- if (queryTokens.length === 0)
17
- return scores;
18
- const querySet = new Set(queryTokens);
19
- // Cache path tokens per file path (many chunks share the same file)
20
- const pathTokenCache = new Map();
21
- let maxScore = 0;
22
- for (const chunk of chunks) {
23
- let pathTokenSet = pathTokenCache.get(chunk.filePath);
24
- if (!pathTokenSet) {
25
- pathTokenSet = new Set(tokenize(chunk.filePath));
26
- pathTokenCache.set(chunk.filePath, pathTokenSet);
27
- }
28
- // Score = |intersection| / |query tokens| (recall-oriented)
29
- let intersection = 0;
30
- for (const qt of querySet) {
31
- if (pathTokenSet.has(qt)) {
32
- intersection++;
33
- }
34
- }
35
- const score = intersection / queryTokens.length;
36
- if (score > 0) {
37
- scores.set(chunk.id, score);
38
- if (score > maxScore)
39
- maxScore = score;
40
- }
41
- }
42
- // Normalize to 0-1 (divide by max if not already bounded)
43
- if (maxScore > 0 && maxScore !== 1) {
44
- for (const [id, score] of scores) {
45
- scores.set(id, score / maxScore);
46
- }
47
- }
48
- }
49
- catch {
50
- // If anything fails, return empty map
51
- }
52
- return scores;
53
- }
54
- //# sourceMappingURL=path-match.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"path-match.js","sourceRoot":"","sources":["../../../../src/core/search/metrics/path-match.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAa,EAAE,MAAqB,EAAuB;IACjG,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEzC,IAAI,CAAC;QACJ,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QACpC,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,MAAM,CAAC;QAE5C,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC;QAEtC,oEAAoE;QACpE,MAAM,cAAc,GAAG,IAAI,GAAG,EAAuB,CAAC;QAEtD,IAAI,QAAQ,GAAG,CAAC,CAAC;QAEjB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC5B,IAAI,YAAY,GAAG,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YACtD,IAAI,CAAC,YAAY,EAAE,CAAC;gBACnB,YAAY,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;gBACjD,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;YAClD,CAAC;YAED,4DAA4D;YAC5D,IAAI,YAAY,GAAG,CAAC,CAAC;YACrB,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;gBAC3B,IAAI,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;oBAC1B,YAAY,EAAE,CAAC;gBAChB,CAAC;YACF,CAAC;YAED,MAAM,KAAK,GAAG,YAAY,GAAG,WAAW,CAAC,MAAM,CAAC;YAChD,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;gBACf,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;gBAC5B,IAAI,KAAK,GAAG,QAAQ;oBAAE,QAAQ,GAAG,KAAK,CAAC;YACxC,CAAC;QACF,CAAC;QAED,0DAA0D;QAC1D,IAAI,QAAQ,GAAG,CAAC,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;YACpC,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;gBAClC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,GAAG,QAAQ,CAAC,CAAC;YAClC,CAAC;QACF,CAAC;IACF,CAAC;IAAC,MAAM,CAAC;QACR,sCAAsC;IACvC,CAAC;IAED,OAAO,MAAM,CAAC;AAAA,CACd","sourcesContent":["/**\n * Path/filename similarity metric.\n *\n * Tokenizes query and file paths, computes a recall-oriented overlap score\n * (what fraction of query tokens appear in the path).\n */\n\nimport type { StoredChunk } from \"../types.js\";\nimport { tokenize } from \"./tokenize.js\";\n\n/**\n * Compute path/filename similarity scores.\n * Tokenizes query and file paths, returns Jaccard-like overlap score.\n */\nexport function computePathMatchScores(query: string, chunks: StoredChunk[]): Map<number, number> {\n\tconst scores = new Map<number, number>();\n\n\ttry {\n\t\tconst queryTokens = tokenize(query);\n\t\tif (queryTokens.length === 0) return scores;\n\n\t\tconst querySet = new Set(queryTokens);\n\n\t\t// Cache path tokens per file path (many chunks share the same file)\n\t\tconst pathTokenCache = new Map<string, Set<string>>();\n\n\t\tlet maxScore = 0;\n\n\t\tfor (const chunk of chunks) {\n\t\t\tlet pathTokenSet = pathTokenCache.get(chunk.filePath);\n\t\t\tif (!pathTokenSet) {\n\t\t\t\tpathTokenSet = new Set(tokenize(chunk.filePath));\n\t\t\t\tpathTokenCache.set(chunk.filePath, pathTokenSet);\n\t\t\t}\n\n\t\t\t// Score = |intersection| / |query tokens| (recall-oriented)\n\t\t\tlet intersection = 0;\n\t\t\tfor (const qt of querySet) {\n\t\t\t\tif (pathTokenSet.has(qt)) {\n\t\t\t\t\tintersection++;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst score = intersection / queryTokens.length;\n\t\t\tif (score > 0) {\n\t\t\t\tscores.set(chunk.id, score);\n\t\t\t\tif (score > maxScore) maxScore = score;\n\t\t\t}\n\t\t}\n\n\t\t// Normalize to 0-1 (divide by max if not already bounded)\n\t\tif (maxScore > 0 && maxScore !== 1) {\n\t\t\tfor (const [id, score] of scores) {\n\t\t\t\tscores.set(id, score / maxScore);\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// If anything fails, return empty map\n\t}\n\n\treturn scores;\n}\n"]}
@@ -1,12 +0,0 @@
1
- /**
2
- * Symbol name match metric.
3
- *
4
- * Compares query terms against symbol names (function/class/etc names)
5
- * using tokenized overlap with bonuses for exact substring matches.
6
- */
7
- /**
8
- * Compute symbol name match scores.
9
- * Compares query terms against symbol names (function/class/etc names).
10
- */
11
- export declare function computeSymbolMatchScores(query: string, symbols: Map<number, string[]>): Map<number, number>;
12
- //# sourceMappingURL=symbol-match.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"symbol-match.d.ts","sourceRoot":"","sources":["../../../../src/core/search/metrics/symbol-match.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAyD3G","sourcesContent":["/**\n * Symbol name match metric.\n *\n * Compares query terms against symbol names (function/class/etc names)\n * using tokenized overlap with bonuses for exact substring matches.\n */\n\nimport { tokenize } from \"./tokenize.js\";\n\n/**\n * Compute symbol name match scores.\n * Compares query terms against symbol names (function/class/etc names).\n */\nexport function computeSymbolMatchScores(query: string, symbols: Map<number, string[]>): Map<number, number> {\n\tconst scores = new Map<number, number>();\n\n\ttry {\n\t\tconst queryTokens = tokenize(query);\n\t\tif (queryTokens.length === 0) return scores;\n\n\t\tconst queryLower = query.toLowerCase();\n\n\t\tlet maxScore = 0;\n\n\t\tfor (const [chunkId, symbolNames] of symbols) {\n\t\t\tlet bestScore = 0;\n\n\t\t\tfor (const symbolName of symbolNames) {\n\t\t\t\tconst symbolTokens = new Set(tokenize(symbolName));\n\t\t\t\tconst symbolLower = symbolName.toLowerCase();\n\n\t\t\t\t// Token overlap: fraction of query tokens found in symbol\n\t\t\t\tlet matchCount = 0;\n\t\t\t\tfor (const qt of queryTokens) {\n\t\t\t\t\tif (symbolTokens.has(qt)) {\n\t\t\t\t\t\tmatchCount++;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet score = matchCount / queryTokens.length;\n\n\t\t\t\t// Bonus for exact substring match of query in symbol name\n\t\t\t\tif (symbolLower.includes(queryLower)) {\n\t\t\t\t\tscore = Math.min(1, score + 0.3);\n\t\t\t\t}\n\t\t\t\t// Bonus for exact substring match of symbol in query\n\t\t\t\telse if (queryLower.includes(symbolLower) && symbolLower.length >= 3) {\n\t\t\t\t\tscore = Math.min(1, score + 0.2);\n\t\t\t\t}\n\n\t\t\t\tif (score > bestScore) bestScore = score;\n\t\t\t}\n\n\t\t\tif (bestScore > 0) {\n\t\t\t\tscores.set(chunkId, bestScore);\n\t\t\t\tif (bestScore > maxScore) maxScore = bestScore;\n\t\t\t}\n\t\t}\n\n\t\t// Normalize to 0-1\n\t\tif (maxScore > 0 && maxScore !== 1) {\n\t\t\tfor (const [id, score] of scores) {\n\t\t\t\tscores.set(id, score / maxScore);\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// If anything fails, return empty map\n\t}\n\n\treturn scores;\n}\n"]}
@@ -1,62 +0,0 @@
1
- /**
2
- * Symbol name match metric.
3
- *
4
- * Compares query terms against symbol names (function/class/etc names)
5
- * using tokenized overlap with bonuses for exact substring matches.
6
- */
7
- import { tokenize } from "./tokenize.js";
8
- /**
9
- * Compute symbol name match scores.
10
- * Compares query terms against symbol names (function/class/etc names).
11
- */
12
- export function computeSymbolMatchScores(query, symbols) {
13
- const scores = new Map();
14
- try {
15
- const queryTokens = tokenize(query);
16
- if (queryTokens.length === 0)
17
- return scores;
18
- const queryLower = query.toLowerCase();
19
- let maxScore = 0;
20
- for (const [chunkId, symbolNames] of symbols) {
21
- let bestScore = 0;
22
- for (const symbolName of symbolNames) {
23
- const symbolTokens = new Set(tokenize(symbolName));
24
- const symbolLower = symbolName.toLowerCase();
25
- // Token overlap: fraction of query tokens found in symbol
26
- let matchCount = 0;
27
- for (const qt of queryTokens) {
28
- if (symbolTokens.has(qt)) {
29
- matchCount++;
30
- }
31
- }
32
- let score = matchCount / queryTokens.length;
33
- // Bonus for exact substring match of query in symbol name
34
- if (symbolLower.includes(queryLower)) {
35
- score = Math.min(1, score + 0.3);
36
- }
37
- // Bonus for exact substring match of symbol in query
38
- else if (queryLower.includes(symbolLower) && symbolLower.length >= 3) {
39
- score = Math.min(1, score + 0.2);
40
- }
41
- if (score > bestScore)
42
- bestScore = score;
43
- }
44
- if (bestScore > 0) {
45
- scores.set(chunkId, bestScore);
46
- if (bestScore > maxScore)
47
- maxScore = bestScore;
48
- }
49
- }
50
- // Normalize to 0-1
51
- if (maxScore > 0 && maxScore !== 1) {
52
- for (const [id, score] of scores) {
53
- scores.set(id, score / maxScore);
54
- }
55
- }
56
- }
57
- catch {
58
- // If anything fails, return empty map
59
- }
60
- return scores;
61
- }
62
- //# sourceMappingURL=symbol-match.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"symbol-match.js","sourceRoot":"","sources":["../../../../src/core/search/metrics/symbol-match.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,KAAa,EAAE,OAA8B,EAAuB;IAC5G,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEzC,IAAI,CAAC;QACJ,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QACpC,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,MAAM,CAAC;QAE5C,MAAM,UAAU,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;QAEvC,IAAI,QAAQ,GAAG,CAAC,CAAC;QAEjB,KAAK,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC,IAAI,OAAO,EAAE,CAAC;YAC9C,IAAI,SAAS,GAAG,CAAC,CAAC;YAElB,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;gBACtC,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;gBACnD,MAAM,WAAW,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC;gBAE7C,0DAA0D;gBAC1D,IAAI,UAAU,GAAG,CAAC,CAAC;gBACnB,KAAK,MAAM,EAAE,IAAI,WAAW,EAAE,CAAC;oBAC9B,IAAI,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;wBAC1B,UAAU,EAAE,CAAC;oBACd,CAAC;gBACF,CAAC;gBAED,IAAI,KAAK,GAAG,UAAU,GAAG,WAAW,CAAC,MAAM,CAAC;gBAE5C,0DAA0D;gBAC1D,IAAI,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;oBACtC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,GAAG,CAAC,CAAC;gBAClC,CAAC;gBACD,qDAAqD;qBAChD,IAAI,UAAU,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,WAAW,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;oBACtE,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,GAAG,CAAC,CAAC;gBAClC,CAAC;gBAED,IAAI,KAAK,GAAG,SAAS;oBAAE,SAAS,GAAG,KAAK,CAAC;YAC1C,CAAC;YAED,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;gBACnB,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;gBAC/B,IAAI,SAAS,GAAG,QAAQ;oBAAE,QAAQ,GAAG,SAAS,CAAC;YAChD,CAAC;QACF,CAAC;QAED,mBAAmB;QACnB,IAAI,QAAQ,GAAG,CAAC,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;YACpC,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;gBAClC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,GAAG,QAAQ,CAAC,CAAC;YAClC,CAAC;QACF,CAAC;IACF,CAAC;IAAC,MAAM,CAAC;QACR,sCAAsC;IACvC,CAAC;IAED,OAAO,MAAM,CAAC;AAAA,CACd","sourcesContent":["/**\n * Symbol name match metric.\n *\n * Compares query terms against symbol names (function/class/etc names)\n * using tokenized overlap with bonuses for exact substring matches.\n */\n\nimport { tokenize } from \"./tokenize.js\";\n\n/**\n * Compute symbol name match scores.\n * Compares query terms against symbol names (function/class/etc names).\n */\nexport function computeSymbolMatchScores(query: string, symbols: Map<number, string[]>): Map<number, number> {\n\tconst scores = new Map<number, number>();\n\n\ttry {\n\t\tconst queryTokens = tokenize(query);\n\t\tif (queryTokens.length === 0) return scores;\n\n\t\tconst queryLower = query.toLowerCase();\n\n\t\tlet maxScore = 0;\n\n\t\tfor (const [chunkId, symbolNames] of symbols) {\n\t\t\tlet bestScore = 0;\n\n\t\t\tfor (const symbolName of symbolNames) {\n\t\t\t\tconst symbolTokens = new Set(tokenize(symbolName));\n\t\t\t\tconst symbolLower = symbolName.toLowerCase();\n\n\t\t\t\t// Token overlap: fraction of query tokens found in symbol\n\t\t\t\tlet matchCount = 0;\n\t\t\t\tfor (const qt of queryTokens) {\n\t\t\t\t\tif (symbolTokens.has(qt)) {\n\t\t\t\t\t\tmatchCount++;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet score = matchCount / queryTokens.length;\n\n\t\t\t\t// Bonus for exact substring match of query in symbol name\n\t\t\t\tif (symbolLower.includes(queryLower)) {\n\t\t\t\t\tscore = Math.min(1, score + 0.3);\n\t\t\t\t}\n\t\t\t\t// Bonus for exact substring match of symbol in query\n\t\t\t\telse if (queryLower.includes(symbolLower) && symbolLower.length >= 3) {\n\t\t\t\t\tscore = Math.min(1, score + 0.2);\n\t\t\t\t}\n\n\t\t\t\tif (score > bestScore) bestScore = score;\n\t\t\t}\n\n\t\t\tif (bestScore > 0) {\n\t\t\t\tscores.set(chunkId, bestScore);\n\t\t\t\tif (bestScore > maxScore) maxScore = bestScore;\n\t\t\t}\n\t\t}\n\n\t\t// Normalize to 0-1\n\t\tif (maxScore > 0 && maxScore !== 1) {\n\t\t\tfor (const [id, score] of scores) {\n\t\t\t\tscores.set(id, score / maxScore);\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// If anything fails, return empty map\n\t}\n\n\treturn scores;\n}\n"]}
@@ -1,12 +0,0 @@
1
- /**
2
- * Shared tokenizer for path-match and symbol-match metrics.
3
- *
4
- * Splits text on common code boundaries (spaces, path separators, dots,
5
- * dashes, underscores, camelCase) and normalizes to lowercase.
6
- */
7
- /**
8
- * Tokenize text by splitting on spaces, `/`, `\`, `.`, `-`, `_`, and
9
- * camelCase boundaries. Returns unique, lowercase tokens ≥ 2 chars.
10
- */
11
- export declare function tokenize(text: string): string[];
12
- //# sourceMappingURL=tokenize.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tokenize.d.ts","sourceRoot":"","sources":["../../../../src/core/search/metrics/tokenize.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAoB/C","sourcesContent":["/**\n * Shared tokenizer for path-match and symbol-match metrics.\n *\n * Splits text on common code boundaries (spaces, path separators, dots,\n * dashes, underscores, camelCase) and normalizes to lowercase.\n */\n\n/**\n * Tokenize text by splitting on spaces, `/`, `\\`, `.`, `-`, `_`, and\n * camelCase boundaries. Returns unique, lowercase tokens ≥ 2 chars.\n */\nexport function tokenize(text: string): string[] {\n\t// Insert a space before uppercase letters that follow lowercase letters (camelCase)\n\t// or before uppercase letters followed by lowercase (e.g., \"XMLParser\" → \"XML Parser\")\n\tconst spaced = text.replace(/([a-z])([A-Z])/g, \"$1 $2\").replace(/([A-Z]+)([A-Z][a-z])/g, \"$1 $2\");\n\n\t// Split on common delimiters\n\tconst parts = spaced.split(/[\\s/\\\\.\\-_]+/);\n\n\t// Lowercase, deduplicate, filter short tokens\n\tconst seen = new Set<string>();\n\tconst tokens: string[] = [];\n\tfor (const part of parts) {\n\t\tconst lower = part.toLowerCase();\n\t\tif (lower.length >= 2 && !seen.has(lower)) {\n\t\t\tseen.add(lower);\n\t\t\ttokens.push(lower);\n\t\t}\n\t}\n\n\treturn tokens;\n}\n"]}
@@ -1,29 +0,0 @@
1
- /**
2
- * Shared tokenizer for path-match and symbol-match metrics.
3
- *
4
- * Splits text on common code boundaries (spaces, path separators, dots,
5
- * dashes, underscores, camelCase) and normalizes to lowercase.
6
- */
7
- /**
8
- * Tokenize text by splitting on spaces, `/`, `\`, `.`, `-`, `_`, and
9
- * camelCase boundaries. Returns unique, lowercase tokens ≥ 2 chars.
10
- */
11
- export function tokenize(text) {
12
- // Insert a space before uppercase letters that follow lowercase letters (camelCase)
13
- // or before uppercase letters followed by lowercase (e.g., "XMLParser" → "XML Parser")
14
- const spaced = text.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2");
15
- // Split on common delimiters
16
- const parts = spaced.split(/[\s/\\.\-_]+/);
17
- // Lowercase, deduplicate, filter short tokens
18
- const seen = new Set();
19
- const tokens = [];
20
- for (const part of parts) {
21
- const lower = part.toLowerCase();
22
- if (lower.length >= 2 && !seen.has(lower)) {
23
- seen.add(lower);
24
- tokens.push(lower);
25
- }
26
- }
27
- return tokens;
28
- }
29
- //# sourceMappingURL=tokenize.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tokenize.js","sourceRoot":"","sources":["../../../../src/core/search/metrics/tokenize.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAY;IAChD,oFAAoF;IACpF,yFAAuF;IACvF,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;IAElG,6BAA6B;IAC7B,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IAE3C,8CAA8C;IAC9C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QACjC,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3C,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAChB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpB,CAAC;IACF,CAAC;IAED,OAAO,MAAM,CAAC;AAAA,CACd","sourcesContent":["/**\n * Shared tokenizer for path-match and symbol-match metrics.\n *\n * Splits text on common code boundaries (spaces, path separators, dots,\n * dashes, underscores, camelCase) and normalizes to lowercase.\n */\n\n/**\n * Tokenize text by splitting on spaces, `/`, `\\`, `.`, `-`, `_`, and\n * camelCase boundaries. Returns unique, lowercase tokens ≥ 2 chars.\n */\nexport function tokenize(text: string): string[] {\n\t// Insert a space before uppercase letters that follow lowercase letters (camelCase)\n\t// or before uppercase letters followed by lowercase (e.g., \"XMLParser\" → \"XML Parser\")\n\tconst spaced = text.replace(/([a-z])([A-Z])/g, \"$1 $2\").replace(/([A-Z]+)([A-Z][a-z])/g, \"$1 $2\");\n\n\t// Split on common delimiters\n\tconst parts = spaced.split(/[\\s/\\\\.\\-_]+/);\n\n\t// Lowercase, deduplicate, filter short tokens\n\tconst seen = new Set<string>();\n\tconst tokens: string[] = [];\n\tfor (const part of parts) {\n\t\tconst lower = part.toLowerCase();\n\t\tif (lower.length >= 2 && !seen.has(lower)) {\n\t\t\tseen.add(lower);\n\t\t\ttokens.push(lower);\n\t\t}\n\t}\n\n\treturn tokens;\n}\n"]}