opencode-codebase-index 0.21.0 → 0.22.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.
@@ -328,7 +328,7 @@ var require_ignore = __commonJS({
328
328
  // path matching.
329
329
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
330
330
  // @returns {TestResult} true if a file is ignored
331
- test(path20, checkUnignored, mode) {
331
+ test(path21, checkUnignored, mode) {
332
332
  let ignored = false;
333
333
  let unignored = false;
334
334
  let matchedRule;
@@ -337,7 +337,7 @@ var require_ignore = __commonJS({
337
337
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
338
338
  return;
339
339
  }
340
- const matched = rule[mode].test(path20);
340
+ const matched = rule[mode].test(path21);
341
341
  if (!matched) {
342
342
  return;
343
343
  }
@@ -358,17 +358,17 @@ var require_ignore = __commonJS({
358
358
  var throwError = (message, Ctor) => {
359
359
  throw new Ctor(message);
360
360
  };
361
- var checkPath = (path20, originalPath, doThrow) => {
362
- if (!isString(path20)) {
361
+ var checkPath = (path21, originalPath, doThrow) => {
362
+ if (!isString(path21)) {
363
363
  return doThrow(
364
364
  `path must be a string, but got \`${originalPath}\``,
365
365
  TypeError
366
366
  );
367
367
  }
368
- if (!path20) {
368
+ if (!path21) {
369
369
  return doThrow(`path must not be empty`, TypeError);
370
370
  }
371
- if (checkPath.isNotRelative(path20)) {
371
+ if (checkPath.isNotRelative(path21)) {
372
372
  const r = "`path.relative()`d";
373
373
  return doThrow(
374
374
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -377,7 +377,7 @@ var require_ignore = __commonJS({
377
377
  }
378
378
  return true;
379
379
  };
380
- var isNotRelative = (path20) => REGEX_TEST_INVALID_PATH.test(path20);
380
+ var isNotRelative = (path21) => REGEX_TEST_INVALID_PATH.test(path21);
381
381
  checkPath.isNotRelative = isNotRelative;
382
382
  checkPath.convert = (p) => p;
383
383
  var Ignore2 = class {
@@ -407,19 +407,19 @@ var require_ignore = __commonJS({
407
407
  }
408
408
  // @returns {TestResult}
409
409
  _test(originalPath, cache, checkUnignored, slices) {
410
- const path20 = originalPath && checkPath.convert(originalPath);
410
+ const path21 = originalPath && checkPath.convert(originalPath);
411
411
  checkPath(
412
- path20,
412
+ path21,
413
413
  originalPath,
414
414
  this._strictPathCheck ? throwError : RETURN_FALSE
415
415
  );
416
- return this._t(path20, cache, checkUnignored, slices);
416
+ return this._t(path21, cache, checkUnignored, slices);
417
417
  }
418
- checkIgnore(path20) {
419
- if (!REGEX_TEST_TRAILING_SLASH.test(path20)) {
420
- return this.test(path20);
418
+ checkIgnore(path21) {
419
+ if (!REGEX_TEST_TRAILING_SLASH.test(path21)) {
420
+ return this.test(path21);
421
421
  }
422
- const slices = path20.split(SLASH).filter(Boolean);
422
+ const slices = path21.split(SLASH).filter(Boolean);
423
423
  slices.pop();
424
424
  if (slices.length) {
425
425
  const parent = this._t(
@@ -432,18 +432,18 @@ var require_ignore = __commonJS({
432
432
  return parent;
433
433
  }
434
434
  }
435
- return this._rules.test(path20, false, MODE_CHECK_IGNORE);
435
+ return this._rules.test(path21, false, MODE_CHECK_IGNORE);
436
436
  }
437
- _t(path20, cache, checkUnignored, slices) {
438
- if (path20 in cache) {
439
- return cache[path20];
437
+ _t(path21, cache, checkUnignored, slices) {
438
+ if (path21 in cache) {
439
+ return cache[path21];
440
440
  }
441
441
  if (!slices) {
442
- slices = path20.split(SLASH).filter(Boolean);
442
+ slices = path21.split(SLASH).filter(Boolean);
443
443
  }
444
444
  slices.pop();
445
445
  if (!slices.length) {
446
- return cache[path20] = this._rules.test(path20, checkUnignored, MODE_IGNORE);
446
+ return cache[path21] = this._rules.test(path21, checkUnignored, MODE_IGNORE);
447
447
  }
448
448
  const parent = this._t(
449
449
  slices.join(SLASH) + SLASH,
@@ -451,29 +451,29 @@ var require_ignore = __commonJS({
451
451
  checkUnignored,
452
452
  slices
453
453
  );
454
- return cache[path20] = parent.ignored ? parent : this._rules.test(path20, checkUnignored, MODE_IGNORE);
454
+ return cache[path21] = parent.ignored ? parent : this._rules.test(path21, checkUnignored, MODE_IGNORE);
455
455
  }
456
- ignores(path20) {
457
- return this._test(path20, this._ignoreCache, false).ignored;
456
+ ignores(path21) {
457
+ return this._test(path21, this._ignoreCache, false).ignored;
458
458
  }
459
459
  createFilter() {
460
- return (path20) => !this.ignores(path20);
460
+ return (path21) => !this.ignores(path21);
461
461
  }
462
462
  filter(paths) {
463
463
  return makeArray(paths).filter(this.createFilter());
464
464
  }
465
465
  // @returns {TestResult}
466
- test(path20) {
467
- return this._test(path20, this._testCache, true);
466
+ test(path21) {
467
+ return this._test(path21, this._testCache, true);
468
468
  }
469
469
  };
470
470
  var factory = (options) => new Ignore2(options);
471
- var isPathValid = (path20) => checkPath(path20 && checkPath.convert(path20), path20, RETURN_FALSE);
471
+ var isPathValid = (path21) => checkPath(path21 && checkPath.convert(path21), path21, RETURN_FALSE);
472
472
  var setupWindows = () => {
473
473
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
474
474
  checkPath.convert = makePosix;
475
475
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
476
- checkPath.isNotRelative = (path20) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path20) || isNotRelative(path20);
476
+ checkPath.isNotRelative = (path21) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path21) || isNotRelative(path21);
477
477
  };
478
478
  if (
479
479
  // Detect `process` so that it can run in browsers.
@@ -804,7 +804,8 @@ function getDefaultSearchConfig() {
804
804
  contextLines: 0,
805
805
  routingHints: true,
806
806
  routingGraphHandoffHints: false,
807
- routingHintRole: "system"
807
+ routingHintRole: "system",
808
+ communityBoost: 0
808
809
  };
809
810
  }
810
811
  function getDefaultRerankerBaseUrl(provider) {
@@ -937,7 +938,8 @@ function parseConfig(raw) {
937
938
  contextLines: typeof rawSearch.contextLines === "number" ? Math.min(50, Math.max(0, rawSearch.contextLines)) : defaultSearch.contextLines,
938
939
  routingHints: typeof rawSearch.routingHints === "boolean" ? rawSearch.routingHints : defaultSearch.routingHints,
939
940
  routingGraphHandoffHints: typeof rawSearch.routingGraphHandoffHints === "boolean" ? rawSearch.routingGraphHandoffHints : defaultSearch.routingGraphHandoffHints,
940
- routingHintRole: rawSearch.routingHintRole === "developer" || rawSearch.routingHintRole === "system" ? rawSearch.routingHintRole : defaultSearch.routingHintRole
941
+ routingHintRole: rawSearch.routingHintRole === "developer" || rawSearch.routingHintRole === "system" ? rawSearch.routingHintRole : defaultSearch.routingHintRole,
942
+ communityBoost: typeof rawSearch.communityBoost === "number" && Number.isFinite(rawSearch.communityBoost) ? Math.min(1, Math.max(0, rawSearch.communityBoost)) : defaultSearch.communityBoost
941
943
  };
942
944
  const rawDebug = input.debug && typeof input.debug === "object" ? input.debug : {};
943
945
  const debug = {
@@ -1437,9 +1439,122 @@ function formatPrImpact(result) {
1437
1439
  return lines.join("\n");
1438
1440
  }
1439
1441
 
1442
+ // src/tools/format-communities.ts
1443
+ function compareText(left, right) {
1444
+ return left < right ? -1 : left > right ? 1 : 0;
1445
+ }
1446
+ function buildCodeCommunitiesResult(communities, centrality, couplings = [], options = {}) {
1447
+ const minSize = options.minSize ?? 1;
1448
+ const limit = options.limit ?? 20;
1449
+ const hubThreshold = options.hubThreshold ?? 5;
1450
+ const minCoupling = options.minCoupling ?? 1;
1451
+ const couplingLimit = options.couplingLimit ?? 20;
1452
+ const communityMap = /* @__PURE__ */ new Map();
1453
+ for (const c of communities) {
1454
+ let entry = communityMap.get(c.communityId);
1455
+ if (!entry) {
1456
+ entry = { label: c.communityLabel, members: [] };
1457
+ communityMap.set(c.communityId, entry);
1458
+ }
1459
+ entry.members.push(c);
1460
+ }
1461
+ const sortedCommunities = Array.from(communityMap.entries()).map(([id, entry]) => ({
1462
+ id,
1463
+ label: entry.label,
1464
+ symbolCount: entry.members.length,
1465
+ members: entry.members.map((m) => ({
1466
+ symbolId: m.symbolId,
1467
+ symbolName: m.symbolName,
1468
+ filePath: m.filePath
1469
+ })).sort((a, b) => compareText(a.symbolName, b.symbolName) || compareText(a.symbolId, b.symbolId))
1470
+ })).filter((c) => c.symbolCount >= minSize).sort((a, b) => b.symbolCount - a.symbolCount || compareText(a.label, b.label) || a.id - b.id).slice(0, limit);
1471
+ const communityBySymbol = new Map(communities.map((community) => [community.symbolId, community]));
1472
+ const communityLabelById = new Map(communities.map((community) => [community.communityId, community.communityLabel]));
1473
+ const hubNodes = centrality.map((c) => ({
1474
+ symbolId: c.symbolId,
1475
+ symbolName: c.symbolName,
1476
+ filePath: c.filePath,
1477
+ callerCount: c.callerCount,
1478
+ calleeCount: c.calleeCount,
1479
+ totalConnections: c.totalConnections,
1480
+ crossCommunityConnections: communityBySymbol.get(c.symbolId)?.crossCommunityConnections ?? 0
1481
+ })).filter((h) => h.crossCommunityConnections >= hubThreshold).sort(
1482
+ (a, b) => b.crossCommunityConnections - a.crossCommunityConnections || b.totalConnections - a.totalConnections || compareText(a.symbolId, b.symbolId)
1483
+ ).slice(0, limit);
1484
+ const canonicalCoupling = (value) => Math.trunc(value);
1485
+ const couplingItems = couplings.map((entry) => {
1486
+ const relationships = entry.relationships ?? entry.representativeRelationships ?? [];
1487
+ const normalizedRelationships = relationships.map((relationship) => ({
1488
+ fromSymbolId: relationship.fromSymbolId,
1489
+ fromSymbolName: relationship.fromSymbolName,
1490
+ fromFilePath: relationship.fromFilePath,
1491
+ toSymbolId: relationship.toSymbolId,
1492
+ toSymbolName: relationship.toSymbolName,
1493
+ toFilePath: relationship.toFilePath
1494
+ })).sort(
1495
+ (left, right) => compareText(left.fromSymbolName, right.fromSymbolName) || compareText(left.fromSymbolId, right.fromSymbolId) || compareText(left.toSymbolName, right.toSymbolName) || compareText(left.toSymbolId, right.toSymbolId) || compareText(left.fromFilePath, right.fromFilePath) || compareText(left.toFilePath, right.toFilePath)
1496
+ ).slice(0, 5);
1497
+ const communityA = canonicalCoupling(Math.min(entry.communityA, entry.communityB));
1498
+ const communityB = canonicalCoupling(Math.max(entry.communityA, entry.communityB));
1499
+ return {
1500
+ communityA,
1501
+ communityB,
1502
+ communityAName: communityLabelById.get(communityA) ?? `Community ${communityA}`,
1503
+ communityBName: communityLabelById.get(communityB) ?? `Community ${communityB}`,
1504
+ distinctConnections: canonicalCoupling(entry.count),
1505
+ representativeRelationships: normalizedRelationships
1506
+ };
1507
+ }).filter((entry) => entry.distinctConnections >= minCoupling).sort(
1508
+ (left, right) => right.distinctConnections - left.distinctConnections || compareText(left.communityAName, right.communityAName) || compareText(left.communityBName, right.communityBName) || compareText(left.communityAName + left.communityBName, right.communityAName + right.communityBName) || left.communityA - right.communityA || left.communityB - right.communityB
1509
+ ).slice(0, Math.max(1, Math.floor(couplingLimit)));
1510
+ return {
1511
+ communities: sortedCommunities,
1512
+ hubNodes,
1513
+ totalSymbols: communities.length,
1514
+ totalCommunities: communityMap.size,
1515
+ couplings: couplingItems
1516
+ };
1517
+ }
1518
+ function formatCodeCommunities(result) {
1519
+ const lines = [];
1520
+ lines.push(`\u2192 Communities: ${result.totalCommunities} (${result.communities.length} shown, ${result.totalSymbols} symbols total)`);
1521
+ for (const community of result.communities) {
1522
+ lines.push(` Community ${community.id} (${community.label}): ${community.symbolCount} symbols`);
1523
+ const shownMembers = community.members.slice(0, 8);
1524
+ for (const m of shownMembers) {
1525
+ lines.push(` - ${m.symbolName} (${m.filePath})`);
1526
+ }
1527
+ if (community.members.length > 8) {
1528
+ lines.push(` ... and ${community.members.length - 8} more`);
1529
+ }
1530
+ }
1531
+ if (result.hubNodes.length > 0) {
1532
+ lines.push(`\u2192 Hub nodes (${result.hubNodes.length} shown, cross-community connections):`);
1533
+ for (const hub of result.hubNodes) {
1534
+ lines.push(
1535
+ ` - ${hub.symbolName} (${hub.crossCommunityConnections} cross-community, ${hub.callerCount} callers, ${hub.calleeCount} callees) at ${hub.filePath}`
1536
+ );
1537
+ }
1538
+ } else {
1539
+ lines.push("\u2192 Hub nodes: none with significant cross-community connections");
1540
+ }
1541
+ if (result.couplings.length > 0) {
1542
+ lines.push(`\u2192 Community couplings: ${result.couplings.length} shown`);
1543
+ for (const coupling of result.couplings) {
1544
+ lines.push(` - ${coupling.communityAName} \u2194 ${coupling.communityBName}: ${coupling.distinctConnections} distinct connections`);
1545
+ for (const relationship of coupling.representativeRelationships) {
1546
+ lines.push(` - ${relationship.fromSymbolName} (${relationship.fromFilePath}) -> ${relationship.toSymbolName} (${relationship.toFilePath})`);
1547
+ }
1548
+ }
1549
+ } else {
1550
+ lines.push("\u2192 Community couplings: none above minCoupling threshold");
1551
+ }
1552
+ return lines.join("\n");
1553
+ }
1554
+
1440
1555
  // src/tools/operations.ts
1441
- import { existsSync as existsSync11, realpathSync as realpathSync5, statSync as statSync5 } from "fs";
1442
- import * as path19 from "path";
1556
+ import { existsSync as existsSync12, realpathSync as realpathSync5, statSync as statSync5 } from "fs";
1557
+ import * as path20 from "path";
1443
1558
 
1444
1559
  // src/config/paths.ts
1445
1560
  import { existsSync as existsSync4 } from "fs";
@@ -1925,6 +2040,15 @@ function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot3) {
1925
2040
  );
1926
2041
  }
1927
2042
 
2043
+ // src/tools/contracts.ts
2044
+ var CODE_COMMUNITIES_MIN_SIZE = 1;
2045
+ var CODE_COMMUNITIES_DEFAULT_LIMIT = 20;
2046
+ var CODE_COMMUNITIES_MAX_LIMIT = 100;
2047
+ var CODE_COMMUNITIES_DEFAULT_HUB_THRESHOLD = 5;
2048
+ var CODE_COMMUNITIES_MIN_COUPLING = 1;
2049
+ var CODE_COMMUNITIES_DEFAULT_COUPLING_LIMIT = 20;
2050
+ var CODE_COMMUNITIES_MAX_COUPLING_LIMIT = 100;
2051
+
1928
2052
  // src/tools/context-pack.ts
1929
2053
  import { get_encoding } from "tiktoken";
1930
2054
 
@@ -2568,8 +2692,8 @@ function formatExactSearchHandoff(results) {
2568
2692
  }
2569
2693
  function formatContextEvidence(result, index) {
2570
2694
  const symbol = result.name ? ` ${JSON.stringify(compactEvidenceValue(result.name, 80))}` : "";
2571
- const path20 = compactEvidenceValue(result.filePath, 120);
2572
- return `[${index}] ${result.chunkType}${symbol} in ${path20}:${result.startLine}-${result.endLine} (score ${result.score.toFixed(2)})`;
2695
+ const path21 = compactEvidenceValue(result.filePath, 120);
2696
+ return `[${index}] ${result.chunkType}${symbol} in ${path21}:${result.startLine}-${result.endLine} (score ${result.score.toFixed(2)})`;
2573
2697
  }
2574
2698
  function formatContextPack(heading, selected, candidateCount, duplicateCount, limitOmittedCount, budgetOmittedCount, includeExactSearchHandoff) {
2575
2699
  const lines = selected.map((result, index) => formatContextEvidence(result, index + 1));
@@ -4752,8 +4876,8 @@ function saveConfig(projectRoot3, config, host) {
4752
4876
  }
4753
4877
 
4754
4878
  // src/indexer/index.ts
4755
- import { existsSync as existsSync10, readFileSync as readFileSync7, statSync as statSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, unlinkSync as unlinkSync2, mkdirSync as mkdirSync3, promises as fsPromises3 } from "fs";
4756
- import * as path18 from "path";
4879
+ import { existsSync as existsSync11, readFileSync as readFileSync8, statSync as statSync4, writeFileSync as writeFileSync3, renameSync as renameSync3, unlinkSync as unlinkSync2, mkdirSync as mkdirSync4, promises as fsPromises3 } from "fs";
4880
+ import * as path19 from "path";
4757
4881
  import { performance as performance2 } from "perf_hooks";
4758
4882
  import { execFile as execFile5 } from "child_process";
4759
4883
  import { promisify as promisify4 } from "util";
@@ -7020,6 +7144,9 @@ function createMockNativeBinding() {
7020
7144
  detectCommunities() {
7021
7145
  throw error;
7022
7146
  }
7147
+ detectCommunityCouplings() {
7148
+ throw error;
7149
+ }
7023
7150
  computeCentrality() {
7024
7151
  throw error;
7025
7152
  }
@@ -7257,6 +7384,18 @@ var Database = class _Database {
7257
7384
  }
7258
7385
  this.closed = true;
7259
7386
  }
7387
+ beginWriteTransaction() {
7388
+ this.throwIfClosed();
7389
+ this.inner.beginWriteTransaction();
7390
+ }
7391
+ commitWriteTransaction() {
7392
+ this.throwIfClosed();
7393
+ this.inner.commitWriteTransaction();
7394
+ }
7395
+ rollbackWriteTransaction() {
7396
+ this.throwIfClosed();
7397
+ this.inner.rollbackWriteTransaction();
7398
+ }
7260
7399
  embeddingExists(contentHash) {
7261
7400
  this.throwIfClosed();
7262
7401
  return this.inner.embeddingExists(contentHash);
@@ -7516,6 +7655,13 @@ var Database = class _Database {
7516
7655
  this.throwIfClosed();
7517
7656
  return this.inner.computeCentrality(branch);
7518
7657
  }
7658
+ detectCommunityCouplings(branch) {
7659
+ this.throwIfClosed();
7660
+ return this.inner.detectCommunityCouplings(branch).map((entry) => ({
7661
+ ...entry,
7662
+ relationships: entry.representativeRelationships ?? []
7663
+ }));
7664
+ }
7519
7665
  };
7520
7666
 
7521
7667
  // src/git/branch-materialization.ts
@@ -8285,6 +8431,21 @@ async function getChunkGitBlame(projectRoot3, filePath, startLine, endLine) {
8285
8431
  }
8286
8432
 
8287
8433
  // src/indexer/search-ranking.ts
8434
+ function applyCommunityBoost(candidates, sameCommunityCandidateIds, boost) {
8435
+ if (boost <= 0 || sameCommunityCandidateIds.size === 0 || candidates.length <= 1) {
8436
+ return candidates;
8437
+ }
8438
+ const result = candidates.map((candidate) => sameCommunityCandidateIds.has(candidate.id) ? { ...candidate, score: candidate.score * (1 + boost) } : candidate);
8439
+ for (let index = 1; index < result.length; index += 1) {
8440
+ const candidate = result[index];
8441
+ const previous = result[index - 1];
8442
+ if (candidate && previous && sameCommunityCandidateIds.has(candidate.id) && !sameCommunityCandidateIds.has(previous.id) && candidate.score > previous.score) {
8443
+ result[index - 1] = candidate;
8444
+ result[index] = previous;
8445
+ }
8446
+ }
8447
+ return result;
8448
+ }
8288
8449
  var RANK_HYBRID_CACHE_LIMIT = 256;
8289
8450
  var rankHybridResultsCache = /* @__PURE__ */ new WeakMap();
8290
8451
  function classifyQueryIntentRaw(query) {
@@ -8463,6 +8624,117 @@ function rankSemanticOnlyResults(query, semanticResults, options) {
8463
8624
  });
8464
8625
  }
8465
8626
 
8627
+ // src/tools/symbol-inference.ts
8628
+ var IDENTIFIER_RE = /[A-Za-z_$][A-Za-z0-9_$]*/g;
8629
+ var QUOTED_BACKTICK_RE = /`([^`]+)`/g;
8630
+ var QUOTED_SINGLE_RE = /'([^'\\]+)'/g;
8631
+ var QUOTED_DOUBLE_RE = /"([^"]+)"/g;
8632
+ var SYMBOL_LIKE_RE = /^(?:[A-Za-z_$][A-Za-z0-9_$]*)$/;
8633
+ var CAMEL_CASE_RE = /^[a-z_][A-Za-z0-9_$]*[A-Z][A-Za-z0-9_$]*$/;
8634
+ var PASCAL_CASE_RE = /^[A-Z][A-Za-z0-9_$]*$/;
8635
+ var SNAKE_CASE_RE = /^[a-z][a-z0-9_]*_[a-z0-9_]+$/;
8636
+ var DEFINITION_INTENT_RE = /\b(where|defined|definition|define|declaration|symbol|function|method|class|interface|type)\b/i;
8637
+ var STOP_WORDS = /* @__PURE__ */ new Set([
8638
+ "a",
8639
+ "an",
8640
+ "and",
8641
+ "are",
8642
+ "at",
8643
+ "for",
8644
+ "find",
8645
+ "how",
8646
+ "i",
8647
+ "in",
8648
+ "is",
8649
+ "it",
8650
+ "of",
8651
+ "on",
8652
+ "that",
8653
+ "the",
8654
+ "definition",
8655
+ "show",
8656
+ "to",
8657
+ "where",
8658
+ "which",
8659
+ "what",
8660
+ "you",
8661
+ "your",
8662
+ "with"
8663
+ ]);
8664
+ function stripCallSuffix(token) {
8665
+ return token.replace(/\(\s*\)$/, "");
8666
+ }
8667
+ function isLikelySymbolName(token) {
8668
+ if (!SYMBOL_LIKE_RE.test(token)) {
8669
+ return false;
8670
+ }
8671
+ if (STOP_WORDS.has(token.toLowerCase())) {
8672
+ return false;
8673
+ }
8674
+ return CAMEL_CASE_RE.test(token) || PASCAL_CASE_RE.test(token) || SNAKE_CASE_RE.test(token);
8675
+ }
8676
+ function extractQuotedIdentifiers(query) {
8677
+ const identifiers = /* @__PURE__ */ new Set();
8678
+ for (const match of query.matchAll(QUOTED_BACKTICK_RE)) {
8679
+ const candidate = stripCallSuffix(match[1].trim());
8680
+ if (candidate && isLikelySymbolName(candidate)) {
8681
+ identifiers.add(candidate);
8682
+ }
8683
+ }
8684
+ for (const match of query.matchAll(QUOTED_SINGLE_RE)) {
8685
+ const candidate = stripCallSuffix(match[1].trim());
8686
+ if (candidate && isLikelySymbolName(candidate)) {
8687
+ identifiers.add(candidate);
8688
+ }
8689
+ }
8690
+ for (const match of query.matchAll(QUOTED_DOUBLE_RE)) {
8691
+ const candidate = stripCallSuffix(match[1].trim());
8692
+ if (candidate && isLikelySymbolName(candidate)) {
8693
+ identifiers.add(candidate);
8694
+ }
8695
+ }
8696
+ return [...identifiers];
8697
+ }
8698
+ function extractBareIdentifiers(query) {
8699
+ const unquoted = query.replace(QUOTED_BACKTICK_RE, " ").replace(QUOTED_SINGLE_RE, " ").replace(QUOTED_DOUBLE_RE, " ");
8700
+ const identifiers = /* @__PURE__ */ new Set();
8701
+ for (const match of unquoted.matchAll(IDENTIFIER_RE)) {
8702
+ const candidate = stripCallSuffix(match[0]);
8703
+ if (isLikelySymbolName(candidate)) {
8704
+ identifiers.add(candidate);
8705
+ }
8706
+ }
8707
+ return [...identifiers];
8708
+ }
8709
+ function isSingleMeaningfulToken(query, symbol) {
8710
+ const tokens = query.replace(/[`'"()]/g, " ").split(/[^A-Za-z0-9_$]+/).map((token) => token.trim().toLowerCase()).filter((token) => token.length > 0).filter((token) => !STOP_WORDS.has(token));
8711
+ return tokens.length === 1 && tokens[0] === symbol.toLowerCase();
8712
+ }
8713
+ function inferExactSymbolFromQuery(query) {
8714
+ if (analyzeQueryIntent(query).explicitArtifactIntent) {
8715
+ return void 0;
8716
+ }
8717
+ const quoted = extractQuotedIdentifiers(query);
8718
+ if (quoted.length === 1) {
8719
+ return quoted[0];
8720
+ }
8721
+ if (quoted.length > 1) {
8722
+ return void 0;
8723
+ }
8724
+ const candidates = extractBareIdentifiers(query);
8725
+ if (candidates.length !== 1) {
8726
+ return void 0;
8727
+ }
8728
+ const candidate = candidates[0];
8729
+ if (DEFINITION_INTENT_RE.test(query)) {
8730
+ return candidate;
8731
+ }
8732
+ if (isSingleMeaningfulToken(query, candidate)) {
8733
+ return candidate;
8734
+ }
8735
+ return void 0;
8736
+ }
8737
+
8466
8738
  // src/indexer/call-graph-constants.ts
8467
8739
  var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
8468
8740
  "function_declaration",
@@ -8965,22 +9237,6 @@ function getUniquePendingChunksFromRequests(requests) {
8965
9237
  }
8966
9238
  return Array.from(uniqueChunks.values());
8967
9239
  }
8968
- function coalesceFailedBatches(batches) {
8969
- const grouped = /* @__PURE__ */ new Map();
8970
- for (const batch of batches) {
8971
- const key = `${batch.attemptCount}:${batch.lastAttempt}:${batch.error}`;
8972
- const existing = grouped.get(key);
8973
- if (!existing) {
8974
- grouped.set(key, {
8975
- ...batch,
8976
- chunks: [...batch.chunks]
8977
- });
8978
- continue;
8979
- }
8980
- existing.chunks.push(...batch.chunks);
8981
- }
8982
- return Array.from(grouped.values());
8983
- }
8984
9240
  function poolEmbeddingVectors(vectors, weights) {
8985
9241
  const firstVector = vectors[0];
8986
9242
  if (!firstVector) {
@@ -9013,9 +9269,268 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
9013
9269
  return true;
9014
9270
  }
9015
9271
 
9272
+ // src/indexer/failed-state-persistence.ts
9273
+ import * as fs2 from "fs";
9274
+ import { createHash, randomBytes as randomBytes3 } from "crypto";
9275
+ import * as path18 from "path";
9276
+ import { StringDecoder } from "string_decoder";
9277
+ var CURRENT_FAILED_BATCH_VERSION = 1;
9278
+ var DEFAULT_MALFORMED_LINE_ACTION = "skip";
9279
+ function* readFailedBatchRecords(filePath, options = {}) {
9280
+ if (!fs2.existsSync(filePath)) {
9281
+ return;
9282
+ }
9283
+ const fileFormat = detectFailedBatchFileFormat(filePath);
9284
+ if (fileFormat === "legacy") {
9285
+ yield* readLegacyFailedBatchRecords(filePath, options);
9286
+ return;
9287
+ }
9288
+ yield* readJsonlFailedBatchRecords(filePath, options);
9289
+ }
9290
+ function createFailedBatchWriter(targetPath) {
9291
+ const temporaryPath = createTemporaryPath(targetPath);
9292
+ let finalized = false;
9293
+ fs2.mkdirSync(path18.dirname(targetPath), { recursive: true });
9294
+ fs2.closeSync(fs2.openSync(temporaryPath, "w"));
9295
+ const write = (record) => {
9296
+ if (finalized) {
9297
+ throw new Error("Failed batch writer has been finalized");
9298
+ }
9299
+ const lines = record.chunks.map((chunk) => {
9300
+ const lineRecord = {
9301
+ version: CURRENT_FAILED_BATCH_VERSION,
9302
+ chunks: [chunk],
9303
+ error: record.error,
9304
+ attemptCount: record.attemptCount,
9305
+ lastAttempt: record.lastAttempt
9306
+ };
9307
+ return JSON.stringify(lineRecord);
9308
+ });
9309
+ if (lines.length === 0) {
9310
+ return;
9311
+ }
9312
+ fs2.mkdirSync(path18.dirname(targetPath), { recursive: true });
9313
+ fs2.appendFileSync(temporaryPath, `${lines.join("\n")}
9314
+ `, "utf-8");
9315
+ };
9316
+ const commit = () => {
9317
+ if (finalized) {
9318
+ return;
9319
+ }
9320
+ fs2.mkdirSync(path18.dirname(targetPath), { recursive: true });
9321
+ fs2.renameSync(temporaryPath, targetPath);
9322
+ finalized = true;
9323
+ };
9324
+ const cleanup = () => {
9325
+ if (finalized) {
9326
+ return;
9327
+ }
9328
+ fs2.rmSync(temporaryPath, { force: true });
9329
+ };
9330
+ return {
9331
+ write,
9332
+ commit,
9333
+ cleanup,
9334
+ temporaryPath
9335
+ };
9336
+ }
9337
+ function* readLegacyFailedBatchRecords(filePath, options) {
9338
+ const rawData = fs2.readFileSync(filePath, "utf-8");
9339
+ const trimmed = stripLeadingBomAndWhitespace(rawData).trim();
9340
+ if (trimmed.length === 0) {
9341
+ return;
9342
+ }
9343
+ let parsed;
9344
+ try {
9345
+ parsed = JSON.parse(trimmed);
9346
+ } catch (error) {
9347
+ handleMalformedLine(filePath, 1, trimmed, error, options);
9348
+ return;
9349
+ }
9350
+ if (!Array.isArray(parsed)) {
9351
+ handleMalformedLine(filePath, 1, trimmed, new Error("Expected legacy failed-batch file to contain a JSON array"), options);
9352
+ return;
9353
+ }
9354
+ for (const entry of parsed) {
9355
+ const normalized = normalizeFailedBatchRecord(entry);
9356
+ if (normalized) {
9357
+ yield normalized;
9358
+ }
9359
+ }
9360
+ }
9361
+ function* readJsonlFailedBatchRecords(filePath, options) {
9362
+ const handle = fs2.openSync(filePath, "r");
9363
+ const decoder = new StringDecoder("utf8");
9364
+ const readBuffer = Buffer.allocUnsafe(64 * 1024);
9365
+ let buffer = "";
9366
+ let lineNumber = 0;
9367
+ try {
9368
+ let bytesRead = 0;
9369
+ do {
9370
+ bytesRead = fs2.readSync(handle, readBuffer, 0, readBuffer.length, null);
9371
+ buffer += decoder.write(readBuffer.subarray(0, bytesRead));
9372
+ let newlineIndex = buffer.indexOf("\n");
9373
+ while (newlineIndex >= 0) {
9374
+ const rawLine = buffer.slice(0, newlineIndex);
9375
+ buffer = buffer.slice(newlineIndex + 1);
9376
+ lineNumber += 1;
9377
+ const normalized = parseFailedBatchLine(rawLine, filePath, lineNumber, options);
9378
+ if (normalized) {
9379
+ yield normalized;
9380
+ }
9381
+ newlineIndex = buffer.indexOf("\n");
9382
+ }
9383
+ } while (bytesRead > 0);
9384
+ buffer += decoder.end();
9385
+ const finalLine = buffer.trimEnd();
9386
+ if (finalLine.length > 0) {
9387
+ lineNumber += 1;
9388
+ const normalized = parseFailedBatchLine(finalLine, filePath, lineNumber, options);
9389
+ if (normalized) {
9390
+ yield normalized;
9391
+ }
9392
+ }
9393
+ } finally {
9394
+ fs2.closeSync(handle);
9395
+ }
9396
+ }
9397
+ function parseFailedBatchLine(rawLine, filePath, lineNumber, options) {
9398
+ const line = rawLine.trimEnd();
9399
+ if (line.length === 0) {
9400
+ return null;
9401
+ }
9402
+ try {
9403
+ const parsed = JSON.parse(line);
9404
+ const normalized = normalizeFailedBatchRecord(parsed);
9405
+ if (!normalized) {
9406
+ handleMalformedLine(filePath, lineNumber, line, new Error("Malformed failed-batch record"), options);
9407
+ return null;
9408
+ }
9409
+ return normalized;
9410
+ } catch (error) {
9411
+ handleMalformedLine(filePath, lineNumber, line, error, options);
9412
+ return null;
9413
+ }
9414
+ }
9415
+ function normalizeFailedBatchRecord(rawRecord) {
9416
+ if (!rawRecord || typeof rawRecord !== "object" || Array.isArray(rawRecord)) {
9417
+ return null;
9418
+ }
9419
+ const typed = rawRecord;
9420
+ const chunks = Array.isArray(typed.chunks) ? typed.chunks : null;
9421
+ if (!chunks || chunks.length === 0) {
9422
+ return null;
9423
+ }
9424
+ return {
9425
+ version: typeof typed.version === "number" && Number.isFinite(typed.version) ? typed.version : CURRENT_FAILED_BATCH_VERSION,
9426
+ chunks,
9427
+ error: typeof typed.error === "string" ? typed.error : "Unknown embedding error",
9428
+ attemptCount: typeof typed.attemptCount === "number" && Number.isFinite(typed.attemptCount) ? typed.attemptCount : 1,
9429
+ lastAttempt: typeof typed.lastAttempt === "string" ? typed.lastAttempt : (/* @__PURE__ */ new Date()).toISOString()
9430
+ };
9431
+ }
9432
+ function detectFailedBatchFileFormat(filePath) {
9433
+ const handle = fs2.openSync(filePath, "r");
9434
+ try {
9435
+ const buffer = Buffer.alloc(4096);
9436
+ const bytesRead = fs2.readSync(handle, buffer, 0, buffer.length, 0);
9437
+ if (bytesRead <= 0) {
9438
+ return "jsonl";
9439
+ }
9440
+ const prefix = stripLeadingBomAndWhitespace(buffer.subarray(0, bytesRead).toString("utf-8"));
9441
+ return prefix.startsWith("[") ? "legacy" : "jsonl";
9442
+ } finally {
9443
+ fs2.closeSync(handle);
9444
+ }
9445
+ }
9446
+ function stripLeadingBomAndWhitespace(value) {
9447
+ let result = value.trimStart();
9448
+ if (result.charCodeAt(0) === 65279) {
9449
+ result = result.slice(1);
9450
+ }
9451
+ return result;
9452
+ }
9453
+ function createTemporaryPath(targetPath) {
9454
+ const randomId = createHash("sha1").update(`${Date.now()}:${randomBytes3(8).toString("hex")}`).digest("hex");
9455
+ const targetDir = path18.dirname(targetPath);
9456
+ const baseName = path18.basename(targetPath);
9457
+ return path18.join(targetDir, `.${baseName}.${randomId}.tmp`);
9458
+ }
9459
+ function handleMalformedLine(filePath, lineNumber, line, error, options) {
9460
+ const action = options.malformedLineAction ?? DEFAULT_MALFORMED_LINE_ACTION;
9461
+ const normalizedError = error instanceof Error ? error : new Error(String(error));
9462
+ if (options.onMalformedLine) {
9463
+ options.onMalformedLine(normalizedError, line, lineNumber, filePath);
9464
+ }
9465
+ if (action === "fail") {
9466
+ throw normalizedError;
9467
+ }
9468
+ }
9469
+
9470
+ // src/indexer/file-batches.ts
9471
+ var INDEX_FILE_BATCH_LIMITS = Object.freeze({
9472
+ maxFiles: 64,
9473
+ maxBytes: 8 * 1024 * 1024
9474
+ });
9475
+ function* iterateOrderedFileBatches(items, getBytes, limits = INDEX_FILE_BATCH_LIMITS) {
9476
+ const maxFiles = Math.max(1, Math.floor(limits.maxFiles));
9477
+ const maxBytes = Math.max(1, Math.floor(limits.maxBytes));
9478
+ let batch = [];
9479
+ let batchBytes = 0;
9480
+ for (const item of items) {
9481
+ const itemBytes = Math.max(0, Math.floor(getBytes(item)));
9482
+ if (batch.length > 0 && (batch.length >= maxFiles || batchBytes + itemBytes > maxBytes)) {
9483
+ yield batch;
9484
+ batch = [];
9485
+ batchBytes = 0;
9486
+ }
9487
+ batch.push(item);
9488
+ batchBytes += itemBytes;
9489
+ }
9490
+ if (batch.length > 0) {
9491
+ yield batch;
9492
+ }
9493
+ }
9494
+
9016
9495
  // src/indexer/index.ts
9017
9496
  var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "swift", "php", "apex", "zig", "gdscript", "matlab", "bash", "c", "cpp", "metal"]);
9018
9497
  var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex", "php"]);
9498
+ function candidateOverlapsSymbol(candidate, symbol) {
9499
+ return candidate.metadata.filePath === symbol.filePath && candidate.metadata.startLine <= symbol.endLine && candidate.metadata.endLine >= symbol.startLine;
9500
+ }
9501
+ function resolveSameCommunityCandidateIds(query, candidates, database, branchCatalogKeys) {
9502
+ const anchorName = inferExactSymbolFromQuery(query);
9503
+ if (!anchorName || candidates.length === 0) {
9504
+ return /* @__PURE__ */ new Set();
9505
+ }
9506
+ const catalogs = branchCatalogKeys.map((branchKey) => ({
9507
+ branchKey,
9508
+ symbols: database.getSymbolsForBranch(branchKey)
9509
+ }));
9510
+ const exactAnchors = catalogs.flatMap(({ branchKey, symbols }) => symbols.filter((symbol) => symbol.name === anchorName).map((symbol) => ({ branchKey, symbol })));
9511
+ const anchors = exactAnchors.length > 0 ? exactAnchors : catalogs.flatMap(({ branchKey, symbols }) => symbols.filter((symbol) => symbol.name.toLowerCase() === anchorName.toLowerCase()).map((symbol) => ({ branchKey, symbol })));
9512
+ const uniqueAnchors = new Map(anchors.map((anchor2) => [anchor2.symbol.id, anchor2]));
9513
+ if (uniqueAnchors.size !== 1) {
9514
+ return /* @__PURE__ */ new Set();
9515
+ }
9516
+ const anchor = uniqueAnchors.values().next().value;
9517
+ const branchSymbols = catalogs.find((catalog) => catalog.branchKey === anchor.branchKey)?.symbols ?? [];
9518
+ const candidateSymbols = branchSymbols.filter(
9519
+ (symbol) => candidates.some((candidate) => candidateOverlapsSymbol(candidate, symbol))
9520
+ );
9521
+ const assignments = database.detectCommunities(
9522
+ anchor.branchKey,
9523
+ [anchor.symbol.id, ...candidateSymbols.map((symbol) => symbol.id)]
9524
+ );
9525
+ const anchorCommunity = assignments.find((assignment) => assignment.symbolId === anchor.symbol.id)?.communityId;
9526
+ if (anchorCommunity === void 0) {
9527
+ return /* @__PURE__ */ new Set();
9528
+ }
9529
+ const sameCommunitySymbolIds = new Set(assignments.filter((assignment) => assignment.communityId === anchorCommunity).map((assignment) => assignment.symbolId));
9530
+ return new Set(candidates.filter((candidate) => candidateSymbols.some(
9531
+ (symbol) => sameCommunitySymbolIds.has(symbol.id) && candidateOverlapsSymbol(candidate, symbol)
9532
+ )).map((candidate) => candidate.id));
9533
+ }
9019
9534
  var CALL_GRAPH_RESOLUTION_VERSION = "4";
9020
9535
  var PHP_FUNCTION_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
9021
9536
  "function_declaration",
@@ -9116,6 +9631,16 @@ function isSqliteCorruptionError(error) {
9116
9631
  }
9117
9632
  var STARTUP_WARNING_METADATA_KEY = "index.startupWarning";
9118
9633
  var READER_ARTIFACT_RETRY_INTERVAL_MS = 1e3;
9634
+ function getFailedBatchGroupKey(record) {
9635
+ return `${record.attemptCount}:${record.lastAttempt}:${record.error}`;
9636
+ }
9637
+ function getPendingChunkId(rawChunk) {
9638
+ if (!rawChunk || typeof rawChunk !== "object") {
9639
+ return null;
9640
+ }
9641
+ const id = rawChunk.id;
9642
+ return typeof id === "string" ? id : null;
9643
+ }
9119
9644
  function metadataFromBlame(blame) {
9120
9645
  if (!blame) {
9121
9646
  return {};
@@ -9163,9 +9688,9 @@ var SWIFT_PARSER_VERSION = "1";
9163
9688
  var METAL_PARSER_VERSION = "1";
9164
9689
  var SYMBOL_EXTRACTOR_VERSION = "1";
9165
9690
  function isPathWithinRoot2(filePath, rootPath) {
9166
- const normalizedFilePath = path18.resolve(filePath);
9167
- const normalizedRoot = path18.resolve(rootPath);
9168
- return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path18.sep}`);
9691
+ const normalizedFilePath = path19.resolve(filePath);
9692
+ const normalizedRoot = path19.resolve(rootPath);
9693
+ return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path19.sep}`);
9169
9694
  }
9170
9695
  function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCandidates, database, branchChunkIds, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
9171
9696
  if (combined.length === 0) {
@@ -9496,10 +10021,10 @@ function matchesHardSearchFilters(candidate, options, projectRoot3) {
9496
10021
  }
9497
10022
  if (options?.directory) {
9498
10023
  const candidatePath = canonicalizePathForComparison(
9499
- path18.resolve(projectRoot3, candidate.metadata.filePath.replace(/\\/g, path18.sep))
10024
+ path19.resolve(projectRoot3, candidate.metadata.filePath.replace(/\\/g, path19.sep))
9500
10025
  );
9501
10026
  const directoryPath = canonicalizePathForComparison(
9502
- path18.resolve(projectRoot3, options.directory.trim().replace(/\\/g, path18.sep))
10027
+ path19.resolve(projectRoot3, options.directory.trim().replace(/\\/g, path19.sep))
9503
10028
  );
9504
10029
  if (!isPathWithinRoot2(candidatePath, directoryPath)) return false;
9505
10030
  }
@@ -9575,6 +10100,7 @@ var Indexer = class _Indexer {
9575
10100
  readerArtifactFingerprint = null;
9576
10101
  writerArtifactFingerprint = null;
9577
10102
  readerArtifactRetryAfter = /* @__PURE__ */ new Map();
10103
+ fileBatchLimits;
9578
10104
  constructor(projectRoot3, config, host, runtimeOptions = {}) {
9579
10105
  this.projectRoot = projectRoot3;
9580
10106
  this.projectIdentityHash = hashContent(this.getCanonicalPath(projectRoot3)).slice(0, 16);
@@ -9586,6 +10112,7 @@ var Indexer = class _Indexer {
9586
10112
  }
9587
10113
  this.expectedCommitOverride = runtimeOptions.expectedCommit?.toLowerCase();
9588
10114
  this.indexPathOverride = runtimeOptions.indexPath;
10115
+ this.fileBatchLimits = runtimeOptions.fileBatchLimits;
9589
10116
  this.config = config;
9590
10117
  this.host = host;
9591
10118
  if (isGitRepo(this.materializedProjectRoot)) {
@@ -9603,26 +10130,26 @@ var Indexer = class _Indexer {
9603
10130
  return this.indexPathOverride ?? resolveProjectIndexPath(this.projectRoot, this.config.scope, this.host);
9604
10131
  }
9605
10132
  toCanonicalFilePath(filePath) {
9606
- if (!path18.isAbsolute(filePath)) {
10133
+ if (!path19.isAbsolute(filePath)) {
9607
10134
  return this.resolveStoredFilePath(filePath, this.projectRoot);
9608
10135
  }
9609
- if (path18.resolve(this.materializedProjectRoot) === path18.resolve(this.projectRoot) || !isPathWithinRoot2(filePath, this.materializedProjectRoot)) {
10136
+ if (path19.resolve(this.materializedProjectRoot) === path19.resolve(this.projectRoot) || !isPathWithinRoot2(filePath, this.materializedProjectRoot)) {
9610
10137
  return filePath;
9611
10138
  }
9612
- return path18.resolve(this.projectRoot, path18.relative(this.materializedProjectRoot, filePath));
10139
+ return path19.resolve(this.projectRoot, path19.relative(this.materializedProjectRoot, filePath));
9613
10140
  }
9614
10141
  toStoredFilePath(filePath) {
9615
10142
  const canonicalFilePath = this.toCanonicalFilePath(filePath);
9616
10143
  if (this.config.scope !== "project" || !isPathWithinRoot2(canonicalFilePath, this.projectRoot)) {
9617
10144
  return canonicalFilePath;
9618
10145
  }
9619
- return path18.relative(this.projectRoot, canonicalFilePath).split(path18.sep).join("/");
10146
+ return path19.relative(this.projectRoot, canonicalFilePath).split(path19.sep).join("/");
9620
10147
  }
9621
10148
  resolveStoredFilePath(filePath, rootPath = this.projectRoot) {
9622
- if (path18.isAbsolute(filePath)) {
10149
+ if (path19.isAbsolute(filePath)) {
9623
10150
  return filePath;
9624
10151
  }
9625
- const resolvedPath = path18.resolve(rootPath, ...filePath.split("/"));
10152
+ const resolvedPath = path19.resolve(rootPath, ...filePath.split("/"));
9626
10153
  if (!isPathWithinRoot2(resolvedPath, rootPath)) {
9627
10154
  throw new Error(`Stored project path escapes project root: ${JSON.stringify(filePath)}`);
9628
10155
  }
@@ -9646,7 +10173,7 @@ var Indexer = class _Indexer {
9646
10173
  }
9647
10174
  toMaterializedFilePath(filePath) {
9648
10175
  const storedFilePath = this.toStoredFilePath(filePath);
9649
- if (path18.isAbsolute(storedFilePath)) {
10176
+ if (path19.isAbsolute(storedFilePath)) {
9650
10177
  return storedFilePath;
9651
10178
  }
9652
10179
  return this.resolveStoredFilePath(storedFilePath, this.materializedProjectRoot);
@@ -9663,10 +10190,10 @@ var Indexer = class _Indexer {
9663
10190
  }
9664
10191
  getRuntimeArtifactPath(fileName) {
9665
10192
  const namespace = this.getRuntimeArtifactNamespace();
9666
- if (!namespace) return path18.join(this.indexPath, fileName);
9667
- const extension = path18.extname(fileName);
10193
+ if (!namespace) return path19.join(this.indexPath, fileName);
10194
+ const extension = path19.extname(fileName);
9668
10195
  const baseName = fileName.slice(0, fileName.length - extension.length);
9669
- return path18.join(this.indexPath, `${baseName}.${namespace}${extension}`);
10196
+ return path19.join(this.indexPath, `${baseName}.${namespace}${extension}`);
9670
10197
  }
9671
10198
  refreshRuntimeArtifactPaths() {
9672
10199
  this.fileHashCachePath = this.getRuntimeArtifactPath("file-hashes.json");
@@ -9679,14 +10206,14 @@ var Indexer = class _Indexer {
9679
10206
  getMaterializedKnowledgeBases() {
9680
10207
  const canonicalProjectRoot = this.getCanonicalPath(this.projectRoot);
9681
10208
  return this.config.knowledgeBases.map((knowledgeBase) => {
9682
- const configuredPath = path18.isAbsolute(knowledgeBase) ? knowledgeBase : path18.resolve(this.projectRoot, knowledgeBase);
10209
+ const configuredPath = path19.isAbsolute(knowledgeBase) ? knowledgeBase : path19.resolve(this.projectRoot, knowledgeBase);
9683
10210
  const canonicalPath = this.getCanonicalPath(configuredPath);
9684
10211
  if (!isPathWithinRoot2(canonicalPath, canonicalProjectRoot)) {
9685
10212
  return canonicalPath;
9686
10213
  }
9687
- return path18.resolve(
10214
+ return path19.resolve(
9688
10215
  this.materializedProjectRoot,
9689
- path18.relative(canonicalProjectRoot, canonicalPath)
10216
+ path19.relative(canonicalProjectRoot, canonicalPath)
9690
10217
  );
9691
10218
  });
9692
10219
  }
@@ -9694,7 +10221,7 @@ var Indexer = class _Indexer {
9694
10221
  try {
9695
10222
  return canonicalizePathForComparison(targetPath);
9696
10223
  } catch {
9697
- return path18.resolve(targetPath);
10224
+ return path19.resolve(targetPath);
9698
10225
  }
9699
10226
  }
9700
10227
  isProjectOwnedIndexPath() {
@@ -9770,7 +10297,7 @@ var Indexer = class _Indexer {
9770
10297
  } catch (error) {
9771
10298
  releaseError = error;
9772
10299
  this.writerArtifactFingerprint = null;
9773
- if (!existsSync10(lease.lockPath) && this.activeIndexLease?.owner.token === lease.owner.token) {
10300
+ if (!existsSync11(lease.lockPath) && this.activeIndexLease?.owner.token === lease.owner.token) {
9774
10301
  this.activeIndexLease = null;
9775
10302
  }
9776
10303
  }
@@ -9788,11 +10315,11 @@ var Indexer = class _Indexer {
9788
10315
  return this.activeIndexLease;
9789
10316
  }
9790
10317
  loadFileHashCache() {
9791
- if (!existsSync10(this.fileHashCachePath)) {
10318
+ if (!existsSync11(this.fileHashCachePath)) {
9792
10319
  return;
9793
10320
  }
9794
10321
  try {
9795
- const data = readFileSync7(this.fileHashCachePath, "utf-8");
10322
+ const data = readFileSync8(this.fileHashCachePath, "utf-8");
9796
10323
  const parsed = JSON.parse(data);
9797
10324
  this.fileHashCache = new Map(Object.entries(parsed));
9798
10325
  } catch (error) {
@@ -9814,24 +10341,24 @@ var Indexer = class _Indexer {
9814
10341
  atomicWriteSync(targetPath, data) {
9815
10342
  const lease = this.requireActiveLease();
9816
10343
  const tempPath = createLeaseTemporaryPath(targetPath, lease.owner, "tmp");
9817
- mkdirSync3(path18.dirname(targetPath), { recursive: true });
10344
+ mkdirSync4(path19.dirname(targetPath), { recursive: true });
9818
10345
  try {
9819
10346
  writeFileSync3(tempPath, data);
9820
- renameSync2(tempPath, targetPath);
10347
+ renameSync3(tempPath, targetPath);
9821
10348
  } finally {
9822
10349
  removeLeaseTemporaryPath(tempPath);
9823
10350
  }
9824
10351
  }
9825
10352
  saveInvertedIndex(invertedIndex) {
9826
10353
  this.atomicWriteSync(
9827
- path18.join(this.indexPath, "inverted-index.json"),
10354
+ path19.join(this.indexPath, "inverted-index.json"),
9828
10355
  invertedIndex.serialize()
9829
10356
  );
9830
10357
  }
9831
10358
  getScopedRoots() {
9832
10359
  const roots = /* @__PURE__ */ new Set([this.getCanonicalPath(this.projectRoot)]);
9833
10360
  for (const kbRoot of this.config.knowledgeBases) {
9834
- roots.add(this.getCanonicalPath(path18.resolve(this.projectRoot, kbRoot)));
10361
+ roots.add(this.getCanonicalPath(path19.resolve(this.projectRoot, kbRoot)));
9835
10362
  }
9836
10363
  return Array.from(roots);
9837
10364
  }
@@ -9847,6 +10374,9 @@ var Indexer = class _Indexer {
9847
10374
  }
9848
10375
  return `${this.projectIdentityHash}:${branchName}`;
9849
10376
  }
10377
+ resolveBranchCatalogKey(branchName) {
10378
+ return branchName === void 0 ? this.getBranchCatalogKey() : this.getBranchCatalogKeyFor(branchName);
10379
+ }
9850
10380
  getBranchCommitMetadataKey(catalogIdentity = this.getBranchCatalogIdentity()) {
9851
10381
  const branchKey = this.getBranchCatalogKeyFor(catalogIdentity);
9852
10382
  return `index.branchCommit.${hashContent(branchKey).slice(0, 24)}`;
@@ -9941,13 +10471,13 @@ var Indexer = class _Indexer {
9941
10471
  if (Array.from(this.fileHashCache.keys()).some((filePath) => this.isFileInCurrentScope(filePath, roots))) {
9942
10472
  return true;
9943
10473
  }
9944
- if (this.loadSerializedFailedBatches().some(
9945
- (batch) => batch.chunks.some((chunk) => {
10474
+ for (const batch of this.loadSerializedFailedBatches()) {
10475
+ if (batch.chunks.some((chunk) => {
9946
10476
  const filePath = getPendingChunkFilePath(chunk);
9947
10477
  return filePath !== null && this.isFileInCurrentScope(filePath, roots);
9948
- })
9949
- )) {
9950
- return true;
10478
+ })) {
10479
+ return true;
10480
+ }
9951
10481
  }
9952
10482
  if (!this.database) {
9953
10483
  return false;
@@ -10088,40 +10618,25 @@ var Indexer = class _Indexer {
10088
10618
  }
10089
10619
  this.saveFileHashCache();
10090
10620
  }
10091
- partitionFailedBatches(roots, maxChunkTokens) {
10092
- const scoped = [];
10093
- const retained = [];
10094
- for (const batch of this.loadSerializedFailedBatches()) {
10095
- const scopedChunks = batch.chunks.filter((chunk) => {
10096
- const filePath = getPendingChunkFilePath(chunk);
10097
- return filePath !== null && this.isFileInCurrentScope(filePath, roots);
10098
- });
10099
- const retainedChunks = batch.chunks.filter((chunk) => {
10100
- const filePath = getPendingChunkFilePath(chunk);
10101
- return filePath === null || !this.isFileInCurrentScope(filePath, roots);
10102
- });
10103
- if (scopedChunks.length > 0) {
10104
- const normalizedBatch = normalizeFailedBatch({ ...batch, chunks: scopedChunks }, maxChunkTokens);
10105
- if (normalizedBatch) {
10106
- scoped.push(normalizedBatch);
10107
- }
10108
- }
10109
- if (retainedChunks.length > 0) {
10110
- retained.push({ ...batch, chunks: retainedChunks });
10111
- }
10112
- }
10113
- return { scoped, retained };
10114
- }
10115
10621
  clearScopedFailedBatches(roots) {
10116
- const { retained: retainedBatches } = this.partitionFailedBatches(roots);
10117
- this.saveFailedBatches(retainedBatches);
10622
+ this.rewriteFailedBatchState((chunk) => {
10623
+ const filePath = getPendingChunkFilePath(chunk);
10624
+ return filePath === null || !this.isFileInCurrentScope(filePath, roots);
10625
+ });
10118
10626
  }
10119
10627
  hasForeignScopedFileHashData(roots) {
10120
10628
  return Array.from(this.fileHashCache.keys()).some((filePath) => !this.isFileInCurrentScope(filePath, roots));
10121
10629
  }
10122
10630
  hasForeignScopedFailedBatches(roots) {
10123
- const { retained } = this.partitionFailedBatches(roots);
10124
- return retained.length > 0;
10631
+ for (const batch of this.loadSerializedFailedBatches()) {
10632
+ if (batch.chunks.some((chunk) => {
10633
+ const filePath = getPendingChunkFilePath(chunk);
10634
+ return filePath === null || !this.isFileInCurrentScope(filePath, roots);
10635
+ })) {
10636
+ return true;
10637
+ }
10638
+ }
10639
+ return false;
10125
10640
  }
10126
10641
  hasForeignScopedBranchData() {
10127
10642
  if (!this.database || this.config.scope !== "global") {
@@ -10146,10 +10661,6 @@ var Indexer = class _Indexer {
10146
10661
  }
10147
10662
  );
10148
10663
  }
10149
- saveScopedFailedBatches(batches, roots) {
10150
- const { retained } = this.partitionFailedBatches(roots);
10151
- this.saveFailedBatches([...retained, ...batches]);
10152
- }
10153
10664
  clearSharedIndexProjectData(store, invertedIndex, database, roots) {
10154
10665
  const allMetadata = store.getAllMetadata();
10155
10666
  const scopedEntries = allMetadata.filter(({ metadata }) => this.isFileInCurrentScope(metadata.filePath, roots));
@@ -10239,77 +10750,144 @@ var Indexer = class _Indexer {
10239
10750
  });
10240
10751
  }
10241
10752
  if (this.config.scope === "global") {
10242
- if (existsSync10(this.fileHashCachePath)) {
10753
+ if (existsSync11(this.fileHashCachePath)) {
10243
10754
  unlinkSync2(this.fileHashCachePath);
10244
10755
  }
10245
10756
  await this.healthCheckUnlocked();
10246
10757
  }
10247
10758
  this.logger.info("Recovery complete, next index will re-process all files");
10248
10759
  }
10249
- loadFailedBatches(maxChunkTokens) {
10250
- try {
10251
- return this.loadSerializedFailedBatches().map((batch) => normalizeFailedBatch(batch, maxChunkTokens)).filter((batch) => batch !== null);
10252
- } catch (error) {
10253
- const message = error instanceof Error ? error.message : String(error);
10760
+ *loadSerializedFailedBatches() {
10761
+ let warned = false;
10762
+ const warn = (error) => {
10763
+ if (warned) return;
10764
+ warned = true;
10254
10765
  this.logger.warn("Failed to load failed batch state, skipping persisted retries", {
10255
10766
  failedBatchesPath: this.failedBatchesPath,
10256
- error: message
10767
+ error: getErrorMessage4(error)
10257
10768
  });
10258
- return [];
10769
+ };
10770
+ try {
10771
+ for (const record of readFailedBatchRecords(this.failedBatchesPath, {
10772
+ malformedLineAction: "skip",
10773
+ onMalformedLine: (error) => warn(error)
10774
+ })) {
10775
+ yield {
10776
+ chunks: record.chunks,
10777
+ error: record.error,
10778
+ attemptCount: record.attemptCount,
10779
+ lastAttempt: record.lastAttempt
10780
+ };
10781
+ }
10782
+ } catch (error) {
10783
+ warn(error);
10259
10784
  }
10260
10785
  }
10261
- loadSerializedFailedBatches() {
10262
- if (!existsSync10(this.failedBatchesPath)) {
10263
- return [];
10786
+ createFailedBatchWriteState() {
10787
+ return {
10788
+ writer: createFailedBatchWriter(this.failedBatchesPath),
10789
+ recordsWritten: 0
10790
+ };
10791
+ }
10792
+ writeFailedBatchRecord(state, record) {
10793
+ state.writer.write(record);
10794
+ state.recordsWritten += record.chunks.length;
10795
+ }
10796
+ finalizeFailedBatchWriteState(state) {
10797
+ if (state.recordsWritten > 0) {
10798
+ state.writer.commit();
10799
+ return;
10264
10800
  }
10265
- const data = readFileSync7(this.failedBatchesPath, "utf-8");
10266
- const parsed = JSON.parse(data);
10267
- return parsed.map((batch) => {
10268
- const chunks = Array.isArray(batch.chunks) ? batch.chunks : [];
10269
- if (chunks.length === 0) {
10270
- return null;
10801
+ state.writer.cleanup();
10802
+ this.clearFailedBatchState();
10803
+ }
10804
+ clearFailedBatchState() {
10805
+ if (existsSync11(this.failedBatchesPath)) {
10806
+ try {
10807
+ unlinkSync2(this.failedBatchesPath);
10808
+ } catch {
10271
10809
  }
10272
- return {
10273
- chunks,
10274
- error: typeof batch.error === "string" ? batch.error : "Unknown embedding error",
10275
- attemptCount: typeof batch.attemptCount === "number" ? batch.attemptCount : 1,
10276
- lastAttempt: typeof batch.lastAttempt === "string" ? batch.lastAttempt : (/* @__PURE__ */ new Date()).toISOString()
10277
- };
10278
- }).filter((batch) => batch !== null);
10810
+ }
10279
10811
  }
10280
- saveFailedBatches(batches) {
10281
- if (batches.length === 0) {
10282
- if (existsSync10(this.failedBatchesPath)) {
10283
- try {
10284
- unlinkSync2(this.failedBatchesPath);
10285
- } catch {
10812
+ rewriteFailedBatchState(shouldRetain) {
10813
+ const state = this.createFailedBatchWriteState();
10814
+ try {
10815
+ for (const batch of this.loadSerializedFailedBatches()) {
10816
+ const retainedChunks = batch.chunks.filter(shouldRetain);
10817
+ if (retainedChunks.length > 0) {
10818
+ this.writeFailedBatchRecord(state, { ...batch, chunks: retainedChunks });
10286
10819
  }
10287
10820
  }
10288
- return;
10821
+ this.finalizeFailedBatchWriteState(state);
10822
+ } catch (error) {
10823
+ state.writer.cleanup();
10824
+ throw error;
10825
+ }
10826
+ }
10827
+ prepareFailedBatchProcessing(roots, shouldProcess) {
10828
+ const state = this.createFailedBatchWriteState();
10829
+ const latestById = /* @__PURE__ */ new Map();
10830
+ try {
10831
+ for (const batch of this.loadSerializedFailedBatches()) {
10832
+ for (const rawChunk of batch.chunks) {
10833
+ const filePath = getPendingChunkFilePath(rawChunk);
10834
+ const inScope = roots === null || filePath !== null && this.isFileInCurrentScope(filePath, roots);
10835
+ if (!inScope) {
10836
+ this.writeFailedBatchRecord(state, { ...batch, chunks: [rawChunk] });
10837
+ continue;
10838
+ }
10839
+ if (!shouldProcess(filePath)) {
10840
+ continue;
10841
+ }
10842
+ const chunkId = getPendingChunkId(rawChunk);
10843
+ if (!chunkId) {
10844
+ continue;
10845
+ }
10846
+ const existing = latestById.get(chunkId);
10847
+ if (!existing || batch.attemptCount >= existing.attemptCount) {
10848
+ latestById.set(chunkId, {
10849
+ attemptCount: batch.attemptCount,
10850
+ error: batch.error,
10851
+ lastAttempt: batch.lastAttempt
10852
+ });
10853
+ }
10854
+ }
10855
+ }
10856
+ return { state, latestById };
10857
+ } catch (error) {
10858
+ state.writer.cleanup();
10859
+ throw error;
10289
10860
  }
10290
- this.atomicWriteSync(this.failedBatchesPath, JSON.stringify(batches, null, 2));
10291
10861
  }
10292
- collectRetryableFailedChunks(currentFileHashes, unchangedFilePaths, maxChunkTokens) {
10293
- const retryableById = /* @__PURE__ */ new Map();
10294
- for (const batch of this.loadFailedBatches(maxChunkTokens)) {
10295
- for (const chunk of batch.chunks) {
10296
- const filePath = chunk.metadata.filePath;
10297
- if (!currentFileHashes.has(filePath)) {
10862
+ *iterateLatestFailedChunks(latestById, roots, shouldProcess, maxChunkTokens) {
10863
+ const yielded = /* @__PURE__ */ new Set();
10864
+ for (const batch of this.loadSerializedFailedBatches()) {
10865
+ for (const rawChunk of batch.chunks) {
10866
+ const chunkId = getPendingChunkId(rawChunk);
10867
+ if (!chunkId || yielded.has(chunkId)) {
10298
10868
  continue;
10299
10869
  }
10300
- if (!unchangedFilePaths.has(filePath)) {
10870
+ const latest = latestById.get(chunkId);
10871
+ if (!latest || latest.attemptCount !== batch.attemptCount || latest.error !== batch.error || latest.lastAttempt !== batch.lastAttempt) {
10301
10872
  continue;
10302
10873
  }
10303
- const existing = retryableById.get(chunk.id);
10304
- if (!existing || batch.attemptCount > existing.attemptCount) {
10305
- retryableById.set(chunk.id, {
10306
- chunk,
10307
- attemptCount: batch.attemptCount
10308
- });
10874
+ const filePath = getPendingChunkFilePath(rawChunk);
10875
+ const inScope = roots === null || filePath !== null && this.isFileInCurrentScope(filePath, roots);
10876
+ if (!inScope || !shouldProcess(filePath)) {
10877
+ continue;
10878
+ }
10879
+ const normalized = normalizeFailedBatch({ ...batch, chunks: [rawChunk] }, maxChunkTokens);
10880
+ const chunk = normalized?.chunks[0];
10881
+ if (!chunk) {
10882
+ continue;
10309
10883
  }
10884
+ yielded.add(chunkId);
10885
+ yield {
10886
+ chunk,
10887
+ attemptCount: batch.attemptCount
10888
+ };
10310
10889
  }
10311
10890
  }
10312
- return Array.from(retryableById.values());
10313
10891
  }
10314
10892
  getProviderRateLimits(provider) {
10315
10893
  switch (provider) {
@@ -10334,6 +10912,242 @@ var Indexer = class _Indexer {
10334
10912
  return { concurrency: 3, intervalMs: 1e3, minRetryMs: 1e3, maxRetryMs: 3e4 };
10335
10913
  }
10336
10914
  }
10915
+ async processPendingChunkBatch(chunks, options) {
10916
+ const result = {
10917
+ indexedChunks: 0,
10918
+ failedChunks: 0,
10919
+ tokensUsed: 0,
10920
+ failedChunkIds: /* @__PURE__ */ new Set()
10921
+ };
10922
+ if (chunks.length === 0) {
10923
+ return result;
10924
+ }
10925
+ const chunksNeedingEmbedding = [];
10926
+ let cachedChunkCount = 0;
10927
+ if (options.reuseCachedEmbeddings && !options.forceReembed) {
10928
+ const missingHashes = new Set(options.database.getMissingEmbeddings(chunks.map((chunk) => chunk.contentHash)));
10929
+ for (const chunk of chunks) {
10930
+ if (missingHashes.has(chunk.contentHash)) {
10931
+ chunksNeedingEmbedding.push(chunk);
10932
+ continue;
10933
+ }
10934
+ const embeddingBuffer = options.database.getEmbedding(chunk.contentHash);
10935
+ if (!embeddingBuffer) {
10936
+ chunksNeedingEmbedding.push(chunk);
10937
+ continue;
10938
+ }
10939
+ options.store.add(chunk.id, Array.from(bufferToFloat32Array(embeddingBuffer)), chunk.metadata);
10940
+ options.invertedIndex.removeChunk(chunk.id);
10941
+ options.invertedIndex.addChunk(chunk.id, chunk.content);
10942
+ options.onSucceeded?.([chunk]);
10943
+ result.indexedChunks += 1;
10944
+ cachedChunkCount += 1;
10945
+ }
10946
+ } else {
10947
+ chunksNeedingEmbedding.push(...chunks);
10948
+ }
10949
+ this.logger.cache("info", "Embedding cache lookup", {
10950
+ needsEmbedding: chunksNeedingEmbedding.length,
10951
+ fromCache: cachedChunkCount
10952
+ });
10953
+ if (cachedChunkCount > 0) {
10954
+ this.logger.recordChunksFromCache(cachedChunkCount);
10955
+ options.onProgress?.(result);
10956
+ }
10957
+ if (chunksNeedingEmbedding.length === 0) {
10958
+ return result;
10959
+ }
10960
+ const pendingChunksById = new Map(chunksNeedingEmbedding.map((chunk) => [chunk.id, chunk]));
10961
+ const embeddingPartsByChunk = /* @__PURE__ */ new Map();
10962
+ const completedVectorsByChunkId = /* @__PURE__ */ new Map();
10963
+ const completedChunkIds = /* @__PURE__ */ new Set();
10964
+ const requestBatches = createPendingEmbeddingRequestBatches(
10965
+ chunksNeedingEmbedding,
10966
+ getDynamicBatchOptions(options.configuredProviderInfo)
10967
+ );
10968
+ let fatalError;
10969
+ for (const requestBatch of requestBatches) {
10970
+ await options.queue.onSizeLessThan(Math.max(1, options.providerRateLimits.concurrency));
10971
+ const task = options.queue.add(async () => {
10972
+ if (options.rateLimitState.backoffMs > 0) {
10973
+ await new Promise((resolve13) => setTimeout(resolve13, options.rateLimitState.backoffMs));
10974
+ }
10975
+ try {
10976
+ const embeddingResult = await pRetry(
10977
+ async () => {
10978
+ const texts = requestBatch.map((request) => request.text);
10979
+ return options.provider.embedBatch(texts);
10980
+ },
10981
+ {
10982
+ retries: this.config.indexing.retries,
10983
+ minTimeout: Math.max(this.config.indexing.retryDelayMs, options.providerRateLimits.minRetryMs),
10984
+ maxTimeout: options.providerRateLimits.maxRetryMs,
10985
+ factor: 2,
10986
+ shouldRetry: (error) => !(error.error instanceof CustomProviderNonRetryableError),
10987
+ onFailedAttempt: (error) => {
10988
+ const message = getErrorMessage4(error);
10989
+ if (isRateLimitError(error)) {
10990
+ options.rateLimitState.backoffMs = Math.min(
10991
+ options.providerRateLimits.maxRetryMs,
10992
+ (options.rateLimitState.backoffMs || options.providerRateLimits.minRetryMs) * 2
10993
+ );
10994
+ this.logger.embedding("warn", "Rate limited, backing off", {
10995
+ attempt: error.attemptNumber,
10996
+ retriesLeft: error.retriesLeft,
10997
+ backoffMs: options.rateLimitState.backoffMs
10998
+ });
10999
+ } else {
11000
+ this.logger.embedding("error", "Embedding batch failed", {
11001
+ attempt: error.attemptNumber,
11002
+ error: message
11003
+ });
11004
+ }
11005
+ }
11006
+ }
11007
+ );
11008
+ if (options.rateLimitState.backoffMs > 0) {
11009
+ options.rateLimitState.backoffMs = Math.max(0, options.rateLimitState.backoffMs - 2e3);
11010
+ }
11011
+ const touchedChunkIds = /* @__PURE__ */ new Set();
11012
+ requestBatch.forEach((request, index) => {
11013
+ if (result.failedChunkIds.has(request.chunk.id) || completedChunkIds.has(request.chunk.id)) {
11014
+ return;
11015
+ }
11016
+ const vector = embeddingResult.embeddings[index];
11017
+ if (!vector) {
11018
+ throw new Error(`Embedding API returned too few vectors for chunk ${request.chunk.id}`);
11019
+ }
11020
+ const parts = embeddingPartsByChunk.get(request.chunk.id) ?? [];
11021
+ parts[request.partIndex] = {
11022
+ vector,
11023
+ tokenCount: request.tokenCount
11024
+ };
11025
+ embeddingPartsByChunk.set(request.chunk.id, parts);
11026
+ touchedChunkIds.add(request.chunk.id);
11027
+ });
11028
+ const pooledResults = [];
11029
+ for (const chunkId of touchedChunkIds) {
11030
+ if (result.failedChunkIds.has(chunkId) || completedChunkIds.has(chunkId)) {
11031
+ continue;
11032
+ }
11033
+ const chunk = pendingChunksById.get(chunkId);
11034
+ if (!chunk) {
11035
+ continue;
11036
+ }
11037
+ const parts = embeddingPartsByChunk.get(chunk.id) ?? [];
11038
+ if (!hasAllEmbeddingParts(parts, chunk.texts.length)) {
11039
+ continue;
11040
+ }
11041
+ const orderedParts = parts;
11042
+ pooledResults.push({
11043
+ chunk,
11044
+ vector: poolEmbeddingVectors(
11045
+ orderedParts.map((part) => part.vector),
11046
+ orderedParts.map((part) => part.tokenCount)
11047
+ )
11048
+ });
11049
+ }
11050
+ if (pooledResults.length > 0) {
11051
+ options.database.upsertEmbeddingsBatch(pooledResults.map(({ chunk, vector }) => ({
11052
+ contentHash: chunk.contentHash,
11053
+ embedding: float32ArrayToBuffer(vector),
11054
+ chunkText: chunk.storageText,
11055
+ model: options.configuredProviderInfo.modelInfo.model
11056
+ })));
11057
+ const succeededChunks = pooledResults.map(({ chunk }) => chunk);
11058
+ for (const { chunk, vector } of pooledResults) {
11059
+ completedVectorsByChunkId.set(chunk.id, vector);
11060
+ }
11061
+ for (const chunk of succeededChunks) {
11062
+ completedChunkIds.add(chunk.id);
11063
+ embeddingPartsByChunk.delete(chunk.id);
11064
+ }
11065
+ }
11066
+ result.tokensUsed += embeddingResult.totalTokensUsed;
11067
+ this.logger.recordEmbeddingApiCall(embeddingResult.totalTokensUsed);
11068
+ this.logger.embedding("debug", "Embedded batch", {
11069
+ batchSize: pooledResults.length,
11070
+ requestCount: requestBatch.length,
11071
+ tokens: embeddingResult.totalTokensUsed
11072
+ });
11073
+ } catch (error) {
11074
+ const failedChunks = getUniquePendingChunksFromRequests(requestBatch).filter((chunk) => !completedChunkIds.has(chunk.id)).filter((chunk) => options.incrementRepeatedFailures || !result.failedChunkIds.has(chunk.id));
11075
+ const failureMessage = getErrorMessage4(error);
11076
+ const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
11077
+ for (const chunk of failedChunks) {
11078
+ if (!result.failedChunkIds.has(chunk.id)) {
11079
+ result.failedChunkIds.add(chunk.id);
11080
+ result.failedChunks += 1;
11081
+ }
11082
+ embeddingPartsByChunk.delete(chunk.id);
11083
+ const attemptCount = (options.attemptCounts.get(chunk.id) ?? 0) + 1;
11084
+ options.attemptCounts.set(chunk.id, attemptCount);
11085
+ this.writeFailedBatchRecord(options.failedState, {
11086
+ chunks: [chunk],
11087
+ error: failureMessage,
11088
+ attemptCount,
11089
+ lastAttempt: failureTimestamp
11090
+ });
11091
+ }
11092
+ this.logger.recordEmbeddingError();
11093
+ this.logger.embedding("error", "Failed to embed batch after retries", {
11094
+ batchSize: failedChunks.length,
11095
+ requestCount: requestBatch.length,
11096
+ error: failureMessage
11097
+ });
11098
+ }
11099
+ options.onProgress?.(result);
11100
+ });
11101
+ void task.catch((error) => {
11102
+ fatalError ??= error;
11103
+ });
11104
+ }
11105
+ await options.queue.onIdle();
11106
+ if (fatalError !== void 0) {
11107
+ throw fatalError;
11108
+ }
11109
+ const orderedSucceededChunks = chunksNeedingEmbedding.filter((chunk) => completedVectorsByChunkId.has(chunk.id));
11110
+ if (orderedSucceededChunks.length > 0) {
11111
+ try {
11112
+ options.store.addBatch(orderedSucceededChunks.map((chunk) => ({
11113
+ id: chunk.id,
11114
+ vector: completedVectorsByChunkId.get(chunk.id),
11115
+ metadata: chunk.metadata
11116
+ })));
11117
+ for (const chunk of orderedSucceededChunks) {
11118
+ options.invertedIndex.removeChunk(chunk.id);
11119
+ options.invertedIndex.addChunk(chunk.id, chunk.content);
11120
+ }
11121
+ options.onSucceeded?.(orderedSucceededChunks);
11122
+ result.indexedChunks += orderedSucceededChunks.length;
11123
+ this.logger.recordChunksEmbedded(orderedSucceededChunks.length);
11124
+ } catch (error) {
11125
+ const failureMessage = getErrorMessage4(error);
11126
+ const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
11127
+ for (const chunk of orderedSucceededChunks) {
11128
+ options.store.remove(chunk.id);
11129
+ options.invertedIndex.removeChunk(chunk.id);
11130
+ result.failedChunkIds.add(chunk.id);
11131
+ result.failedChunks += 1;
11132
+ const attemptCount = (options.attemptCounts.get(chunk.id) ?? 0) + 1;
11133
+ options.attemptCounts.set(chunk.id, attemptCount);
11134
+ this.writeFailedBatchRecord(options.failedState, {
11135
+ chunks: [chunk],
11136
+ error: failureMessage,
11137
+ attemptCount,
11138
+ lastAttempt: failureTimestamp
11139
+ });
11140
+ }
11141
+ this.logger.recordEmbeddingError();
11142
+ this.logger.embedding("error", "Failed to publish embedded chunks", {
11143
+ batchSize: orderedSucceededChunks.length,
11144
+ error: failureMessage
11145
+ });
11146
+ }
11147
+ options.onProgress?.(result);
11148
+ }
11149
+ return result;
11150
+ }
10337
11151
  async rerankCandidatesWithApi(query, candidates, options) {
10338
11152
  const reranker = this.config.reranker;
10339
11153
  if (!reranker || !reranker.enabled || candidates.length <= 1) {
@@ -10575,12 +11389,12 @@ var Indexer = class _Indexer {
10575
11389
  }
10576
11390
  }
10577
11391
  captureReaderArtifactFingerprint() {
10578
- const storePath = path18.join(this.indexPath, "vectors");
11392
+ const storePath = path19.join(this.indexPath, "vectors");
10579
11393
  return {
10580
11394
  vectors: `${this.getReaderFileFingerprint(storePath)}|${this.getReaderFileFingerprint(`${storePath}.meta.json`)}`,
10581
- keyword: this.getReaderFileFingerprint(path18.join(this.indexPath, "inverted-index.json")),
10582
- database: this.getReaderFileFingerprint(path18.join(this.indexPath, "codebase.db")),
10583
- databaseIdentity: this.getReaderFileFingerprint(path18.join(this.indexPath, "codebase.db"), true)
11395
+ keyword: this.getReaderFileFingerprint(path19.join(this.indexPath, "inverted-index.json")),
11396
+ database: this.getReaderFileFingerprint(path19.join(this.indexPath, "codebase.db")),
11397
+ databaseIdentity: this.getReaderFileFingerprint(path19.join(this.indexPath, "codebase.db"), true)
10584
11398
  };
10585
11399
  }
10586
11400
  refreshReaderArtifacts() {
@@ -10605,13 +11419,13 @@ var Indexer = class _Indexer {
10605
11419
  issues.set(component, this.createReadIssue(component, message));
10606
11420
  this.readerArtifactRetryAfter.set(component, Date.now() + READER_ARTIFACT_RETRY_INTERVAL_MS);
10607
11421
  };
10608
- const storePath = path18.join(this.indexPath, "vectors");
11422
+ const storePath = path19.join(this.indexPath, "vectors");
10609
11423
  const vectorMetadataPath = `${storePath}.meta.json`;
10610
- const invertedIndexPath = path18.join(this.indexPath, "inverted-index.json");
10611
- const dbPath = path18.join(this.indexPath, "codebase.db");
11424
+ const invertedIndexPath = path19.join(this.indexPath, "inverted-index.json");
11425
+ const dbPath = path19.join(this.indexPath, "codebase.db");
10612
11426
  if (vectorsChanged || retryDue("vectors")) {
10613
- const vectorStoreExists = existsSync10(storePath);
10614
- const vectorMetadataExists = existsSync10(vectorMetadataPath);
11427
+ const vectorStoreExists = existsSync11(storePath);
11428
+ const vectorMetadataExists = existsSync11(vectorMetadataPath);
10615
11429
  if (vectorStoreExists && vectorMetadataExists) {
10616
11430
  try {
10617
11431
  const store = new VectorStore(storePath, this.configuredProviderInfo.modelInfo.dimensions);
@@ -10626,8 +11440,8 @@ var Indexer = class _Indexer {
10626
11440
  setIssue("vectors", this.getVectorReadIssueMessage());
10627
11441
  }
10628
11442
  }
10629
- if (keywordChanged || retryDue("keyword") || !existsSync10(invertedIndexPath) && (this.store?.count() ?? 0) > 0) {
10630
- if (existsSync10(invertedIndexPath)) {
11443
+ if (keywordChanged || retryDue("keyword") || !existsSync11(invertedIndexPath) && (this.store?.count() ?? 0) > 0) {
11444
+ if (existsSync11(invertedIndexPath)) {
10631
11445
  try {
10632
11446
  const invertedIndex = new InvertedIndex(invertedIndexPath);
10633
11447
  invertedIndex.load();
@@ -10642,7 +11456,7 @@ var Indexer = class _Indexer {
10642
11456
  }
10643
11457
  }
10644
11458
  if (databaseReplaced || databaseChanged && issues.has("database") || retryDue("database")) {
10645
- if (existsSync10(dbPath)) {
11459
+ if (existsSync11(dbPath)) {
10646
11460
  try {
10647
11461
  const database = Database.openReadOnly(dbPath);
10648
11462
  if (this.database) {
@@ -10733,11 +11547,11 @@ var Indexer = class _Indexer {
10733
11547
  }
10734
11548
  }
10735
11549
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
10736
- const storePath = path18.join(this.indexPath, "vectors");
11550
+ const storePath = path19.join(this.indexPath, "vectors");
10737
11551
  const vectorMetadataPath = `${storePath}.meta.json`;
10738
- const invertedIndexPath = path18.join(this.indexPath, "inverted-index.json");
10739
- const dbPath = path18.join(this.indexPath, "codebase.db");
10740
- let dbIsNew = !existsSync10(dbPath);
11552
+ const invertedIndexPath = path19.join(this.indexPath, "inverted-index.json");
11553
+ const dbPath = path19.join(this.indexPath, "codebase.db");
11554
+ let dbIsNew = !existsSync11(dbPath);
10741
11555
  const readerArtifactFingerprint = mode === "reader" ? this.captureReaderArtifactFingerprint() : null;
10742
11556
  if (mode === "writer") {
10743
11557
  await fsPromises3.mkdir(this.indexPath, { recursive: true });
@@ -10756,14 +11570,14 @@ var Indexer = class _Indexer {
10756
11570
  await this.resetLocalIndexArtifacts();
10757
11571
  }
10758
11572
  this.store = new VectorStore(storePath, dimensions);
10759
- if (existsSync10(storePath) || existsSync10(vectorMetadataPath)) {
11573
+ if (existsSync11(storePath) || existsSync11(vectorMetadataPath)) {
10760
11574
  this.store.load();
10761
11575
  }
10762
11576
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
10763
11577
  try {
10764
11578
  this.invertedIndex.load();
10765
11579
  } catch {
10766
- if (existsSync10(invertedIndexPath)) {
11580
+ if (existsSync11(invertedIndexPath)) {
10767
11581
  await fsPromises3.unlink(invertedIndexPath);
10768
11582
  }
10769
11583
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
@@ -10781,8 +11595,8 @@ var Indexer = class _Indexer {
10781
11595
  }
10782
11596
  } else {
10783
11597
  this.store = new VectorStore(storePath, dimensions);
10784
- const vectorStoreExists = existsSync10(storePath);
10785
- const vectorMetadataExists = existsSync10(vectorMetadataPath);
11598
+ const vectorStoreExists = existsSync11(storePath);
11599
+ const vectorMetadataExists = existsSync11(vectorMetadataPath);
10786
11600
  const vectorReadFailureMessage = this.getVectorReadIssueMessage();
10787
11601
  if (vectorStoreExists !== vectorMetadataExists) {
10788
11602
  this.recordReadIssue("vectors", vectorReadFailureMessage);
@@ -10795,7 +11609,7 @@ var Indexer = class _Indexer {
10795
11609
  }
10796
11610
  }
10797
11611
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
10798
- if (existsSync10(invertedIndexPath)) {
11612
+ if (existsSync11(invertedIndexPath)) {
10799
11613
  try {
10800
11614
  this.invertedIndex.load();
10801
11615
  } catch (error) {
@@ -10809,7 +11623,7 @@ var Indexer = class _Indexer {
10809
11623
  } else if (this.store.count() > 0) {
10810
11624
  this.recordReadIssue("keyword", this.getKeywordReadIssueMessage());
10811
11625
  }
10812
- if (existsSync10(dbPath)) {
11626
+ if (existsSync11(dbPath)) {
10813
11627
  try {
10814
11628
  this.database = Database.openReadOnly(dbPath);
10815
11629
  } catch (error) {
@@ -10901,7 +11715,7 @@ var Indexer = class _Indexer {
10901
11715
  if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
10902
11716
  return {
10903
11717
  resetCorruptedIndex: true,
10904
- warning: this.getCorruptedIndexWarning(path18.join(this.indexPath, "codebase.db"))
11718
+ warning: this.getCorruptedIndexWarning(path19.join(this.indexPath, "codebase.db"))
10905
11719
  };
10906
11720
  }
10907
11721
  throw error;
@@ -10916,7 +11730,7 @@ var Indexer = class _Indexer {
10916
11730
  return;
10917
11731
  }
10918
11732
  const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
10919
- const storeBasePath = path18.join(this.indexPath, "vectors");
11733
+ const storeBasePath = path19.join(this.indexPath, "vectors");
10920
11734
  const storeIndexPath = storeBasePath;
10921
11735
  const storeMetadataPath = `${storeBasePath}.meta.json`;
10922
11736
  const lease = this.requireActiveLease();
@@ -10926,19 +11740,19 @@ var Indexer = class _Indexer {
10926
11740
  let backedUpMetadata = false;
10927
11741
  let rebuiltCount = 0;
10928
11742
  let skippedCount = 0;
10929
- if (existsSync10(backupIndexPath)) {
11743
+ if (existsSync11(backupIndexPath)) {
10930
11744
  unlinkSync2(backupIndexPath);
10931
11745
  }
10932
- if (existsSync10(backupMetadataPath)) {
11746
+ if (existsSync11(backupMetadataPath)) {
10933
11747
  unlinkSync2(backupMetadataPath);
10934
11748
  }
10935
11749
  try {
10936
- if (existsSync10(storeIndexPath)) {
10937
- renameSync2(storeIndexPath, backupIndexPath);
11750
+ if (existsSync11(storeIndexPath)) {
11751
+ renameSync3(storeIndexPath, backupIndexPath);
10938
11752
  backedUpIndex = true;
10939
11753
  }
10940
- if (existsSync10(storeMetadataPath)) {
10941
- renameSync2(storeMetadataPath, backupMetadataPath);
11754
+ if (existsSync11(storeMetadataPath)) {
11755
+ renameSync3(storeMetadataPath, backupMetadataPath);
10942
11756
  backedUpMetadata = true;
10943
11757
  }
10944
11758
  store.clear();
@@ -10958,10 +11772,10 @@ var Indexer = class _Indexer {
10958
11772
  rebuiltCount += 1;
10959
11773
  }
10960
11774
  store.save();
10961
- if (backedUpIndex && existsSync10(backupIndexPath)) {
11775
+ if (backedUpIndex && existsSync11(backupIndexPath)) {
10962
11776
  unlinkSync2(backupIndexPath);
10963
11777
  }
10964
- if (backedUpMetadata && existsSync10(backupMetadataPath)) {
11778
+ if (backedUpMetadata && existsSync11(backupMetadataPath)) {
10965
11779
  unlinkSync2(backupMetadataPath);
10966
11780
  }
10967
11781
  this.logger.gc("info", "Rebuilt vector store to avoid native remove", {
@@ -10974,17 +11788,17 @@ var Indexer = class _Indexer {
10974
11788
  store.clear();
10975
11789
  } catch {
10976
11790
  }
10977
- if (existsSync10(storeIndexPath)) {
11791
+ if (existsSync11(storeIndexPath)) {
10978
11792
  unlinkSync2(storeIndexPath);
10979
11793
  }
10980
- if (existsSync10(storeMetadataPath)) {
11794
+ if (existsSync11(storeMetadataPath)) {
10981
11795
  unlinkSync2(storeMetadataPath);
10982
11796
  }
10983
- if (backedUpIndex && existsSync10(backupIndexPath)) {
10984
- renameSync2(backupIndexPath, storeIndexPath);
11797
+ if (backedUpIndex && existsSync11(backupIndexPath)) {
11798
+ renameSync3(backupIndexPath, storeIndexPath);
10985
11799
  }
10986
- if (backedUpMetadata && existsSync10(backupMetadataPath)) {
10987
- renameSync2(backupMetadataPath, storeMetadataPath);
11800
+ if (backedUpMetadata && existsSync11(backupMetadataPath)) {
11801
+ renameSync3(backupMetadataPath, storeMetadataPath);
10988
11802
  }
10989
11803
  if (backedUpIndex || backedUpMetadata) {
10990
11804
  store.load();
@@ -10999,11 +11813,11 @@ var Indexer = class _Indexer {
10999
11813
  return `Detected a corrupted local SQLite index at ${dbPath} and reset the local index. Run index_codebase to rebuild search data.`;
11000
11814
  }
11001
11815
  async removeProjectRuntimeStateArtifacts() {
11002
- if (!existsSync10(this.indexPath)) return;
11816
+ if (!existsSync11(this.indexPath)) return;
11003
11817
  const names = await fsPromises3.readdir(this.indexPath);
11004
11818
  const runtimeStatePattern = /^(?:file-hashes|failed-batches)(?:\.[a-f0-9]{16})?\.json$/;
11005
11819
  await Promise.all(
11006
- names.filter((name) => runtimeStatePattern.test(name)).map((name) => fsPromises3.rm(path18.join(this.indexPath, name), { force: true }))
11820
+ names.filter((name) => runtimeStatePattern.test(name)).map((name) => fsPromises3.rm(path19.join(this.indexPath, name), { force: true }))
11007
11821
  );
11008
11822
  }
11009
11823
  async resetLocalIndexArtifacts() {
@@ -11019,13 +11833,13 @@ var Indexer = class _Indexer {
11019
11833
  this.readerArtifactRetryAfter.clear();
11020
11834
  this.fileHashCache.clear();
11021
11835
  const resetPaths = [
11022
- path18.join(this.indexPath, "codebase.db"),
11023
- path18.join(this.indexPath, "codebase.db-shm"),
11024
- path18.join(this.indexPath, "codebase.db-wal"),
11025
- path18.join(this.indexPath, "vectors"),
11026
- path18.join(this.indexPath, "vectors.usearch"),
11027
- path18.join(this.indexPath, "vectors.meta.json"),
11028
- path18.join(this.indexPath, "inverted-index.json")
11836
+ path19.join(this.indexPath, "codebase.db"),
11837
+ path19.join(this.indexPath, "codebase.db-shm"),
11838
+ path19.join(this.indexPath, "codebase.db-wal"),
11839
+ path19.join(this.indexPath, "vectors"),
11840
+ path19.join(this.indexPath, "vectors.usearch"),
11841
+ path19.join(this.indexPath, "vectors.meta.json"),
11842
+ path19.join(this.indexPath, "inverted-index.json")
11029
11843
  ];
11030
11844
  await Promise.all(resetPaths.map((targetPath) => fsPromises3.rm(targetPath, { recursive: true, force: true })));
11031
11845
  await this.removeProjectRuntimeStateArtifacts();
@@ -11035,7 +11849,7 @@ var Indexer = class _Indexer {
11035
11849
  if (!isSqliteCorruptionError(error)) {
11036
11850
  return false;
11037
11851
  }
11038
- const dbPath = path18.join(this.indexPath, "codebase.db");
11852
+ const dbPath = path19.join(this.indexPath, "codebase.db");
11039
11853
  const warning = this.getCorruptedIndexWarning(dbPath);
11040
11854
  const errorMessage = getErrorMessage4(error);
11041
11855
  if (this.config.scope === "global") {
@@ -11344,7 +12158,6 @@ var Indexer = class _Indexer {
11344
12158
  skippedFiles: [],
11345
12159
  parseFailures: []
11346
12160
  };
11347
- const failedBatchesForCurrentRun = [];
11348
12161
  onProgress?.({
11349
12162
  phase: "scanning",
11350
12163
  filesProcessed: 0,
@@ -11359,14 +12172,10 @@ var Indexer = class _Indexer {
11359
12172
  const reparseCachedMetalFiles = database.getMetadata(metalParserMetadataKey) !== METAL_PARSER_VERSION;
11360
12173
  const symbolExtractorMetadataKey = this.getSymbolExtractorVersionMetadataKey();
11361
12174
  const refreshCachedSymbols = database.getMetadata(symbolExtractorMetadataKey) !== SYMBOL_EXTRACTOR_VERSION;
11362
- if (reparseCachedSwiftFiles && Array.from(this.fileHashCache.keys()).some(
11363
- (filePath) => path18.extname(filePath).toLowerCase() === ".swift"
11364
- )) {
12175
+ if (reparseCachedSwiftFiles && Array.from(this.fileHashCache.keys()).some((filePath) => path19.extname(filePath).toLowerCase() === ".swift")) {
11365
12176
  this.logger.info("Reindexing cached Swift files for parser support");
11366
12177
  }
11367
- if (reparseCachedMetalFiles && Array.from(this.fileHashCache.keys()).some(
11368
- (filePath) => path18.extname(filePath).toLowerCase() === ".metal"
11369
- )) {
12178
+ if (reparseCachedMetalFiles && Array.from(this.fileHashCache.keys()).some((filePath) => path19.extname(filePath).toLowerCase() === ".metal")) {
11370
12179
  this.logger.info("Reindexing cached Metal files for parser support");
11371
12180
  }
11372
12181
  const includePatterns = [...this.config.include, ...this.config.additionalInclude];
@@ -11388,46 +12197,44 @@ var Indexer = class _Indexer {
11388
12197
  totalFiles: files.length,
11389
12198
  skippedFiles: skipped.length
11390
12199
  });
11391
- const changedFiles = [];
12200
+ const changedFileDescriptors = [];
11392
12201
  const unchangedFilePaths = /* @__PURE__ */ new Set();
11393
12202
  const currentFileHashes = /* @__PURE__ */ new Map();
11394
12203
  const needsCallGraphResolutionMigration = database.getMetadata(this.getCallGraphResolutionMetadataKey()) !== CALL_GRAPH_RESOLUTION_VERSION;
11395
- for (const f of files) {
11396
- const storedPath = this.toStoredFilePath(f.path);
11397
- const currentHash = hashFile(f.path);
12204
+ for (const file of files) {
12205
+ const storedPath = this.toStoredFilePath(file.path);
12206
+ const currentHash = hashFile(file.path);
11398
12207
  currentFileHashes.set(storedPath, currentHash);
11399
12208
  const cachedHashMatches = this.fileHashCache.get(storedPath) === currentHash;
11400
12209
  const needsCallGraphRefresh = cachedHashMatches && needsCallGraphResolutionMigration && database.getChunksByFile(storedPath).some(
11401
12210
  (chunk) => chunk.language === "php" || chunk.language === "c" || chunk.language === "cpp"
11402
12211
  );
11403
- const requiresSwiftParserUpgrade = reparseCachedSwiftFiles && path18.extname(storedPath).toLowerCase() === ".swift";
11404
- const requiresMetalParserUpgrade = reparseCachedMetalFiles && path18.extname(storedPath).toLowerCase() === ".metal";
12212
+ const requiresSwiftParserUpgrade = reparseCachedSwiftFiles && path19.extname(storedPath).toLowerCase() === ".swift";
12213
+ const requiresMetalParserUpgrade = reparseCachedMetalFiles && path19.extname(storedPath).toLowerCase() === ".metal";
11405
12214
  if (cachedHashMatches && !needsCallGraphRefresh && !requiresSwiftParserUpgrade && !requiresMetalParserUpgrade && !refreshCachedSymbols) {
11406
12215
  unchangedFilePaths.add(storedPath);
11407
12216
  this.logger.recordCacheHit();
11408
12217
  } else {
11409
- const content = await fsPromises3.readFile(f.path, "utf-8");
11410
- changedFiles.push({ path: storedPath, content, hash: currentHash });
12218
+ changedFileDescriptors.push({
12219
+ storedPath,
12220
+ materializedPath: file.path,
12221
+ hash: currentHash,
12222
+ sourceBytes: file.size
12223
+ });
11411
12224
  this.logger.recordCacheMiss();
11412
12225
  }
11413
12226
  }
11414
12227
  this.logger.cache("info", "File hash cache results", {
11415
12228
  unchanged: unchangedFilePaths.size,
11416
- changed: changedFiles.length
12229
+ changed: changedFileDescriptors.length
11417
12230
  });
11418
12231
  onProgress?.({
11419
12232
  phase: "parsing",
11420
- filesProcessed: 0,
12233
+ filesProcessed: unchangedFilePaths.size,
11421
12234
  totalFiles: files.length,
11422
12235
  chunksProcessed: 0,
11423
12236
  totalChunks: 0
11424
12237
  });
11425
- const parseStartTime = performance2.now();
11426
- const parsedFiles = parseFiles(changedFiles);
11427
- const parseMs = performance2.now() - parseStartTime;
11428
- this.logger.recordFilesParsed(parsedFiles.length);
11429
- this.logger.recordParseDuration(parseMs);
11430
- this.logger.debug("Parsed changed files", { parsedCount: parsedFiles.length, parseMs: parseMs.toFixed(2) });
11431
12238
  const existingChunks = /* @__PURE__ */ new Map();
11432
12239
  const existingChunksByFile = /* @__PURE__ */ new Map();
11433
12240
  const existingMetadataById = /* @__PURE__ */ new Map();
@@ -11443,17 +12250,17 @@ var Indexer = class _Indexer {
11443
12250
  }
11444
12251
  existingChunks.set(key, metadata.hash);
11445
12252
  existingMetadataById.set(key, metadata);
11446
- const fileChunks = existingChunksByFile.get(metadata.filePath) || /* @__PURE__ */ new Set();
12253
+ const fileChunks = existingChunksByFile.get(metadata.filePath) ?? /* @__PURE__ */ new Set();
11447
12254
  fileChunks.add(key);
11448
12255
  existingChunksByFile.set(metadata.filePath, fileChunks);
11449
12256
  }
11450
12257
  const currentChunkIds = /* @__PURE__ */ new Set();
11451
- const currentFilePaths = /* @__PURE__ */ new Set();
11452
- const pendingChunks = [];
12258
+ const allSymbolIds = /* @__PURE__ */ new Set();
12259
+ const failedChunkIds = /* @__PURE__ */ new Set();
12260
+ const retryableChunksWithExistingData = /* @__PURE__ */ new Set();
11453
12261
  const gitBlameEnabled = this.config.indexing.gitBlame.enabled && isGitRepo(this.materializedProjectRoot);
11454
12262
  let backfilledBlameMetadata = false;
11455
12263
  for (const filePath of unchangedFilePaths) {
11456
- currentFilePaths.add(filePath);
11457
12264
  const fileChunks = existingChunksByFile.get(filePath);
11458
12265
  if (fileChunks) {
11459
12266
  for (const chunkId of fileChunks) {
@@ -11461,635 +12268,552 @@ var Indexer = class _Indexer {
11461
12268
  }
11462
12269
  }
11463
12270
  }
11464
- const chunkDataBatch = [];
11465
- if (gitBlameEnabled) {
11466
- const backfillItems = [];
11467
- for (const chunkId of currentChunkIds) {
11468
- const metadata = existingMetadataById.get(chunkId);
11469
- if (!metadata || hasBlameMetadata(metadata)) {
11470
- continue;
11471
- }
11472
- const chunk = database.getChunk(chunkId);
11473
- if (!chunk) {
11474
- continue;
12271
+ const shouldRetryFailedPath = (filePath) => filePath !== null && currentFileHashes.has(filePath) && unchangedFilePaths.has(filePath);
12272
+ const failedProcessing = this.prepareFailedBatchProcessing(scopedRoots, shouldRetryFailedPath);
12273
+ const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
12274
+ const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
12275
+ const queue = new PQueue({
12276
+ concurrency: providerRateLimits.concurrency,
12277
+ interval: providerRateLimits.intervalMs,
12278
+ intervalCap: providerRateLimits.concurrency
12279
+ });
12280
+ const rateLimitState = { backoffMs: 0 };
12281
+ let writeTransactionActive = false;
12282
+ try {
12283
+ database.beginWriteTransaction();
12284
+ writeTransactionActive = true;
12285
+ const blameChunkDataBatch = [];
12286
+ if (gitBlameEnabled) {
12287
+ const backfillItems = [];
12288
+ for (const chunkId of currentChunkIds) {
12289
+ const metadata = existingMetadataById.get(chunkId);
12290
+ if (!metadata || hasBlameMetadata(metadata)) {
12291
+ continue;
12292
+ }
12293
+ const chunk = database.getChunk(chunkId);
12294
+ if (!chunk) {
12295
+ continue;
12296
+ }
12297
+ const blame = await getChunkGitBlame(
12298
+ this.materializedProjectRoot,
12299
+ this.toMaterializedFilePath(chunk.filePath),
12300
+ chunk.startLine,
12301
+ chunk.endLine
12302
+ );
12303
+ const blameMetadata = metadataFromBlame(blame);
12304
+ if (!blameMetadata.blameSha) {
12305
+ continue;
12306
+ }
12307
+ blameChunkDataBatch.push({
12308
+ ...chunk,
12309
+ blameSha: blameMetadata.blameSha,
12310
+ blameAuthor: blameMetadata.blameAuthor,
12311
+ blameAuthorEmail: blameMetadata.blameAuthorEmail,
12312
+ blameCommittedAt: blameMetadata.blameCommittedAt,
12313
+ blameSummary: blameMetadata.blameSummary
12314
+ });
12315
+ const embeddingBuffer = database.getEmbedding(chunk.contentHash);
12316
+ if (embeddingBuffer) {
12317
+ backfillItems.push({
12318
+ id: chunkId,
12319
+ vector: Array.from(bufferToFloat32Array(embeddingBuffer)),
12320
+ metadata: { ...metadata, ...blameMetadata }
12321
+ });
12322
+ }
11475
12323
  }
11476
- const blame = await getChunkGitBlame(
11477
- this.materializedProjectRoot,
11478
- this.toMaterializedFilePath(chunk.filePath),
11479
- chunk.startLine,
11480
- chunk.endLine
11481
- );
11482
- const blameMetadata = metadataFromBlame(blame);
11483
- if (!blameMetadata.blameSha) {
11484
- continue;
12324
+ if (blameChunkDataBatch.length > 0) {
12325
+ database.upsertChunksBatch(blameChunkDataBatch);
11485
12326
  }
11486
- chunkDataBatch.push({
11487
- ...chunk,
11488
- blameSha: blameMetadata.blameSha,
11489
- blameAuthor: blameMetadata.blameAuthor,
11490
- blameAuthorEmail: blameMetadata.blameAuthorEmail,
11491
- blameCommittedAt: blameMetadata.blameCommittedAt,
11492
- blameSummary: blameMetadata.blameSummary
11493
- });
11494
- const embeddingBuffer = database.getEmbedding(chunk.contentHash);
11495
- if (!embeddingBuffer) {
11496
- continue;
12327
+ if (backfillItems.length > 0) {
12328
+ store.addBatch(backfillItems);
12329
+ backfilledBlameMetadata = true;
11497
12330
  }
11498
- backfillItems.push({
11499
- id: chunkId,
11500
- vector: Array.from(bufferToFloat32Array(embeddingBuffer)),
11501
- metadata: {
11502
- ...metadata,
11503
- ...blameMetadata
11504
- }
11505
- });
11506
12331
  }
11507
- if (backfillItems.length > 0) {
11508
- store.addBatch(backfillItems);
11509
- backfilledBlameMetadata = true;
11510
- }
11511
- }
11512
- for (const parsed of parsedFiles) {
11513
- currentFilePaths.add(parsed.path);
11514
- if (parsed.chunks.length === 0) {
11515
- stats.parseFailures.push(path18.isAbsolute(parsed.path) ? path18.relative(this.projectRoot, parsed.path) : parsed.path);
11516
- }
11517
- let chunksToProcess = parsed.chunks;
11518
- if (this.config.indexing.fallbackToTextOnMaxChunks && chunksToProcess.length > this.config.indexing.maxChunksPerFile) {
11519
- const changedFile = changedFiles.find((f) => f.path === parsed.path);
11520
- if (changedFile) {
11521
- const textChunks = parseFileAsText(parsed.path, changedFile.content);
11522
- chunksToProcess = textChunks;
12332
+ for (const filePath of unchangedFilePaths) {
12333
+ for (const symbol of database.getSymbolsByFile(filePath)) {
12334
+ if (!restrictExistingChunksToBranch || previousBranchSymbolIdSet.has(symbol.id)) {
12335
+ allSymbolIds.add(symbol.id);
12336
+ }
11523
12337
  }
11524
12338
  }
11525
- chunksToProcess = selectIndexableChunks(
11526
- chunksToProcess,
11527
- this.config.indexing.maxChunksPerFile,
11528
- this.config.indexing.semanticOnly
11529
- );
11530
- for (const chunk of chunksToProcess) {
11531
- const id = this.getPreparedChunkId(generateChunkId(parsed.path, chunk));
11532
- const contentHash = generateChunkHash(chunk);
11533
- const existingContentHash = existingChunks.get(id);
11534
- const existingChunk = gitBlameEnabled ? database.getChunk(id) : null;
11535
- const blame = gitBlameEnabled && existingContentHash !== contentHash ? await getChunkGitBlame(
11536
- this.materializedProjectRoot,
11537
- this.toMaterializedFilePath(parsed.path),
11538
- chunk.startLine,
11539
- chunk.endLine
11540
- ) : blameFromChunkData(existingChunk);
11541
- const blameMetadata = metadataFromBlame(blame);
11542
- currentChunkIds.add(id);
11543
- chunkDataBatch.push({
11544
- chunkId: id,
11545
- contentHash,
11546
- filePath: parsed.path,
11547
- startLine: chunk.startLine,
11548
- endLine: chunk.endLine,
11549
- nodeType: chunk.chunkType,
11550
- name: chunk.name,
11551
- language: chunk.language,
11552
- blameSha: blameMetadata.blameSha,
11553
- blameAuthor: blameMetadata.blameAuthor,
11554
- blameAuthorEmail: blameMetadata.blameAuthorEmail,
11555
- blameCommittedAt: blameMetadata.blameCommittedAt,
11556
- blameSummary: blameMetadata.blameSummary
12339
+ let processedChangedFiles = 0;
12340
+ for (const descriptorBatch of iterateOrderedFileBatches(
12341
+ changedFileDescriptors,
12342
+ (descriptor) => descriptor.sourceBytes,
12343
+ this.fileBatchLimits
12344
+ )) {
12345
+ const loadedFiles = await Promise.all(descriptorBatch.map(async (descriptor) => ({
12346
+ path: descriptor.storedPath,
12347
+ content: await fsPromises3.readFile(descriptor.materializedPath, "utf-8"),
12348
+ hash: descriptor.hash
12349
+ })));
12350
+ const loadedByPath = new Map(loadedFiles.map((file) => [file.path, file]));
12351
+ const descriptorByPath = new Map(descriptorBatch.map((descriptor) => [descriptor.storedPath, descriptor]));
12352
+ const parseStartTime = performance2.now();
12353
+ const parsedFiles = parseFiles(loadedFiles);
12354
+ const parseMs = performance2.now() - parseStartTime;
12355
+ this.logger.recordFilesParsed(parsedFiles.length);
12356
+ this.logger.recordParseDuration(parseMs);
12357
+ this.logger.debug("Parsed changed file batch", {
12358
+ parsedCount: parsedFiles.length,
12359
+ parseMs: parseMs.toFixed(2)
11557
12360
  });
11558
- if (existingContentHash === contentHash) {
11559
- continue;
12361
+ const chunkDataBatch = [];
12362
+ const pendingChunks = [];
12363
+ const symbolBatch = [];
12364
+ const edgeBatch = [];
12365
+ for (const parsed of parsedFiles) {
12366
+ const loadedFile = loadedByPath.get(parsed.path);
12367
+ const descriptor = descriptorByPath.get(parsed.path);
12368
+ if (!loadedFile || !descriptor) {
12369
+ throw new Error(`Parsed file was not present in its source batch: ${parsed.path}`);
12370
+ }
12371
+ if (parsed.chunks.length === 0) {
12372
+ stats.parseFailures.push(path19.isAbsolute(parsed.path) ? path19.relative(this.projectRoot, parsed.path) : parsed.path);
12373
+ }
12374
+ let chunksToProcess = parsed.chunks;
12375
+ if (this.config.indexing.fallbackToTextOnMaxChunks && chunksToProcess.length > this.config.indexing.maxChunksPerFile) {
12376
+ chunksToProcess = parseFileAsText(parsed.path, loadedFile.content);
12377
+ }
12378
+ chunksToProcess = selectIndexableChunks(
12379
+ chunksToProcess,
12380
+ this.config.indexing.maxChunksPerFile,
12381
+ this.config.indexing.semanticOnly
12382
+ );
12383
+ for (const chunk of chunksToProcess) {
12384
+ const id = this.getPreparedChunkId(generateChunkId(parsed.path, chunk));
12385
+ const contentHash = generateChunkHash(chunk);
12386
+ const existingContentHash = existingChunks.get(id);
12387
+ const existingChunk = gitBlameEnabled ? database.getChunk(id) : null;
12388
+ const blame = gitBlameEnabled && existingContentHash !== contentHash ? await getChunkGitBlame(
12389
+ this.materializedProjectRoot,
12390
+ descriptor.materializedPath,
12391
+ chunk.startLine,
12392
+ chunk.endLine
12393
+ ) : blameFromChunkData(existingChunk);
12394
+ const blameMetadata = metadataFromBlame(blame);
12395
+ currentChunkIds.add(id);
12396
+ chunkDataBatch.push({
12397
+ chunkId: id,
12398
+ contentHash,
12399
+ filePath: parsed.path,
12400
+ startLine: chunk.startLine,
12401
+ endLine: chunk.endLine,
12402
+ nodeType: chunk.chunkType,
12403
+ name: chunk.name,
12404
+ language: chunk.language,
12405
+ blameSha: blameMetadata.blameSha,
12406
+ blameAuthor: blameMetadata.blameAuthor,
12407
+ blameAuthorEmail: blameMetadata.blameAuthorEmail,
12408
+ blameCommittedAt: blameMetadata.blameCommittedAt,
12409
+ blameSummary: blameMetadata.blameSummary
12410
+ });
12411
+ if (existingContentHash === contentHash) {
12412
+ continue;
12413
+ }
12414
+ const texts = createEmbeddingTexts(chunk, parsed.path, maxChunkTokens).map((text3) => ({
12415
+ text: text3,
12416
+ tokenCount: estimateTokens(text3)
12417
+ }));
12418
+ pendingChunks.push({
12419
+ id,
12420
+ texts,
12421
+ storageText: createPendingChunkStorageText(texts),
12422
+ content: chunk.content,
12423
+ contentHash,
12424
+ metadata: {
12425
+ filePath: parsed.path,
12426
+ startLine: chunk.startLine,
12427
+ endLine: chunk.endLine,
12428
+ chunkType: chunk.chunkType,
12429
+ name: chunk.name,
12430
+ language: chunk.language,
12431
+ hash: contentHash,
12432
+ ...blameMetadata
12433
+ }
12434
+ });
12435
+ }
12436
+ const fileSymbols = [];
12437
+ for (const parsedSymbol of parsed.symbols) {
12438
+ if (!CALL_GRAPH_SYMBOL_CHUNK_TYPES.has(parsedSymbol.kind)) {
12439
+ continue;
12440
+ }
12441
+ const preparedNamespace = this.getPreparedBranchNamespace();
12442
+ const symbolId = `sym_${hashContent(
12443
+ (preparedNamespace ? `${preparedNamespace}:` : "") + parsed.path + ":" + parsedSymbol.name + ":" + parsedSymbol.kind + ":" + parsedSymbol.startLine + ":" + parsedSymbol.startCol + ":" + descriptor.hash
12444
+ ).slice(0, 16)}`;
12445
+ const symbol = {
12446
+ id: symbolId,
12447
+ filePath: parsed.path,
12448
+ name: parsedSymbol.name,
12449
+ kind: parsedSymbol.kind,
12450
+ startLine: parsedSymbol.startLine,
12451
+ startCol: parsedSymbol.startCol,
12452
+ endLine: parsedSymbol.endLine,
12453
+ endCol: parsedSymbol.endCol,
12454
+ language: parsedSymbol.language
12455
+ };
12456
+ fileSymbols.push(symbol);
12457
+ symbolBatch.push(symbol);
12458
+ allSymbolIds.add(symbolId);
12459
+ }
12460
+ const fileLanguage = parsed.symbols[0]?.language ?? parsed.chunks[0]?.language;
12461
+ if (!fileLanguage || !CALL_GRAPH_LANGUAGES.has(fileLanguage)) {
12462
+ continue;
12463
+ }
12464
+ const isCaseInsensitiveLanguage = CASE_INSENSITIVE_LANGUAGES.has(fileLanguage);
12465
+ const normalizeSymbolKey = (name) => isCaseInsensitiveLanguage ? name.toLowerCase() : name;
12466
+ const symbolsByName = /* @__PURE__ */ new Map();
12467
+ for (const symbol of fileSymbols) {
12468
+ const key = normalizeSymbolKey(symbol.name);
12469
+ const symbols = symbolsByName.get(key) ?? [];
12470
+ symbols.push(symbol);
12471
+ symbolsByName.set(key, symbols);
12472
+ }
12473
+ for (const site of extractCalls(loadedFile.content, fileLanguage)) {
12474
+ const enclosingSymbol = findEnclosingSymbol(fileSymbols, site.line, site.column);
12475
+ if (!enclosingSymbol) {
12476
+ continue;
12477
+ }
12478
+ let candidates = symbolsByName.get(normalizeSymbolKey(site.calleeName));
12479
+ if (fileLanguage === "php" && candidates) {
12480
+ if (site.callType === "Constructor") {
12481
+ candidates = candidates.filter((candidate) => PHP_CLASS_SYMBOL_CHUNK_TYPES.has(candidate.kind));
12482
+ } else if (site.callType === "Call") {
12483
+ candidates = candidates.filter((candidate) => PHP_FUNCTION_SYMBOL_CHUNK_TYPES.has(candidate.kind));
12484
+ }
12485
+ }
12486
+ candidates = candidates?.filter(
12487
+ (symbol) => isCompatibleCFamilyCallTarget(fileLanguage, site.callType, symbol.kind)
12488
+ );
12489
+ const resolvedTarget = candidates?.length === 1 ? candidates[0] : void 0;
12490
+ edgeBatch.push({
12491
+ id: `edge_${hashContent(
12492
+ enclosingSymbol.id + ":" + site.calleeName + ":" + site.line + ":" + site.column
12493
+ ).slice(0, 16)}`,
12494
+ fromSymbolId: enclosingSymbol.id,
12495
+ targetName: site.calleeName,
12496
+ toSymbolId: resolvedTarget?.id,
12497
+ callType: site.callType,
12498
+ confidence: site.confidence,
12499
+ line: site.line,
12500
+ col: site.column,
12501
+ isResolved: resolvedTarget !== void 0
12502
+ });
12503
+ }
11560
12504
  }
11561
- const texts = createEmbeddingTexts(chunk, parsed.path, getSafeEmbeddingChunkTokenLimit(configuredProviderInfo)).map((text3) => ({
11562
- text: text3,
11563
- tokenCount: estimateTokens(text3)
11564
- }));
11565
- const metadata = {
11566
- filePath: parsed.path,
11567
- startLine: chunk.startLine,
11568
- endLine: chunk.endLine,
11569
- chunkType: chunk.chunkType,
11570
- name: chunk.name,
11571
- language: chunk.language,
11572
- hash: contentHash,
11573
- ...blameMetadata
11574
- };
11575
- pendingChunks.push({
11576
- id,
11577
- texts,
11578
- storageText: createPendingChunkStorageText(texts),
11579
- content: chunk.content,
11580
- contentHash,
11581
- metadata
11582
- });
11583
- }
11584
- }
11585
- const retryableFailedChunks = this.collectRetryableFailedChunks(
11586
- currentFileHashes,
11587
- unchangedFilePaths,
11588
- getSafeEmbeddingChunkTokenLimit(configuredProviderInfo)
11589
- );
11590
- const retryableFailedAttemptCounts = /* @__PURE__ */ new Map();
11591
- const retryableChunksWithExistingData = /* @__PURE__ */ new Set();
11592
- if (retryableFailedChunks.length > 0) {
11593
- const pendingChunkIds = new Set(pendingChunks.map((chunk) => chunk.id));
11594
- for (const { chunk, attemptCount } of retryableFailedChunks) {
11595
- retryableFailedAttemptCounts.set(chunk.id, attemptCount);
11596
- if (existingChunks.has(chunk.id)) {
11597
- retryableChunksWithExistingData.add(chunk.id);
12505
+ if (chunkDataBatch.length > 0) {
12506
+ database.upsertChunksBatch(chunkDataBatch);
11598
12507
  }
11599
- if (!pendingChunkIds.has(chunk.id)) {
11600
- pendingChunks.push(chunk);
11601
- pendingChunkIds.add(chunk.id);
11602
- currentChunkIds.add(chunk.id);
12508
+ if (symbolBatch.length > 0) {
12509
+ database.upsertSymbolsBatch(symbolBatch);
11603
12510
  }
11604
- }
11605
- }
11606
- if (chunkDataBatch.length > 0) {
11607
- database.upsertChunksBatch(chunkDataBatch);
11608
- }
11609
- const allSymbolIds = /* @__PURE__ */ new Set();
11610
- const symbolsByFile = /* @__PURE__ */ new Map();
11611
- for (let i = 0; i < parsedFiles.length; i++) {
11612
- const parsed = parsedFiles[i];
11613
- const changedFile = changedFiles[i];
11614
- const fileSymbols = [];
11615
- for (const parsedSymbol of parsed.symbols) {
11616
- if (!CALL_GRAPH_SYMBOL_CHUNK_TYPES.has(parsedSymbol.kind)) continue;
11617
- const preparedNamespace = this.getPreparedBranchNamespace();
11618
- const symbolId = `sym_${hashContent(
11619
- (preparedNamespace ? `${preparedNamespace}:` : "") + parsed.path + ":" + parsedSymbol.name + ":" + parsedSymbol.kind + ":" + parsedSymbol.startLine + ":" + parsedSymbol.startCol + ":" + changedFile.hash
11620
- ).slice(0, 16)}`;
11621
- const symbol = {
11622
- id: symbolId,
11623
- filePath: parsed.path,
11624
- name: parsedSymbol.name,
11625
- kind: parsedSymbol.kind,
11626
- startLine: parsedSymbol.startLine,
11627
- startCol: parsedSymbol.startCol,
11628
- endLine: parsedSymbol.endLine,
11629
- endCol: parsedSymbol.endCol,
11630
- language: parsedSymbol.language
11631
- };
11632
- fileSymbols.push(symbol);
11633
- allSymbolIds.add(symbolId);
11634
- }
11635
- const fileLanguage = parsed.symbols[0]?.language ?? parsed.chunks[0]?.language;
11636
- const isCaseInsensitiveLanguage = !!fileLanguage && CASE_INSENSITIVE_LANGUAGES.has(fileLanguage);
11637
- const normalizeSymbolKey = (name) => isCaseInsensitiveLanguage ? name.toLowerCase() : name;
11638
- const symbolsByName = /* @__PURE__ */ new Map();
11639
- for (const symbol of fileSymbols) {
11640
- const key = normalizeSymbolKey(symbol.name);
11641
- const existing = symbolsByName.get(key) ?? [];
11642
- existing.push(symbol);
11643
- symbolsByName.set(key, existing);
11644
- }
11645
- if (fileSymbols.length > 0) {
11646
- database.upsertSymbolsBatch(fileSymbols);
11647
- symbolsByFile.set(parsed.path, fileSymbols);
11648
- }
11649
- if (!fileLanguage || !CALL_GRAPH_LANGUAGES.has(fileLanguage)) continue;
11650
- const callSites = extractCalls(changedFile.content, fileLanguage);
11651
- if (callSites.length === 0) continue;
11652
- const edges = [];
11653
- for (const site of callSites) {
11654
- const enclosingSymbol = findEnclosingSymbol(
11655
- fileSymbols,
11656
- site.line,
11657
- site.column
11658
- );
11659
- if (!enclosingSymbol) continue;
11660
- const edgeId = `edge_${hashContent(enclosingSymbol.id + ":" + site.calleeName + ":" + site.line + ":" + site.column).slice(0, 16)}`;
11661
- edges.push({
11662
- id: edgeId,
11663
- fromSymbolId: enclosingSymbol.id,
11664
- targetName: site.calleeName,
11665
- toSymbolId: void 0,
11666
- callType: site.callType,
11667
- confidence: site.confidence,
11668
- line: site.line,
11669
- col: site.column,
11670
- isResolved: false
12511
+ if (edgeBatch.length > 0) {
12512
+ database.upsertCallEdgesBatch(edgeBatch);
12513
+ }
12514
+ processedChangedFiles += descriptorBatch.length;
12515
+ stats.totalChunks += pendingChunks.length;
12516
+ onProgress?.({
12517
+ phase: "parsing",
12518
+ filesProcessed: unchangedFilePaths.size + processedChangedFiles,
12519
+ totalFiles: files.length,
12520
+ chunksProcessed: stats.indexedChunks,
12521
+ totalChunks: stats.totalChunks
11671
12522
  });
11672
- }
11673
- if (edges.length > 0) {
11674
- database.upsertCallEdgesBatch(edges);
11675
- for (const edge of edges) {
11676
- let candidates = symbolsByName.get(normalizeSymbolKey(edge.targetName));
11677
- if (fileLanguage === "php" && candidates) {
11678
- if (edge.callType === "Constructor") {
11679
- candidates = candidates.filter(
11680
- (candidate) => PHP_CLASS_SYMBOL_CHUNK_TYPES.has(candidate.kind)
11681
- );
11682
- } else if (edge.callType === "Call") {
11683
- candidates = candidates.filter(
11684
- (candidate) => PHP_FUNCTION_SYMBOL_CHUNK_TYPES.has(candidate.kind)
11685
- );
12523
+ if (pendingChunks.length > 0) {
12524
+ onProgress?.({
12525
+ phase: "embedding",
12526
+ filesProcessed: unchangedFilePaths.size + processedChangedFiles,
12527
+ totalFiles: files.length,
12528
+ chunksProcessed: stats.indexedChunks,
12529
+ totalChunks: stats.totalChunks
12530
+ });
12531
+ const batchResult = await this.processPendingChunkBatch(pendingChunks, {
12532
+ store,
12533
+ provider,
12534
+ invertedIndex,
12535
+ database,
12536
+ configuredProviderInfo,
12537
+ queue,
12538
+ providerRateLimits,
12539
+ rateLimitState,
12540
+ failedState: failedProcessing.state,
12541
+ attemptCounts: /* @__PURE__ */ new Map(),
12542
+ forceReembed: forceScopedReembed,
12543
+ reuseCachedEmbeddings: true,
12544
+ incrementRepeatedFailures: true,
12545
+ onProgress: (batchProgress) => onProgress?.({
12546
+ phase: "embedding",
12547
+ filesProcessed: unchangedFilePaths.size + processedChangedFiles,
12548
+ totalFiles: files.length,
12549
+ chunksProcessed: stats.indexedChunks + batchProgress.indexedChunks,
12550
+ totalChunks: stats.totalChunks
12551
+ })
12552
+ });
12553
+ stats.indexedChunks += batchResult.indexedChunks;
12554
+ stats.failedChunks += batchResult.failedChunks;
12555
+ stats.tokensUsed += batchResult.tokensUsed;
12556
+ for (const chunkId of batchResult.failedChunkIds) {
12557
+ failedChunkIds.add(chunkId);
12558
+ if (forceScopedReembed) {
12559
+ failedForcedChunkIds.add(chunkId);
11686
12560
  }
11687
12561
  }
11688
- candidates = candidates?.filter(
11689
- (symbol) => isCompatibleCFamilyCallTarget(fileLanguage, edge.callType, symbol.kind)
11690
- );
11691
- if (candidates && candidates.length === 1) {
11692
- database.resolveCallEdge(edge.id, candidates[0].id);
12562
+ }
12563
+ }
12564
+ const retryableFailedChunks = this.iterateLatestFailedChunks(
12565
+ failedProcessing.latestById,
12566
+ scopedRoots,
12567
+ shouldRetryFailedPath,
12568
+ maxChunkTokens
12569
+ );
12570
+ for (const retryBatch of iterateOrderedFileBatches(
12571
+ retryableFailedChunks,
12572
+ ({ chunk }) => Buffer.byteLength(chunk.content, "utf-8"),
12573
+ this.fileBatchLimits
12574
+ )) {
12575
+ const pendingChunks = retryBatch.map(({ chunk }) => chunk);
12576
+ const attemptCounts = new Map(retryBatch.map(({ chunk, attemptCount }) => [chunk.id, attemptCount]));
12577
+ for (const chunk of pendingChunks) {
12578
+ currentChunkIds.add(chunk.id);
12579
+ if (existingChunks.has(chunk.id)) {
12580
+ retryableChunksWithExistingData.add(chunk.id);
12581
+ }
12582
+ }
12583
+ stats.totalChunks += pendingChunks.length;
12584
+ onProgress?.({
12585
+ phase: "embedding",
12586
+ filesProcessed: files.length,
12587
+ totalFiles: files.length,
12588
+ chunksProcessed: stats.indexedChunks,
12589
+ totalChunks: stats.totalChunks
12590
+ });
12591
+ const batchResult = await this.processPendingChunkBatch(pendingChunks, {
12592
+ store,
12593
+ provider,
12594
+ invertedIndex,
12595
+ database,
12596
+ configuredProviderInfo,
12597
+ queue,
12598
+ providerRateLimits,
12599
+ rateLimitState,
12600
+ failedState: failedProcessing.state,
12601
+ attemptCounts,
12602
+ forceReembed: forceScopedReembed,
12603
+ reuseCachedEmbeddings: true,
12604
+ incrementRepeatedFailures: true,
12605
+ onProgress: (batchProgress) => onProgress?.({
12606
+ phase: "embedding",
12607
+ filesProcessed: files.length,
12608
+ totalFiles: files.length,
12609
+ chunksProcessed: stats.indexedChunks + batchProgress.indexedChunks,
12610
+ totalChunks: stats.totalChunks
12611
+ })
12612
+ });
12613
+ stats.indexedChunks += batchResult.indexedChunks;
12614
+ stats.failedChunks += batchResult.failedChunks;
12615
+ stats.tokensUsed += batchResult.tokensUsed;
12616
+ for (const chunkId of batchResult.failedChunkIds) {
12617
+ failedChunkIds.add(chunkId);
12618
+ if (forceScopedReembed) {
12619
+ failedForcedChunkIds.add(chunkId);
11693
12620
  }
11694
12621
  }
11695
12622
  }
11696
- }
11697
- for (const filePath of unchangedFilePaths) {
11698
- const existingSymbols = database.getSymbolsByFile(filePath);
11699
- for (const sym of existingSymbols) {
11700
- if (!restrictExistingChunksToBranch || previousBranchSymbolIdSet.has(sym.id)) {
11701
- allSymbolIds.add(sym.id);
12623
+ const removedChunkIds = [];
12624
+ for (const [chunkId] of existingChunks) {
12625
+ if (!currentChunkIds.has(chunkId)) {
12626
+ removedChunkIds.push(chunkId);
11702
12627
  }
11703
12628
  }
11704
- }
11705
- const removedChunkIds = [];
11706
- for (const [chunkId] of existingChunks) {
11707
- if (!currentChunkIds.has(chunkId)) {
11708
- removedChunkIds.push(chunkId);
12629
+ const removedCount = removedChunkIds.length;
12630
+ stats.existingChunks = currentChunkIds.size - stats.totalChunks;
12631
+ stats.removedChunks = removedCount;
12632
+ this.logger.recordChunksProcessed(currentChunkIds.size);
12633
+ this.logger.recordChunksRemoved(removedCount);
12634
+ this.logger.info("Chunk analysis complete", {
12635
+ pending: stats.totalChunks,
12636
+ existing: stats.existingChunks,
12637
+ removed: removedCount
12638
+ });
12639
+ if (stats.totalChunks === 0 && removedCount === 0) {
12640
+ const removedStoredChunks = this.replaceBranchCatalog(
12641
+ store,
12642
+ invertedIndex,
12643
+ database,
12644
+ branchCatalogKey,
12645
+ previousBranchChunkIds,
12646
+ Array.from(currentChunkIds),
12647
+ previousBranchSymbolIds,
12648
+ Array.from(allSymbolIds)
12649
+ );
12650
+ const vectorPath = path19.join(this.indexPath, "vectors");
12651
+ const shouldFingerprintLegacyPair = !store.hasFingerprint() && existsSync11(vectorPath) && existsSync11(`${vectorPath}.meta.json`);
12652
+ if (backfilledBlameMetadata || shouldFingerprintLegacyPair || removedStoredChunks) {
12653
+ store.save();
12654
+ }
12655
+ if (removedStoredChunks) {
12656
+ this.saveInvertedIndex(invertedIndex);
12657
+ }
12658
+ if (scopedRoots) {
12659
+ this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
12660
+ } else {
12661
+ this.fileHashCache = currentFileHashes;
12662
+ this.saveFileHashCache();
12663
+ }
12664
+ this.finalizeFailedBatchWriteState(failedProcessing.state);
12665
+ database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
12666
+ database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
12667
+ database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
12668
+ this.saveBranchCommit(database, indexedCommit);
12669
+ this.saveIndexMetadata(configuredProviderInfo);
12670
+ this.indexCompatibility = { compatible: true };
12671
+ database.commitWriteTransaction();
12672
+ writeTransactionActive = false;
12673
+ stats.durationMs = Date.now() - startTime;
12674
+ onProgress?.({
12675
+ phase: "complete",
12676
+ filesProcessed: files.length,
12677
+ totalFiles: files.length,
12678
+ chunksProcessed: 0,
12679
+ totalChunks: 0
12680
+ });
12681
+ return stats;
11709
12682
  }
11710
- }
11711
- const removedCount = removedChunkIds.length;
11712
- stats.totalChunks = pendingChunks.length;
11713
- stats.existingChunks = currentChunkIds.size - pendingChunks.length;
11714
- stats.removedChunks = removedCount;
11715
- this.logger.recordChunksProcessed(currentChunkIds.size);
11716
- this.logger.recordChunksRemoved(removedCount);
11717
- this.logger.info("Chunk analysis complete", {
11718
- pending: pendingChunks.length,
11719
- existing: stats.existingChunks,
11720
- removed: removedCount
11721
- });
11722
- if (pendingChunks.length === 0 && removedCount === 0) {
11723
- const removedStoredChunks = this.replaceBranchCatalog(
11724
- store,
11725
- invertedIndex,
11726
- database,
11727
- branchCatalogKey,
11728
- previousBranchChunkIds,
11729
- Array.from(currentChunkIds),
11730
- previousBranchSymbolIds,
11731
- Array.from(allSymbolIds)
11732
- );
11733
- const vectorPath = path18.join(this.indexPath, "vectors");
11734
- const shouldFingerprintLegacyPair = !store.hasFingerprint() && existsSync10(vectorPath) && existsSync10(`${vectorPath}.meta.json`);
11735
- if (backfilledBlameMetadata || shouldFingerprintLegacyPair || removedStoredChunks) {
12683
+ if (stats.totalChunks === 0) {
12684
+ this.replaceBranchCatalog(
12685
+ store,
12686
+ invertedIndex,
12687
+ database,
12688
+ branchCatalogKey,
12689
+ previousBranchChunkIds,
12690
+ Array.from(currentChunkIds),
12691
+ previousBranchSymbolIds,
12692
+ Array.from(allSymbolIds)
12693
+ );
11736
12694
  store.save();
11737
- }
11738
- if (removedStoredChunks) {
11739
12695
  this.saveInvertedIndex(invertedIndex);
12696
+ if (scopedRoots) {
12697
+ this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
12698
+ } else {
12699
+ this.fileHashCache = currentFileHashes;
12700
+ this.saveFileHashCache();
12701
+ }
12702
+ this.finalizeFailedBatchWriteState(failedProcessing.state);
12703
+ database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
12704
+ database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
12705
+ database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
12706
+ this.saveBranchCommit(database, indexedCommit);
12707
+ this.saveIndexMetadata(configuredProviderInfo);
12708
+ this.indexCompatibility = { compatible: true };
12709
+ database.commitWriteTransaction();
12710
+ writeTransactionActive = false;
12711
+ stats.durationMs = Date.now() - startTime;
12712
+ onProgress?.({
12713
+ phase: "complete",
12714
+ filesProcessed: files.length,
12715
+ totalFiles: files.length,
12716
+ chunksProcessed: 0,
12717
+ totalChunks: 0
12718
+ });
12719
+ return stats;
11740
12720
  }
11741
- if (scopedRoots) {
11742
- this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
11743
- this.clearScopedFailedBatches(scopedRoots);
11744
- } else {
11745
- this.fileHashCache = currentFileHashes;
11746
- this.saveFileHashCache();
11747
- this.saveFailedBatches([]);
11748
- }
11749
- database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
11750
- database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
11751
- database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
11752
- this.saveBranchCommit(database, indexedCommit);
11753
- this.saveIndexMetadata(configuredProviderInfo);
11754
- this.indexCompatibility = { compatible: true };
11755
- stats.durationMs = Date.now() - startTime;
11756
12721
  onProgress?.({
11757
- phase: "complete",
12722
+ phase: "storing",
11758
12723
  filesProcessed: files.length,
11759
12724
  totalFiles: files.length,
11760
- chunksProcessed: 0,
11761
- totalChunks: 0
12725
+ chunksProcessed: stats.indexedChunks,
12726
+ totalChunks: stats.totalChunks
12727
+ });
12728
+ const branchChunkIds = Array.from(currentChunkIds).filter((chunkId) => {
12729
+ const isNewlyFailed = failedChunkIds.has(chunkId) && !retryableChunksWithExistingData.has(chunkId);
12730
+ const isForcedFailed = forceScopedReembed && failedForcedChunkIds.has(chunkId);
12731
+ return !isNewlyFailed && !isForcedFailed;
11762
12732
  });
11763
- return stats;
11764
- }
11765
- if (pendingChunks.length === 0) {
11766
12733
  this.replaceBranchCatalog(
11767
12734
  store,
11768
12735
  invertedIndex,
11769
12736
  database,
11770
12737
  branchCatalogKey,
11771
12738
  previousBranchChunkIds,
11772
- Array.from(currentChunkIds),
12739
+ branchChunkIds,
11773
12740
  previousBranchSymbolIds,
11774
12741
  Array.from(allSymbolIds)
11775
12742
  );
11776
12743
  store.save();
11777
12744
  this.saveInvertedIndex(invertedIndex);
11778
- if (scopedRoots) {
11779
- this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
11780
- this.clearScopedFailedBatches(scopedRoots);
11781
- } else {
11782
- this.fileHashCache = currentFileHashes;
11783
- this.saveFileHashCache();
11784
- this.saveFailedBatches([]);
11785
- }
11786
- database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
11787
- database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
11788
- database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
11789
- this.saveBranchCommit(database, indexedCommit);
11790
- this.saveIndexMetadata(configuredProviderInfo);
11791
- this.indexCompatibility = { compatible: true };
11792
- stats.durationMs = Date.now() - startTime;
11793
- onProgress?.({
11794
- phase: "complete",
11795
- filesProcessed: files.length,
11796
- totalFiles: files.length,
11797
- chunksProcessed: 0,
11798
- totalChunks: 0
11799
- });
11800
- return stats;
11801
- }
11802
- onProgress?.({
11803
- phase: "embedding",
11804
- filesProcessed: files.length,
11805
- totalFiles: files.length,
11806
- chunksProcessed: 0,
11807
- totalChunks: pendingChunks.length
11808
- });
11809
- const allContentHashes = pendingChunks.map((c) => c.contentHash);
11810
- const missingHashes = new Set(database.getMissingEmbeddings(allContentHashes));
11811
- const forcedReembedChunkIds = forceScopedReembed ? new Set(pendingChunks.map((chunk) => chunk.id)) : /* @__PURE__ */ new Set();
11812
- const chunksNeedingEmbedding = pendingChunks.filter((c) => forcedReembedChunkIds.has(c.id) || missingHashes.has(c.contentHash));
11813
- const chunksWithExistingEmbedding = pendingChunks.filter((c) => !forcedReembedChunkIds.has(c.id) && !missingHashes.has(c.contentHash));
11814
- this.logger.cache("info", "Embedding cache lookup", {
11815
- needsEmbedding: chunksNeedingEmbedding.length,
11816
- fromCache: chunksWithExistingEmbedding.length
11817
- });
11818
- this.logger.recordChunksFromCache(chunksWithExistingEmbedding.length);
11819
- for (const chunk of chunksWithExistingEmbedding) {
11820
- const embeddingBuffer = database.getEmbedding(chunk.contentHash);
11821
- if (embeddingBuffer) {
11822
- const vector = bufferToFloat32Array(embeddingBuffer);
11823
- store.add(chunk.id, Array.from(vector), chunk.metadata);
11824
- invertedIndex.removeChunk(chunk.id);
11825
- invertedIndex.addChunk(chunk.id, chunk.content);
11826
- stats.indexedChunks++;
11827
- }
11828
- }
11829
- const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
11830
- const queue = new PQueue({
11831
- concurrency: providerRateLimits.concurrency,
11832
- interval: providerRateLimits.intervalMs,
11833
- intervalCap: providerRateLimits.concurrency
11834
- });
11835
- const pendingChunksById = new Map(chunksNeedingEmbedding.map((chunk) => [chunk.id, chunk]));
11836
- const embeddingPartsByChunk = /* @__PURE__ */ new Map();
11837
- const completedChunkIds = /* @__PURE__ */ new Set();
11838
- const failedChunkIds = /* @__PURE__ */ new Set();
11839
- const requestBatches = createPendingEmbeddingRequestBatches(
11840
- chunksNeedingEmbedding,
11841
- getDynamicBatchOptions(configuredProviderInfo)
11842
- );
11843
- let rateLimitBackoffMs = 0;
11844
- for (const requestBatch of requestBatches) {
11845
- queue.add(async () => {
11846
- if (rateLimitBackoffMs > 0) {
11847
- await new Promise((resolve13) => setTimeout(resolve13, rateLimitBackoffMs));
11848
- }
11849
- try {
11850
- const result = await pRetry(
11851
- async () => {
11852
- const texts = requestBatch.map((request) => request.text);
11853
- return provider.embedBatch(texts);
11854
- },
11855
- {
11856
- retries: this.config.indexing.retries,
11857
- minTimeout: Math.max(this.config.indexing.retryDelayMs, providerRateLimits.minRetryMs),
11858
- maxTimeout: providerRateLimits.maxRetryMs,
11859
- factor: 2,
11860
- shouldRetry: (error) => !(error.error instanceof CustomProviderNonRetryableError),
11861
- onFailedAttempt: (error) => {
11862
- const message = getErrorMessage4(error);
11863
- if (isRateLimitError(error)) {
11864
- rateLimitBackoffMs = Math.min(providerRateLimits.maxRetryMs, (rateLimitBackoffMs || providerRateLimits.minRetryMs) * 2);
11865
- this.logger.embedding("warn", `Rate limited, backing off`, {
11866
- attempt: error.attemptNumber,
11867
- retriesLeft: error.retriesLeft,
11868
- backoffMs: rateLimitBackoffMs
11869
- });
11870
- } else {
11871
- this.logger.embedding("error", `Embedding batch failed`, {
11872
- attempt: error.attemptNumber,
11873
- error: message
11874
- });
11875
- }
11876
- }
11877
- }
11878
- );
11879
- if (rateLimitBackoffMs > 0) {
11880
- rateLimitBackoffMs = Math.max(0, rateLimitBackoffMs - 2e3);
11881
- }
11882
- const touchedChunkIds = /* @__PURE__ */ new Set();
11883
- requestBatch.forEach((request, idx) => {
11884
- if (failedChunkIds.has(request.chunk.id) || completedChunkIds.has(request.chunk.id)) {
11885
- return;
11886
- }
11887
- const vector = result.embeddings[idx];
11888
- if (!vector) {
11889
- throw new Error(`Embedding API returned too few vectors for chunk ${request.chunk.id}`);
11890
- }
11891
- const parts = embeddingPartsByChunk.get(request.chunk.id) ?? [];
11892
- parts[request.partIndex] = {
11893
- vector,
11894
- tokenCount: request.tokenCount
11895
- };
11896
- embeddingPartsByChunk.set(request.chunk.id, parts);
11897
- touchedChunkIds.add(request.chunk.id);
11898
- });
11899
- const pooledResults = [];
11900
- for (const chunkId of touchedChunkIds) {
11901
- if (failedChunkIds.has(chunkId) || completedChunkIds.has(chunkId)) {
11902
- continue;
11903
- }
11904
- const chunk = pendingChunksById.get(chunkId);
11905
- if (!chunk) {
11906
- continue;
11907
- }
11908
- const parts = embeddingPartsByChunk.get(chunk.id) ?? [];
11909
- if (!hasAllEmbeddingParts(parts, chunk.texts.length)) {
11910
- continue;
11911
- }
11912
- const orderedParts = parts;
11913
- pooledResults.push({
11914
- chunk,
11915
- vector: poolEmbeddingVectors(
11916
- orderedParts.map((part) => part.vector),
11917
- orderedParts.map((part) => part.tokenCount)
11918
- )
11919
- });
11920
- }
11921
- if (pooledResults.length > 0) {
11922
- const items = pooledResults.map(({ chunk, vector }) => ({
11923
- id: chunk.id,
11924
- vector,
11925
- metadata: chunk.metadata
11926
- }));
11927
- store.addBatch(items);
11928
- const embeddingBatchItems = pooledResults.map(({ chunk, vector }) => ({
11929
- contentHash: chunk.contentHash,
11930
- embedding: float32ArrayToBuffer(vector),
11931
- chunkText: chunk.storageText,
11932
- model: configuredProviderInfo.modelInfo.model
11933
- }));
11934
- try {
11935
- database.upsertEmbeddingsBatch(embeddingBatchItems);
11936
- } catch (dbError) {
11937
- this.rebuildVectorStoreExcludingChunkIds(
11938
- store,
11939
- database,
11940
- pooledResults.map(({ chunk }) => chunk.id)
11941
- );
11942
- throw dbError;
11943
- }
11944
- for (const { chunk } of pooledResults) {
11945
- invertedIndex.removeChunk(chunk.id);
11946
- invertedIndex.addChunk(chunk.id, chunk.content);
11947
- completedChunkIds.add(chunk.id);
11948
- embeddingPartsByChunk.delete(chunk.id);
11949
- }
11950
- stats.indexedChunks += pooledResults.length;
11951
- this.logger.recordChunksEmbedded(pooledResults.length);
11952
- }
11953
- stats.tokensUsed += result.totalTokensUsed;
11954
- this.logger.recordEmbeddingApiCall(result.totalTokensUsed);
11955
- this.logger.embedding("debug", `Embedded batch`, {
11956
- batchSize: pooledResults.length,
11957
- requestCount: requestBatch.length,
11958
- tokens: result.totalTokensUsed
11959
- });
11960
- onProgress?.({
11961
- phase: "embedding",
11962
- filesProcessed: files.length,
11963
- totalFiles: files.length,
11964
- chunksProcessed: stats.indexedChunks,
11965
- totalChunks: pendingChunks.length
11966
- });
11967
- } catch (error) {
11968
- const failedChunks = getUniquePendingChunksFromRequests(requestBatch).filter((chunk) => !completedChunkIds.has(chunk.id));
11969
- const failureMessage = getErrorMessage4(error);
11970
- const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
11971
- for (const chunk of failedChunks) {
11972
- if (!failedChunkIds.has(chunk.id)) {
11973
- failedChunkIds.add(chunk.id);
11974
- stats.failedChunks += 1;
11975
- }
11976
- if (forceScopedReembed) {
11977
- failedForcedChunkIds.add(chunk.id);
11978
- }
11979
- embeddingPartsByChunk.delete(chunk.id);
11980
- const existingFailedBatchIndex = failedBatchesForCurrentRun.findIndex(
11981
- (failedBatch2) => failedBatch2.chunks[0]?.id === chunk.id
11982
- );
11983
- const existingFailedBatch = existingFailedBatchIndex === -1 ? void 0 : failedBatchesForCurrentRun[existingFailedBatchIndex];
11984
- const failedBatch = {
11985
- chunks: [chunk],
11986
- error: failureMessage,
11987
- attemptCount: (existingFailedBatch?.attemptCount ?? retryableFailedAttemptCounts.get(chunk.id) ?? 0) + 1,
11988
- lastAttempt: failureTimestamp
11989
- };
11990
- if (existingFailedBatchIndex === -1) {
11991
- failedBatchesForCurrentRun.push(failedBatch);
11992
- } else {
11993
- failedBatchesForCurrentRun[existingFailedBatchIndex] = failedBatch;
11994
- }
11995
- }
11996
- this.logger.recordEmbeddingError();
11997
- this.logger.embedding("error", `Failed to embed batch after retries`, {
11998
- batchSize: failedChunks.length,
11999
- requestCount: requestBatch.length,
12000
- error: failureMessage
12745
+ if (scopedRoots) {
12746
+ this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
12747
+ } else {
12748
+ this.fileHashCache = currentFileHashes;
12749
+ this.saveFileHashCache();
12750
+ }
12751
+ this.finalizeFailedBatchWriteState(failedProcessing.state);
12752
+ database.commitWriteTransaction();
12753
+ writeTransactionActive = false;
12754
+ if (this.config.indexing.autoGc && stats.removedChunks > 0) {
12755
+ const gcReset = await this.maybeRunOrphanGc();
12756
+ if (gcReset) {
12757
+ stats.durationMs = Date.now() - startTime;
12758
+ stats.warning = gcReset.warning;
12759
+ stats.resetCorruptedIndex = true;
12760
+ this.logger.recordIndexingEnd();
12761
+ this.logger.warn("Indexing ended after resetting corrupted local index during automatic GC", {
12762
+ files: stats.totalFiles,
12763
+ indexed: stats.indexedChunks,
12764
+ existing: stats.existingChunks,
12765
+ removed: stats.removedChunks,
12766
+ failed: stats.failedChunks,
12767
+ tokens: stats.tokensUsed,
12768
+ durationMs: stats.durationMs
12001
12769
  });
12770
+ return stats;
12002
12771
  }
12772
+ }
12773
+ stats.durationMs = Date.now() - startTime;
12774
+ if (forceScopedReembed && failedForcedChunkIds.size === 0) {
12775
+ database.deleteMetadata(this.getProjectForceReembedMetadataKey());
12776
+ }
12777
+ database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
12778
+ database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
12779
+ database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
12780
+ this.saveBranchCommit(database, indexedCommit);
12781
+ this.saveIndexMetadata(configuredProviderInfo);
12782
+ this.indexCompatibility = { compatible: true };
12783
+ this.logger.recordIndexingEnd();
12784
+ this.logger.info("Indexing complete", {
12785
+ files: stats.totalFiles,
12786
+ indexed: stats.indexedChunks,
12787
+ existing: stats.existingChunks,
12788
+ removed: stats.removedChunks,
12789
+ failed: stats.failedChunks,
12790
+ tokens: stats.tokensUsed,
12791
+ durationMs: stats.durationMs
12003
12792
  });
12004
- }
12005
- await queue.onIdle();
12006
- if (scopedRoots) {
12007
- this.saveScopedFailedBatches(coalesceFailedBatches(failedBatchesForCurrentRun), scopedRoots);
12008
- } else {
12009
- this.saveFailedBatches(coalesceFailedBatches(failedBatchesForCurrentRun));
12010
- }
12011
- onProgress?.({
12012
- phase: "storing",
12013
- filesProcessed: files.length,
12014
- totalFiles: files.length,
12015
- chunksProcessed: stats.indexedChunks,
12016
- totalChunks: pendingChunks.length
12017
- });
12018
- const branchChunkIds = Array.from(currentChunkIds).filter(
12019
- (chunkId) => {
12020
- const isNewlyFailed = failedChunkIds.has(chunkId) && !retryableChunksWithExistingData.has(chunkId);
12021
- const isForcedFailed = forceScopedReembed && failedForcedChunkIds.has(chunkId);
12022
- return !isNewlyFailed && !isForcedFailed;
12793
+ if (stats.failedChunks > 0) {
12794
+ stats.failedBatchesPath = this.failedBatchesPath;
12023
12795
  }
12024
- );
12025
- this.replaceBranchCatalog(
12026
- store,
12027
- invertedIndex,
12028
- database,
12029
- branchCatalogKey,
12030
- previousBranchChunkIds,
12031
- branchChunkIds,
12032
- previousBranchSymbolIds,
12033
- Array.from(allSymbolIds)
12034
- );
12035
- store.save();
12036
- this.saveInvertedIndex(invertedIndex);
12037
- if (scopedRoots) {
12038
- this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
12039
- } else {
12040
- this.fileHashCache = currentFileHashes;
12041
- this.saveFileHashCache();
12042
- }
12043
- if (this.config.indexing.autoGc && stats.removedChunks > 0) {
12044
- const gcReset = await this.maybeRunOrphanGc();
12045
- if (gcReset) {
12046
- stats.durationMs = Date.now() - startTime;
12047
- stats.warning = gcReset.warning;
12048
- stats.resetCorruptedIndex = true;
12049
- this.logger.recordIndexingEnd();
12050
- this.logger.warn("Indexing ended after resetting corrupted local index during automatic GC", {
12051
- files: stats.totalFiles,
12052
- indexed: stats.indexedChunks,
12053
- existing: stats.existingChunks,
12054
- removed: stats.removedChunks,
12055
- failed: stats.failedChunks,
12056
- tokens: stats.tokensUsed,
12057
- durationMs: stats.durationMs
12058
- });
12059
- return stats;
12796
+ onProgress?.({
12797
+ phase: "complete",
12798
+ filesProcessed: files.length,
12799
+ totalFiles: files.length,
12800
+ chunksProcessed: stats.indexedChunks,
12801
+ totalChunks: stats.totalChunks
12802
+ });
12803
+ return stats;
12804
+ } catch (error) {
12805
+ failedProcessing.state.writer.cleanup();
12806
+ if (writeTransactionActive) {
12807
+ try {
12808
+ database.rollbackWriteTransaction();
12809
+ } catch (rollbackError) {
12810
+ this.logger.error("Failed to roll back indexing database transaction", {
12811
+ error: getErrorMessage4(rollbackError)
12812
+ });
12813
+ }
12060
12814
  }
12815
+ throw error;
12061
12816
  }
12062
- stats.durationMs = Date.now() - startTime;
12063
- if (forceScopedReembed && failedForcedChunkIds.size === 0) {
12064
- database.deleteMetadata(this.getProjectForceReembedMetadataKey());
12065
- }
12066
- database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
12067
- database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
12068
- database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
12069
- this.saveBranchCommit(database, indexedCommit);
12070
- this.saveIndexMetadata(configuredProviderInfo);
12071
- this.indexCompatibility = { compatible: true };
12072
- this.logger.recordIndexingEnd();
12073
- this.logger.info("Indexing complete", {
12074
- files: stats.totalFiles,
12075
- indexed: stats.indexedChunks,
12076
- existing: stats.existingChunks,
12077
- removed: stats.removedChunks,
12078
- failed: stats.failedChunks,
12079
- tokens: stats.tokensUsed,
12080
- durationMs: stats.durationMs
12081
- });
12082
- if (stats.failedChunks > 0) {
12083
- stats.failedBatchesPath = this.failedBatchesPath;
12084
- }
12085
- onProgress?.({
12086
- phase: "complete",
12087
- filesProcessed: files.length,
12088
- totalFiles: files.length,
12089
- chunksProcessed: stats.indexedChunks,
12090
- totalChunks: pendingChunks.length
12091
- });
12092
- return stats;
12093
12817
  }
12094
12818
  async getQueryEmbedding(query, provider) {
12095
12819
  const now2 = Date.now();
@@ -12327,10 +13051,31 @@ var Indexer = class _Indexer {
12327
13051
  const baseFiltered = tiered.filter(
12328
13052
  (r) => matchesSearchFilters(r, options, this.config.search.minScore, this.projectRoot)
12329
13053
  );
12330
- const implementationOnly = baseFiltered.filter(
13054
+ let communityRanked = baseFiltered;
13055
+ if (this.config.search.communityBoost > 0) {
13056
+ try {
13057
+ const sameCommunityCandidateIds = resolveSameCommunityCandidateIds(
13058
+ query,
13059
+ baseFiltered,
13060
+ database,
13061
+ this.getBranchCatalogKeys()
13062
+ );
13063
+ communityRanked = applyCommunityBoost(
13064
+ baseFiltered,
13065
+ sameCommunityCandidateIds,
13066
+ this.config.search.communityBoost
13067
+ );
13068
+ } catch (error) {
13069
+ this.logger.search("debug", "Community-aware ranking unavailable; using existing ranking", {
13070
+ query,
13071
+ error: getErrorMessage4(error)
13072
+ });
13073
+ }
13074
+ }
13075
+ const implementationOnly = communityRanked.filter(
12331
13076
  (r) => isLikelyImplementationPath2(r.metadata.filePath) && isImplementationChunkType(r.metadata.chunkType)
12332
13077
  );
12333
- const filtered = (sourceIntent && hasCodeHints && implementationOnly.length > 0 ? implementationOnly : baseFiltered).slice(0, maxResults);
13078
+ const filtered = (sourceIntent && hasCodeHints && implementationOnly.length > 0 ? implementationOnly : communityRanked).slice(0, maxResults);
12334
13079
  const identifierFallback = !options?.definitionIntent && filtered.length === 0 && identifierHints.length > 0 ? buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbolIds, maxResults, union, true).filter((r) => matchesSearchFilters(r, options, this.config.search.minScore, this.projectRoot)).slice(0, maxResults) : [];
12335
13080
  const finalResults = filtered.length > 0 ? filtered : identifierFallback;
12336
13081
  const totalSearchMs = performance2.now() - searchStartTime;
@@ -12547,7 +13292,7 @@ var Indexer = class _Indexer {
12547
13292
  this.saveFileHashCache();
12548
13293
  database.clearAllIndexedData();
12549
13294
  this.deleteBranchCommitMetadata(database, clearedBranchKeys2);
12550
- this.saveFailedBatches([]);
13295
+ this.clearFailedBatchState();
12551
13296
  database.deleteMetadata("index.version");
12552
13297
  database.deleteMetadata("index.pathStorageVersion");
12553
13298
  database.deleteMetadata("index.embeddingProvider");
@@ -12615,7 +13360,7 @@ var Indexer = class _Indexer {
12615
13360
  const missingChunkKeys = [];
12616
13361
  const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
12617
13362
  for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
12618
- if (!existsSync10(this.toMaterializedFilePath(filePath))) {
13363
+ if (!existsSync11(this.toMaterializedFilePath(filePath))) {
12619
13364
  chunkKeysByRemovedFile.set(filePath, chunkKeys);
12620
13365
  for (const key of chunkKeys) {
12621
13366
  missingChunkKeys.push(key);
@@ -12678,7 +13423,7 @@ var Indexer = class _Indexer {
12678
13423
  gcOrphanSymbols: 0,
12679
13424
  gcOrphanCallEdges: 0,
12680
13425
  resetCorruptedIndex: true,
12681
- warning: this.getCorruptedIndexWarning(path18.join(this.indexPath, "codebase.db"))
13426
+ warning: this.getCorruptedIndexWarning(path19.join(this.indexPath, "codebase.db"))
12682
13427
  };
12683
13428
  }
12684
13429
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
@@ -12708,180 +13453,94 @@ var Indexer = class _Indexer {
12708
13453
  const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
12709
13454
  const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
12710
13455
  const roots = this.config.scope === "global" ? this.getScopedRoots() : null;
12711
- const { scoped: scopedFailedBatches, retained: retainedFailedBatches } = roots ? this.partitionFailedBatches(roots, maxChunkTokens) : { scoped: this.loadFailedBatches(maxChunkTokens), retained: [] };
12712
- const failedBatches = scopedFailedBatches;
12713
- if (failedBatches.length === 0) {
13456
+ const failedProcessing = this.prepareFailedBatchProcessing(roots, () => true);
13457
+ if (failedProcessing.latestById.size === 0) {
13458
+ this.finalizeFailedBatchWriteState(failedProcessing.state);
12714
13459
  return { succeeded: 0, failed: 0, remaining: 0 };
12715
13460
  }
13461
+ const queue = new PQueue({ concurrency: 1 });
13462
+ const rateLimitState = { backoffMs: 0 };
12716
13463
  let succeeded = 0;
12717
13464
  let failed = 0;
12718
- const stillFailing = [];
12719
- for (const batch of failedBatches) {
12720
- const batchChunksById = new Map(batch.chunks.map((chunk) => [chunk.id, chunk]));
12721
- const embeddingPartsByChunk = /* @__PURE__ */ new Map();
12722
- const completedChunkIds = /* @__PURE__ */ new Set();
12723
- const failedChunkIds = /* @__PURE__ */ new Set();
12724
- const failedChunksForBatch = /* @__PURE__ */ new Map();
12725
- const pooledResults = [];
12726
- try {
12727
- const requestBatches = createPendingEmbeddingRequestBatches(
12728
- batch.chunks,
12729
- getDynamicBatchOptions(configuredProviderInfo)
12730
- );
12731
- for (const requestBatch of requestBatches) {
12732
- try {
12733
- const result = await pRetry(
12734
- async () => {
12735
- const texts = requestBatch.map((request) => request.text);
12736
- return provider.embedBatch(texts);
12737
- },
12738
- {
12739
- retries: this.config.indexing.retries,
12740
- minTimeout: Math.max(this.config.indexing.retryDelayMs, providerRateLimits.minRetryMs),
12741
- maxTimeout: providerRateLimits.maxRetryMs,
12742
- factor: 2,
12743
- shouldRetry: (error) => !(error.error instanceof CustomProviderNonRetryableError)
12744
- }
12745
- );
12746
- const touchedChunkIds = /* @__PURE__ */ new Set();
12747
- requestBatch.forEach((request, idx) => {
12748
- if (failedChunkIds.has(request.chunk.id) || completedChunkIds.has(request.chunk.id)) {
12749
- return;
12750
- }
12751
- const vector = result.embeddings[idx];
12752
- if (!vector) {
12753
- throw new Error(`Embedding API returned too few vectors for chunk ${request.chunk.id}`);
12754
- }
12755
- const parts = embeddingPartsByChunk.get(request.chunk.id) ?? [];
12756
- parts[request.partIndex] = {
12757
- vector,
12758
- tokenCount: request.tokenCount
12759
- };
12760
- embeddingPartsByChunk.set(request.chunk.id, parts);
12761
- touchedChunkIds.add(request.chunk.id);
12762
- });
12763
- for (const chunkId of touchedChunkIds) {
12764
- if (failedChunkIds.has(chunkId) || completedChunkIds.has(chunkId)) {
12765
- continue;
12766
- }
12767
- const chunk = batchChunksById.get(chunkId);
12768
- if (!chunk) {
12769
- continue;
12770
- }
12771
- const parts = embeddingPartsByChunk.get(chunk.id) ?? [];
12772
- if (!hasAllEmbeddingParts(parts, chunk.texts.length)) {
12773
- continue;
12774
- }
12775
- const orderedParts = parts;
12776
- pooledResults.push({
12777
- chunk,
12778
- vector: poolEmbeddingVectors(
12779
- orderedParts.map((part) => part.vector),
12780
- orderedParts.map((part) => part.tokenCount)
12781
- )
12782
- });
12783
- }
12784
- this.logger.recordEmbeddingApiCall(result.totalTokensUsed);
12785
- } catch (error) {
12786
- const failureMessage = String(error);
12787
- const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
12788
- const failedChunks = getUniquePendingChunksFromRequests(requestBatch).filter((chunk) => !completedChunkIds.has(chunk.id) && !failedChunkIds.has(chunk.id));
12789
- for (const chunk of failedChunks) {
12790
- failedChunkIds.add(chunk.id);
12791
- embeddingPartsByChunk.delete(chunk.id);
12792
- failedChunksForBatch.set(chunk.id, {
12793
- chunks: [chunk],
12794
- attemptCount: batch.attemptCount + 1,
12795
- lastAttempt: failureTimestamp,
12796
- error: failureMessage
12797
- });
12798
- }
12799
- failed += failedChunks.length;
12800
- this.logger.recordEmbeddingError();
12801
- }
12802
- }
12803
- const successfulResults = pooledResults.filter(({ chunk }) => !failedChunkIds.has(chunk.id));
12804
- const items = successfulResults.map(({ chunk, vector }) => ({
12805
- id: chunk.id,
12806
- vector,
12807
- metadata: chunk.metadata
12808
- }));
12809
- if (items.length > 0) {
12810
- store.addBatch(items);
12811
- }
12812
- if (successfulResults.length > 0) {
12813
- try {
12814
- database.upsertEmbeddingsBatch(
12815
- successfulResults.map(({ chunk, vector }) => ({
12816
- contentHash: chunk.contentHash,
12817
- embedding: float32ArrayToBuffer(vector),
12818
- chunkText: chunk.storageText,
12819
- model: configuredProviderInfo.modelInfo.model
12820
- }))
12821
- );
12822
- } catch (dbError) {
12823
- this.rebuildVectorStoreExcludingChunkIds(
12824
- store,
12825
- database,
12826
- successfulResults.map(({ chunk }) => chunk.id)
13465
+ try {
13466
+ const retryableChunks = this.iterateLatestFailedChunks(
13467
+ failedProcessing.latestById,
13468
+ roots,
13469
+ () => true,
13470
+ maxChunkTokens
13471
+ );
13472
+ for (const retryBatch of iterateOrderedFileBatches(
13473
+ retryableChunks,
13474
+ ({ chunk }) => Buffer.byteLength(chunk.content, "utf-8"),
13475
+ this.fileBatchLimits
13476
+ )) {
13477
+ const chunks = retryBatch.map(({ chunk }) => chunk);
13478
+ const attemptCounts = new Map(retryBatch.map(({ chunk, attemptCount }) => [chunk.id, attemptCount]));
13479
+ const batchResult = await this.processPendingChunkBatch(chunks, {
13480
+ store,
13481
+ provider,
13482
+ invertedIndex,
13483
+ database,
13484
+ configuredProviderInfo,
13485
+ queue,
13486
+ providerRateLimits,
13487
+ rateLimitState,
13488
+ failedState: failedProcessing.state,
13489
+ attemptCounts,
13490
+ forceReembed: false,
13491
+ reuseCachedEmbeddings: false,
13492
+ incrementRepeatedFailures: false,
13493
+ onSucceeded: (succeededChunks) => {
13494
+ database.addChunksToBranchBatch(
13495
+ this.getBranchCatalogKey(),
13496
+ succeededChunks.map((chunk) => chunk.id)
12827
13497
  );
12828
- throw dbError;
12829
13498
  }
12830
- }
12831
- for (const { chunk } of successfulResults) {
12832
- invertedIndex.removeChunk(chunk.id);
12833
- invertedIndex.addChunk(chunk.id, chunk.content);
12834
- completedChunkIds.add(chunk.id);
12835
- embeddingPartsByChunk.delete(chunk.id);
12836
- }
12837
- database.addChunksToBranchBatch(
12838
- this.getBranchCatalogKey(),
12839
- successfulResults.map(({ chunk }) => chunk.id)
12840
- );
12841
- this.logger.recordChunksEmbedded(successfulResults.length);
12842
- succeeded += successfulResults.length;
12843
- stillFailing.push(...failedChunksForBatch.values());
12844
- } catch (error) {
12845
- const failureMessage = getErrorMessage4(error);
12846
- const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
12847
- const unaccountedChunks = batch.chunks.filter(
12848
- (chunk) => !failedChunksForBatch.has(chunk.id) && !completedChunkIds.has(chunk.id)
12849
- );
12850
- for (const chunk of unaccountedChunks) {
12851
- failedChunksForBatch.set(chunk.id, {
12852
- chunks: [chunk],
12853
- attemptCount: batch.attemptCount + 1,
12854
- lastAttempt: failureTimestamp,
12855
- error: failureMessage
12856
- });
12857
- }
12858
- failed += unaccountedChunks.length;
12859
- this.logger.recordEmbeddingError();
12860
- stillFailing.push(...coalesceFailedBatches(Array.from(failedChunksForBatch.values())));
13499
+ });
13500
+ succeeded += batchResult.indexedChunks;
13501
+ failed += batchResult.failedChunks;
12861
13502
  }
13503
+ this.finalizeFailedBatchWriteState(failedProcessing.state);
13504
+ } catch (error) {
13505
+ failedProcessing.state.writer.cleanup();
13506
+ throw error;
12862
13507
  }
12863
- const persistedStillFailing = coalesceFailedBatches(stillFailing);
12864
- if (roots) {
12865
- this.saveFailedBatches([...retainedFailedBatches, ...persistedStillFailing]);
12866
- } else {
12867
- this.saveFailedBatches(persistedStillFailing);
12868
- }
13508
+ const remaining = this.getFailedBatchesCount();
12869
13509
  if (succeeded > 0) {
12870
13510
  store.save();
12871
13511
  this.saveInvertedIndex(invertedIndex);
12872
13512
  }
12873
- if (roots && succeeded > 0 && persistedStillFailing.length === 0 && this.hasProjectForceReembedPending()) {
13513
+ if (roots && succeeded > 0 && remaining === 0 && this.hasProjectForceReembedPending()) {
12874
13514
  database.deleteMetadata(this.getProjectForceReembedMetadataKey());
12875
13515
  this.saveIndexMetadata(configuredProviderInfo);
12876
13516
  this.indexCompatibility = { compatible: true };
12877
13517
  }
12878
- return { succeeded, failed, remaining: persistedStillFailing.length };
13518
+ return { succeeded, failed, remaining };
12879
13519
  }
12880
13520
  getFailedBatchesCount() {
12881
- if (this.config.scope === "global") {
12882
- return this.partitionFailedBatches(this.getScopedRoots()).scoped.length;
13521
+ const roots = this.config.scope === "global" ? this.getScopedRoots() : null;
13522
+ const latestById = /* @__PURE__ */ new Map();
13523
+ for (const batch of this.loadSerializedFailedBatches()) {
13524
+ for (const rawChunk of batch.chunks) {
13525
+ const filePath = getPendingChunkFilePath(rawChunk);
13526
+ if (roots && (filePath === null || !this.isFileInCurrentScope(filePath, roots))) {
13527
+ continue;
13528
+ }
13529
+ const chunkId = getPendingChunkId(rawChunk);
13530
+ if (!chunkId) {
13531
+ continue;
13532
+ }
13533
+ const existing = latestById.get(chunkId);
13534
+ if (!existing || batch.attemptCount >= existing.attemptCount) {
13535
+ latestById.set(chunkId, {
13536
+ attemptCount: batch.attemptCount,
13537
+ error: batch.error,
13538
+ lastAttempt: batch.lastAttempt
13539
+ });
13540
+ }
13541
+ }
12883
13542
  }
12884
- return this.loadFailedBatches().length;
13543
+ return new Set(Array.from(latestById.values(), getFailedBatchGroupKey)).size;
12885
13544
  }
12886
13545
  getCurrentBranch() {
12887
13546
  return this.currentBranch;
@@ -13069,9 +13728,9 @@ var Indexer = class _Indexer {
13069
13728
  this.requireReadableComponents(readIssues, "database");
13070
13729
  let shortest = [];
13071
13730
  for (const branchKey of this.getBranchCatalogKeys()) {
13072
- const path20 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
13073
- if (path20.length > 0 && (shortest.length === 0 || path20.length < shortest.length)) {
13074
- shortest = path20;
13731
+ const path21 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
13732
+ if (path21.length > 0 && (shortest.length === 0 || path21.length < shortest.length)) {
13733
+ shortest = path21;
13075
13734
  }
13076
13735
  }
13077
13736
  return shortest.map((hop) => this.resolveFilePathRecord(hop));
@@ -13119,13 +13778,13 @@ var Indexer = class _Indexer {
13119
13778
  }
13120
13779
  }
13121
13780
  if (!found) continue;
13122
- const path20 = [];
13781
+ const path21 = [];
13123
13782
  let currentSymbolId = toSymbolId;
13124
13783
  while (true) {
13125
13784
  const symbol = symbolsById.get(currentSymbolId);
13126
13785
  if (!symbol) break;
13127
13786
  const parent = parentBySymbolId.get(currentSymbolId);
13128
- path20.push({
13787
+ path21.push({
13129
13788
  symbolId: symbol.id,
13130
13789
  symbolName: symbol.name,
13131
13790
  filePath: symbol.filePath,
@@ -13135,9 +13794,9 @@ var Indexer = class _Indexer {
13135
13794
  if (!parent) break;
13136
13795
  currentSymbolId = parent.parentId;
13137
13796
  }
13138
- path20.reverse();
13139
- if (path20.length > 0 && (shortest.length === 0 || path20.length < shortest.length)) {
13140
- shortest = path20;
13797
+ path21.reverse();
13798
+ if (path21.length > 0 && (shortest.length === 0 || path21.length < shortest.length)) {
13799
+ shortest = path21;
13141
13800
  }
13142
13801
  }
13143
13802
  return shortest.map((hop) => this.resolveFilePathRecord(hop));
@@ -13156,13 +13815,13 @@ var Indexer = class _Indexer {
13156
13815
  async getSymbolsForBranch(branch) {
13157
13816
  const { database, readIssues } = await this.ensureInitialized();
13158
13817
  this.requireReadableComponents(readIssues, "database");
13159
- const resolvedBranch = branch ?? this.getBranchCatalogKey();
13818
+ const resolvedBranch = this.resolveBranchCatalogKey(branch);
13160
13819
  return database.getSymbolsForBranch(resolvedBranch).map((symbol) => this.resolveFilePathRecord(symbol));
13161
13820
  }
13162
13821
  async getSymbolsForFiles(filePaths, branch) {
13163
13822
  const { database, readIssues } = await this.ensureInitialized();
13164
13823
  this.requireReadableComponents(readIssues, "database");
13165
- const resolvedBranch = branch ?? this.getBranchCatalogKey();
13824
+ const resolvedBranch = this.resolveBranchCatalogKey(branch);
13166
13825
  const storedFilePaths = filePaths.map((filePath) => this.toStoredFilePath(filePath));
13167
13826
  return database.getSymbolsForFiles(storedFilePaths, resolvedBranch).map((symbol) => this.resolveFilePathRecord(symbol));
13168
13827
  }
@@ -13175,13 +13834,26 @@ var Indexer = class _Indexer {
13175
13834
  async detectCommunities(branch, symbolIds) {
13176
13835
  const { database, readIssues } = await this.ensureInitialized();
13177
13836
  this.requireReadableComponents(readIssues, "database");
13178
- const resolvedBranch = branch ?? this.getBranchCatalogKey();
13837
+ const resolvedBranch = this.resolveBranchCatalogKey(branch);
13179
13838
  return database.detectCommunities(resolvedBranch, symbolIds).map((entry) => this.resolveFilePathRecord(entry));
13180
13839
  }
13840
+ async detectCommunityCouplings(branch) {
13841
+ const { database, readIssues } = await this.ensureInitialized();
13842
+ this.requireReadableComponents(readIssues, "database");
13843
+ const resolvedBranch = this.resolveBranchCatalogKey(branch);
13844
+ return database.detectCommunityCouplings(resolvedBranch).map((entry) => ({
13845
+ ...entry,
13846
+ relationships: (entry.relationships ?? entry.representativeRelationships ?? []).map((relationship) => ({
13847
+ ...relationship,
13848
+ fromFilePath: this.resolveStoredFilePath(relationship.fromFilePath),
13849
+ toFilePath: this.resolveStoredFilePath(relationship.toFilePath)
13850
+ }))
13851
+ }));
13852
+ }
13181
13853
  async computeCentrality(branch) {
13182
13854
  const { database, readIssues } = await this.ensureInitialized();
13183
13855
  this.requireReadableComponents(readIssues, "database");
13184
- const resolvedBranch = branch ?? this.getBranchCatalogKey();
13856
+ const resolvedBranch = this.resolveBranchCatalogKey(branch);
13185
13857
  return database.computeCentrality(resolvedBranch).map((entry) => this.resolveFilePathRecord(entry));
13186
13858
  }
13187
13859
  async getPrImpact(opts, onPreparationProgress) {
@@ -13275,7 +13947,7 @@ var Indexer = class _Indexer {
13275
13947
  );
13276
13948
  }
13277
13949
  }
13278
- const toStoredChangedFiles = (filePaths) => filePaths.map((filePath) => this.toStoredFilePath(path18.resolve(this.projectRoot, filePath)));
13950
+ const toStoredChangedFiles = (filePaths) => filePaths.map((filePath) => this.toStoredFilePath(path19.resolve(this.projectRoot, filePath)));
13279
13951
  const storedChangedFiles = toStoredChangedFiles(changedFiles);
13280
13952
  const directSymbols = database.getSymbolsForFiles(storedChangedFiles, branchKey);
13281
13953
  const directIds = directSymbols.map((s) => s.id);
@@ -13424,12 +14096,12 @@ var Indexer = class _Indexer {
13424
14096
  if (meta.filePath) filePaths.add(meta.filePath);
13425
14097
  }
13426
14098
  const directory = options?.directory?.replace(/\/$/, "");
13427
- const absoluteDirectoryFilter = directory ? path18.resolve(this.projectRoot, directory) : void 0;
14099
+ const absoluteDirectoryFilter = directory ? path19.resolve(this.projectRoot, directory) : void 0;
13428
14100
  for (const filePath of filePaths) {
13429
14101
  if (directory) {
13430
14102
  const absoluteFilePath = this.resolveStoredFilePath(filePath);
13431
14103
  const matchesRelative = filePath === directory || filePath.startsWith(directory + "/");
13432
- const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path18.sep));
14104
+ const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path19.sep));
13433
14105
  if (!matchesRelative && !matchesProjectRelative) {
13434
14106
  continue;
13435
14107
  }
@@ -13589,7 +14261,7 @@ function trimOrUndefined(value) {
13589
14261
  return normalized || void 0;
13590
14262
  }
13591
14263
  function normalizeCallGraphPath(value) {
13592
- let normalized = path19.posix.normalize(value.trim().replaceAll("\\", "/"));
14264
+ let normalized = path20.posix.normalize(value.trim().replaceAll("\\", "/"));
13593
14265
  if (normalized.startsWith("./")) {
13594
14266
  normalized = normalized.slice(2);
13595
14267
  }
@@ -13773,12 +14445,12 @@ async function getCallGraphPath(projectRoot3, host, from, to, maxDepth, fromFile
13773
14445
  if (fromResolution.status !== "resolved" || toResolution.status !== "resolved") {
13774
14446
  return { from: fromResolution, to: toResolution, path: [] };
13775
14447
  }
13776
- const path20 = await indexer.findCallPathBySymbolIds(
14448
+ const path21 = await indexer.findCallPathBySymbolIds(
13777
14449
  fromResolution.symbolId,
13778
14450
  toResolution.symbolId,
13779
14451
  maxDepth
13780
14452
  );
13781
- return { from: fromResolution, to: toResolution, path: path20 };
14453
+ return { from: fromResolution, to: toResolution, path: path21 };
13782
14454
  }
13783
14455
  async function runIndexCodebase(projectRoot3, host, args, onProgress) {
13784
14456
  const root = getProjectRoot(projectRoot3, host);
@@ -13854,6 +14526,34 @@ async function getPrImpact(projectRoot3, host, params) {
13854
14526
  direction: params.direction
13855
14527
  });
13856
14528
  }
14529
+ async function getCodeCommunities(projectRoot3, host, params) {
14530
+ await ensureAutoIndexReadyForRetrieval(projectRoot3, host);
14531
+ const indexer = getIndexerForProject(projectRoot3, host);
14532
+ const [communities, centrality, couplings] = await Promise.all([
14533
+ indexer.detectCommunities(params.branch),
14534
+ indexer.computeCentrality(params.branch),
14535
+ indexer.detectCommunityCouplings(params.branch)
14536
+ ]);
14537
+ return buildCodeCommunitiesResult(communities, centrality, couplings, {
14538
+ minSize: Math.max(CODE_COMMUNITIES_MIN_SIZE, Math.floor(params.minSize ?? CODE_COMMUNITIES_MIN_SIZE)),
14539
+ limit: Math.min(
14540
+ CODE_COMMUNITIES_MAX_LIMIT,
14541
+ Math.max(1, Math.floor(params.limit ?? CODE_COMMUNITIES_DEFAULT_LIMIT))
14542
+ ),
14543
+ hubThreshold: Math.max(
14544
+ 0,
14545
+ Math.floor(params.hubThreshold ?? CODE_COMMUNITIES_DEFAULT_HUB_THRESHOLD)
14546
+ ),
14547
+ minCoupling: Math.max(
14548
+ CODE_COMMUNITIES_MIN_COUPLING,
14549
+ Math.floor(params.minCoupling ?? CODE_COMMUNITIES_MIN_COUPLING)
14550
+ ),
14551
+ couplingLimit: Math.min(
14552
+ CODE_COMMUNITIES_MAX_COUPLING_LIMIT,
14553
+ Math.max(1, Math.floor(params.couplingLimit ?? CODE_COMMUNITIES_DEFAULT_COUPLING_LIMIT))
14554
+ )
14555
+ });
14556
+ }
13857
14557
  async function getIndexMetrics(projectRoot3, host, args = {}) {
13858
14558
  const root = getProjectRoot(projectRoot3, host);
13859
14559
  const key = getIndexerCacheKey(root, host);
@@ -13945,10 +14645,10 @@ async function getIndexLogs(projectRoot3, host, args) {
13945
14645
  function addKnowledgeBase(projectRoot3, host, knowledgeBasePath) {
13946
14646
  const root = getProjectRoot(projectRoot3, host);
13947
14647
  const inputPath = knowledgeBasePath.trim();
13948
- const normalizedPath = path19.resolve(
13949
- path19.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
14648
+ const normalizedPath = path20.resolve(
14649
+ path20.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
13950
14650
  );
13951
- if (!existsSync11(normalizedPath)) {
14651
+ if (!existsSync12(normalizedPath)) {
13952
14652
  return `Error: Directory does not exist: ${normalizedPath}`;
13953
14653
  }
13954
14654
  let realPath;
@@ -13982,7 +14682,7 @@ function addKnowledgeBase(projectRoot3, host, knowledgeBasePath) {
13982
14682
  }
13983
14683
  }
13984
14684
  for (const dotDir of sensitiveDotDirs) {
13985
- const sensitiveDir = path19.join(homeDir, dotDir);
14685
+ const sensitiveDir = path20.join(homeDir, dotDir);
13986
14686
  if (sensitiveDir && (realPath === sensitiveDir || realPath.startsWith(`${sensitiveDir}/`))) {
13987
14687
  return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath}`;
13988
14688
  }
@@ -14028,7 +14728,7 @@ function listKnowledgeBases(projectRoot3, host) {
14028
14728
  for (let i = 0; i < knowledgeBases.length; i++) {
14029
14729
  const kb = knowledgeBases[i];
14030
14730
  const resolvedPath = resolveKnowledgeBasePath(kb, root);
14031
- const exists = existsSync11(resolvedPath);
14731
+ const exists = existsSync12(resolvedPath);
14032
14732
  result += `[${i + 1}] ${kb}
14033
14733
  `;
14034
14734
  result += ` Resolved: ${resolvedPath}
@@ -14045,7 +14745,7 @@ function listKnowledgeBases(projectRoot3, host) {
14045
14745
  }
14046
14746
  result += "\n";
14047
14747
  }
14048
- const hasHostConfig = existsSync11(path19.join(root, getHostProjectConfigRelativePath(host)));
14748
+ const hasHostConfig = existsSync12(path20.join(root, getHostProjectConfigRelativePath(host)));
14049
14749
  if (hasHostConfig) {
14050
14750
  result += `
14051
14751
  Config sources: 1 file(s).`;
@@ -14078,117 +14778,6 @@ Run /index to rebuild the index without the removed knowledge base.`;
14078
14778
  return result;
14079
14779
  }
14080
14780
 
14081
- // src/tools/symbol-inference.ts
14082
- var IDENTIFIER_RE = /[A-Za-z_$][A-Za-z0-9_$]*/g;
14083
- var QUOTED_BACKTICK_RE = /`([^`]+)`/g;
14084
- var QUOTED_SINGLE_RE = /'([^'\\]+)'/g;
14085
- var QUOTED_DOUBLE_RE = /"([^"]+)"/g;
14086
- var SYMBOL_LIKE_RE = /^(?:[A-Za-z_$][A-Za-z0-9_$]*)$/;
14087
- var CAMEL_CASE_RE = /^[a-z_][A-Za-z0-9_$]*[A-Z][A-Za-z0-9_$]*$/;
14088
- var PASCAL_CASE_RE = /^[A-Z][A-Za-z0-9_$]*$/;
14089
- var SNAKE_CASE_RE = /^[a-z][a-z0-9_]*_[a-z0-9_]+$/;
14090
- var DEFINITION_INTENT_RE = /\b(where|defined|definition|define|declaration|symbol|function|method|class|interface|type)\b/i;
14091
- var STOP_WORDS = /* @__PURE__ */ new Set([
14092
- "a",
14093
- "an",
14094
- "and",
14095
- "are",
14096
- "at",
14097
- "for",
14098
- "find",
14099
- "how",
14100
- "i",
14101
- "in",
14102
- "is",
14103
- "it",
14104
- "of",
14105
- "on",
14106
- "that",
14107
- "the",
14108
- "definition",
14109
- "show",
14110
- "to",
14111
- "where",
14112
- "which",
14113
- "what",
14114
- "you",
14115
- "your",
14116
- "with"
14117
- ]);
14118
- function stripCallSuffix(token) {
14119
- return token.replace(/\(\s*\)$/, "");
14120
- }
14121
- function isLikelySymbolName(token) {
14122
- if (!SYMBOL_LIKE_RE.test(token)) {
14123
- return false;
14124
- }
14125
- if (STOP_WORDS.has(token.toLowerCase())) {
14126
- return false;
14127
- }
14128
- return CAMEL_CASE_RE.test(token) || PASCAL_CASE_RE.test(token) || SNAKE_CASE_RE.test(token);
14129
- }
14130
- function extractQuotedIdentifiers(query) {
14131
- const identifiers = /* @__PURE__ */ new Set();
14132
- for (const match of query.matchAll(QUOTED_BACKTICK_RE)) {
14133
- const candidate = stripCallSuffix(match[1].trim());
14134
- if (candidate && isLikelySymbolName(candidate)) {
14135
- identifiers.add(candidate);
14136
- }
14137
- }
14138
- for (const match of query.matchAll(QUOTED_SINGLE_RE)) {
14139
- const candidate = stripCallSuffix(match[1].trim());
14140
- if (candidate && isLikelySymbolName(candidate)) {
14141
- identifiers.add(candidate);
14142
- }
14143
- }
14144
- for (const match of query.matchAll(QUOTED_DOUBLE_RE)) {
14145
- const candidate = stripCallSuffix(match[1].trim());
14146
- if (candidate && isLikelySymbolName(candidate)) {
14147
- identifiers.add(candidate);
14148
- }
14149
- }
14150
- return [...identifiers];
14151
- }
14152
- function extractBareIdentifiers(query) {
14153
- const unquoted = query.replace(QUOTED_BACKTICK_RE, " ").replace(QUOTED_SINGLE_RE, " ").replace(QUOTED_DOUBLE_RE, " ");
14154
- const identifiers = /* @__PURE__ */ new Set();
14155
- for (const match of unquoted.matchAll(IDENTIFIER_RE)) {
14156
- const candidate = stripCallSuffix(match[0]);
14157
- if (isLikelySymbolName(candidate)) {
14158
- identifiers.add(candidate);
14159
- }
14160
- }
14161
- return [...identifiers];
14162
- }
14163
- function isSingleMeaningfulToken(query, symbol) {
14164
- const tokens = query.replace(/[`'"()]/g, " ").split(/[^A-Za-z0-9_$]+/).map((token) => token.trim().toLowerCase()).filter((token) => token.length > 0).filter((token) => !STOP_WORDS.has(token));
14165
- return tokens.length === 1 && tokens[0] === symbol.toLowerCase();
14166
- }
14167
- function inferExactSymbolFromQuery(query) {
14168
- if (analyzeQueryIntent(query).explicitArtifactIntent) {
14169
- return void 0;
14170
- }
14171
- const quoted = extractQuotedIdentifiers(query);
14172
- if (quoted.length === 1) {
14173
- return quoted[0];
14174
- }
14175
- if (quoted.length > 1) {
14176
- return void 0;
14177
- }
14178
- const candidates = extractBareIdentifiers(query);
14179
- if (candidates.length !== 1) {
14180
- return void 0;
14181
- }
14182
- const candidate = candidates[0];
14183
- if (DEFINITION_INTENT_RE.test(query)) {
14184
- return candidate;
14185
- }
14186
- if (isSingleMeaningfulToken(query, candidate)) {
14187
- return candidate;
14188
- }
14189
- return void 0;
14190
- }
14191
-
14192
14781
  // src/tools/context-search.ts
14193
14782
  var MIN_CONTEXT_RESULT_LIMIT = 1;
14194
14783
  var MAX_CONTEXT_RESULT_LIMIT = 100;
@@ -14539,7 +15128,7 @@ async function resolveCodebaseContextUnmeasured(projectRoot3, host, input) {
14539
15128
  const directory = input.directory ?? void 0;
14540
15129
  const tokenBudget = input.tokenBudget ?? void 0;
14541
15130
  if (from && to) {
14542
- const path20 = await getCallGraphPath(
15131
+ const path21 = await getCallGraphPath(
14543
15132
  projectRoot3,
14544
15133
  host,
14545
15134
  from,
@@ -14548,25 +15137,25 @@ async function resolveCodebaseContextUnmeasured(projectRoot3, host, input) {
14548
15137
  fromFilePath,
14549
15138
  toFilePath
14550
15139
  );
14551
- const pathText = formatCallGraphPathResult(path20);
14552
- if (path20.path.length > 0) {
15140
+ const pathText = formatCallGraphPathResult(path21);
15141
+ if (path21.path.length > 0) {
14553
15142
  const fitted2 = fitTextToContextBudget(
14554
15143
  pathText,
14555
15144
  tokenBudget
14556
15145
  );
14557
15146
  return {
14558
15147
  text: fitted2.text,
14559
- details: fittedDetails("path", fitted2, path20.path.length)
15148
+ details: fittedDetails("path", fitted2, path21.path.length)
14560
15149
  };
14561
15150
  }
14562
- if (path20.from.status !== "resolved" || path20.to.status !== "resolved") {
15151
+ if (path21.from.status !== "resolved" || path21.to.status !== "resolved") {
14563
15152
  const fitted2 = fitTextToContextBudget(pathText, tokenBudget);
14564
15153
  return {
14565
15154
  text: fitted2.text,
14566
15155
  details: fittedDetails("path", fitted2, 0)
14567
15156
  };
14568
15157
  }
14569
- const resolvedFrom = path20.from;
15158
+ const resolvedFrom = path21.from;
14570
15159
  const { callers } = await getCallGraphData(projectRoot3, host, {
14571
15160
  name: to,
14572
15161
  direction: "callers",
@@ -14686,6 +15275,7 @@ var TOOL_NAME = {
14686
15275
  CALL_GRAPH: "call_graph",
14687
15276
  CALL_GRAPH_PATH: "call_graph_path",
14688
15277
  PR_IMPACT: "pr_impact",
15278
+ CODE_COMMUNITIES: "code_communities",
14689
15279
  ADD_KNOWLEDGE_BASE: "add_knowledge_base",
14690
15280
  LIST_KNOWLEDGE_BASES: "list_knowledge_bases",
14691
15281
  REMOVE_KNOWLEDGE_BASE: "remove_knowledge_base",
@@ -14707,7 +15297,8 @@ var PORTABLE_TOOL_NAMES = [
14707
15297
  TOOL_NAME.IMPLEMENTATION_LOOKUP,
14708
15298
  TOOL_NAME.CALL_GRAPH,
14709
15299
  TOOL_NAME.CALL_GRAPH_PATH,
14710
- TOOL_NAME.PR_IMPACT
15300
+ TOOL_NAME.PR_IMPACT,
15301
+ TOOL_NAME.CODE_COMMUNITIES
14711
15302
  ];
14712
15303
  var OPENCODE_TOOL_NAMES = [
14713
15304
  TOOL_NAME.CODEBASE_CONTEXT,
@@ -14726,6 +15317,7 @@ var OPENCODE_TOOL_NAMES = [
14726
15317
  TOOL_NAME.LIST_KNOWLEDGE_BASES,
14727
15318
  TOOL_NAME.REMOVE_KNOWLEDGE_BASE,
14728
15319
  TOOL_NAME.PR_IMPACT,
15320
+ TOOL_NAME.CODE_COMMUNITIES,
14729
15321
  TOOL_NAME.INDEX_VISUALIZE
14730
15322
  ];
14731
15323
  var PI_TOOL_NAMES = [
@@ -14742,6 +15334,7 @@ var PI_TOOL_NAMES = [
14742
15334
  TOOL_NAME.CALL_GRAPH,
14743
15335
  TOOL_NAME.CALL_GRAPH_PATH,
14744
15336
  TOOL_NAME.PR_IMPACT,
15337
+ TOOL_NAME.CODE_COMMUNITIES,
14745
15338
  TOOL_NAME.PI_KNOWLEDGE_BASE_LIST,
14746
15339
  TOOL_NAME.PI_KNOWLEDGE_BASE_ADD,
14747
15340
  TOOL_NAME.PI_KNOWLEDGE_BASE_REMOVE
@@ -15036,6 +15629,25 @@ Check index_status first when index readiness is unknown. For repository questio
15036
15629
  return text2(formatPrImpact(result), result);
15037
15630
  }
15038
15631
  });
15632
+ pi.registerTool({
15633
+ name: TOOL_NAME.CODE_COMMUNITIES,
15634
+ label: "Code Communities",
15635
+ description: "Discover natural module boundaries and hub symbols using graph community detection. Clusters symbols by call-graph connectivity to reveal architecture.",
15636
+ parameters: Type2.Object({
15637
+ branch: Type2.Optional(Type2.String()),
15638
+ minSize: Type2.Optional(Type2.Integer({ minimum: CODE_COMMUNITIES_MIN_SIZE, default: CODE_COMMUNITIES_MIN_SIZE })),
15639
+ limit: Type2.Optional(Type2.Integer({ minimum: 1, maximum: CODE_COMMUNITIES_MAX_LIMIT, default: CODE_COMMUNITIES_DEFAULT_LIMIT })),
15640
+ hubThreshold: Type2.Optional(Type2.Integer({ minimum: 0, default: CODE_COMMUNITIES_DEFAULT_HUB_THRESHOLD })),
15641
+ minCoupling: Type2.Optional(Type2.Integer({ minimum: CODE_COMMUNITIES_MIN_COUPLING, default: CODE_COMMUNITIES_MIN_COUPLING })),
15642
+ couplingLimit: Type2.Optional(
15643
+ Type2.Integer({ minimum: 1, maximum: CODE_COMMUNITIES_MAX_COUPLING_LIMIT, default: CODE_COMMUNITIES_DEFAULT_COUPLING_LIMIT })
15644
+ )
15645
+ }),
15646
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
15647
+ const result = await getCodeCommunities(projectRoot2(ctx), HOST2, params);
15648
+ return text2(formatCodeCommunities(result), result);
15649
+ }
15650
+ });
15039
15651
  pi.registerTool({
15040
15652
  name: TOOL_NAME.PI_KNOWLEDGE_BASE_LIST,
15041
15653
  label: "List Knowledge Bases",