@ninjaxtools/slopdex 0.18.0 → 0.20.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 (62) hide show
  1. package/.gitignore +2 -0
  2. package/README.md +136 -48
  3. package/binary-install.js +348 -0
  4. package/binary.js +124 -0
  5. package/install.js +4 -0
  6. package/npm-shrinkwrap.json +52 -0
  7. package/package.json +78 -62
  8. package/run-slopdex.js +4 -0
  9. package/.agents/skills/slopdex/SKILL.md +0 -241
  10. package/dist/chunk-6LHZCHEB.js +0 -1112
  11. package/dist/chunk-6LHZCHEB.js.map +0 -1
  12. package/dist/chunk-BXWO2KMC.js +0 -20
  13. package/dist/chunk-BXWO2KMC.js.map +0 -1
  14. package/dist/chunk-DKRB2XT5.js +0 -33
  15. package/dist/chunk-DKRB2XT5.js.map +0 -1
  16. package/dist/chunk-DTI7SXGL.js +0 -109
  17. package/dist/chunk-DTI7SXGL.js.map +0 -1
  18. package/dist/chunk-IQU3YVZ3.js +0 -85
  19. package/dist/chunk-IQU3YVZ3.js.map +0 -1
  20. package/dist/chunk-JAVPHCZH.js +0 -24
  21. package/dist/chunk-JAVPHCZH.js.map +0 -1
  22. package/dist/chunk-KQRS5P4U.js +0 -10
  23. package/dist/chunk-KQRS5P4U.js.map +0 -1
  24. package/dist/chunk-KUV6PKKL.js +0 -425
  25. package/dist/chunk-KUV6PKKL.js.map +0 -1
  26. package/dist/chunk-MKUTTXZB.js +0 -74
  27. package/dist/chunk-MKUTTXZB.js.map +0 -1
  28. package/dist/chunk-N6D66ASM.js +0 -2217
  29. package/dist/chunk-N6D66ASM.js.map +0 -1
  30. package/dist/chunk-OIKBO3NJ.js +0 -133
  31. package/dist/chunk-OIKBO3NJ.js.map +0 -1
  32. package/dist/chunk-S225GYCL.js +0 -79
  33. package/dist/chunk-S225GYCL.js.map +0 -1
  34. package/dist/chunk-TSURHRFF.js +0 -240
  35. package/dist/chunk-TSURHRFF.js.map +0 -1
  36. package/dist/chunk-VH5VGRCI.js +0 -103
  37. package/dist/chunk-VH5VGRCI.js.map +0 -1
  38. package/dist/cli.d.ts +0 -1
  39. package/dist/cli.js +0 -1130
  40. package/dist/cli.js.map +0 -1
  41. package/dist/code-index-YEFTNG7E.js +0 -14
  42. package/dist/code-index-YEFTNG7E.js.map +0 -1
  43. package/dist/cross-search-FVKDMGP7.js +0 -10
  44. package/dist/cross-search-FVKDMGP7.js.map +0 -1
  45. package/dist/database-KAPV2YMP.js +0 -14
  46. package/dist/database-KAPV2YMP.js.map +0 -1
  47. package/dist/hosted-GHIUHKOU.js +0 -13
  48. package/dist/hosted-GHIUHKOU.js.map +0 -1
  49. package/dist/index.d.ts +0 -681
  50. package/dist/index.js +0 -102
  51. package/dist/index.js.map +0 -1
  52. package/dist/jina-43RL7C6M.js +0 -12
  53. package/dist/jina-43RL7C6M.js.map +0 -1
  54. package/dist/openai-CYUPNBGB.js +0 -12
  55. package/dist/openai-CYUPNBGB.js.map +0 -1
  56. package/dist/openai-EC6THXKV.js +0 -21
  57. package/dist/openai-EC6THXKV.js.map +0 -1
  58. package/dist/openai-TCRXM66O.js +0 -11
  59. package/dist/openai-TCRXM66O.js.map +0 -1
  60. package/docs/implementation.md +0 -162
  61. package/docs/reference.md +0 -187
  62. package/scripts/install-opencode-skill.mjs +0 -11
@@ -1,425 +0,0 @@
1
- import {
2
- analysisSimilarity
3
- } from "./chunk-KQRS5P4U.js";
4
- import {
5
- assertPositiveInteger,
6
- compileNameRegex,
7
- throwIfAborted
8
- } from "./chunk-VH5VGRCI.js";
9
- import {
10
- CodeIndexError,
11
- IncompatibleIndexError
12
- } from "./chunk-DKRB2XT5.js";
13
-
14
- // src/search/cross-search.ts
15
- import { realpath, stat } from "node:fs/promises";
16
- import path2 from "node:path";
17
-
18
- // src/analysis/cohesion.ts
19
- import path from "node:path";
20
- async function analyzeCohesion(options) {
21
- const status = options.source.status();
22
- const scoring = analysisSimilarity(status);
23
- const neighbors = options.neighbors ?? 20;
24
- const limit = options.limit ?? 50;
25
- const minSimilarity = options.minSimilarity ?? 0.8;
26
- const minLines = options.minLines ?? 2;
27
- const sourceFilter = options.sourceFilter ?? { type: "all" };
28
- assertPositiveInteger(neighbors, "neighbors");
29
- assertPositiveInteger(limit, "limit");
30
- assertPositiveInteger(minLines, "minLines");
31
- if (!Number.isFinite(minSimilarity) || minSimilarity < -1 || minSimilarity >= 1) {
32
- throw new CodeIndexError("minSimilarity must be at least -1 and less than 1.");
33
- }
34
- if (options.maxSimilarity !== void 0 && (!Number.isFinite(options.maxSimilarity) || options.maxSimilarity <= minSimilarity)) {
35
- throw new CodeIndexError("maxSimilarity must be greater than minSimilarity.");
36
- }
37
- const nameRegex = compileNameRegex(options.nameRegex);
38
- const candidateFunctions = options.source.allFunctions().filter((callable) => callable.lineCount >= minLines && (!nameRegex || nameRegex.test(callable.qualifiedName)));
39
- const candidateIds = new Set(candidateFunctions.map((callable) => callable.id));
40
- const sourceFunctions = (await options.source.sourceFunctions(sourceFilter)).filter((callable) => candidateIds.has(callable.id));
41
- const neighborCache = /* @__PURE__ */ new Map();
42
- const useCache = !options.source.readOnly;
43
- if (useCache) {
44
- await options.source.refreshSimilarityCache({
45
- width: Math.min(200, Math.max(50, neighbors * 5)),
46
- minSimilarity,
47
- ...options.signal ? { signal: options.signal } : {},
48
- ...options.onCacheProgress ? { onProgress: options.onCacheProgress } : {}
49
- });
50
- }
51
- const cacheReader = useCache ? options.source.cachedSimilarityReader({
52
- includeDescriptions: scoring.similarityMode === "code-description-file-average"
53
- }) : void 0;
54
- const neighborsFor = (callable) => {
55
- throwIfAborted(options.signal);
56
- const query = {
57
- includeDescriptions: scoring.similarityMode === "code-description-file-average",
58
- limit: neighbors,
59
- minSimilarity,
60
- ...options.maxSimilarity !== void 0 ? { maxSimilarity: options.maxSimilarity } : {},
61
- minLines,
62
- ...options.nameRegex !== void 0 ? { nameRegex: options.nameRegex } : {}
63
- };
64
- const matches = (cacheReader ? cacheReader.similarToFunction(callable.id, query) : options.source.similarToFunction(callable.id, query)).filter((match) => candidateIds.has(match.function.id));
65
- neighborCache.set(callable.id, new Set(matches.map((match) => match.function.id)));
66
- return matches;
67
- };
68
- const edges = /* @__PURE__ */ new Map();
69
- for (let index = 0; index < sourceFunctions.length; index += 1) {
70
- const source = sourceFunctions[index];
71
- for (const match of neighborsFor(source)) {
72
- const [left, right] = orderedFunctions(source, match.function);
73
- const key = pairKey(left.id, right.id);
74
- const existing = edges.get(key);
75
- if (!existing || match.similarity > existing.similarity) {
76
- const { function: _function, ...scores } = match;
77
- edges.set(key, { left, right, ...scores });
78
- }
79
- }
80
- options.onProgress?.({ completed: index + 1, total: sourceFunctions.length });
81
- }
82
- const scoredPairs = [...edges.values()].map((edge) => {
83
- const leftNeighbors = neighborCache.get(edge.left.id);
84
- const rightNeighbors = neighborCache.get(edge.right.id);
85
- const reciprocal = leftNeighbors && rightNeighbors ? leftNeighbors.has(edge.right.id) && rightNeighbors.has(edge.left.id) : null;
86
- return scorePair(edge, reciprocal, minSimilarity);
87
- }).sort(comparePairs);
88
- scoredPairs.forEach((pair, index) => {
89
- pair.rank = index + 1;
90
- });
91
- const repositoryScope = sourceFilter.type === "all" && sourceFilter.path === void 0 && sourceFilter.nameRegex === void 0;
92
- const metrics = calculateMetrics(
93
- repositoryScope ? "repository" : "selected-sources",
94
- sourceFunctions.length,
95
- candidateFunctions.length,
96
- scoredPairs
97
- );
98
- const reportedPairs = scoredPairs.slice(0, limit);
99
- const files = aggregateFiles(sourceFunctions, scoredPairs).filter((file) => file.internalAffinity + file.sameFolderAffinity + file.externalAffinity > 0).slice(0, limit);
100
- const groups = buildGroups(reportedPairs);
101
- return {
102
- schemaVersion: 3,
103
- repository: {
104
- generation: status.generation,
105
- gitCheckpoint: status.gitCheckpoint,
106
- embeddingProfile: status.embeddingProfile,
107
- descriptionProfile: status.descriptionProfile
108
- },
109
- parameters: {
110
- ...scoring,
111
- neighbors,
112
- limit,
113
- minSimilarity,
114
- ...options.maxSimilarity !== void 0 ? { maxSimilarity: options.maxSimilarity } : {},
115
- minLines,
116
- ...options.nameRegex !== void 0 ? { nameRegex: options.nameRegex } : {},
117
- sourceFilter
118
- },
119
- metrics,
120
- pairs: reportedPairs,
121
- files,
122
- groups
123
- };
124
- }
125
- function cohesionLocation(leftPath, rightPath) {
126
- const normalizedLeft = leftPath.replaceAll("\\", "/");
127
- const normalizedRight = rightPath.replaceAll("\\", "/");
128
- const leftDirectory = directoryParts(normalizedLeft);
129
- const rightDirectory = directoryParts(normalizedRight);
130
- let commonLength = 0;
131
- while (commonLength < leftDirectory.length && commonLength < rightDirectory.length && leftDirectory[commonLength] === rightDirectory[commonLength]) {
132
- commonLength += 1;
133
- }
134
- const commonParts = leftDirectory.slice(0, commonLength);
135
- const commonAncestor = commonParts.length > 0 ? commonParts.join("/") : null;
136
- const sameFile = normalizedLeft === normalizedRight;
137
- const folderHops = sameFile ? 0 : leftDirectory.length - commonLength + rightDirectory.length - commonLength;
138
- return {
139
- category: sameFile ? "same-file" : folderHops === 0 ? "same-folder" : "different-folder",
140
- physicalDistance: sameFile ? 0 : 1 + folderHops,
141
- folderHops,
142
- commonAncestor,
143
- sourceTestPair: isTestPath(normalizedLeft) !== isTestPath(normalizedRight)
144
- };
145
- }
146
- function scorePair(edge, reciprocal, minSimilarity) {
147
- const location = cohesionLocation(edge.left.path, edge.right.path);
148
- const semanticWeight = clamp((edge.similarity - minSimilarity) / (1 - minSimilarity));
149
- const separationWeight = 1 - Math.exp(-location.physicalDistance / 2);
150
- return {
151
- rank: 0,
152
- left: edge.left,
153
- right: edge.right,
154
- similarity: edge.similarity,
155
- ...edge.codeSimilarity !== void 0 ? { codeSimilarity: edge.codeSimilarity } : {},
156
- ...edge.descriptionSimilarity !== void 0 ? { descriptionSimilarity: edge.descriptionSimilarity } : {},
157
- ...edge.fileDescriptionSimilarity !== void 0 ? { fileDescriptionSimilarity: edge.fileDescriptionSimilarity } : {},
158
- reciprocal,
159
- semanticWeight,
160
- separationWeight,
161
- cohesionGap: semanticWeight * separationWeight,
162
- location
163
- };
164
- }
165
- function calculateMetrics(scope, functionsAnalyzed, candidateFunctions, pairs) {
166
- const totalWeight = pairs.reduce((sum, pair) => sum + pair.semanticWeight, 0);
167
- const sameFileWeight = pairs.filter((pair) => pair.location.category === "same-file").reduce((sum, pair) => sum + pair.semanticWeight, 0);
168
- const sameFolderWeight = pairs.filter((pair) => pair.location.category === "same-folder").reduce((sum, pair) => sum + pair.semanticWeight, 0);
169
- const remoteWeight = pairs.filter((pair) => pair.location.category === "different-folder").reduce((sum, pair) => sum + pair.semanticWeight, 0);
170
- const weightedDistance = pairs.reduce(
171
- (sum, pair) => sum + pair.semanticWeight * pair.location.physicalDistance,
172
- 0
173
- );
174
- return {
175
- scope,
176
- functionsAnalyzed,
177
- candidateFunctions,
178
- semanticEdges: pairs.length,
179
- sameFileRatio: ratio(sameFileWeight, totalWeight),
180
- sameFolderRatio: ratio(sameFolderWeight, totalWeight),
181
- remoteRatio: ratio(remoteWeight, totalWeight),
182
- weightedMeanDistance: ratio(weightedDistance, totalWeight)
183
- };
184
- }
185
- function aggregateFiles(sourceFunctions, pairs) {
186
- const sourceIds = new Set(sourceFunctions.map((callable) => callable.id));
187
- const counts = /* @__PURE__ */ new Map();
188
- for (const callable of sourceFunctions) counts.set(callable.path, (counts.get(callable.path) ?? 0) + 1);
189
- const reports = /* @__PURE__ */ new Map();
190
- for (const [filePath, functionCount] of counts) {
191
- reports.set(filePath, {
192
- path: filePath,
193
- functionCount,
194
- internalAffinity: 0,
195
- sameFolderAffinity: 0,
196
- externalAffinity: 0,
197
- externalAffinityRatio: 0
198
- });
199
- }
200
- for (const pair of pairs) {
201
- if (sourceIds.has(pair.left.id)) addFileAffinity(reports.get(pair.left.path), pair.right, pair);
202
- if (sourceIds.has(pair.right.id)) addFileAffinity(reports.get(pair.right.path), pair.left, pair);
203
- }
204
- for (const report of reports.values()) {
205
- const total = report.internalAffinity + report.sameFolderAffinity + report.externalAffinity;
206
- report.externalAffinityRatio = ratio(report.externalAffinity, total);
207
- }
208
- return [...reports.values()].sort((left, right) => right.externalAffinityRatio - left.externalAffinityRatio || right.externalAffinity - left.externalAffinity || left.path.localeCompare(right.path));
209
- }
210
- function addFileAffinity(report, match, pair) {
211
- if (pair.location.category === "same-file") report.internalAffinity += pair.semanticWeight;
212
- else if (pair.location.category === "same-folder") report.sameFolderAffinity += pair.semanticWeight;
213
- else {
214
- report.externalAffinity += pair.semanticWeight;
215
- updateStrongestExternal(report, match, pair);
216
- }
217
- }
218
- function updateStrongestExternal(report, callable, pair) {
219
- if (!report.strongestExternalMatch || pair.cohesionGap > report.strongestExternalMatch.cohesionGap) {
220
- report.strongestExternalMatch = {
221
- function: callable,
222
- similarity: pair.similarity,
223
- ...pair.codeSimilarity !== void 0 ? { codeSimilarity: pair.codeSimilarity } : {},
224
- ...pair.descriptionSimilarity !== void 0 ? { descriptionSimilarity: pair.descriptionSimilarity } : {},
225
- ...pair.fileDescriptionSimilarity !== void 0 ? { fileDescriptionSimilarity: pair.fileDescriptionSimilarity } : {},
226
- cohesionGap: pair.cohesionGap
227
- };
228
- }
229
- }
230
- function buildGroups(pairs) {
231
- const functions = /* @__PURE__ */ new Map();
232
- const neighbors = /* @__PURE__ */ new Map();
233
- for (const pair of pairs) {
234
- functions.set(pair.left.id, pair.left);
235
- functions.set(pair.right.id, pair.right);
236
- addNeighbor(neighbors, pair.left.id, pair.right.id);
237
- addNeighbor(neighbors, pair.right.id, pair.left.id);
238
- }
239
- const seen = /* @__PURE__ */ new Set();
240
- const groups = [];
241
- for (const start of neighbors.keys()) {
242
- if (seen.has(start)) continue;
243
- const pending = [start];
244
- const memberIds = /* @__PURE__ */ new Set();
245
- while (pending.length > 0) {
246
- const id = pending.pop();
247
- if (seen.has(id)) continue;
248
- seen.add(id);
249
- memberIds.add(id);
250
- for (const neighbor of neighbors.get(id) ?? []) pending.push(neighbor);
251
- }
252
- const groupPairs = pairs.filter((pair) => memberIds.has(pair.left.id) && memberIds.has(pair.right.id));
253
- const members = [...memberIds].map((id) => functions.get(id)).sort(compareFunctions);
254
- groups.push({
255
- rank: 0,
256
- memberCount: members.length,
257
- fileCount: new Set(members.map((member) => member.path)).size,
258
- minimumEdgeSimilarity: Math.min(...groupPairs.map((pair) => pair.similarity)),
259
- maximumEdgeSimilarity: Math.max(...groupPairs.map((pair) => pair.similarity)),
260
- maximumPhysicalDistance: Math.max(...groupPairs.map((pair) => pair.location.physicalDistance)),
261
- maximumCohesionGap: Math.max(...groupPairs.map((pair) => pair.cohesionGap)),
262
- members
263
- });
264
- }
265
- groups.sort((left, right) => right.maximumCohesionGap - left.maximumCohesionGap || right.memberCount - left.memberCount || compareFunctions(left.members[0], right.members[0]));
266
- groups.forEach((group, index) => {
267
- group.rank = index + 1;
268
- });
269
- return groups;
270
- }
271
- function directoryParts(filePath) {
272
- const directory = path.posix.dirname(filePath.replaceAll("\\", "/"));
273
- return directory === "." ? [] : directory.split("/").filter(Boolean);
274
- }
275
- function isTestPath(filePath) {
276
- const original = filePath.replaceAll("\\", "/");
277
- const normalized = original.toLowerCase();
278
- const segments = normalized.split("/");
279
- const fileName = segments.at(-1) ?? "";
280
- return segments.some((segment) => segment === "test" || segment === "tests" || segment === "__tests__") || /\.(?:test|spec)\.[cm]?[jt]sx?$/.test(fileName) || /_test\.go$/.test(fileName) || /^(?:test_.+|.+_test)\.(?:py|pyw|rs|c|h)$/.test(fileName) || /^(?:Test.+|.+Tests?|.+TestCase)\.java$/.test(original.split("/").at(-1) ?? "");
281
- }
282
- function orderedFunctions(left, right) {
283
- return compareFunctions(left, right) <= 0 ? [left, right] : [right, left];
284
- }
285
- function compareFunctions(left, right) {
286
- return left.path.localeCompare(right.path) || left.startLine - right.startLine || left.startColumn - right.startColumn || left.qualifiedName.localeCompare(right.qualifiedName) || left.id - right.id;
287
- }
288
- function comparePairs(left, right) {
289
- return right.cohesionGap - left.cohesionGap || right.similarity - left.similarity || Number(right.reciprocal === true) - Number(left.reciprocal === true) || compareFunctions(left.left, right.left) || compareFunctions(left.right, right.right);
290
- }
291
- function pairKey(leftId, rightId) {
292
- return leftId < rightId ? `${leftId}:${rightId}` : `${rightId}:${leftId}`;
293
- }
294
- function addNeighbor(neighbors, source, target) {
295
- const values = neighbors.get(source) ?? /* @__PURE__ */ new Set();
296
- values.add(target);
297
- neighbors.set(source, values);
298
- }
299
- function ratio(numerator, denominator) {
300
- return denominator === 0 ? 0 : numerator / denominator;
301
- }
302
- function clamp(value) {
303
- return Math.max(0, Math.min(1, value));
304
- }
305
-
306
- // src/search/cross-search.ts
307
- async function* crossSearch(options) {
308
- const target = options.target ?? options.source;
309
- const sourceStatus = options.source.status();
310
- const targetStatus = target.status();
311
- const sourceProfile = JSON.stringify(sourceStatus.embeddingProfile);
312
- const targetProfile = JSON.stringify(targetStatus.embeddingProfile);
313
- if (sourceProfile !== targetProfile) {
314
- throw new IncompatibleIndexError("Cross-search requires identical embedding profiles.");
315
- }
316
- const scoring = {
317
- ...analysisSimilarity(sourceStatus, targetStatus),
318
- sourceDescriptionProfile: sourceStatus.descriptionProfile,
319
- targetDescriptionProfile: targetStatus.descriptionProfile
320
- };
321
- const includeDescriptions = scoring.similarityMode === "code-description-file-average";
322
- const limit = options.limitPerFunction ?? 5;
323
- assertPositiveInteger(limit, "limitPerFunction");
324
- const minLines = options.minLines ?? 2;
325
- assertPositiveInteger(minLines, "minLines");
326
- const nameRegex = compileNameRegex(options.nameRegex);
327
- const sourceFunctions = (await options.source.sourceFunctions(options.sourceFilter ?? { type: "all" })).filter((callable) => callable.lineCount >= minLines && (!nameRegex || nameRegex.test(callable.qualifiedName)));
328
- const sameIndex = target.indexPath === options.source.indexPath || await fileIdentity(target.indexPath) === await fileIdentity(options.source.indexPath);
329
- const sourceRoot = options.crossFileOnly ? await canonicalRoot(options.source.rootDir) : void 0;
330
- const targetRoot = options.crossFileOnly ? sameIndex ? sourceRoot : await canonicalRoot(target.rootDir) : void 0;
331
- const rootsDiffer = options.cohesion && !sameIndex ? await canonicalRoot(options.source.rootDir) !== await canonicalRoot(target.rootDir) : false;
332
- const canonicalFiles = /* @__PURE__ */ new Map();
333
- const canonicalFile = (root, filePath) => {
334
- const absolutePath = path2.resolve(root, filePath);
335
- let result = canonicalFiles.get(absolutePath);
336
- if (!result) {
337
- result = fileIdentity(absolutePath);
338
- canonicalFiles.set(absolutePath, result);
339
- }
340
- return result;
341
- };
342
- const targetPathsByCanonicalFile = /* @__PURE__ */ new Map();
343
- if (targetRoot) {
344
- const targetPaths = [...new Set(target.allFunctions().map((callable) => callable.path))];
345
- await Promise.all(targetPaths.map(async (targetPath) => {
346
- const canonicalPath = await canonicalFile(targetRoot, targetPath);
347
- const paths = targetPathsByCanonicalFile.get(canonicalPath) ?? [];
348
- paths.push(targetPath);
349
- targetPathsByCanonicalFile.set(canonicalPath, paths);
350
- }));
351
- }
352
- const seenPairs = /* @__PURE__ */ new Set();
353
- const useCache = sameIndex && !options.source.readOnly;
354
- if (useCache) {
355
- await options.source.refreshSimilarityCache({
356
- width: Math.min(200, Math.max(50, limit * 5)),
357
- minSimilarity: options.minSimilarity ?? -1,
358
- ...options.signal ? { signal: options.signal } : {},
359
- ...options.onCacheProgress ? { onProgress: options.onCacheProgress } : {}
360
- });
361
- }
362
- const cacheReader = useCache ? options.source.cachedSimilarityReader({ includeDescriptions }) : void 0;
363
- for (let index = 0; index < sourceFunctions.length; index += 1) {
364
- throwIfAborted(options.signal);
365
- const source = sourceFunctions[index];
366
- const canonicalSourceFile = sourceRoot ? await canonicalFile(sourceRoot, source.path) : void 0;
367
- const excludedTargetPaths = canonicalSourceFile ? targetPathsByCanonicalFile.get(canonicalSourceFile) : void 0;
368
- const excludePaths = excludedTargetPaths ? { excludePaths: excludedTargetPaths } : {};
369
- const similar = cacheReader ? cacheReader.similarToFunction : options.source.similarToFunction.bind(options.source);
370
- const candidates = sameIndex ? similar(source.id, {
371
- includeDescriptions,
372
- limit,
373
- minSimilarity: options.minSimilarity ?? -1,
374
- ...options.maxSimilarity !== void 0 ? { maxSimilarity: options.maxSimilarity } : {},
375
- minLines,
376
- ...options.nameRegex !== void 0 ? { nameRegex: options.nameRegex } : {},
377
- ...excludePaths
378
- }) : target.searchByVector(options.source.vectorForFunction(source.id), {
379
- ...includeDescriptions ? {
380
- descriptionVector: options.source.vectorForFunction(source.id, "description"),
381
- fileDescriptionVector: options.source.vectorForFile(source.path)
382
- } : {},
383
- limit,
384
- minSimilarity: options.minSimilarity ?? -1,
385
- ...options.maxSimilarity !== void 0 ? { maxSimilarity: options.maxSimilarity } : {},
386
- minLines,
387
- ...options.nameRegex !== void 0 ? { nameRegex: options.nameRegex } : {},
388
- ...excludePaths
389
- });
390
- const rankedCandidates = options.cohesion ? candidates.map((match) => ({
391
- ...match,
392
- physicalDistance: cohesionLocation(source.path, match.function.path).physicalDistance + Number(rootsDiffer)
393
- })).sort((left, right) => right.physicalDistance - left.physicalDistance || right.similarity - left.similarity || left.function.path.localeCompare(right.function.path) || left.function.startLine - right.function.startLine || left.function.id - right.function.id) : candidates;
394
- const matches = sameIndex && !options.includeSymmetricDuplicates ? rankedCandidates.filter((match) => {
395
- const pair = source.id < match.function.id ? `${source.id}:${match.function.id}` : `${match.function.id}:${source.id}`;
396
- if (seenPairs.has(pair)) return false;
397
- seenPairs.add(pair);
398
- return true;
399
- }) : rankedCandidates;
400
- if (matches.length > 0) yield { source, matches, scoring };
401
- options.onProgress?.({ completed: index + 1, total: sourceFunctions.length });
402
- }
403
- }
404
- async function canonicalRoot(root) {
405
- try {
406
- return await realpath(root);
407
- } catch {
408
- return path2.resolve(root);
409
- }
410
- }
411
- async function fileIdentity(filePath) {
412
- try {
413
- const metadata = await stat(filePath);
414
- return `inode:${metadata.dev}:${metadata.ino}`;
415
- } catch {
416
- return `path:${path2.resolve(filePath)}`;
417
- }
418
- }
419
-
420
- export {
421
- analyzeCohesion,
422
- cohesionLocation,
423
- crossSearch
424
- };
425
- //# sourceMappingURL=chunk-KUV6PKKL.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/search/cross-search.ts","../src/analysis/cohesion.ts"],"sourcesContent":["import { realpath, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { IncompatibleIndexError } from \"../errors.js\";\nimport { cohesionLocation } from \"../analysis/cohesion.js\";\nimport type { CrossSearchOptions, CrossSearchResult } from \"../types.js\";\nimport { assertPositiveInteger, compileNameRegex, throwIfAborted } from \"../utils.js\";\nimport { analysisSimilarity } from \"./similarity.js\";\n\nexport async function* crossSearch(options: CrossSearchOptions): AsyncGenerator<CrossSearchResult> {\n const target = options.target ?? options.source;\n const sourceStatus = options.source.status();\n const targetStatus = target.status();\n const sourceProfile = JSON.stringify(sourceStatus.embeddingProfile);\n const targetProfile = JSON.stringify(targetStatus.embeddingProfile);\n if (sourceProfile !== targetProfile) {\n throw new IncompatibleIndexError(\"Cross-search requires identical embedding profiles.\");\n }\n const scoring = {\n ...analysisSimilarity(sourceStatus, targetStatus),\n sourceDescriptionProfile: sourceStatus.descriptionProfile,\n targetDescriptionProfile: targetStatus.descriptionProfile,\n };\n const includeDescriptions = scoring.similarityMode === \"code-description-file-average\";\n\n const limit = options.limitPerFunction ?? 5;\n assertPositiveInteger(limit, \"limitPerFunction\");\n const minLines = options.minLines ?? 2;\n assertPositiveInteger(minLines, \"minLines\");\n const nameRegex = compileNameRegex(options.nameRegex);\n const sourceFunctions = (await options.source.sourceFunctions(options.sourceFilter ?? { type: \"all\" }))\n .filter((callable) => callable.lineCount >= minLines && (!nameRegex || nameRegex.test(callable.qualifiedName)));\n const sameIndex = target.indexPath === options.source.indexPath\n || await fileIdentity(target.indexPath) === await fileIdentity(options.source.indexPath);\n const sourceRoot = options.crossFileOnly ? await canonicalRoot(options.source.rootDir) : undefined;\n const targetRoot = options.crossFileOnly\n ? sameIndex ? sourceRoot! : await canonicalRoot(target.rootDir)\n : undefined;\n const rootsDiffer = options.cohesion && !sameIndex\n ? await canonicalRoot(options.source.rootDir) !== await canonicalRoot(target.rootDir)\n : false;\n const canonicalFiles = new Map<string, Promise<string>>();\n const canonicalFile = (root: string, filePath: string): Promise<string> => {\n const absolutePath = path.resolve(root, filePath);\n let result = canonicalFiles.get(absolutePath);\n if (!result) {\n result = fileIdentity(absolutePath);\n canonicalFiles.set(absolutePath, result);\n }\n return result;\n };\n const targetPathsByCanonicalFile = new Map<string, string[]>();\n if (targetRoot) {\n const targetPaths = [...new Set(target.allFunctions().map((callable) => callable.path))];\n await Promise.all(targetPaths.map(async (targetPath) => {\n const canonicalPath = await canonicalFile(targetRoot, targetPath);\n const paths = targetPathsByCanonicalFile.get(canonicalPath) ?? [];\n paths.push(targetPath);\n targetPathsByCanonicalFile.set(canonicalPath, paths);\n }));\n }\n const seenPairs = new Set<string>();\n const useCache = sameIndex && !options.source.readOnly;\n if (useCache) {\n await options.source.refreshSimilarityCache({\n width: Math.min(200, Math.max(50, limit * 5)),\n minSimilarity: options.minSimilarity ?? -1,\n ...(options.signal ? { signal: options.signal } : {}),\n ...(options.onCacheProgress ? { onProgress: options.onCacheProgress } : {}),\n });\n }\n // One validity snapshot for the whole run: per-function lookups must not\n // re-read the state tables for every source.\n const cacheReader = useCache ? options.source.cachedSimilarityReader({ includeDescriptions }) : undefined;\n for (let index = 0; index < sourceFunctions.length; index += 1) {\n throwIfAborted(options.signal);\n const source = sourceFunctions[index]!;\n const canonicalSourceFile = sourceRoot ? await canonicalFile(sourceRoot, source.path) : undefined;\n const excludedTargetPaths = canonicalSourceFile ? targetPathsByCanonicalFile.get(canonicalSourceFile) : undefined;\n const excludePaths = excludedTargetPaths ? { excludePaths: excludedTargetPaths } : {};\n const similar = cacheReader ? cacheReader.similarToFunction : options.source.similarToFunction.bind(options.source);\n const candidates = sameIndex\n ? similar(source.id, {\n includeDescriptions,\n limit,\n minSimilarity: options.minSimilarity ?? -1,\n ...(options.maxSimilarity !== undefined ? { maxSimilarity: options.maxSimilarity } : {}),\n minLines,\n ...(options.nameRegex !== undefined ? { nameRegex: options.nameRegex } : {}),\n ...excludePaths,\n })\n : target.searchByVector(options.source.vectorForFunction(source.id), {\n ...(includeDescriptions ? {\n descriptionVector: options.source.vectorForFunction(source.id, \"description\"),\n fileDescriptionVector: options.source.vectorForFile(source.path),\n } : {}),\n limit,\n minSimilarity: options.minSimilarity ?? -1,\n ...(options.maxSimilarity !== undefined ? { maxSimilarity: options.maxSimilarity } : {}),\n minLines,\n ...(options.nameRegex !== undefined ? { nameRegex: options.nameRegex } : {}),\n ...excludePaths,\n });\n const rankedCandidates = options.cohesion\n ? candidates.map((match) => ({\n ...match,\n physicalDistance: cohesionLocation(source.path, match.function.path).physicalDistance + Number(rootsDiffer),\n })).sort((left, right) => right.physicalDistance - left.physicalDistance\n || right.similarity - left.similarity\n || left.function.path.localeCompare(right.function.path)\n || left.function.startLine - right.function.startLine\n || left.function.id - right.function.id)\n : candidates;\n const matches = sameIndex && !options.includeSymmetricDuplicates\n ? rankedCandidates.filter((match) => {\n const pair = source.id < match.function.id\n ? `${source.id}:${match.function.id}`\n : `${match.function.id}:${source.id}`;\n if (seenPairs.has(pair)) return false;\n seenPairs.add(pair);\n return true;\n })\n : rankedCandidates;\n if (matches.length > 0) yield { source, matches, scoring };\n options.onProgress?.({ completed: index + 1, total: sourceFunctions.length });\n }\n}\n\nasync function canonicalRoot(root: string): Promise<string> {\n try {\n return await realpath(root);\n } catch {\n return path.resolve(root);\n }\n}\n\nasync function fileIdentity(filePath: string): Promise<string> {\n try {\n const metadata = await stat(filePath);\n return `inode:${metadata.dev}:${metadata.ino}`;\n } catch {\n return `path:${path.resolve(filePath)}`;\n }\n}\n","import path from \"node:path\";\n\nimport { CodeIndexError } from \"../errors.js\";\nimport { analysisSimilarity } from \"../search/similarity.js\";\nimport type {\n CohesionAnalysisOptions,\n CohesionFileReport,\n CohesionGroup,\n CohesionLocation,\n CohesionPair,\n CohesionReport,\n IndexedFunction,\n SimilarityResult,\n SimilarityScores,\n} from \"../types.js\";\nimport { assertPositiveInteger, compileNameRegex, throwIfAborted } from \"../utils.js\";\n\ninterface CandidateEdge extends SimilarityScores {\n left: IndexedFunction;\n right: IndexedFunction;\n}\n\nexport async function analyzeCohesion(options: CohesionAnalysisOptions): Promise<CohesionReport> {\n const status = options.source.status();\n const scoring = analysisSimilarity(status);\n const neighbors = options.neighbors ?? 20;\n const limit = options.limit ?? 50;\n const minSimilarity = options.minSimilarity ?? 0.8;\n const minLines = options.minLines ?? 2;\n const sourceFilter = options.sourceFilter ?? { type: \"all\" };\n assertPositiveInteger(neighbors, \"neighbors\");\n assertPositiveInteger(limit, \"limit\");\n assertPositiveInteger(minLines, \"minLines\");\n if (!Number.isFinite(minSimilarity) || minSimilarity < -1 || minSimilarity >= 1) {\n throw new CodeIndexError(\"minSimilarity must be at least -1 and less than 1.\");\n }\n if (options.maxSimilarity !== undefined\n && (!Number.isFinite(options.maxSimilarity) || options.maxSimilarity <= minSimilarity)) {\n throw new CodeIndexError(\"maxSimilarity must be greater than minSimilarity.\");\n }\n const nameRegex = compileNameRegex(options.nameRegex);\n const candidateFunctions = options.source.allFunctions()\n .filter((callable) => callable.lineCount >= minLines && (!nameRegex || nameRegex.test(callable.qualifiedName)));\n const candidateIds = new Set(candidateFunctions.map((callable) => callable.id));\n const sourceFunctions = (await options.source.sourceFunctions(sourceFilter))\n .filter((callable) => candidateIds.has(callable.id));\n\n const neighborCache = new Map<number, Set<number>>();\n const useCache = !options.source.readOnly;\n if (useCache) {\n await options.source.refreshSimilarityCache({\n width: Math.min(200, Math.max(50, neighbors * 5)),\n minSimilarity,\n ...(options.signal ? { signal: options.signal } : {}),\n ...(options.onCacheProgress ? { onProgress: options.onCacheProgress } : {}),\n });\n }\n // One validity snapshot for the whole run: per-function lookups must not\n // re-read the state tables for every source.\n const cacheReader = useCache ? options.source.cachedSimilarityReader({\n includeDescriptions: scoring.similarityMode === \"code-description-file-average\",\n }) : undefined;\n const neighborsFor = (callable: IndexedFunction): SimilarityResult[] => {\n throwIfAborted(options.signal);\n const query = {\n includeDescriptions: scoring.similarityMode === \"code-description-file-average\",\n limit: neighbors,\n minSimilarity,\n ...(options.maxSimilarity !== undefined ? { maxSimilarity: options.maxSimilarity } : {}),\n minLines,\n ...(options.nameRegex !== undefined ? { nameRegex: options.nameRegex } : {}),\n };\n const matches = (cacheReader ? cacheReader.similarToFunction(callable.id, query)\n : options.source.similarToFunction(callable.id, query))\n .filter((match) => candidateIds.has(match.function.id));\n neighborCache.set(callable.id, new Set(matches.map((match) => match.function.id)));\n return matches;\n };\n\n const edges = new Map<string, CandidateEdge>();\n for (let index = 0; index < sourceFunctions.length; index += 1) {\n const source = sourceFunctions[index]!;\n for (const match of neighborsFor(source)) {\n const [left, right] = orderedFunctions(source, match.function);\n const key = pairKey(left.id, right.id);\n const existing = edges.get(key);\n if (!existing || match.similarity > existing.similarity) {\n const { function: _function, ...scores } = match;\n edges.set(key, { left, right, ...scores });\n }\n }\n options.onProgress?.({ completed: index + 1, total: sourceFunctions.length });\n }\n\n const scoredPairs = [...edges.values()].map((edge) => {\n const leftNeighbors = neighborCache.get(edge.left.id);\n const rightNeighbors = neighborCache.get(edge.right.id);\n const reciprocal = leftNeighbors && rightNeighbors\n ? leftNeighbors.has(edge.right.id) && rightNeighbors.has(edge.left.id)\n : null;\n return scorePair(edge, reciprocal, minSimilarity);\n }).sort(comparePairs);\n scoredPairs.forEach((pair, index) => {\n pair.rank = index + 1;\n });\n\n const repositoryScope = sourceFilter.type === \"all\" && sourceFilter.path === undefined && sourceFilter.nameRegex === undefined;\n const metrics = calculateMetrics(\n repositoryScope ? \"repository\" : \"selected-sources\",\n sourceFunctions.length,\n candidateFunctions.length,\n scoredPairs,\n );\n const reportedPairs = scoredPairs.slice(0, limit);\n const files = aggregateFiles(sourceFunctions, scoredPairs)\n .filter((file) => file.internalAffinity + file.sameFolderAffinity + file.externalAffinity > 0)\n .slice(0, limit);\n const groups = buildGroups(reportedPairs);\n return {\n schemaVersion: 3,\n repository: {\n generation: status.generation,\n gitCheckpoint: status.gitCheckpoint,\n embeddingProfile: status.embeddingProfile,\n descriptionProfile: status.descriptionProfile,\n },\n parameters: {\n ...scoring,\n neighbors,\n limit,\n minSimilarity,\n ...(options.maxSimilarity !== undefined ? { maxSimilarity: options.maxSimilarity } : {}),\n minLines,\n ...(options.nameRegex !== undefined ? { nameRegex: options.nameRegex } : {}),\n sourceFilter,\n },\n metrics,\n pairs: reportedPairs,\n files,\n groups,\n };\n}\n\nexport function cohesionLocation(leftPath: string, rightPath: string): CohesionLocation {\n const normalizedLeft = leftPath.replaceAll(\"\\\\\", \"/\");\n const normalizedRight = rightPath.replaceAll(\"\\\\\", \"/\");\n const leftDirectory = directoryParts(normalizedLeft);\n const rightDirectory = directoryParts(normalizedRight);\n let commonLength = 0;\n while (commonLength < leftDirectory.length\n && commonLength < rightDirectory.length\n && leftDirectory[commonLength] === rightDirectory[commonLength]) {\n commonLength += 1;\n }\n const commonParts = leftDirectory.slice(0, commonLength);\n const commonAncestor = commonParts.length > 0 ? commonParts.join(\"/\") : null;\n const sameFile = normalizedLeft === normalizedRight;\n const folderHops = sameFile\n ? 0\n : leftDirectory.length - commonLength + rightDirectory.length - commonLength;\n return {\n category: sameFile ? \"same-file\" : folderHops === 0 ? \"same-folder\" : \"different-folder\",\n physicalDistance: sameFile ? 0 : 1 + folderHops,\n folderHops,\n commonAncestor,\n sourceTestPair: isTestPath(normalizedLeft) !== isTestPath(normalizedRight),\n };\n}\n\nfunction scorePair(edge: CandidateEdge, reciprocal: boolean | null, minSimilarity: number): CohesionPair {\n const location = cohesionLocation(edge.left.path, edge.right.path);\n const semanticWeight = clamp((edge.similarity - minSimilarity) / (1 - minSimilarity));\n const separationWeight = 1 - Math.exp(-location.physicalDistance / 2);\n return {\n rank: 0,\n left: edge.left,\n right: edge.right,\n similarity: edge.similarity,\n ...(edge.codeSimilarity !== undefined ? { codeSimilarity: edge.codeSimilarity } : {}),\n ...(edge.descriptionSimilarity !== undefined ? { descriptionSimilarity: edge.descriptionSimilarity } : {}),\n ...(edge.fileDescriptionSimilarity !== undefined ? { fileDescriptionSimilarity: edge.fileDescriptionSimilarity } : {}),\n reciprocal,\n semanticWeight,\n separationWeight,\n cohesionGap: semanticWeight * separationWeight,\n location,\n };\n}\n\nfunction calculateMetrics(\n scope: CohesionReport[\"metrics\"][\"scope\"],\n functionsAnalyzed: number,\n candidateFunctions: number,\n pairs: readonly CohesionPair[],\n): CohesionReport[\"metrics\"] {\n const totalWeight = pairs.reduce((sum, pair) => sum + pair.semanticWeight, 0);\n const sameFileWeight = pairs\n .filter((pair) => pair.location.category === \"same-file\")\n .reduce((sum, pair) => sum + pair.semanticWeight, 0);\n const sameFolderWeight = pairs\n .filter((pair) => pair.location.category === \"same-folder\")\n .reduce((sum, pair) => sum + pair.semanticWeight, 0);\n const remoteWeight = pairs\n .filter((pair) => pair.location.category === \"different-folder\")\n .reduce((sum, pair) => sum + pair.semanticWeight, 0);\n const weightedDistance = pairs.reduce(\n (sum, pair) => sum + pair.semanticWeight * pair.location.physicalDistance,\n 0,\n );\n return {\n scope,\n functionsAnalyzed,\n candidateFunctions,\n semanticEdges: pairs.length,\n sameFileRatio: ratio(sameFileWeight, totalWeight),\n sameFolderRatio: ratio(sameFolderWeight, totalWeight),\n remoteRatio: ratio(remoteWeight, totalWeight),\n weightedMeanDistance: ratio(weightedDistance, totalWeight),\n };\n}\n\nfunction aggregateFiles(\n sourceFunctions: readonly IndexedFunction[],\n pairs: readonly CohesionPair[],\n): CohesionFileReport[] {\n const sourceIds = new Set(sourceFunctions.map((callable) => callable.id));\n const counts = new Map<string, number>();\n for (const callable of sourceFunctions) counts.set(callable.path, (counts.get(callable.path) ?? 0) + 1);\n const reports = new Map<string, CohesionFileReport>();\n for (const [filePath, functionCount] of counts) {\n reports.set(filePath, {\n path: filePath,\n functionCount,\n internalAffinity: 0,\n sameFolderAffinity: 0,\n externalAffinity: 0,\n externalAffinityRatio: 0,\n });\n }\n for (const pair of pairs) {\n if (sourceIds.has(pair.left.id)) addFileAffinity(reports.get(pair.left.path)!, pair.right, pair);\n if (sourceIds.has(pair.right.id)) addFileAffinity(reports.get(pair.right.path)!, pair.left, pair);\n }\n for (const report of reports.values()) {\n const total = report.internalAffinity + report.sameFolderAffinity + report.externalAffinity;\n report.externalAffinityRatio = ratio(report.externalAffinity, total);\n }\n return [...reports.values()].sort((left, right) => right.externalAffinityRatio - left.externalAffinityRatio\n || right.externalAffinity - left.externalAffinity\n || left.path.localeCompare(right.path));\n}\n\nfunction addFileAffinity(\n report: CohesionFileReport,\n match: IndexedFunction,\n pair: CohesionPair,\n): void {\n if (pair.location.category === \"same-file\") report.internalAffinity += pair.semanticWeight;\n else if (pair.location.category === \"same-folder\") report.sameFolderAffinity += pair.semanticWeight;\n else {\n report.externalAffinity += pair.semanticWeight;\n updateStrongestExternal(report, match, pair);\n }\n}\n\nfunction updateStrongestExternal(\n report: CohesionFileReport,\n callable: IndexedFunction,\n pair: CohesionPair,\n): void {\n if (!report.strongestExternalMatch || pair.cohesionGap > report.strongestExternalMatch.cohesionGap) {\n report.strongestExternalMatch = {\n function: callable,\n similarity: pair.similarity,\n ...(pair.codeSimilarity !== undefined ? { codeSimilarity: pair.codeSimilarity } : {}),\n ...(pair.descriptionSimilarity !== undefined ? { descriptionSimilarity: pair.descriptionSimilarity } : {}),\n ...(pair.fileDescriptionSimilarity !== undefined ? { fileDescriptionSimilarity: pair.fileDescriptionSimilarity } : {}),\n cohesionGap: pair.cohesionGap,\n };\n }\n}\n\nfunction buildGroups(pairs: readonly CohesionPair[]): CohesionGroup[] {\n const functions = new Map<number, IndexedFunction>();\n const neighbors = new Map<number, Set<number>>();\n for (const pair of pairs) {\n functions.set(pair.left.id, pair.left);\n functions.set(pair.right.id, pair.right);\n addNeighbor(neighbors, pair.left.id, pair.right.id);\n addNeighbor(neighbors, pair.right.id, pair.left.id);\n }\n const seen = new Set<number>();\n const groups: CohesionGroup[] = [];\n for (const start of neighbors.keys()) {\n if (seen.has(start)) continue;\n const pending = [start];\n const memberIds = new Set<number>();\n while (pending.length > 0) {\n const id = pending.pop()!;\n if (seen.has(id)) continue;\n seen.add(id);\n memberIds.add(id);\n for (const neighbor of neighbors.get(id) ?? []) pending.push(neighbor);\n }\n const groupPairs = pairs.filter((pair) => memberIds.has(pair.left.id) && memberIds.has(pair.right.id));\n const members = [...memberIds].map((id) => functions.get(id)!).sort(compareFunctions);\n groups.push({\n rank: 0,\n memberCount: members.length,\n fileCount: new Set(members.map((member) => member.path)).size,\n minimumEdgeSimilarity: Math.min(...groupPairs.map((pair) => pair.similarity)),\n maximumEdgeSimilarity: Math.max(...groupPairs.map((pair) => pair.similarity)),\n maximumPhysicalDistance: Math.max(...groupPairs.map((pair) => pair.location.physicalDistance)),\n maximumCohesionGap: Math.max(...groupPairs.map((pair) => pair.cohesionGap)),\n members,\n });\n }\n groups.sort((left, right) => right.maximumCohesionGap - left.maximumCohesionGap\n || right.memberCount - left.memberCount\n || compareFunctions(left.members[0]!, right.members[0]!));\n groups.forEach((group, index) => {\n group.rank = index + 1;\n });\n return groups;\n}\n\nfunction directoryParts(filePath: string): string[] {\n const directory = path.posix.dirname(filePath.replaceAll(\"\\\\\", \"/\"));\n return directory === \".\" ? [] : directory.split(\"/\").filter(Boolean);\n}\n\nfunction isTestPath(filePath: string): boolean {\n const original = filePath.replaceAll(\"\\\\\", \"/\");\n const normalized = original.toLowerCase();\n const segments = normalized.split(\"/\");\n const fileName = segments.at(-1) ?? \"\";\n return segments.some((segment) => segment === \"test\" || segment === \"tests\" || segment === \"__tests__\")\n || /\\.(?:test|spec)\\.[cm]?[jt]sx?$/.test(fileName)\n || /_test\\.go$/.test(fileName)\n || /^(?:test_.+|.+_test)\\.(?:py|pyw|rs|c|h)$/.test(fileName)\n || /^(?:Test.+|.+Tests?|.+TestCase)\\.java$/.test(original.split(\"/\").at(-1) ?? \"\");\n}\n\nfunction orderedFunctions(left: IndexedFunction, right: IndexedFunction): [IndexedFunction, IndexedFunction] {\n return compareFunctions(left, right) <= 0 ? [left, right] : [right, left];\n}\n\nfunction compareFunctions(left: IndexedFunction, right: IndexedFunction): number {\n return left.path.localeCompare(right.path)\n || left.startLine - right.startLine\n || left.startColumn - right.startColumn\n || left.qualifiedName.localeCompare(right.qualifiedName)\n || left.id - right.id;\n}\n\nfunction comparePairs(left: CohesionPair, right: CohesionPair): number {\n return right.cohesionGap - left.cohesionGap\n || right.similarity - left.similarity\n || Number(right.reciprocal === true) - Number(left.reciprocal === true)\n || compareFunctions(left.left, right.left)\n || compareFunctions(left.right, right.right);\n}\n\nfunction pairKey(leftId: number, rightId: number): string {\n return leftId < rightId ? `${leftId}:${rightId}` : `${rightId}:${leftId}`;\n}\n\nfunction addNeighbor(neighbors: Map<number, Set<number>>, source: number, target: number): void {\n const values = neighbors.get(source) ?? new Set<number>();\n values.add(target);\n neighbors.set(source, values);\n}\n\nfunction ratio(numerator: number, denominator: number): number {\n return denominator === 0 ? 0 : numerator / denominator;\n}\n\nfunction clamp(value: number): number {\n return Math.max(0, Math.min(1, value));\n}\n"],"mappings":"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","names":["path","path"]}
@@ -1,74 +0,0 @@
1
- import {
2
- requestEmbeddings
3
- } from "./chunk-JAVPHCZH.js";
4
- import {
5
- reportModelCall
6
- } from "./chunk-BXWO2KMC.js";
7
- import {
8
- DEFAULT_PARALLELISM
9
- } from "./chunk-VH5VGRCI.js";
10
-
11
- // src/embeddings/openai.ts
12
- import { createOpenAI } from "@ai-sdk/openai";
13
- import { embed, embedMany } from "ai";
14
- import { Tiktoken } from "js-tiktoken/lite";
15
- import cl100kBase from "js-tiktoken/ranks/cl100k_base";
16
- var MAX_INPUT_TOKENS = 8192;
17
- var tokenizer;
18
- function truncateInput(input) {
19
- tokenizer ??= new Tiktoken(cl100kBase);
20
- const tokens = tokenizer.encode(input, [], []);
21
- return tokens.length <= MAX_INPUT_TOKENS ? input : tokenizer.decode(tokens.slice(0, MAX_INPUT_TOKENS));
22
- }
23
- var OpenAIEmbeddingProvider = class {
24
- profile;
25
- #model;
26
- #verbose;
27
- #parallelism;
28
- constructor(options = {}) {
29
- const apiKey = options.apiKey ?? process.env.OPENAI_API_KEY ?? "";
30
- if (!apiKey) throw new Error("OPENAI_API_KEY is required.");
31
- const model = options.model ?? "text-embedding-3-large";
32
- const dimensions = options.dimensions ?? 3072;
33
- if (!Number.isInteger(dimensions) || dimensions < 1) throw new Error("dimensions must be a positive integer.");
34
- this.profile = { provider: "openai", model, dimensions, strategyVersion: "callable-v2" };
35
- this.#verbose = options.verbose ?? false;
36
- this.#parallelism = options.parallelism ?? DEFAULT_PARALLELISM;
37
- this.#model = createOpenAI({
38
- apiKey,
39
- baseURL: (options.baseUrl ?? "https://api.openai.com/v1").replace(/\/$/, "")
40
- }).embeddingModel(model);
41
- }
42
- async embedDocuments(inputs, options) {
43
- if (inputs.length === 0) return [];
44
- reportModelCall("vectors", this.profile, this.#verbose, this.#parallelism);
45
- return requestEmbeddings(
46
- embedMany({
47
- model: this.#model,
48
- values: inputs.map(truncateInput),
49
- providerOptions: { openai: { dimensions: this.profile.dimensions } },
50
- ...options?.signal ? { abortSignal: options.signal } : {}
51
- }).then(({ embeddings }) => embeddings),
52
- this.profile.dimensions,
53
- options?.signal
54
- );
55
- }
56
- async embedQuery(input, options) {
57
- reportModelCall("vectors", this.profile, this.#verbose, 1);
58
- return (await requestEmbeddings(
59
- embed({
60
- model: this.#model,
61
- value: truncateInput(input),
62
- providerOptions: { openai: { dimensions: this.profile.dimensions } },
63
- ...options?.signal ? { abortSignal: options.signal } : {}
64
- }).then(({ embedding }) => [embedding]),
65
- this.profile.dimensions,
66
- options?.signal
67
- ))[0];
68
- }
69
- };
70
-
71
- export {
72
- OpenAIEmbeddingProvider
73
- };
74
- //# sourceMappingURL=chunk-MKUTTXZB.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/embeddings/openai.ts"],"sourcesContent":["import { createOpenAI } from \"@ai-sdk/openai\";\nimport { embed, embedMany } from \"ai\";\nimport { Tiktoken } from \"js-tiktoken/lite\";\nimport cl100kBase from \"js-tiktoken/ranks/cl100k_base\";\n\nimport type { EmbeddingProvider } from \"../types.js\";\nimport { reportModelCall } from \"../model-call-notice.js\";\nimport { DEFAULT_PARALLELISM } from \"../utils.js\";\nimport { requestEmbeddings } from \"./ai-sdk.js\";\n\nconst MAX_INPUT_TOKENS = 8192;\nlet tokenizer: Tiktoken | undefined;\n\nfunction truncateInput(input: string): string {\n tokenizer ??= new Tiktoken(cl100kBase);\n const tokens = tokenizer.encode(input, [], []);\n return tokens.length <= MAX_INPUT_TOKENS ? input : tokenizer.decode(tokens.slice(0, MAX_INPUT_TOKENS));\n}\n\nexport interface OpenAIEmbeddingProviderOptions {\n apiKey?: string;\n model?: string;\n dimensions?: number;\n baseUrl?: string;\n verbose?: boolean;\n parallelism?: number;\n}\n\nexport class OpenAIEmbeddingProvider implements EmbeddingProvider {\n public readonly profile;\n readonly #model;\n readonly #verbose: boolean;\n readonly #parallelism: number;\n\n public constructor(options: OpenAIEmbeddingProviderOptions = {}) {\n const apiKey = options.apiKey ?? process.env.OPENAI_API_KEY ?? \"\";\n if (!apiKey) throw new Error(\"OPENAI_API_KEY is required.\");\n const model = options.model ?? \"text-embedding-3-large\";\n const dimensions = options.dimensions ?? 3072;\n if (!Number.isInteger(dimensions) || dimensions < 1) throw new Error(\"dimensions must be a positive integer.\");\n this.profile = { provider: \"openai\", model, dimensions, strategyVersion: \"callable-v2\" } as const;\n this.#verbose = options.verbose ?? false;\n this.#parallelism = options.parallelism ?? DEFAULT_PARALLELISM;\n this.#model = createOpenAI({\n apiKey,\n baseURL: (options.baseUrl ?? \"https://api.openai.com/v1\").replace(/\\/$/, \"\"),\n }).embeddingModel(model);\n }\n\n public async embedDocuments(inputs: readonly string[], options?: { signal?: AbortSignal }): Promise<number[][]> {\n if (inputs.length === 0) return [];\n reportModelCall(\"vectors\", this.profile, this.#verbose, this.#parallelism);\n return requestEmbeddings(\n embedMany({\n model: this.#model,\n values: inputs.map(truncateInput),\n providerOptions: { openai: { dimensions: this.profile.dimensions } },\n ...(options?.signal ? { abortSignal: options.signal } : {}),\n }).then(({ embeddings }) => embeddings),\n this.profile.dimensions,\n options?.signal,\n );\n }\n\n public async embedQuery(input: string, options?: { signal?: AbortSignal }): Promise<number[]> {\n reportModelCall(\"vectors\", this.profile, this.#verbose, 1);\n return (await requestEmbeddings(\n embed({\n model: this.#model,\n value: truncateInput(input),\n providerOptions: { openai: { dimensions: this.profile.dimensions } },\n ...(options?.signal ? { abortSignal: options.signal } : {}),\n }).then(({ embedding }) => [embedding]),\n this.profile.dimensions,\n options?.signal,\n ))[0]!;\n }\n}\n"],"mappings":";;;;;;;;;;;AAAA,SAAS,oBAAoB;AAC7B,SAAS,OAAO,iBAAiB;AACjC,SAAS,gBAAgB;AACzB,OAAO,gBAAgB;AAOvB,IAAM,mBAAmB;AACzB,IAAI;AAEJ,SAAS,cAAc,OAAuB;AAC5C,gBAAc,IAAI,SAAS,UAAU;AACrC,QAAM,SAAS,UAAU,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC;AAC7C,SAAO,OAAO,UAAU,mBAAmB,QAAQ,UAAU,OAAO,OAAO,MAAM,GAAG,gBAAgB,CAAC;AACvG;AAWO,IAAM,0BAAN,MAA2D;AAAA,EAChD;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EAEF,YAAY,UAA0C,CAAC,GAAG;AAC/D,UAAM,SAAS,QAAQ,UAAU,QAAQ,IAAI,kBAAkB;AAC/D,QAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,6BAA6B;AAC1D,UAAM,QAAQ,QAAQ,SAAS;AAC/B,UAAM,aAAa,QAAQ,cAAc;AACzC,QAAI,CAAC,OAAO,UAAU,UAAU,KAAK,aAAa,EAAG,OAAM,IAAI,MAAM,wCAAwC;AAC7G,SAAK,UAAU,EAAE,UAAU,UAAU,OAAO,YAAY,iBAAiB,cAAc;AACvF,SAAK,WAAW,QAAQ,WAAW;AACnC,SAAK,eAAe,QAAQ,eAAe;AAC3C,SAAK,SAAS,aAAa;AAAA,MACzB;AAAA,MACA,UAAU,QAAQ,WAAW,6BAA6B,QAAQ,OAAO,EAAE;AAAA,IAC7E,CAAC,EAAE,eAAe,KAAK;AAAA,EACzB;AAAA,EAEA,MAAa,eAAe,QAA2B,SAAyD;AAC9G,QAAI,OAAO,WAAW,EAAG,QAAO,CAAC;AACjC,oBAAgB,WAAW,KAAK,SAAS,KAAK,UAAU,KAAK,YAAY;AACzE,WAAO;AAAA,MACL,UAAU;AAAA,QACR,OAAO,KAAK;AAAA,QACZ,QAAQ,OAAO,IAAI,aAAa;AAAA,QAChC,iBAAiB,EAAE,QAAQ,EAAE,YAAY,KAAK,QAAQ,WAAW,EAAE;AAAA,QACnE,GAAI,SAAS,SAAS,EAAE,aAAa,QAAQ,OAAO,IAAI,CAAC;AAAA,MAC3D,CAAC,EAAE,KAAK,CAAC,EAAE,WAAW,MAAM,UAAU;AAAA,MACtC,KAAK,QAAQ;AAAA,MACb,SAAS;AAAA,IACX;AAAA,EACF;AAAA,EAEA,MAAa,WAAW,OAAe,SAAuD;AAC5F,oBAAgB,WAAW,KAAK,SAAS,KAAK,UAAU,CAAC;AACzD,YAAQ,MAAM;AAAA,MACZ,MAAM;AAAA,QACJ,OAAO,KAAK;AAAA,QACZ,OAAO,cAAc,KAAK;AAAA,QAC1B,iBAAiB,EAAE,QAAQ,EAAE,YAAY,KAAK,QAAQ,WAAW,EAAE;AAAA,QACnE,GAAI,SAAS,SAAS,EAAE,aAAa,QAAQ,OAAO,IAAI,CAAC;AAAA,MAC3D,CAAC,EAAE,KAAK,CAAC,EAAE,UAAU,MAAM,CAAC,SAAS,CAAC;AAAA,MACtC,KAAK,QAAQ;AAAA,MACb,SAAS;AAAA,IACX,GAAG,CAAC;AAAA,EACN;AACF;","names":[]}