@aiderdesk/tree-sitter-utils 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BENCHMARK.md +112 -0
- package/README.md +34 -0
- package/dist/__tests__/mention-extractor.test.d.ts +2 -0
- package/dist/__tests__/mention-extractor.test.d.ts.map +1 -0
- package/dist/__tests__/tree-renderer.test.d.ts +2 -0
- package/dist/__tests__/tree-renderer.test.d.ts.map +1 -0
- package/dist/cache-manager.d.ts +7 -4
- package/dist/cache-manager.d.ts.map +1 -1
- package/dist/dependency-index.d.ts +9 -0
- package/dist/dependency-index.d.ts.map +1 -0
- package/dist/file-utils.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +670 -487
- package/dist/index.js.map +4 -4
- package/dist/mention-extractor.d.ts +4 -0
- package/dist/mention-extractor.d.ts.map +1 -0
- package/dist/repo-map-service.d.ts +28 -0
- package/dist/repo-map-service.d.ts.map +1 -0
- package/dist/repo-map-worker.d.ts +2 -0
- package/dist/repo-map-worker.d.ts.map +1 -0
- package/dist/repo-map-worker.js +907 -0
- package/dist/repo-map-worker.js.map +7 -0
- package/dist/symbol-extractor.d.ts +6 -2
- package/dist/symbol-extractor.d.ts.map +1 -1
- package/dist/tree-renderer.d.ts +1 -1
- package/dist/tree-renderer.d.ts.map +1 -1
- package/dist/tree-sitter-analyzer.d.ts +10 -4
- package/dist/tree-sitter-analyzer.d.ts.map +1 -1
- package/dist/tree-sitter-parser.d.ts +6 -5
- package/dist/tree-sitter-parser.d.ts.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +11 -8
- package/resources/queries/c_sharp-tags.scm +9 -4
package/dist/index.js
CHANGED
|
@@ -97,6 +97,7 @@ __export(index_exports, {
|
|
|
97
97
|
CacheManager: () => CacheManager,
|
|
98
98
|
GraphBuilder: () => GraphBuilder,
|
|
99
99
|
PageRankCalculator: () => PageRankCalculator,
|
|
100
|
+
RepoMapService: () => RepoMapService,
|
|
100
101
|
SymbolExtractor: () => SymbolExtractor,
|
|
101
102
|
TreeRenderer: () => TreeRenderer,
|
|
102
103
|
TreeSitterAnalyzer: () => TreeSitterAnalyzer,
|
|
@@ -116,6 +117,9 @@ __export(index_exports, {
|
|
|
116
117
|
});
|
|
117
118
|
module.exports = __toCommonJS(index_exports);
|
|
118
119
|
|
|
120
|
+
// src/tree-sitter-analyzer.ts
|
|
121
|
+
var path9 = __toESM(require("path"));
|
|
122
|
+
|
|
119
123
|
// src/tree-sitter-parser.ts
|
|
120
124
|
var fs2 = __toESM(require("fs/promises"));
|
|
121
125
|
var path4 = __toESM(require("path"));
|
|
@@ -210,8 +214,8 @@ var LANGUAGE_WASM_MAP = {
|
|
|
210
214
|
file: "tree-sitter-ocaml_interface.wasm"
|
|
211
215
|
},
|
|
212
216
|
c_sharp: {
|
|
213
|
-
url: "https://unpkg.com/tree-sitter-c-sharp/tree-sitter-c_sharp.wasm",
|
|
214
|
-
file: "tree-sitter-c_sharp.wasm"
|
|
217
|
+
url: "https://unpkg.com/tree-sitter-c-sharp@0.23.5/tree-sitter-c_sharp.wasm",
|
|
218
|
+
file: "tree-sitter-c_sharp-0.23.5.wasm"
|
|
215
219
|
},
|
|
216
220
|
haskell: {
|
|
217
221
|
url: "https://unpkg.com/tree-sitter-haskell/tree-sitter-haskell.wasm",
|
|
@@ -223,11 +227,11 @@ var LANGUAGE_WASM_MAP = {
|
|
|
223
227
|
}
|
|
224
228
|
};
|
|
225
229
|
function getCacheDir() {
|
|
226
|
-
const
|
|
230
|
+
const platform2 = os.platform();
|
|
227
231
|
const homeDir = os.homedir();
|
|
228
|
-
if (
|
|
232
|
+
if (platform2 === "darwin") {
|
|
229
233
|
return path2.join(homeDir, "Library", "Caches", "aiderdesk", "tree-sitter");
|
|
230
|
-
} else if (
|
|
234
|
+
} else if (platform2 === "win32") {
|
|
231
235
|
return path2.join(process.env.APPDATA || path2.join(homeDir, "AppData", "Roaming"), "aiderdesk", "tree-sitter");
|
|
232
236
|
} else {
|
|
233
237
|
return path2.join(homeDir, ".cache", "aiderdesk", "tree-sitter");
|
|
@@ -286,175 +290,179 @@ var QUERIES_DIR = path3.join(RESOURCES_DIR, "queries");
|
|
|
286
290
|
var WASM_DIR = path3.join(RESOURCES_DIR, "wasm");
|
|
287
291
|
|
|
288
292
|
// src/tree-sitter-parser.ts
|
|
293
|
+
var runtimeInitialization;
|
|
289
294
|
var TreeSitterParser = class {
|
|
290
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
291
295
|
parser = null;
|
|
292
296
|
languages = /* @__PURE__ */ new Map();
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
297
|
+
initialization;
|
|
298
|
+
queue = Promise.resolve();
|
|
299
|
+
initialize() {
|
|
300
|
+
this.initialization ??= this.initializeParser();
|
|
301
|
+
return this.initialization;
|
|
302
|
+
}
|
|
303
|
+
async initializeParser() {
|
|
304
|
+
runtimeInitialization ??= import_web_tree_sitter.Parser.init();
|
|
305
|
+
await runtimeInitialization;
|
|
306
|
+
this.parser = new import_web_tree_sitter.Parser();
|
|
307
|
+
}
|
|
308
|
+
loadLanguage(name) {
|
|
309
|
+
let pending = this.languages.get(name);
|
|
310
|
+
if (!pending) {
|
|
311
|
+
pending = (async () => {
|
|
312
|
+
const buffer = await downloadWasmForLanguage(name);
|
|
313
|
+
if (!buffer) {
|
|
314
|
+
throw new Error(`No WASM available for ${name}`);
|
|
315
|
+
}
|
|
316
|
+
const language = await import_web_tree_sitter.Language.load(buffer);
|
|
317
|
+
const source = await fs2.readFile(path4.join(QUERIES_DIR, `${name}-tags.scm`), "utf-8");
|
|
318
|
+
const query = new import_web_tree_sitter.Query(language, source);
|
|
319
|
+
if (name === "c_sharp") {
|
|
320
|
+
for (const capture of query.captureNames) {
|
|
321
|
+
if (!capture.startsWith("name.")) query.disableCapture(capture);
|
|
310
322
|
}
|
|
311
323
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
logger_default.info(`[TreeSitterParser] Initialized with ${this.queries.size} language queries`);
|
|
317
|
-
} catch (error) {
|
|
318
|
-
logger_default.error("[TreeSitterParser] Initialization failed:", error);
|
|
319
|
-
throw error;
|
|
324
|
+
return { language, query };
|
|
325
|
+
})();
|
|
326
|
+
this.languages.set(name, pending);
|
|
327
|
+
void pending.catch(() => this.languages.delete(name));
|
|
320
328
|
}
|
|
329
|
+
return pending;
|
|
321
330
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
try {
|
|
327
|
-
const wasmBuffer = await downloadWasmForLanguage(language);
|
|
328
|
-
if (!wasmBuffer) {
|
|
329
|
-
return null;
|
|
330
|
-
}
|
|
331
|
-
const lang = await import_web_tree_sitter.Language.load(wasmBuffer);
|
|
332
|
-
this.languages.set(language, lang);
|
|
333
|
-
return lang;
|
|
334
|
-
} catch (error) {
|
|
335
|
-
logger_default.error(`[TreeSitterParser] Failed to load language ${language}:`, error);
|
|
336
|
-
return null;
|
|
337
|
-
}
|
|
331
|
+
parseFile(filePath, rootDir, compactReferences = false) {
|
|
332
|
+
const result = this.queue.then(() => this.parse(filePath, rootDir, compactReferences));
|
|
333
|
+
this.queue = result.catch(() => void 0);
|
|
334
|
+
return result;
|
|
338
335
|
}
|
|
339
|
-
async
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
if (!this.parser) {
|
|
336
|
+
async parse(filePath, rootDir, compactReferences = false) {
|
|
337
|
+
await this.initialize();
|
|
338
|
+
const name = detectLanguage(filePath);
|
|
339
|
+
if (!name || !this.parser) {
|
|
344
340
|
return [];
|
|
345
341
|
}
|
|
346
|
-
const language =
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
return [];
|
|
353
|
-
}
|
|
354
|
-
const query = this.queries.get(language);
|
|
355
|
-
if (!query) {
|
|
356
|
-
return [];
|
|
342
|
+
const { language, query } = await this.loadLanguage(name);
|
|
343
|
+
const source = await fs2.readFile(filePath, "utf-8");
|
|
344
|
+
this.parser.setLanguage(language);
|
|
345
|
+
const tree = this.parser.parse(source);
|
|
346
|
+
if (!tree) {
|
|
347
|
+
throw new Error(`Unable to parse ${filePath}`);
|
|
357
348
|
}
|
|
358
349
|
try {
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
return [];
|
|
364
|
-
}
|
|
365
|
-
const tags = this.extractTags(tree.rootNode, query, filePath, lang, rootDir);
|
|
366
|
-
return tags;
|
|
367
|
-
} catch (error) {
|
|
368
|
-
logger_default.error(`[TreeSitterParser] Error parsing file ${filePath}:`, error);
|
|
369
|
-
return [];
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
extractTags(node, query, filePath, language, rootDir) {
|
|
373
|
-
const tags = [];
|
|
374
|
-
try {
|
|
375
|
-
const queryObj = new import_web_tree_sitter.Query(language, query);
|
|
376
|
-
const matches = queryObj.matches(node);
|
|
377
|
-
logger_default.debug(`[TreeSitterParser] Found ${matches.length} matches in ${filePath}`);
|
|
378
|
-
const relFname = rootDir ? path4.relative(rootDir, filePath) : filePath;
|
|
379
|
-
for (const match of matches) {
|
|
350
|
+
const tags = [];
|
|
351
|
+
const references = /* @__PURE__ */ new Map();
|
|
352
|
+
const relative5 = rootDir ? path4.relative(rootDir, filePath).replaceAll(path4.sep, "/") : filePath;
|
|
353
|
+
for (const match of query.matches(tree.rootNode)) {
|
|
380
354
|
for (const capture of match.captures) {
|
|
381
|
-
if (
|
|
382
|
-
|
|
355
|
+
if (capture.name.startsWith("name.")) {
|
|
356
|
+
const kind = capture.name.includes("definition") ? "def" : "ref";
|
|
357
|
+
const name2 = capture.node.text;
|
|
358
|
+
const previous = compactReferences && kind === "ref" ? references.get(name2) : void 0;
|
|
359
|
+
if (previous) {
|
|
360
|
+
previous.count = (previous.count ?? 1) + 1;
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
const tag = {
|
|
364
|
+
rel_fname: relative5,
|
|
365
|
+
fname: filePath,
|
|
366
|
+
line: capture.node.startPosition.row,
|
|
367
|
+
// Detach sliced node text so cached tags cannot retain entire source files.
|
|
368
|
+
name: Buffer.from(name2, "utf8").toString("utf8"),
|
|
369
|
+
kind
|
|
370
|
+
};
|
|
371
|
+
tags.push(tag);
|
|
372
|
+
if (compactReferences && kind === "ref") references.set(tag.name, tag);
|
|
383
373
|
}
|
|
384
|
-
const name = capture.node.text;
|
|
385
|
-
const kind = this.determineKind(capture.name);
|
|
386
|
-
const line = capture.node.startPosition.row;
|
|
387
|
-
logger_default.debug(`[TreeSitterParser] Found tag: ${name} (${kind}) at line ${line}`);
|
|
388
|
-
tags.push({
|
|
389
|
-
rel_fname: relFname,
|
|
390
|
-
fname: filePath,
|
|
391
|
-
line,
|
|
392
|
-
name,
|
|
393
|
-
kind
|
|
394
|
-
});
|
|
395
374
|
}
|
|
396
375
|
}
|
|
397
|
-
|
|
398
|
-
|
|
376
|
+
return tags;
|
|
377
|
+
} finally {
|
|
378
|
+
tree.delete();
|
|
399
379
|
}
|
|
400
|
-
return tags;
|
|
401
380
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
381
|
+
async dispose() {
|
|
382
|
+
await this.queue;
|
|
383
|
+
for (const pending of this.languages.values()) {
|
|
384
|
+
try {
|
|
385
|
+
const { query } = await pending;
|
|
386
|
+
query.delete();
|
|
387
|
+
} catch {
|
|
388
|
+
}
|
|
405
389
|
}
|
|
406
|
-
|
|
390
|
+
this.languages.clear();
|
|
391
|
+
this.parser?.delete();
|
|
392
|
+
this.parser = null;
|
|
393
|
+
this.initialization = void 0;
|
|
407
394
|
}
|
|
408
395
|
};
|
|
409
396
|
|
|
410
397
|
// src/symbol-extractor.ts
|
|
398
|
+
var fs3 = __toESM(require("fs/promises"));
|
|
399
|
+
var path5 = __toESM(require("path"));
|
|
411
400
|
init_language_detector();
|
|
412
401
|
var SymbolExtractor = class {
|
|
413
402
|
constructor(parser) {
|
|
414
403
|
this.parser = parser;
|
|
415
404
|
}
|
|
416
405
|
cacheManager = null;
|
|
406
|
+
memory = /* @__PURE__ */ new Map();
|
|
407
|
+
lastRoot;
|
|
408
|
+
lastFiles = "";
|
|
409
|
+
revision = 0;
|
|
417
410
|
setCacheManager(cacheManager) {
|
|
418
411
|
this.cacheManager = cacheManager;
|
|
419
412
|
}
|
|
420
|
-
async extractTags(files, rootDir) {
|
|
413
|
+
async extractTags(files, rootDir, signal, compactReferences = false) {
|
|
421
414
|
const allTags = [];
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
415
|
+
const supported = files.filter(isLanguageSupported);
|
|
416
|
+
const fileKey = JSON.stringify([compactReferences, supported]);
|
|
417
|
+
if (this.lastRoot !== rootDir || this.lastFiles !== fileKey) {
|
|
418
|
+
this.revision++;
|
|
419
|
+
this.memory.clear();
|
|
420
|
+
this.lastRoot = rootDir;
|
|
421
|
+
this.lastFiles = fileKey;
|
|
422
|
+
}
|
|
423
|
+
for (let i = 0; i < supported.length; i += 50) {
|
|
424
|
+
signal?.throwIfAborted();
|
|
425
|
+
const results = await Promise.all(
|
|
426
|
+
supported.slice(i, i + 50).map(async (file) => {
|
|
429
427
|
try {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
428
|
+
const before = await fs3.stat(file);
|
|
429
|
+
const fingerprint = `${compactReferences}:${before.mtimeMs}:${before.ctimeMs}:${before.size}`;
|
|
430
|
+
const memory = this.memory.get(file);
|
|
431
|
+
if (this.cacheManager && memory?.fingerprint === fingerprint) {
|
|
432
|
+
return memory.tags;
|
|
433
|
+
}
|
|
434
|
+
this.revision++;
|
|
435
|
+
const cached = await this.cacheManager?.getFileCache(file);
|
|
436
|
+
let tags;
|
|
437
|
+
if (cached?.fingerprint === fingerprint) {
|
|
438
|
+
const relative5 = rootDir ? path5.relative(rootDir, file).replaceAll(path5.sep, "/") : file;
|
|
439
|
+
tags = cached.tags.map((tag) => ({
|
|
440
|
+
...tag,
|
|
441
|
+
rel_fname: relative5
|
|
442
|
+
}));
|
|
443
|
+
} else {
|
|
444
|
+
tags = await this.parser.parseFile(file, rootDir, compactReferences);
|
|
445
|
+
const after = await fs3.stat(file);
|
|
446
|
+
if (`${compactReferences}:${after.mtimeMs}:${after.ctimeMs}:${after.size}` !== fingerprint) {
|
|
445
447
|
return tags;
|
|
446
448
|
}
|
|
449
|
+
await this.cacheManager?.setFileCache(file, before.mtimeMs, tags, fingerprint);
|
|
447
450
|
}
|
|
448
|
-
|
|
451
|
+
this.memory.set(file, { fingerprint, tags });
|
|
452
|
+
return tags;
|
|
449
453
|
} catch (error) {
|
|
454
|
+
this.revision++;
|
|
455
|
+
this.memory.delete(file);
|
|
450
456
|
logger_default.error(`[SymbolExtractor] Error processing ${file}:`, error);
|
|
451
457
|
return [];
|
|
452
458
|
}
|
|
453
459
|
})
|
|
454
460
|
);
|
|
455
|
-
|
|
461
|
+
for (const tags of results) {
|
|
462
|
+
for (const tag of tags) allTags.push(tag);
|
|
463
|
+
}
|
|
456
464
|
}
|
|
457
|
-
|
|
465
|
+
signal?.throwIfAborted();
|
|
458
466
|
return allTags;
|
|
459
467
|
}
|
|
460
468
|
filterDefinitions(tags) {
|
|
@@ -474,163 +482,93 @@ var SymbolExtractor = class {
|
|
|
474
482
|
}
|
|
475
483
|
};
|
|
476
484
|
|
|
477
|
-
// src/
|
|
478
|
-
var
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
const references = /* @__PURE__ */ new Map();
|
|
485
|
+
// src/dependency-index.ts
|
|
486
|
+
var DependencyIndex = class {
|
|
487
|
+
definitions = [];
|
|
488
|
+
relations = [];
|
|
489
|
+
outbound = /* @__PURE__ */ new Map();
|
|
490
|
+
constructor(tags) {
|
|
484
491
|
const definitions = /* @__PURE__ */ new Map();
|
|
492
|
+
const references = /* @__PURE__ */ new Map();
|
|
485
493
|
for (const tag of tags) {
|
|
486
494
|
if (tag.kind === "def") {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
} else if (tag.kind === "ref") {
|
|
497
|
-
if (!references.has(tag.name)) {
|
|
498
|
-
references.set(tag.name, []);
|
|
499
|
-
}
|
|
500
|
-
references.get(tag.name).push(tag.rel_fname);
|
|
495
|
+
this.definitions.push(tag);
|
|
496
|
+
let files = definitions.get(tag.name);
|
|
497
|
+
if (!files) definitions.set(tag.name, files = /* @__PURE__ */ new Set());
|
|
498
|
+
files.add(tag.rel_fname);
|
|
499
|
+
this.outbound.set(tag.rel_fname, 0);
|
|
500
|
+
} else {
|
|
501
|
+
let counts = references.get(tag.name);
|
|
502
|
+
if (!counts) references.set(tag.name, counts = /* @__PURE__ */ new Map());
|
|
503
|
+
counts.set(tag.rel_fname, (counts.get(tag.rel_fname) ?? 0) + (tag.count ?? 1));
|
|
501
504
|
}
|
|
502
505
|
}
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
for (const ref of referencers) {
|
|
519
|
-
refCounts.set(ref, (refCounts.get(ref) || 0) + 1);
|
|
520
|
-
}
|
|
521
|
-
for (const referencer of referencers) {
|
|
522
|
-
const numRefs = refCounts.get(referencer) || 1;
|
|
523
|
-
const weight = mul * Math.sqrt(numRefs);
|
|
524
|
-
for (const definer of definers) {
|
|
525
|
-
if (referencer !== definer) {
|
|
526
|
-
if (!graph.hasNode(referencer)) {
|
|
527
|
-
graph.addNode(referencer);
|
|
528
|
-
}
|
|
529
|
-
if (!graph.hasNode(definer)) {
|
|
530
|
-
graph.addNode(definer);
|
|
531
|
-
}
|
|
532
|
-
const edgeKey = `${referencer}->${definer}:${ident}`;
|
|
533
|
-
if (!graph.hasEdge(edgeKey)) {
|
|
534
|
-
graph.addEdgeWithKey(edgeKey, referencer, definer, { weight, ident });
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
}
|
|
506
|
+
for (const [name, definers] of definitions) {
|
|
507
|
+
const counts = references.get(name);
|
|
508
|
+
if (!counts) continue;
|
|
509
|
+
let multiplier = 1;
|
|
510
|
+
if (/^[a-z][a-z0-9_]*$/.test(name) && name.includes("_") || /^[a-z][a-z0-9-]*$/.test(name) && name.includes("-") || /^[a-z][a-zA-Z0-9]*$/.test(name) && /[A-Z]/.test(name))
|
|
511
|
+
multiplier *= 10;
|
|
512
|
+
if (name.startsWith("_")) multiplier *= 0.1;
|
|
513
|
+
if (definers.size > 5) multiplier *= 0.1;
|
|
514
|
+
const sources = /* @__PURE__ */ new Map();
|
|
515
|
+
for (const [file, count] of counts) {
|
|
516
|
+
const targets = definers.size - Number(definers.has(file));
|
|
517
|
+
if (!targets) continue;
|
|
518
|
+
const weight = multiplier * Math.sqrt(count);
|
|
519
|
+
sources.set(file, weight);
|
|
520
|
+
this.outbound.set(file, (this.outbound.get(file) ?? 0) + weight * targets);
|
|
538
521
|
}
|
|
522
|
+
if (sources.size) this.relations.push({ definers, sources });
|
|
539
523
|
}
|
|
540
|
-
logger_default.info(`[GraphBuilder] Built graph with ${graph.order} nodes and ${graph.size} edges`);
|
|
541
|
-
return { graph, defines, references };
|
|
542
|
-
}
|
|
543
|
-
isSnakeCase(str) {
|
|
544
|
-
return /^[a-z][a-z0-9_]*$/.test(str) && str.includes("_");
|
|
545
524
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
return [];
|
|
560
|
-
}
|
|
561
|
-
const personalization = {};
|
|
562
|
-
const nodes = graph.nodes();
|
|
563
|
-
const personalize = nodes.length > 0 ? 100 / nodes.length : 0;
|
|
564
|
-
for (const node of nodes) {
|
|
565
|
-
let score = 0;
|
|
566
|
-
if (mentionedFiles?.has(node)) {
|
|
567
|
-
score += personalize;
|
|
525
|
+
rank(mentionedFiles = /* @__PURE__ */ new Set(), mentionedIdents = /* @__PURE__ */ new Set()) {
|
|
526
|
+
const nodes = [...this.outbound.keys()];
|
|
527
|
+
const count = nodes.length;
|
|
528
|
+
if (!count) return [];
|
|
529
|
+
const mentionedDefinitions = new Set(this.definitions.filter((tag) => mentionedIdents.has(tag.name)).map((tag) => tag.rel_fname));
|
|
530
|
+
const weights = new Map(nodes.map((node) => [node, Number(mentionedFiles.has(node)) + Number(mentionedDefinitions.has(node))]));
|
|
531
|
+
const total = [...weights.values()].reduce((sum, weight) => sum + weight, 0);
|
|
532
|
+
const personalization = new Map(nodes.map((node) => [node, total ? weights.get(node) / total : 1 / count]));
|
|
533
|
+
let ranks = new Map(nodes.map((node) => [node, 1 / count]));
|
|
534
|
+
for (let iteration = 0; iteration < 100; iteration++) {
|
|
535
|
+
let dangling = 0;
|
|
536
|
+
for (const node of nodes) {
|
|
537
|
+
if (!this.outbound.get(node)) dangling += ranks.get(node);
|
|
568
538
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
539
|
+
const base = 0.85 * dangling + 0.15;
|
|
540
|
+
const next = new Map(nodes.map((node) => [node, base * personalization.get(node)]));
|
|
541
|
+
for (const { definers, sources } of this.relations) {
|
|
542
|
+
let incoming = 0;
|
|
543
|
+
for (const [source, weight] of sources) {
|
|
544
|
+
incoming += 0.85 * ranks.get(source) * weight / this.outbound.get(source);
|
|
573
545
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
}
|
|
579
|
-
try {
|
|
580
|
-
const pagerankOptions = {
|
|
581
|
-
alpha: 0.85,
|
|
582
|
-
maxIterations: 100,
|
|
583
|
-
tolerance: 1e-6
|
|
584
|
-
};
|
|
585
|
-
if (Object.keys(personalization).length > 0) {
|
|
586
|
-
pagerankOptions.personalization = personalization;
|
|
587
|
-
}
|
|
588
|
-
const ranks = (0, import_pagerank.default)(graph, pagerankOptions);
|
|
589
|
-
const rankedDefinitions = [];
|
|
590
|
-
const definitions = tags.filter((t) => t.kind === "def");
|
|
591
|
-
for (const def of definitions) {
|
|
592
|
-
const nodeRank = ranks[def.rel_fname] || 0;
|
|
593
|
-
let edgeRank = nodeRank;
|
|
594
|
-
if (graph.hasNode(def.rel_fname)) {
|
|
595
|
-
graph.forEachOutboundEdge(def.rel_fname, (edge) => {
|
|
596
|
-
const weight = graph.getEdgeAttribute(edge, "weight");
|
|
597
|
-
const totalWeight = this.getTotalOutboundWeight(graph, def.rel_fname);
|
|
598
|
-
if (totalWeight > 0) {
|
|
599
|
-
edgeRank += nodeRank * (weight / totalWeight);
|
|
600
|
-
}
|
|
601
|
-
});
|
|
546
|
+
for (const target of definers) {
|
|
547
|
+
const self = sources.get(target);
|
|
548
|
+
const contribution = incoming - (self ? 0.85 * ranks.get(target) * self / this.outbound.get(target) : 0);
|
|
549
|
+
next.set(target, next.get(target) + contribution);
|
|
602
550
|
}
|
|
603
|
-
rankedDefinitions.push({
|
|
604
|
-
rel_fname: def.rel_fname,
|
|
605
|
-
name: def.name,
|
|
606
|
-
rank: edgeRank,
|
|
607
|
-
line: def.line
|
|
608
|
-
});
|
|
609
551
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
return [];
|
|
552
|
+
let error = 0;
|
|
553
|
+
for (const node of nodes) error += Math.abs(next.get(node) - ranks.get(node));
|
|
554
|
+
ranks = next;
|
|
555
|
+
if (error < count * 1e-6) break;
|
|
556
|
+
if (iteration === 99) throw new Error("Repository map PageRank did not converge");
|
|
616
557
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
graph.forEachOutboundEdge(node, (edge) => {
|
|
624
|
-
total += graph.getEdgeAttribute(edge, "weight") || 1;
|
|
625
|
-
});
|
|
626
|
-
return total;
|
|
558
|
+
return this.definitions.map((tag) => ({
|
|
559
|
+
rel_fname: tag.rel_fname,
|
|
560
|
+
name: tag.name,
|
|
561
|
+
line: tag.line,
|
|
562
|
+
rank: ranks.get(tag.rel_fname) * (this.outbound.get(tag.rel_fname) ? 2 : 1)
|
|
563
|
+
})).sort((a, b) => b.rank - a.rank);
|
|
627
564
|
}
|
|
628
565
|
};
|
|
629
566
|
|
|
630
567
|
// src/tree-renderer.ts
|
|
631
|
-
var
|
|
568
|
+
var fs4 = __toESM(require("fs/promises"));
|
|
569
|
+
var path6 = __toESM(require("path"));
|
|
632
570
|
var TreeRenderer = class {
|
|
633
|
-
async render(rankedDefinitions, chatFiles, maxLines) {
|
|
571
|
+
async render(rankedDefinitions, chatFiles, maxLines, root = process.cwd()) {
|
|
634
572
|
const filtered = rankedDefinitions.filter((def) => !chatFiles.has(def.rel_fname));
|
|
635
573
|
const byFile = /* @__PURE__ */ new Map();
|
|
636
574
|
for (const def of filtered) {
|
|
@@ -638,24 +576,26 @@ var TreeRenderer = class {
|
|
|
638
576
|
fileDefs.push(def);
|
|
639
577
|
byFile.set(def.rel_fname, fileDefs);
|
|
640
578
|
}
|
|
579
|
+
const maximumRanks = /* @__PURE__ */ new Map();
|
|
580
|
+
for (const def of filtered) maximumRanks.set(def.rel_fname, Math.max(maximumRanks.get(def.rel_fname) ?? -Infinity, def.rank));
|
|
641
581
|
const sortedFiles = Array.from(byFile.entries()).sort((a, b) => {
|
|
642
|
-
const maxRankA =
|
|
643
|
-
const maxRankB =
|
|
582
|
+
const maxRankA = maximumRanks.get(a[0]);
|
|
583
|
+
const maxRankB = maximumRanks.get(b[0]);
|
|
644
584
|
return maxRankB - maxRankA;
|
|
645
585
|
});
|
|
646
586
|
const totalFiles = sortedFiles.length;
|
|
647
587
|
if (!maxLines) {
|
|
648
|
-
return this.renderAllFiles(sortedFiles);
|
|
588
|
+
return this.renderAllFiles(sortedFiles, root);
|
|
649
589
|
}
|
|
650
|
-
if (totalFiles > maxLines) {
|
|
590
|
+
if (totalFiles * 2 > maxLines) {
|
|
651
591
|
return this.renderFileListOnly(sortedFiles, maxLines);
|
|
652
592
|
}
|
|
653
593
|
const headerLines = totalFiles;
|
|
654
594
|
const availableLines = maxLines - headerLines;
|
|
655
595
|
const linesPerFile = Math.max(1, Math.floor(availableLines / totalFiles));
|
|
656
|
-
return this.renderWithLineLimit(sortedFiles, linesPerFile);
|
|
596
|
+
return this.renderWithLineLimit(sortedFiles, linesPerFile, root);
|
|
657
597
|
}
|
|
658
|
-
async renderAllFiles(sortedFiles) {
|
|
598
|
+
async renderAllFiles(sortedFiles, root) {
|
|
659
599
|
let output = "";
|
|
660
600
|
for (const [file, definitions] of sortedFiles) {
|
|
661
601
|
output += `
|
|
@@ -663,7 +603,7 @@ ${file}:
|
|
|
663
603
|
`;
|
|
664
604
|
definitions.sort((a, b) => a.line - b.line);
|
|
665
605
|
const lines = Array.from(new Set(definitions.map((d) => d.line)));
|
|
666
|
-
const treeLines = await this.renderFileTree(file, lines, Infinity);
|
|
606
|
+
const treeLines = await this.renderFileTree(path6.resolve(root, file), lines, Infinity);
|
|
667
607
|
output += treeLines;
|
|
668
608
|
}
|
|
669
609
|
return output.trim();
|
|
@@ -680,7 +620,7 @@ ${file}:
|
|
|
680
620
|
}
|
|
681
621
|
return output.trim();
|
|
682
622
|
}
|
|
683
|
-
async renderWithLineLimit(sortedFiles, linesPerFile) {
|
|
623
|
+
async renderWithLineLimit(sortedFiles, linesPerFile, root) {
|
|
684
624
|
let output = "";
|
|
685
625
|
for (const [file, definitions] of sortedFiles) {
|
|
686
626
|
definitions.sort((a, b) => {
|
|
@@ -694,7 +634,7 @@ ${file}:
|
|
|
694
634
|
const isTruncated = uniqueLines.length > linesPerFile;
|
|
695
635
|
output += `${file}:
|
|
696
636
|
`;
|
|
697
|
-
const treeLines = await this.renderFileTree(file, limitedLines, linesPerFile, isTruncated);
|
|
637
|
+
const treeLines = await this.renderFileTree(path6.resolve(root, file), limitedLines, linesPerFile, isTruncated);
|
|
698
638
|
output += treeLines;
|
|
699
639
|
}
|
|
700
640
|
return output.trim();
|
|
@@ -704,7 +644,7 @@ ${file}:
|
|
|
704
644
|
return "";
|
|
705
645
|
}
|
|
706
646
|
try {
|
|
707
|
-
const content = await
|
|
647
|
+
const content = await fs4.readFile(filePath, "utf-8");
|
|
708
648
|
const fileLines = content.split("\n");
|
|
709
649
|
let output = "";
|
|
710
650
|
const maxContentLines = isTruncated ? maxLines - 1 : maxLines;
|
|
@@ -733,119 +673,102 @@ ${file}:
|
|
|
733
673
|
|
|
734
674
|
// src/cache-manager.ts
|
|
735
675
|
var import_node_sqlite = require("node:sqlite");
|
|
736
|
-
var
|
|
737
|
-
var
|
|
676
|
+
var fs5 = __toESM(require("fs/promises"));
|
|
677
|
+
var path7 = __toESM(require("path"));
|
|
738
678
|
var os2 = __toESM(require("os"));
|
|
739
|
-
|
|
740
|
-
const
|
|
741
|
-
const
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
cacheDir = path5.join(homeDir, "Library", "Caches", "aiderdesk", "tree-sitter");
|
|
745
|
-
} else if (platform3 === "win32") {
|
|
746
|
-
cacheDir = path5.join(process.env.APPDATA || path5.join(homeDir, "AppData", "Roaming"), "aiderdesk", "tree-sitter");
|
|
747
|
-
} else {
|
|
748
|
-
cacheDir = path5.join(homeDir, ".cache", "aiderdesk", "tree-sitter");
|
|
749
|
-
}
|
|
750
|
-
return path5.join(cacheDir, "cache.db");
|
|
751
|
-
}
|
|
679
|
+
var getCacheDbPath = () => {
|
|
680
|
+
const home = os2.homedir();
|
|
681
|
+
const directory = process.platform === "darwin" ? path7.join(home, "Library", "Caches") : process.platform === "win32" ? process.env.APPDATA || path7.join(home, "AppData", "Roaming") : path7.join(home, ".cache");
|
|
682
|
+
return path7.join(directory, "aiderdesk", "tree-sitter", "cache.db");
|
|
683
|
+
};
|
|
752
684
|
var CacheManager = class {
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
constructor(dbPath) {
|
|
756
|
-
this.dbPath = dbPath || getCacheDbPath();
|
|
685
|
+
constructor(dbPath = getCacheDbPath()) {
|
|
686
|
+
this.dbPath = dbPath;
|
|
757
687
|
}
|
|
688
|
+
db = null;
|
|
689
|
+
select;
|
|
690
|
+
insert;
|
|
758
691
|
async initialize() {
|
|
759
692
|
try {
|
|
760
|
-
await
|
|
693
|
+
await fs5.mkdir(path7.dirname(this.dbPath), { recursive: true });
|
|
761
694
|
this.db = new import_node_sqlite.DatabaseSync(this.dbPath);
|
|
762
|
-
|
|
763
|
-
|
|
695
|
+
this.db.exec("PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL; PRAGMA busy_timeout = 1000;");
|
|
696
|
+
this.db.exec(`
|
|
697
|
+
CREATE TABLE IF NOT EXISTS file_cache_v3 (
|
|
764
698
|
file_path TEXT PRIMARY KEY,
|
|
765
|
-
mtime
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
699
|
+
mtime REAL NOT NULL,
|
|
700
|
+
fingerprint TEXT NOT NULL,
|
|
701
|
+
tags TEXT NOT NULL
|
|
769
702
|
)
|
|
770
|
-
|
|
771
|
-
this.db.
|
|
772
|
-
|
|
703
|
+
`);
|
|
704
|
+
this.select = this.db.prepare("SELECT mtime, fingerprint, tags FROM file_cache_v3 WHERE file_path = ?");
|
|
705
|
+
this.insert = this.db.prepare(`
|
|
706
|
+
INSERT INTO file_cache_v3 (file_path, mtime, fingerprint, tags) VALUES (?, ?, ?, ?)
|
|
707
|
+
ON CONFLICT(file_path) DO UPDATE SET mtime = excluded.mtime, fingerprint = excluded.fingerprint, tags = excluded.tags
|
|
708
|
+
`);
|
|
773
709
|
} catch (error) {
|
|
774
|
-
logger_default.
|
|
775
|
-
this.
|
|
710
|
+
logger_default.warn("[CacheManager] Persistent cache unavailable:", error);
|
|
711
|
+
this.close();
|
|
776
712
|
}
|
|
777
713
|
}
|
|
778
|
-
async getFileCache(
|
|
779
|
-
if (!this.db) {
|
|
780
|
-
return null;
|
|
781
|
-
}
|
|
714
|
+
async getFileCache(file) {
|
|
782
715
|
try {
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
const
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
716
|
+
const result = this.select?.get(file);
|
|
717
|
+
if (!result) return null;
|
|
718
|
+
const stored = JSON.parse(result.tags);
|
|
719
|
+
return {
|
|
720
|
+
...result,
|
|
721
|
+
tags: stored.tags.map(([line, name, kind, count]) => ({
|
|
722
|
+
fname: file,
|
|
723
|
+
rel_fname: stored.relative,
|
|
724
|
+
line,
|
|
725
|
+
name,
|
|
726
|
+
kind,
|
|
727
|
+
count
|
|
728
|
+
}))
|
|
729
|
+
};
|
|
791
730
|
} catch (error) {
|
|
792
|
-
logger_default.
|
|
731
|
+
logger_default.warn(`[CacheManager] Failed to read ${file}:`, error);
|
|
793
732
|
return null;
|
|
794
733
|
}
|
|
795
734
|
}
|
|
796
|
-
async setFileCache(
|
|
797
|
-
if (!this.db) {
|
|
798
|
-
return;
|
|
799
|
-
}
|
|
735
|
+
async setFileCache(file, mtime, tags, fingerprint = String(mtime)) {
|
|
800
736
|
try {
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
tags
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
stmt.run(filePath, mtime, JSON.stringify(tags));
|
|
811
|
-
logger_default.debug(`[CacheManager] Cached ${tags.length} tags for ${filePath}`);
|
|
737
|
+
this.insert?.run(
|
|
738
|
+
file,
|
|
739
|
+
mtime,
|
|
740
|
+
fingerprint,
|
|
741
|
+
JSON.stringify({
|
|
742
|
+
relative: tags[0]?.rel_fname ?? file,
|
|
743
|
+
tags: tags.map((tag) => [tag.line, tag.name, tag.kind, tag.count])
|
|
744
|
+
})
|
|
745
|
+
);
|
|
812
746
|
} catch (error) {
|
|
813
|
-
logger_default.
|
|
747
|
+
logger_default.warn(`[CacheManager] Failed to cache ${file}:`, error);
|
|
814
748
|
}
|
|
815
749
|
}
|
|
816
|
-
async getFileMtime(
|
|
750
|
+
async getFileMtime(file) {
|
|
817
751
|
try {
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
} catch (error) {
|
|
821
|
-
logger_default.error(`[CacheManager] Failed to get mtime for ${filePath}:`, error);
|
|
752
|
+
return (await fs5.stat(file)).mtimeMs;
|
|
753
|
+
} catch {
|
|
822
754
|
return null;
|
|
823
755
|
}
|
|
824
756
|
}
|
|
825
757
|
async clearCache() {
|
|
826
|
-
|
|
827
|
-
return;
|
|
828
|
-
}
|
|
829
|
-
try {
|
|
830
|
-
const sql = "DELETE FROM file_cache";
|
|
831
|
-
this.db.exec(sql);
|
|
832
|
-
logger_default.info("[CacheManager] Cache cleared");
|
|
833
|
-
} catch (error) {
|
|
834
|
-
logger_default.error("[CacheManager] Failed to clear cache:", error);
|
|
835
|
-
}
|
|
758
|
+
this.db?.exec("DELETE FROM file_cache_v3");
|
|
836
759
|
}
|
|
837
760
|
close() {
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
}
|
|
761
|
+
this.select = void 0;
|
|
762
|
+
this.insert = void 0;
|
|
763
|
+
this.db?.close();
|
|
764
|
+
this.db = null;
|
|
843
765
|
}
|
|
844
766
|
};
|
|
845
767
|
|
|
846
768
|
// src/file-utils.ts
|
|
847
|
-
var
|
|
769
|
+
var path8 = __toESM(require("path"));
|
|
848
770
|
var import_glob = require("glob");
|
|
771
|
+
var import_minimatch = require("minimatch");
|
|
849
772
|
var import_simple_git = require("simple-git");
|
|
850
773
|
var DEFAULT_IGNORE_PATTERNS = [
|
|
851
774
|
"node_modules/**",
|
|
@@ -865,170 +788,430 @@ var DEFAULT_IGNORE_PATTERNS = [
|
|
|
865
788
|
"yarn.lock",
|
|
866
789
|
"pnpm-lock.yaml"
|
|
867
790
|
];
|
|
868
|
-
var isGitRepository = async (baseDir) => {
|
|
869
|
-
try {
|
|
870
|
-
const git = (0, import_simple_git.simpleGit)(baseDir);
|
|
871
|
-
await git.revparse(["--git-dir"]);
|
|
872
|
-
return true;
|
|
873
|
-
} catch {
|
|
874
|
-
return false;
|
|
875
|
-
}
|
|
876
|
-
};
|
|
877
791
|
var getAllFilesFromGit = async (baseDir) => {
|
|
878
792
|
try {
|
|
879
|
-
const
|
|
880
|
-
|
|
881
|
-
const files = result.split("\0").filter(Boolean);
|
|
882
|
-
logger_default.debug(`Retrieved ${files.length} tracked files from Git`);
|
|
883
|
-
return files.map((file) => path6.resolve(baseDir, file));
|
|
884
|
-
} catch (gitError) {
|
|
885
|
-
logger_default.warn("Failed to get tracked files from Git, falling back to filesystem", gitError);
|
|
886
|
-
return [];
|
|
887
|
-
}
|
|
888
|
-
};
|
|
889
|
-
var getAllFilesFromFS = async (baseDir, ignore) => {
|
|
890
|
-
try {
|
|
891
|
-
const pattern = path6.join(baseDir, "**/*").replace(/\\/g, "/");
|
|
892
|
-
const files = await (0, import_glob.glob)(pattern, {
|
|
893
|
-
nodir: true,
|
|
894
|
-
absolute: true,
|
|
895
|
-
dot: false,
|
|
896
|
-
ignore
|
|
897
|
-
});
|
|
898
|
-
logger_default.debug(`Retrieved ${files.length} files from filesystem`);
|
|
899
|
-
return files;
|
|
793
|
+
const result = await (0, import_simple_git.simpleGit)(baseDir).raw(["ls-files", "-z"]);
|
|
794
|
+
return result.split(String.fromCharCode(0)).filter(Boolean).map((file) => path8.resolve(baseDir, file));
|
|
900
795
|
} catch (error) {
|
|
901
|
-
logger_default.
|
|
902
|
-
return
|
|
796
|
+
logger_default.debug("Git discovery unavailable, falling back to filesystem", error);
|
|
797
|
+
return null;
|
|
903
798
|
}
|
|
904
799
|
};
|
|
905
|
-
var matchesPattern = (filePath, pattern) => {
|
|
906
|
-
const regexPattern = pattern.replace(/\*\*/g, "<<DOUBLE_STAR>>").replace(/\*/g, "[^/]*").replace(/<<DOUBLE_STAR>>/g, ".*").replace(/\?/g, "[^/]");
|
|
907
|
-
const regex = new RegExp(regexPattern);
|
|
908
|
-
return regex.test(filePath);
|
|
909
|
-
};
|
|
910
|
-
var filterByExcludePatterns = (files, excludePatterns, baseDir) => {
|
|
911
|
-
return files.filter((file) => {
|
|
912
|
-
const relative3 = path6.relative(baseDir, file).replace(/\\/g, "/");
|
|
913
|
-
return !excludePatterns.some((pattern) => matchesPattern(relative3, pattern));
|
|
914
|
-
});
|
|
915
|
-
};
|
|
916
800
|
var getAllFiles = async (baseDir, excludePatterns = []) => {
|
|
917
|
-
const
|
|
801
|
+
const ignore = [...DEFAULT_IGNORE_PATTERNS, ...excludePatterns];
|
|
802
|
+
const matchers = ignore.map((pattern) => ({
|
|
803
|
+
regex: new import_minimatch.Minimatch(pattern, { dot: true }).makeRe(),
|
|
804
|
+
basename: !pattern.includes("/")
|
|
805
|
+
}));
|
|
806
|
+
const gitFiles = await getAllFilesFromGit(baseDir);
|
|
807
|
+
if (gitFiles !== null) {
|
|
808
|
+
return gitFiles.filter((file) => {
|
|
809
|
+
const relative5 = path8.relative(baseDir, file).split(path8.sep).join("/");
|
|
810
|
+
const basename2 = path8.basename(file);
|
|
811
|
+
return !matchers.some((matcher) => matcher.regex && matcher.regex.test(matcher.basename ? basename2 : relative5));
|
|
812
|
+
});
|
|
813
|
+
}
|
|
918
814
|
try {
|
|
919
|
-
|
|
920
|
-
const gitFiles = await getAllFilesFromGit(baseDir);
|
|
921
|
-
if (gitFiles.length > 0) {
|
|
922
|
-
return filterByExcludePatterns(gitFiles, excludePatterns, baseDir);
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
const fsFiles = await getAllFilesFromFS(baseDir, allIgnorePatterns);
|
|
926
|
-
return filterByExcludePatterns(fsFiles, excludePatterns, baseDir);
|
|
815
|
+
return await (0, import_glob.glob)("**/*", { cwd: baseDir, nodir: true, absolute: true, dot: false, ignore });
|
|
927
816
|
} catch (error) {
|
|
928
|
-
logger_default.error("Failed to
|
|
817
|
+
logger_default.error("Failed to discover repository files:", error);
|
|
929
818
|
return [];
|
|
930
819
|
}
|
|
931
820
|
};
|
|
932
821
|
|
|
933
822
|
// src/tree-sitter-analyzer.ts
|
|
934
823
|
var TreeSitterAnalyzer = class {
|
|
935
|
-
parser;
|
|
936
|
-
symbolExtractor;
|
|
937
|
-
|
|
938
|
-
pageRankCalculator;
|
|
939
|
-
treeRenderer;
|
|
824
|
+
parser = new TreeSitterParser();
|
|
825
|
+
symbolExtractor = new SymbolExtractor(this.parser);
|
|
826
|
+
treeRenderer = new TreeRenderer();
|
|
940
827
|
cacheManager;
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
828
|
+
initialization;
|
|
829
|
+
queue = Promise.resolve();
|
|
830
|
+
graph;
|
|
831
|
+
revision = -1;
|
|
832
|
+
outputs = /* @__PURE__ */ new Map();
|
|
833
|
+
constructor(options = {}) {
|
|
834
|
+
this.cacheManager = new CacheManager(options.cachePath);
|
|
946
835
|
this.symbolExtractor.setCacheManager(this.cacheManager);
|
|
947
|
-
this.graphBuilder = new GraphBuilder();
|
|
948
|
-
this.pageRankCalculator = new PageRankCalculator();
|
|
949
|
-
this.treeRenderer = new TreeRenderer();
|
|
950
836
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
837
|
+
initialize() {
|
|
838
|
+
this.initialization ??= Promise.all([this.parser.initialize(), this.cacheManager.initialize()]).then(() => void 0);
|
|
839
|
+
return this.initialization;
|
|
840
|
+
}
|
|
841
|
+
getRepoMap(options) {
|
|
842
|
+
const result = this.queue.then(() => this.generateRepoMap(options));
|
|
843
|
+
this.queue = result.catch(() => void 0);
|
|
844
|
+
return result;
|
|
845
|
+
}
|
|
846
|
+
async generateRepoMap(options) {
|
|
847
|
+
options.signal?.throwIfAborted();
|
|
848
|
+
await this.initialize();
|
|
849
|
+
const root = path9.resolve(options.root);
|
|
850
|
+
const normalize = (files2 = /* @__PURE__ */ new Set()) => new Set([...files2].map((file) => path9.relative(root, path9.resolve(root, file)).replaceAll(path9.sep, "/")));
|
|
851
|
+
const mentionedFiles = normalize(options.mentionedFiles);
|
|
852
|
+
const chatFiles = normalize(options.chatFiles);
|
|
853
|
+
const mentionedIdents = options.mentionedIdents ?? /* @__PURE__ */ new Set();
|
|
854
|
+
const files = options.files?.length ? options.files.map((file) => path9.resolve(root, file)) : await getAllFiles(root, options.excludePatterns);
|
|
855
|
+
options.signal?.throwIfAborted();
|
|
856
|
+
const tags = await this.symbolExtractor.extractTags(files, root, options.signal, true);
|
|
857
|
+
if (!tags.length) return "";
|
|
858
|
+
if (this.revision !== this.symbolExtractor.revision) {
|
|
859
|
+
this.graph = new DependencyIndex(tags);
|
|
860
|
+
this.revision = this.symbolExtractor.revision;
|
|
861
|
+
this.outputs.clear();
|
|
954
862
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
863
|
+
if (options.prepareOnly) return "";
|
|
864
|
+
const key = JSON.stringify([[...mentionedFiles].sort(), [...mentionedIdents].sort(), [...chatFiles].sort(), options.maxLines]);
|
|
865
|
+
const cached = this.outputs.get(key);
|
|
866
|
+
if (cached !== void 0) return cached;
|
|
867
|
+
options.signal?.throwIfAborted();
|
|
868
|
+
const ranked = this.graph.rank(mentionedFiles, mentionedIdents);
|
|
869
|
+
options.signal?.throwIfAborted();
|
|
870
|
+
const output = await this.treeRenderer.render(ranked, chatFiles, options.maxLines, root);
|
|
871
|
+
options.signal?.throwIfAborted();
|
|
872
|
+
if (this.outputs.size >= 8) this.outputs.delete(this.outputs.keys().next().value);
|
|
873
|
+
this.outputs.set(key, output);
|
|
874
|
+
return output;
|
|
959
875
|
}
|
|
960
|
-
|
|
961
|
-
|
|
876
|
+
extractSymbols(filePaths, options) {
|
|
877
|
+
const result = this.queue.then(async () => {
|
|
962
878
|
await this.initialize();
|
|
879
|
+
if (options?.useCache === false) this.symbolExtractor.setCacheManager(null);
|
|
880
|
+
try {
|
|
881
|
+
const tags = await this.symbolExtractor.extractTags(filePaths, options?.rootDir);
|
|
882
|
+
return tags.map((tag) => ({
|
|
883
|
+
name: tag.name,
|
|
884
|
+
kind: tag.kind,
|
|
885
|
+
line: tag.line,
|
|
886
|
+
file: tag.fname,
|
|
887
|
+
relativeFile: tag.rel_fname
|
|
888
|
+
}));
|
|
889
|
+
} finally {
|
|
890
|
+
this.symbolExtractor.setCacheManager(this.cacheManager);
|
|
891
|
+
}
|
|
892
|
+
});
|
|
893
|
+
this.queue = result.catch(() => void 0);
|
|
894
|
+
return result;
|
|
895
|
+
}
|
|
896
|
+
async parseFile(filePath) {
|
|
897
|
+
await this.initialize();
|
|
898
|
+
const { detectLanguage: detectLanguage2 } = await Promise.resolve().then(() => (init_language_detector(), language_detector_exports));
|
|
899
|
+
return {
|
|
900
|
+
tags: await this.parser.parseFile(filePath),
|
|
901
|
+
file: filePath,
|
|
902
|
+
language: detectLanguage2(filePath) || "unknown"
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
async dispose() {
|
|
906
|
+
await this.queue;
|
|
907
|
+
await this.parser.dispose();
|
|
908
|
+
this.cacheManager.close();
|
|
909
|
+
this.graph = void 0;
|
|
910
|
+
this.outputs.clear();
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
|
|
914
|
+
// src/graph-builder.ts
|
|
915
|
+
var import_graphology = __toESM(require("graphology"));
|
|
916
|
+
var GraphBuilder = class {
|
|
917
|
+
buildDependencyGraph(tags) {
|
|
918
|
+
const graph = new import_graphology.default({ type: "directed", allowSelfLoops: false, multi: true });
|
|
919
|
+
const defines = /* @__PURE__ */ new Map();
|
|
920
|
+
const references = /* @__PURE__ */ new Map();
|
|
921
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
922
|
+
for (const tag of tags) {
|
|
923
|
+
if (tag.kind === "def") {
|
|
924
|
+
if (!defines.has(tag.name)) {
|
|
925
|
+
defines.set(tag.name, /* @__PURE__ */ new Set());
|
|
926
|
+
}
|
|
927
|
+
defines.get(tag.name).add(tag.rel_fname);
|
|
928
|
+
const key = `${tag.rel_fname}:${tag.name}`;
|
|
929
|
+
if (!definitions.has(key)) {
|
|
930
|
+
definitions.set(key, []);
|
|
931
|
+
}
|
|
932
|
+
definitions.get(key).push(tag);
|
|
933
|
+
} else if (tag.kind === "ref") {
|
|
934
|
+
if (!references.has(tag.name)) {
|
|
935
|
+
references.set(tag.name, []);
|
|
936
|
+
}
|
|
937
|
+
references.get(tag.name).push(tag.rel_fname);
|
|
938
|
+
}
|
|
963
939
|
}
|
|
964
|
-
const
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
940
|
+
const idents = new Set(Array.from(defines.keys()).filter((k) => references.has(k)));
|
|
941
|
+
for (const ident of Array.from(idents)) {
|
|
942
|
+
const definers = Array.from(defines.get(ident) || []);
|
|
943
|
+
const referencers = references.get(ident) || [];
|
|
944
|
+
let mul = 1;
|
|
945
|
+
if (this.isSnakeCase(ident) || this.isKebabCase(ident) || this.isCamelCase(ident)) {
|
|
946
|
+
mul *= 10;
|
|
947
|
+
}
|
|
948
|
+
if (ident.startsWith("_")) {
|
|
949
|
+
mul *= 0.1;
|
|
971
950
|
}
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
951
|
+
if (definers.length > 5) {
|
|
952
|
+
mul *= 0.1;
|
|
953
|
+
}
|
|
954
|
+
const refCounts = /* @__PURE__ */ new Map();
|
|
955
|
+
for (const ref of referencers) {
|
|
956
|
+
refCounts.set(ref, (refCounts.get(ref) || 0) + 1);
|
|
957
|
+
}
|
|
958
|
+
for (const referencer of referencers) {
|
|
959
|
+
const numRefs = refCounts.get(referencer) || 1;
|
|
960
|
+
const weight = mul * Math.sqrt(numRefs);
|
|
961
|
+
for (const definer of definers) {
|
|
962
|
+
if (referencer !== definer) {
|
|
963
|
+
if (!graph.hasNode(referencer)) {
|
|
964
|
+
graph.addNode(referencer);
|
|
965
|
+
}
|
|
966
|
+
if (!graph.hasNode(definer)) {
|
|
967
|
+
graph.addNode(definer);
|
|
968
|
+
}
|
|
969
|
+
const edgeKey = `${referencer}->${definer}:${ident}`;
|
|
970
|
+
if (!graph.hasEdge(edgeKey)) {
|
|
971
|
+
graph.addEdgeWithKey(edgeKey, referencer, definer, { weight, ident });
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
976
975
|
}
|
|
977
|
-
const { graph, defines: _defines, references: _references } = this.graphBuilder.buildDependencyGraph(tags);
|
|
978
|
-
const rankedDefinitions = this.pageRankCalculator.calculate(graph, tags, mentionedFiles, mentionedIdents);
|
|
979
|
-
const output = await this.treeRenderer.render(rankedDefinitions, chatFiles, maxLines);
|
|
980
|
-
logger_default.info(`[TreeSitterAnalyzer] Generated repository map with ${rankedDefinitions.length} definitions`);
|
|
981
|
-
return output;
|
|
982
|
-
} catch (error) {
|
|
983
|
-
logger_default.error("[TreeSitterAnalyzer] Error generating repository map:", error);
|
|
984
|
-
return "";
|
|
985
976
|
}
|
|
977
|
+
logger_default.info(`[GraphBuilder] Built graph with ${graph.order} nodes and ${graph.size} edges`);
|
|
978
|
+
return { graph, defines, references };
|
|
986
979
|
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
980
|
+
isSnakeCase(str) {
|
|
981
|
+
return /^[a-z][a-z0-9_]*$/.test(str) && str.includes("_");
|
|
982
|
+
}
|
|
983
|
+
isKebabCase(str) {
|
|
984
|
+
return /^[a-z][a-z0-9-]*$/.test(str) && str.includes("-");
|
|
985
|
+
}
|
|
986
|
+
isCamelCase(str) {
|
|
987
|
+
return /^[a-z][a-zA-Z0-9]*$/.test(str) && /[A-Z]/.test(str);
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
|
|
991
|
+
// src/pagerank.ts
|
|
992
|
+
var import_pagerank = __toESM(require("graphology-metrics/centrality/pagerank"));
|
|
993
|
+
var PageRankCalculator = class {
|
|
994
|
+
calculate(graph, tags, mentionedFiles, mentionedIdents) {
|
|
995
|
+
if (graph.order === 0) {
|
|
996
|
+
return [];
|
|
997
|
+
}
|
|
998
|
+
const personalization = {};
|
|
999
|
+
const nodes = graph.nodes();
|
|
1000
|
+
const personalize = nodes.length > 0 ? 100 / nodes.length : 0;
|
|
1001
|
+
for (const node of nodes) {
|
|
1002
|
+
let score = 0;
|
|
1003
|
+
if (mentionedFiles?.has(node)) {
|
|
1004
|
+
score += personalize;
|
|
1005
|
+
}
|
|
1006
|
+
if (mentionedIdents) {
|
|
1007
|
+
const fileTags = tags.filter((t) => t.rel_fname === node && t.kind === "def");
|
|
1008
|
+
if (fileTags.some((t) => mentionedIdents.has(t.name))) {
|
|
1009
|
+
score += personalize;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
if (score > 0) {
|
|
1013
|
+
personalization[node] = score;
|
|
1014
|
+
}
|
|
990
1015
|
}
|
|
991
|
-
const useCache = options?.useCache !== false;
|
|
992
1016
|
try {
|
|
993
|
-
const
|
|
994
|
-
|
|
995
|
-
|
|
1017
|
+
const pagerankOptions = {
|
|
1018
|
+
alpha: 0.85,
|
|
1019
|
+
maxIterations: 100,
|
|
1020
|
+
tolerance: 1e-6
|
|
1021
|
+
};
|
|
1022
|
+
if (Object.keys(personalization).length > 0) {
|
|
1023
|
+
pagerankOptions.personalization = personalization;
|
|
996
1024
|
}
|
|
997
|
-
const
|
|
998
|
-
|
|
999
|
-
|
|
1025
|
+
const ranks = (0, import_pagerank.default)(graph, pagerankOptions);
|
|
1026
|
+
const rankedDefinitions = [];
|
|
1027
|
+
const definitions = tags.filter((t) => t.kind === "def");
|
|
1028
|
+
for (const def of definitions) {
|
|
1029
|
+
const nodeRank = ranks[def.rel_fname] || 0;
|
|
1030
|
+
let edgeRank = nodeRank;
|
|
1031
|
+
if (graph.hasNode(def.rel_fname)) {
|
|
1032
|
+
graph.forEachOutboundEdge(def.rel_fname, (edge) => {
|
|
1033
|
+
const weight = graph.getEdgeAttribute(edge, "weight");
|
|
1034
|
+
const totalWeight = this.getTotalOutboundWeight(graph, def.rel_fname);
|
|
1035
|
+
if (totalWeight > 0) {
|
|
1036
|
+
edgeRank += nodeRank * (weight / totalWeight);
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
rankedDefinitions.push({
|
|
1041
|
+
rel_fname: def.rel_fname,
|
|
1042
|
+
name: def.name,
|
|
1043
|
+
rank: edgeRank,
|
|
1044
|
+
line: def.line
|
|
1045
|
+
});
|
|
1000
1046
|
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
line: tag.line,
|
|
1005
|
-
file: tag.fname,
|
|
1006
|
-
relativeFile: tag.rel_fname
|
|
1007
|
-
}));
|
|
1008
|
-
logger_default.info(`[TreeSitterAnalyzer] Extracted ${symbols.length} symbols from ${filePaths.length} files`);
|
|
1009
|
-
return symbols;
|
|
1047
|
+
rankedDefinitions.sort((a, b) => b.rank - a.rank);
|
|
1048
|
+
logger_default.info(`[PageRankCalculator] Calculated ranks for ${rankedDefinitions.length} definitions`);
|
|
1049
|
+
return rankedDefinitions;
|
|
1010
1050
|
} catch (error) {
|
|
1011
|
-
logger_default.error("[
|
|
1051
|
+
logger_default.error("[PageRankCalculator] Error calculating PageRank:", error);
|
|
1012
1052
|
return [];
|
|
1013
1053
|
}
|
|
1014
1054
|
}
|
|
1015
|
-
|
|
1016
|
-
if (!
|
|
1017
|
-
|
|
1055
|
+
getTotalOutboundWeight(graph, node) {
|
|
1056
|
+
if (!graph.hasNode(node)) {
|
|
1057
|
+
return 0;
|
|
1018
1058
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
file: filePath,
|
|
1025
|
-
language
|
|
1026
|
-
};
|
|
1059
|
+
let total = 0;
|
|
1060
|
+
graph.forEachOutboundEdge(node, (edge) => {
|
|
1061
|
+
total += graph.getEdgeAttribute(edge, "weight") || 1;
|
|
1062
|
+
});
|
|
1063
|
+
return total;
|
|
1027
1064
|
}
|
|
1028
1065
|
};
|
|
1029
1066
|
|
|
1030
1067
|
// src/index.ts
|
|
1031
1068
|
init_language_detector();
|
|
1069
|
+
|
|
1070
|
+
// src/repo-map-service.ts
|
|
1071
|
+
var path10 = __toESM(require("path"));
|
|
1072
|
+
var import_worker_threads = require("worker_threads");
|
|
1073
|
+
var RepoMapService = class {
|
|
1074
|
+
constructor(options = {}) {
|
|
1075
|
+
this.options = options;
|
|
1076
|
+
}
|
|
1077
|
+
worker;
|
|
1078
|
+
active;
|
|
1079
|
+
jobs = /* @__PURE__ */ new Map();
|
|
1080
|
+
queue = [];
|
|
1081
|
+
sequence = 0;
|
|
1082
|
+
idleTimer;
|
|
1083
|
+
getRepoMap(options) {
|
|
1084
|
+
if (options.signal?.aborted) return Promise.reject(new Error("Repository map cancelled", { cause: options.signal.reason }));
|
|
1085
|
+
const root = path10.resolve(options.root);
|
|
1086
|
+
const { signal, ...serializable } = options;
|
|
1087
|
+
const request = { ...serializable, root };
|
|
1088
|
+
const key = JSON.stringify([
|
|
1089
|
+
root,
|
|
1090
|
+
request.files,
|
|
1091
|
+
request.excludePatterns,
|
|
1092
|
+
request.maxLines,
|
|
1093
|
+
request.prepareOnly,
|
|
1094
|
+
[...request.mentionedFiles ?? []].sort(),
|
|
1095
|
+
[...request.mentionedIdents ?? []].sort(),
|
|
1096
|
+
[...request.chatFiles ?? []].sort()
|
|
1097
|
+
]);
|
|
1098
|
+
let job = this.jobs.get(key);
|
|
1099
|
+
if (!job) {
|
|
1100
|
+
if (this.jobs.size >= 64) return Promise.reject(new Error("Repository map queue is full"));
|
|
1101
|
+
job = {
|
|
1102
|
+
id: ++this.sequence,
|
|
1103
|
+
key,
|
|
1104
|
+
options: request,
|
|
1105
|
+
subscribers: /* @__PURE__ */ new Set()
|
|
1106
|
+
};
|
|
1107
|
+
this.jobs.set(key, job);
|
|
1108
|
+
this.queue.push(job);
|
|
1109
|
+
}
|
|
1110
|
+
const pending = job;
|
|
1111
|
+
return new Promise((resolve5, reject) => {
|
|
1112
|
+
const abort = () => {
|
|
1113
|
+
subscriber.cleanup();
|
|
1114
|
+
pending.subscribers.delete(subscriber);
|
|
1115
|
+
reject(new Error("Repository map cancelled", { cause: signal?.reason }));
|
|
1116
|
+
if (!pending.subscribers.size) {
|
|
1117
|
+
this.jobs.delete(pending.key);
|
|
1118
|
+
this.queue = this.queue.filter((queued) => queued !== pending);
|
|
1119
|
+
if (this.active === pending) {
|
|
1120
|
+
this.stopWorker();
|
|
1121
|
+
this.active = void 0;
|
|
1122
|
+
}
|
|
1123
|
+
this.schedule();
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
const subscriber = {
|
|
1127
|
+
resolve: resolve5,
|
|
1128
|
+
reject,
|
|
1129
|
+
cleanup: () => signal?.removeEventListener("abort", abort)
|
|
1130
|
+
};
|
|
1131
|
+
pending.subscribers.add(subscriber);
|
|
1132
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
1133
|
+
this.schedule();
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
schedule() {
|
|
1137
|
+
if (this.active) return;
|
|
1138
|
+
const next = this.queue.shift();
|
|
1139
|
+
if (!next) return;
|
|
1140
|
+
clearTimeout(this.idleTimer);
|
|
1141
|
+
this.active = next;
|
|
1142
|
+
try {
|
|
1143
|
+
if (!this.worker) {
|
|
1144
|
+
const worker = new import_worker_threads.Worker(path10.join(__dirname, "repo-map-worker.js"), {
|
|
1145
|
+
workerData: this.options,
|
|
1146
|
+
resourceLimits: { maxOldGenerationSizeMb: 512 }
|
|
1147
|
+
});
|
|
1148
|
+
this.worker = worker;
|
|
1149
|
+
worker.on("message", (response) => {
|
|
1150
|
+
if (this.worker !== worker || this.active?.id !== response.id) return;
|
|
1151
|
+
this.finish(response);
|
|
1152
|
+
});
|
|
1153
|
+
worker.on("error", (error) => {
|
|
1154
|
+
if (this.worker !== worker) return;
|
|
1155
|
+
this.stopWorker();
|
|
1156
|
+
if (this.active) this.finish({ id: this.active.id, error: error.message });
|
|
1157
|
+
});
|
|
1158
|
+
worker.on("exit", (code) => {
|
|
1159
|
+
if (this.worker !== worker) return;
|
|
1160
|
+
this.worker = void 0;
|
|
1161
|
+
if (this.active)
|
|
1162
|
+
this.finish({
|
|
1163
|
+
id: this.active.id,
|
|
1164
|
+
error: `Repository map worker exited (${code})`
|
|
1165
|
+
});
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
this.worker.ref();
|
|
1169
|
+
this.worker.postMessage({
|
|
1170
|
+
id: next.id,
|
|
1171
|
+
options: next.options
|
|
1172
|
+
});
|
|
1173
|
+
} catch (error) {
|
|
1174
|
+
this.stopWorker();
|
|
1175
|
+
this.finish({ id: next.id, error: String(error) });
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
finish(response) {
|
|
1179
|
+
const job = this.active;
|
|
1180
|
+
if (!job) return;
|
|
1181
|
+
this.jobs.delete(job.key);
|
|
1182
|
+
this.active = void 0;
|
|
1183
|
+
for (const subscriber of job.subscribers) {
|
|
1184
|
+
subscriber.cleanup();
|
|
1185
|
+
if (response.error !== void 0) subscriber.reject(new Error(response.error));
|
|
1186
|
+
else subscriber.resolve(response.output ?? "");
|
|
1187
|
+
}
|
|
1188
|
+
this.worker?.unref();
|
|
1189
|
+
this.idleTimer = setTimeout(() => this.stopWorker(), 5 * 60 * 1e3);
|
|
1190
|
+
this.idleTimer.unref();
|
|
1191
|
+
this.schedule();
|
|
1192
|
+
}
|
|
1193
|
+
stopWorker() {
|
|
1194
|
+
const worker = this.worker;
|
|
1195
|
+
this.worker = void 0;
|
|
1196
|
+
void worker?.terminate();
|
|
1197
|
+
}
|
|
1198
|
+
dispose() {
|
|
1199
|
+
clearTimeout(this.idleTimer);
|
|
1200
|
+
this.stopWorker();
|
|
1201
|
+
for (const job of this.jobs.values()) {
|
|
1202
|
+
for (const subscriber of job.subscribers) {
|
|
1203
|
+
subscriber.cleanup();
|
|
1204
|
+
subscriber.reject(new Error("Repository map service disposed"));
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
this.jobs.clear();
|
|
1208
|
+
this.queue = [];
|
|
1209
|
+
this.active = void 0;
|
|
1210
|
+
}
|
|
1211
|
+
};
|
|
1212
|
+
|
|
1213
|
+
// src/index.ts
|
|
1214
|
+
var repoMapService = new RepoMapService();
|
|
1032
1215
|
var defaultAnalyzer = null;
|
|
1033
1216
|
async function getAnalyzer() {
|
|
1034
1217
|
if (!defaultAnalyzer) {
|
|
@@ -1042,14 +1225,14 @@ async function extractSymbols(filePaths, options) {
|
|
|
1042
1225
|
return analyzer.extractSymbols(filePaths, options);
|
|
1043
1226
|
}
|
|
1044
1227
|
async function getRepoMap(root, options) {
|
|
1045
|
-
|
|
1046
|
-
return analyzer.getRepoMap({ ...options, root });
|
|
1228
|
+
return repoMapService.getRepoMap({ ...options, root });
|
|
1047
1229
|
}
|
|
1048
1230
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1049
1231
|
0 && (module.exports = {
|
|
1050
1232
|
CacheManager,
|
|
1051
1233
|
GraphBuilder,
|
|
1052
1234
|
PageRankCalculator,
|
|
1235
|
+
RepoMapService,
|
|
1053
1236
|
SymbolExtractor,
|
|
1054
1237
|
TreeRenderer,
|
|
1055
1238
|
TreeSitterAnalyzer,
|