@zokizuan/satori-mcp 4.5.0 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/handlers.d.ts +14 -0
- package/dist/core/handlers.js +438 -59
- package/dist/core/search-types.d.ts +21 -0
- package/package.json +2 -2
package/dist/core/handlers.d.ts
CHANGED
|
@@ -79,6 +79,15 @@ export declare class ToolHandlers {
|
|
|
79
79
|
private tokenizeQueryPrefix;
|
|
80
80
|
private unquoteOperatorValue;
|
|
81
81
|
private parseSearchOperators;
|
|
82
|
+
private tokenizeLexicalTerms;
|
|
83
|
+
private isIdentifierLikeToken;
|
|
84
|
+
private buildSearchQueryPlan;
|
|
85
|
+
private escapeLexicalRegex;
|
|
86
|
+
private hasTokenBoundaryMatch;
|
|
87
|
+
private getReferenceUsageKind;
|
|
88
|
+
private hasDeclarationMatch;
|
|
89
|
+
private getLexicalTermFactor;
|
|
90
|
+
private scoreCandidateLexicalEvidence;
|
|
82
91
|
private pathMatchesAnyPattern;
|
|
83
92
|
private tokenMatchesAnyField;
|
|
84
93
|
private resolveRerankDecision;
|
|
@@ -94,6 +103,11 @@ export declare class ToolHandlers {
|
|
|
94
103
|
private getStalenessBucket;
|
|
95
104
|
private compareNullableNumbersAsc;
|
|
96
105
|
private compareNullableStringsAsc;
|
|
106
|
+
private compareSearchCandidates;
|
|
107
|
+
private sortSearchCandidates;
|
|
108
|
+
private isDeclarationSearchGroup;
|
|
109
|
+
private normalizeDeclarationGroupKey;
|
|
110
|
+
private collapseDuplicateDeclarationGroups;
|
|
97
111
|
private buildFallbackGroupId;
|
|
98
112
|
private isCallGraphLanguageSupported;
|
|
99
113
|
private buildCallGraphHint;
|
package/dist/core/handlers.js
CHANGED
|
@@ -21,6 +21,11 @@ const ZILLIZ_FREE_TIER_COLLECTION_LIMIT = 5;
|
|
|
21
21
|
const OUTLINE_SUPPORTED_EXTENSIONS = getSupportedExtensionsForCapability('fileOutline');
|
|
22
22
|
const MIN_RELIABLE_COLLECTION_CREATED_AT_MS = Date.UTC(2000, 0, 1);
|
|
23
23
|
const SEARCH_OPERATOR_KEYS = new Set(['lang', 'path', '-path', 'must', 'exclude']);
|
|
24
|
+
const SEARCH_QUERY_STOPWORDS = new Set([
|
|
25
|
+
'a', 'an', 'and', 'are', 'as', 'at', 'be', 'by', 'find', 'for', 'from', 'how',
|
|
26
|
+
'in', 'is', 'it', 'logic', 'of', 'or', 'the', 'to', 'used', 'uses', 'using',
|
|
27
|
+
'what', 'where', 'which', 'who', 'why'
|
|
28
|
+
]);
|
|
24
29
|
const NAVIGATION_FALLBACK_MESSAGE = 'Call graph not available for this result; use readSpan or fileOutlineWindow to navigate.';
|
|
25
30
|
// Recovery probe threshold for "likely interrupted" indexing states.
|
|
26
31
|
// Keep this shorter than snapshot merge stale semantics for better operator UX.
|
|
@@ -1235,6 +1240,241 @@ export class ToolHandlers {
|
|
|
1235
1240
|
operators.semanticQuery = semanticParts.length > 0 ? semanticParts.join("\n") : trimmedQuery;
|
|
1236
1241
|
return operators;
|
|
1237
1242
|
}
|
|
1243
|
+
tokenizeLexicalTerms(tokens) {
|
|
1244
|
+
const terms = new Map();
|
|
1245
|
+
const addTerm = (value, kind) => {
|
|
1246
|
+
const normalized = value
|
|
1247
|
+
.replace(/^['"`]+|['"`]+$/g, '')
|
|
1248
|
+
.replace(/[(){}\[\],;]+/g, ' ')
|
|
1249
|
+
.trim()
|
|
1250
|
+
.toLowerCase();
|
|
1251
|
+
if (normalized.length === 0) {
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
const existing = terms.get(normalized);
|
|
1255
|
+
if (!existing || (existing.kind === 'fragment' && kind === 'whole')) {
|
|
1256
|
+
terms.set(normalized, { value: normalized, kind });
|
|
1257
|
+
}
|
|
1258
|
+
};
|
|
1259
|
+
for (const token of tokens) {
|
|
1260
|
+
const trimmed = token.trim();
|
|
1261
|
+
if (trimmed.length === 0) {
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
1264
|
+
addTerm(trimmed, 'whole');
|
|
1265
|
+
const expanded = trimmed
|
|
1266
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
1267
|
+
.replace(/[/\\._:-]+/g, ' ')
|
|
1268
|
+
.replace(/[(){}\[\],;]+/g, ' ')
|
|
1269
|
+
.toLowerCase();
|
|
1270
|
+
for (const part of expanded.split(/\s+/)) {
|
|
1271
|
+
const normalizedPart = part.trim();
|
|
1272
|
+
if (normalizedPart.length >= 2) {
|
|
1273
|
+
addTerm(normalizedPart, 'fragment');
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
return Array.from(terms.values());
|
|
1278
|
+
}
|
|
1279
|
+
isIdentifierLikeToken(token) {
|
|
1280
|
+
const trimmed = token.trim();
|
|
1281
|
+
if (trimmed.length === 0) {
|
|
1282
|
+
return false;
|
|
1283
|
+
}
|
|
1284
|
+
return /[A-Z]/.test(trimmed)
|
|
1285
|
+
|| /[_/\\.\-:]/.test(trimmed)
|
|
1286
|
+
|| /\d/.test(trimmed);
|
|
1287
|
+
}
|
|
1288
|
+
buildSearchQueryPlan(semanticQuery) {
|
|
1289
|
+
const hybridEnabled = this.runtimeFingerprint.schemaVersion.startsWith('hybrid');
|
|
1290
|
+
const tokens = semanticQuery
|
|
1291
|
+
.split(/\s+/)
|
|
1292
|
+
.map((token) => token.trim())
|
|
1293
|
+
.filter((token) => token.length > 0);
|
|
1294
|
+
const normalizedQuery = semanticQuery.toLowerCase();
|
|
1295
|
+
const normalizedTokens = tokens.map((token) => token.toLowerCase());
|
|
1296
|
+
const identifierTokens = tokens.filter((token) => this.isIdentifierLikeToken(token));
|
|
1297
|
+
const naturalLanguageTokens = tokens
|
|
1298
|
+
.filter((token) => (!this.isIdentifierLikeToken(token)
|
|
1299
|
+
&& (SEARCH_QUERY_STOPWORDS.has(token.toLowerCase()) || token.length >= 4)))
|
|
1300
|
+
.map((token) => token.toLowerCase());
|
|
1301
|
+
const singleBareLookup = tokens.length === 1
|
|
1302
|
+
&& /^[a-z][a-z0-9]{2,63}$/.test(tokens[0])
|
|
1303
|
+
&& !SEARCH_QUERY_STOPWORDS.has(normalizedTokens[0] || '');
|
|
1304
|
+
const lexicalTerms = this
|
|
1305
|
+
.tokenizeLexicalTerms(identifierTokens.length > 0 ? identifierTokens : tokens)
|
|
1306
|
+
.filter((term) => !SEARCH_QUERY_STOPWORDS.has(term.value))
|
|
1307
|
+
.slice(0, 8);
|
|
1308
|
+
const referenceSeeking = /\b(used|uses|usage|reference|references|referenced|callers?|called|imports?|imported|instantiat(?:e|ed|ion))\b/.test(normalizedQuery)
|
|
1309
|
+
|| /\bwhere\s+is\b/.test(normalizedQuery)
|
|
1310
|
+
|| /\bwho\s+uses\b/.test(normalizedQuery);
|
|
1311
|
+
let intent = 'uncertain';
|
|
1312
|
+
let confidence = 'low';
|
|
1313
|
+
const reasons = [];
|
|
1314
|
+
if (identifierTokens.length > 0 && naturalLanguageTokens.length > 0) {
|
|
1315
|
+
intent = 'mixed';
|
|
1316
|
+
confidence = identifierTokens.length >= 2 ? 'high' : 'medium';
|
|
1317
|
+
reasons.push('identifier_terms_present', 'natural_language_terms_present');
|
|
1318
|
+
}
|
|
1319
|
+
else if (identifierTokens.length > 0) {
|
|
1320
|
+
intent = 'identifier';
|
|
1321
|
+
confidence = tokens.length === identifierTokens.length ? 'high' : 'medium';
|
|
1322
|
+
reasons.push(tokens.length === 1 ? 'single_identifier_token' : 'identifier_tokens_present');
|
|
1323
|
+
}
|
|
1324
|
+
else if (singleBareLookup) {
|
|
1325
|
+
intent = 'uncertain';
|
|
1326
|
+
confidence = 'medium';
|
|
1327
|
+
reasons.push('single_term_lookup');
|
|
1328
|
+
}
|
|
1329
|
+
else if (naturalLanguageTokens.length >= 2 || tokens.length >= 4) {
|
|
1330
|
+
intent = 'semantic';
|
|
1331
|
+
confidence = 'high';
|
|
1332
|
+
reasons.push('natural_language_query');
|
|
1333
|
+
}
|
|
1334
|
+
else {
|
|
1335
|
+
reasons.push('ambiguous_short_query');
|
|
1336
|
+
}
|
|
1337
|
+
if (referenceSeeking) {
|
|
1338
|
+
reasons.push('reference_seeking_query');
|
|
1339
|
+
}
|
|
1340
|
+
return {
|
|
1341
|
+
semanticQuery,
|
|
1342
|
+
intent,
|
|
1343
|
+
confidence,
|
|
1344
|
+
reasons,
|
|
1345
|
+
referenceSeeking,
|
|
1346
|
+
lexicalTerms,
|
|
1347
|
+
retrievalMode: hybridEnabled
|
|
1348
|
+
? (intent === 'identifier' ? 'lexical' : 'hybrid')
|
|
1349
|
+
: 'dense',
|
|
1350
|
+
scorePolicyKind: 'topk_only',
|
|
1351
|
+
lexicalWeight: intent === 'identifier'
|
|
1352
|
+
? 1.35
|
|
1353
|
+
: intent === 'mixed'
|
|
1354
|
+
? (referenceSeeking ? 0.18 : 0.05)
|
|
1355
|
+
: intent === 'uncertain'
|
|
1356
|
+
? 0.60
|
|
1357
|
+
: 0.00,
|
|
1358
|
+
exactMatchPinningEnabled: intent !== 'semantic' && !referenceSeeking,
|
|
1359
|
+
rerankAllowed: intent !== 'identifier',
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
escapeLexicalRegex(value) {
|
|
1363
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1364
|
+
}
|
|
1365
|
+
hasTokenBoundaryMatch(field, term) {
|
|
1366
|
+
if (!field || !term) {
|
|
1367
|
+
return false;
|
|
1368
|
+
}
|
|
1369
|
+
const pattern = new RegExp(`(^|[^a-z0-9])${this.escapeLexicalRegex(term)}([^a-z0-9]|$)`, 'i');
|
|
1370
|
+
return pattern.test(field);
|
|
1371
|
+
}
|
|
1372
|
+
getReferenceUsageKind(content, term) {
|
|
1373
|
+
if (!content || !term) {
|
|
1374
|
+
return null;
|
|
1375
|
+
}
|
|
1376
|
+
const escaped = this.escapeLexicalRegex(term);
|
|
1377
|
+
const executablePatterns = [
|
|
1378
|
+
new RegExp(`\\bnew\\s+${escaped}\\b`, 'i'),
|
|
1379
|
+
new RegExp(`\\b${escaped}\\s*\\(`, 'i'),
|
|
1380
|
+
new RegExp(`\\b${escaped}\\b\\s*=`, 'i'),
|
|
1381
|
+
];
|
|
1382
|
+
if (executablePatterns.some((pattern) => pattern.test(content))) {
|
|
1383
|
+
return 'executable';
|
|
1384
|
+
}
|
|
1385
|
+
const importPatterns = [
|
|
1386
|
+
new RegExp(`\\bimport\\s+.*\\b${escaped}\\b`, 'i'),
|
|
1387
|
+
new RegExp(`\\bfrom\\s+.+\\s+import\\s+.*\\b${escaped}\\b`, 'i'),
|
|
1388
|
+
];
|
|
1389
|
+
return importPatterns.some((pattern) => pattern.test(content)) ? 'import' : null;
|
|
1390
|
+
}
|
|
1391
|
+
hasDeclarationMatch(content, term) {
|
|
1392
|
+
if (!content || !term) {
|
|
1393
|
+
return false;
|
|
1394
|
+
}
|
|
1395
|
+
const escaped = this.escapeLexicalRegex(term);
|
|
1396
|
+
const declarationPatterns = [
|
|
1397
|
+
new RegExp(`\\bclass\\s+${escaped}\\b`, 'i'),
|
|
1398
|
+
new RegExp(`\\bdef\\s+${escaped}\\b`, 'i'),
|
|
1399
|
+
new RegExp(`\\bfunction\\s+${escaped}\\b`, 'i'),
|
|
1400
|
+
new RegExp(`\\btype\\s+${escaped}\\b`, 'i'),
|
|
1401
|
+
new RegExp(`\\binterface\\s+${escaped}\\b`, 'i'),
|
|
1402
|
+
new RegExp(`\\benum\\s+${escaped}\\b`, 'i'),
|
|
1403
|
+
new RegExp(`\\bstruct\\s+${escaped}\\b`, 'i'),
|
|
1404
|
+
new RegExp(`\\b(?:const|let|var)\\s+${escaped}\\b\\s*=\\s*(?:async\\s+)?function\\b`, 'i'),
|
|
1405
|
+
new RegExp(`\\b(?:const|let|var)\\s+${escaped}\\b\\s*=\\s*(?:async\\s*)?(?:\\([^)]*\\)|[a-z_$][\\w$]*)\\s*=>`, 'i'),
|
|
1406
|
+
];
|
|
1407
|
+
return declarationPatterns.some((pattern) => pattern.test(content));
|
|
1408
|
+
}
|
|
1409
|
+
getLexicalTermFactor(plan, term) {
|
|
1410
|
+
if (term.kind === 'whole') {
|
|
1411
|
+
return 1;
|
|
1412
|
+
}
|
|
1413
|
+
if (plan.referenceSeeking) {
|
|
1414
|
+
return 0.18;
|
|
1415
|
+
}
|
|
1416
|
+
if (plan.intent === 'identifier') {
|
|
1417
|
+
return 0.18;
|
|
1418
|
+
}
|
|
1419
|
+
return 0.35;
|
|
1420
|
+
}
|
|
1421
|
+
scoreCandidateLexicalEvidence(plan, result) {
|
|
1422
|
+
if (plan.lexicalTerms.length === 0) {
|
|
1423
|
+
return { score: 0, exactLexicalMatch: false };
|
|
1424
|
+
}
|
|
1425
|
+
const relativePath = typeof result?.relativePath === 'string' ? result.relativePath.toLowerCase() : '';
|
|
1426
|
+
const symbolLabel = typeof result?.symbolLabel === 'string' ? result.symbolLabel.toLowerCase() : '';
|
|
1427
|
+
const content = typeof result?.content === 'string' ? result.content.toLowerCase() : '';
|
|
1428
|
+
const pathSegments = relativePath.split('/').filter((segment) => segment.length > 0);
|
|
1429
|
+
let score = 0;
|
|
1430
|
+
let exactLexicalMatch = false;
|
|
1431
|
+
for (const term of plan.lexicalTerms) {
|
|
1432
|
+
const usageKind = plan.referenceSeeking ? this.getReferenceUsageKind(content, term.value) : null;
|
|
1433
|
+
const declarationMatch = plan.referenceSeeking && this.hasDeclarationMatch(content, term.value);
|
|
1434
|
+
const termFactor = this.getLexicalTermFactor(plan, term);
|
|
1435
|
+
if (usageKind === 'executable' && !declarationMatch) {
|
|
1436
|
+
score = Math.max(score, 1.60 * termFactor);
|
|
1437
|
+
continue;
|
|
1438
|
+
}
|
|
1439
|
+
if (usageKind === 'import' && !declarationMatch) {
|
|
1440
|
+
score = Math.max(score, 0.75 * termFactor);
|
|
1441
|
+
continue;
|
|
1442
|
+
}
|
|
1443
|
+
if (this.hasTokenBoundaryMatch(symbolLabel, term.value)) {
|
|
1444
|
+
score = Math.max(score, (plan.referenceSeeking ? 0.02 : 1.30) * termFactor);
|
|
1445
|
+
if (!plan.referenceSeeking && term.kind === 'whole') {
|
|
1446
|
+
exactLexicalMatch = true;
|
|
1447
|
+
}
|
|
1448
|
+
continue;
|
|
1449
|
+
}
|
|
1450
|
+
if (pathSegments.some((segment) => this.hasTokenBoundaryMatch(segment, term.value))) {
|
|
1451
|
+
score = Math.max(score, (plan.referenceSeeking ? 0.02 : 1.20) * termFactor);
|
|
1452
|
+
if (!plan.referenceSeeking && term.kind === 'whole') {
|
|
1453
|
+
exactLexicalMatch = true;
|
|
1454
|
+
}
|
|
1455
|
+
continue;
|
|
1456
|
+
}
|
|
1457
|
+
if (this.hasTokenBoundaryMatch(content, term.value)) {
|
|
1458
|
+
score = Math.max(score, (plan.referenceSeeking ? (declarationMatch ? 0.10 : 1.25) : 0.90) * termFactor);
|
|
1459
|
+
continue;
|
|
1460
|
+
}
|
|
1461
|
+
if (symbolLabel.includes(term.value)) {
|
|
1462
|
+
score = Math.max(score, (plan.referenceSeeking ? 0.04 : 0.55) * termFactor);
|
|
1463
|
+
continue;
|
|
1464
|
+
}
|
|
1465
|
+
if (relativePath.includes(term.value)) {
|
|
1466
|
+
score = Math.max(score, (plan.referenceSeeking ? 0.04 : 0.45) * termFactor);
|
|
1467
|
+
continue;
|
|
1468
|
+
}
|
|
1469
|
+
if (content.includes(term.value)) {
|
|
1470
|
+
score = Math.max(score, (plan.referenceSeeking ? (declarationMatch ? 0.08 : 0.30) : 0.25) * termFactor);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
return {
|
|
1474
|
+
score: score * plan.lexicalWeight,
|
|
1475
|
+
exactLexicalMatch,
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1238
1478
|
pathMatchesAnyPattern(relativePath, patterns) {
|
|
1239
1479
|
if (patterns.length === 0)
|
|
1240
1480
|
return false;
|
|
@@ -1261,17 +1501,20 @@ export class ToolHandlers {
|
|
|
1261
1501
|
}
|
|
1262
1502
|
return false;
|
|
1263
1503
|
}
|
|
1264
|
-
resolveRerankDecision(scope) {
|
|
1504
|
+
resolveRerankDecision(scope, plan) {
|
|
1265
1505
|
const capabilityPresent = this.capabilities.hasReranker();
|
|
1266
1506
|
const enabledByPolicy = capabilityPresent && this.capabilities.getDefaultRerankEnabled();
|
|
1267
1507
|
const rerankerPresent = this.reranker !== null;
|
|
1268
1508
|
const skippedByScopeDocs = scope === 'docs';
|
|
1509
|
+
const skippedByIdentifierIntent = !plan.rerankAllowed;
|
|
1269
1510
|
return {
|
|
1270
1511
|
enabledByPolicy,
|
|
1271
1512
|
skippedByScopeDocs,
|
|
1513
|
+
skippedByIdentifierIntent,
|
|
1272
1514
|
capabilityPresent,
|
|
1273
1515
|
rerankerPresent,
|
|
1274
|
-
enabled: enabledByPolicy && rerankerPresent && !skippedByScopeDocs,
|
|
1516
|
+
enabled: enabledByPolicy && rerankerPresent && !skippedByScopeDocs && !skippedByIdentifierIntent,
|
|
1517
|
+
exactMatchPinningEnabled: plan.exactMatchPinningEnabled,
|
|
1275
1518
|
};
|
|
1276
1519
|
}
|
|
1277
1520
|
buildRerankDocument(result) {
|
|
@@ -1519,6 +1762,122 @@ export class ToolHandlers {
|
|
|
1519
1762
|
return -1;
|
|
1520
1763
|
return a.localeCompare(b);
|
|
1521
1764
|
}
|
|
1765
|
+
compareSearchCandidates(a, b, options) {
|
|
1766
|
+
if (options?.mustMatchesFirst === true && a.passesMatchedMust !== b.passesMatchedMust) {
|
|
1767
|
+
return a.passesMatchedMust ? -1 : 1;
|
|
1768
|
+
}
|
|
1769
|
+
if (options?.exactMatchFirst === true && a.exactLexicalMatch !== b.exactLexicalMatch) {
|
|
1770
|
+
return a.exactLexicalMatch ? -1 : 1;
|
|
1771
|
+
}
|
|
1772
|
+
if (b.finalScore !== a.finalScore)
|
|
1773
|
+
return b.finalScore - a.finalScore;
|
|
1774
|
+
const fileCmp = this.compareNullableStringsAsc(a.result.relativePath, b.result.relativePath);
|
|
1775
|
+
if (fileCmp !== 0)
|
|
1776
|
+
return fileCmp;
|
|
1777
|
+
const startCmp = this.compareNullableNumbersAsc(a.result.startLine, b.result.startLine);
|
|
1778
|
+
if (startCmp !== 0)
|
|
1779
|
+
return startCmp;
|
|
1780
|
+
const labelCmp = this.compareNullableStringsAsc(a.result.symbolLabel, b.result.symbolLabel);
|
|
1781
|
+
if (labelCmp !== 0)
|
|
1782
|
+
return labelCmp;
|
|
1783
|
+
return this.compareNullableStringsAsc(a.result.symbolId, b.result.symbolId);
|
|
1784
|
+
}
|
|
1785
|
+
sortSearchCandidates(candidates, exactMatchFirst, mustMatchesFirst = false) {
|
|
1786
|
+
const topWithoutPinning = candidates.length > 0
|
|
1787
|
+
? [...candidates].sort((a, b) => this.compareSearchCandidates(a, b, { mustMatchesFirst }))[0]
|
|
1788
|
+
: undefined;
|
|
1789
|
+
candidates.sort((a, b) => this.compareSearchCandidates(a, b, { exactMatchFirst, mustMatchesFirst }));
|
|
1790
|
+
if (!exactMatchFirst || !topWithoutPinning || candidates.length === 0) {
|
|
1791
|
+
return false;
|
|
1792
|
+
}
|
|
1793
|
+
return topWithoutPinning.exactLexicalMatch !== candidates[0].exactLexicalMatch;
|
|
1794
|
+
}
|
|
1795
|
+
isDeclarationSearchGroup(group) {
|
|
1796
|
+
const label = (group.symbolLabel || '').trim().toLowerCase();
|
|
1797
|
+
if (/^(class|type|interface|enum|struct|function|def)\b/.test(label)) {
|
|
1798
|
+
return true;
|
|
1799
|
+
}
|
|
1800
|
+
if (/^(const|let|var)\s+[a-z0-9_$]+\s*=/.test(label)) {
|
|
1801
|
+
return true;
|
|
1802
|
+
}
|
|
1803
|
+
const previewStart = (group.preview || '').slice(0, 240).toLowerCase();
|
|
1804
|
+
return /\b(class|type|interface|enum|struct|function|def)\s+[a-z0-9_]/i.test(previewStart)
|
|
1805
|
+
|| /\b(?:const|let|var)\s+[a-z0-9_$]+\s*=\s*(?:async\s+)?function\b/i.test(previewStart)
|
|
1806
|
+
|| /\b(?:const|let|var)\s+[a-z0-9_$]+\s*=\s*(?:async\s*)?(?:\([^)]*\)|[a-z_$][\w$]*)\s*=>/i.test(previewStart);
|
|
1807
|
+
}
|
|
1808
|
+
normalizeDeclarationGroupKey(group) {
|
|
1809
|
+
if (!group.file || !group.symbolLabel) {
|
|
1810
|
+
return null;
|
|
1811
|
+
}
|
|
1812
|
+
if (!this.isDeclarationSearchGroup(group)) {
|
|
1813
|
+
return null;
|
|
1814
|
+
}
|
|
1815
|
+
const normalizedLabel = group.symbolLabel
|
|
1816
|
+
.toLowerCase()
|
|
1817
|
+
.replace(/\s+/g, ' ')
|
|
1818
|
+
.trim();
|
|
1819
|
+
return `${group.file}::${normalizedLabel}`;
|
|
1820
|
+
}
|
|
1821
|
+
collapseDuplicateDeclarationGroups(groups) {
|
|
1822
|
+
const deduped = new Map();
|
|
1823
|
+
for (const group of groups) {
|
|
1824
|
+
const key = this.normalizeDeclarationGroupKey(group);
|
|
1825
|
+
if (!key) {
|
|
1826
|
+
deduped.set(`unique:${deduped.size}`, group);
|
|
1827
|
+
continue;
|
|
1828
|
+
}
|
|
1829
|
+
const existing = deduped.get(key);
|
|
1830
|
+
if (!existing) {
|
|
1831
|
+
deduped.set(key, group);
|
|
1832
|
+
continue;
|
|
1833
|
+
}
|
|
1834
|
+
const existingComparable = {
|
|
1835
|
+
result: {
|
|
1836
|
+
relativePath: existing.file,
|
|
1837
|
+
startLine: existing.span.startLine,
|
|
1838
|
+
endLine: existing.span.endLine,
|
|
1839
|
+
symbolId: existing.symbolId || undefined,
|
|
1840
|
+
symbolLabel: existing.symbolLabel || undefined,
|
|
1841
|
+
},
|
|
1842
|
+
baseScore: 0,
|
|
1843
|
+
backendScore: 0,
|
|
1844
|
+
backendScoreKind: 'unknown',
|
|
1845
|
+
fusionScore: 0,
|
|
1846
|
+
lexicalScore: existing.debug?.lexicalScore || 0,
|
|
1847
|
+
finalScore: existing.score,
|
|
1848
|
+
pathCategory: existing.debug?.pathCategory || 'neutral',
|
|
1849
|
+
pathMultiplier: existing.debug?.pathMultiplier || 1,
|
|
1850
|
+
changedFilesMultiplier: existing.debug?.changedFilesMultiplier || 1,
|
|
1851
|
+
passesMatchedMust: existing.debug?.matchesMust === true,
|
|
1852
|
+
exactLexicalMatch: existing.__exactLexicalMatch === true,
|
|
1853
|
+
};
|
|
1854
|
+
const nextComparable = {
|
|
1855
|
+
result: {
|
|
1856
|
+
relativePath: group.file,
|
|
1857
|
+
startLine: group.span.startLine,
|
|
1858
|
+
endLine: group.span.endLine,
|
|
1859
|
+
symbolId: group.symbolId || undefined,
|
|
1860
|
+
symbolLabel: group.symbolLabel || undefined,
|
|
1861
|
+
},
|
|
1862
|
+
baseScore: 0,
|
|
1863
|
+
backendScore: 0,
|
|
1864
|
+
backendScoreKind: 'unknown',
|
|
1865
|
+
fusionScore: 0,
|
|
1866
|
+
lexicalScore: group.debug?.lexicalScore || 0,
|
|
1867
|
+
finalScore: group.score,
|
|
1868
|
+
pathCategory: group.debug?.pathCategory || 'neutral',
|
|
1869
|
+
pathMultiplier: group.debug?.pathMultiplier || 1,
|
|
1870
|
+
changedFilesMultiplier: group.debug?.changedFilesMultiplier || 1,
|
|
1871
|
+
passesMatchedMust: group.debug?.matchesMust === true,
|
|
1872
|
+
exactLexicalMatch: group.__exactLexicalMatch === true,
|
|
1873
|
+
};
|
|
1874
|
+
if (this.compareSearchCandidates(nextComparable, existingComparable) < 0) {
|
|
1875
|
+
deduped.set(key, group);
|
|
1876
|
+
continue;
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
return Array.from(deduped.values());
|
|
1880
|
+
}
|
|
1522
1881
|
buildFallbackGroupId(relativePath, span) {
|
|
1523
1882
|
const payload = `${relativePath}:${span.startLine}-${span.endLine}`;
|
|
1524
1883
|
const digest = crypto.createHash('sha1').update(payload, 'utf8').digest('hex').slice(0, 16);
|
|
@@ -2600,6 +2959,7 @@ To force rebuild from scratch: call manage_index with {"action":"create","path":
|
|
|
2600
2959
|
console.log(`${rootTag} 🧠 Using embedding provider: ${encoderEngine.getProvider()} for search`);
|
|
2601
2960
|
const parsedOperators = this.parseSearchOperators(input.query);
|
|
2602
2961
|
const semanticQuery = parsedOperators.semanticQuery;
|
|
2962
|
+
const queryPlan = this.buildSearchQueryPlan(semanticQuery);
|
|
2603
2963
|
const expandedQuery = `${semanticQuery}\nimplementation runtime source entrypoint`;
|
|
2604
2964
|
const maxAttempts = parsedOperators.must.length > 0 ? 1 + SEARCH_MUST_RETRY_ROUNDS : 1;
|
|
2605
2965
|
let candidateLimit = Math.max(1, Math.min(SEARCH_MAX_CANDIDATES, Math.max(input.limit * 8, 32)));
|
|
@@ -2622,7 +2982,9 @@ To force rebuild from scratch: call manage_index with {"action":"create","path":
|
|
|
2622
2982
|
let attemptsUsed = 0;
|
|
2623
2983
|
const searchWarningsSet = new Set();
|
|
2624
2984
|
const passesUsed = new Set();
|
|
2985
|
+
const backendScoreKinds = new Set();
|
|
2625
2986
|
let scored = [];
|
|
2987
|
+
let exactMatchPinningApplied = false;
|
|
2626
2988
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
2627
2989
|
attemptsUsed = attempt + 1;
|
|
2628
2990
|
const passDescriptors = [
|
|
@@ -2635,7 +2997,16 @@ To force rebuild from scratch: call manage_index with {"action":"create","path":
|
|
|
2635
2997
|
if (this.shouldForceSearchPassFailure(passId)) {
|
|
2636
2998
|
throw new Error(`FORCED_TEST_SEARCH_PASS_FAILURE:${passId}`);
|
|
2637
2999
|
}
|
|
2638
|
-
|
|
3000
|
+
const scorePolicy = queryPlan.scorePolicyKind === 'topk_only'
|
|
3001
|
+
? { kind: 'topk_only' }
|
|
3002
|
+
: { kind: 'dense_similarity_min', min: 0.3 };
|
|
3003
|
+
return this.context.semanticSearch({
|
|
3004
|
+
codebasePath: effectiveRoot,
|
|
3005
|
+
query: pass.query,
|
|
3006
|
+
topK: candidateLimit,
|
|
3007
|
+
retrievalMode: queryPlan.retrievalMode,
|
|
3008
|
+
scorePolicy
|
|
3009
|
+
});
|
|
2639
3010
|
}));
|
|
2640
3011
|
const successfulPasses = [];
|
|
2641
3012
|
for (let idx = 0; idx < passSettled.length; idx++) {
|
|
@@ -2682,21 +3053,40 @@ To force rebuild from scratch: call manage_index with {"action":"create","path":
|
|
|
2682
3053
|
const rrf = passWeight * (1 / (SEARCH_RRF_K + rank));
|
|
2683
3054
|
const existing = byChunkKey.get(key);
|
|
2684
3055
|
if (!existing) {
|
|
3056
|
+
const backendScoreKind = typeof result.backendScoreKind === 'string'
|
|
3057
|
+
? result.backendScoreKind
|
|
3058
|
+
: 'unknown';
|
|
3059
|
+
backendScoreKinds.add(backendScoreKind);
|
|
2685
3060
|
byChunkKey.set(key, {
|
|
2686
3061
|
result,
|
|
2687
|
-
baseScore: typeof result.
|
|
3062
|
+
baseScore: typeof result.backendScore === 'number'
|
|
3063
|
+
? result.backendScore
|
|
3064
|
+
: (typeof result.score === 'number' ? result.score : 0),
|
|
3065
|
+
backendScore: typeof result.backendScore === 'number'
|
|
3066
|
+
? result.backendScore
|
|
3067
|
+
: (typeof result.score === 'number' ? result.score : 0),
|
|
3068
|
+
backendScoreKind,
|
|
2688
3069
|
fusionScore: rrf,
|
|
3070
|
+
lexicalScore: 0,
|
|
2689
3071
|
finalScore: 0,
|
|
2690
3072
|
pathCategory: 'neutral',
|
|
2691
3073
|
pathMultiplier: 1.0,
|
|
2692
3074
|
changedFilesMultiplier: 1.0,
|
|
2693
3075
|
passesMatchedMust: false,
|
|
3076
|
+
exactLexicalMatch: false,
|
|
2694
3077
|
});
|
|
2695
3078
|
}
|
|
2696
3079
|
else {
|
|
2697
3080
|
existing.fusionScore += rrf;
|
|
2698
|
-
|
|
2699
|
-
|
|
3081
|
+
const nextScore = typeof result.backendScore === 'number'
|
|
3082
|
+
? result.backendScore
|
|
3083
|
+
: (typeof result.score === 'number' ? result.score : undefined);
|
|
3084
|
+
if (typeof nextScore === 'number') {
|
|
3085
|
+
existing.baseScore = Math.max(existing.baseScore, nextScore);
|
|
3086
|
+
existing.backendScore = Math.max(existing.backendScore, nextScore);
|
|
3087
|
+
}
|
|
3088
|
+
if (typeof result.backendScoreKind === 'string') {
|
|
3089
|
+
backendScoreKinds.add(result.backendScoreKind);
|
|
2700
3090
|
}
|
|
2701
3091
|
}
|
|
2702
3092
|
}
|
|
@@ -2751,34 +3141,24 @@ To force rebuild from scratch: call manage_index with {"action":"create","path":
|
|
|
2751
3141
|
candidate.pathMultiplier = pathMultiplier;
|
|
2752
3142
|
candidate.changedFilesMultiplier = changedFilesMultiplier;
|
|
2753
3143
|
candidate.passesMatchedMust = matchesMust;
|
|
2754
|
-
|
|
3144
|
+
const lexicalEvidence = this.scoreCandidateLexicalEvidence(queryPlan, candidate.result);
|
|
3145
|
+
candidate.lexicalScore = lexicalEvidence.score;
|
|
3146
|
+
candidate.exactLexicalMatch = lexicalEvidence.exactLexicalMatch;
|
|
3147
|
+
candidate.finalScore = (candidate.fusionScore + candidate.lexicalScore) * pathMultiplier * changedFilesMultiplier;
|
|
2755
3148
|
scoredAttempt.push(candidate);
|
|
2756
3149
|
}
|
|
2757
3150
|
searchDiagnostics.resultsBeforeFilter = beforeFilter;
|
|
2758
3151
|
searchDiagnostics.resultsAfterFilter = scoredAttempt.length;
|
|
2759
3152
|
filterSummary = attemptFilterSummary;
|
|
2760
3153
|
scored = scoredAttempt;
|
|
2761
|
-
|
|
2762
|
-
if (b.finalScore !== a.finalScore)
|
|
2763
|
-
return b.finalScore - a.finalScore;
|
|
2764
|
-
const fileCmp = this.compareNullableStringsAsc(a.result.relativePath, b.result.relativePath);
|
|
2765
|
-
if (fileCmp !== 0)
|
|
2766
|
-
return fileCmp;
|
|
2767
|
-
const startCmp = this.compareNullableNumbersAsc(a.result.startLine, b.result.startLine);
|
|
2768
|
-
if (startCmp !== 0)
|
|
2769
|
-
return startCmp;
|
|
2770
|
-
const labelCmp = this.compareNullableStringsAsc(a.result.symbolLabel, b.result.symbolLabel);
|
|
2771
|
-
if (labelCmp !== 0)
|
|
2772
|
-
return labelCmp;
|
|
2773
|
-
return this.compareNullableStringsAsc(a.result.symbolId, b.result.symbolId);
|
|
2774
|
-
});
|
|
3154
|
+
exactMatchPinningApplied = this.sortSearchCandidates(scored, queryPlan.exactMatchPinningEnabled, parsedOperators.must.length > 0) || exactMatchPinningApplied;
|
|
2775
3155
|
if (parsedOperators.must.length === 0 || scored.length >= input.limit || attempt === maxAttempts - 1 || candidateLimit >= SEARCH_MAX_CANDIDATES) {
|
|
2776
3156
|
break;
|
|
2777
3157
|
}
|
|
2778
3158
|
candidateLimit = Math.min(SEARCH_MAX_CANDIDATES, Math.max(candidateLimit + 1, candidateLimit * SEARCH_MUST_RETRY_MULTIPLIER));
|
|
2779
3159
|
}
|
|
2780
3160
|
const searchWarnings = Array.from(searchWarningsSet);
|
|
2781
|
-
const rerankDecision = this.resolveRerankDecision(input.scope);
|
|
3161
|
+
const rerankDecision = this.resolveRerankDecision(input.scope, queryPlan);
|
|
2782
3162
|
let rerankerApplied = false;
|
|
2783
3163
|
let rerankerAttempted = false;
|
|
2784
3164
|
let rerankerFailurePhase;
|
|
@@ -2823,22 +3203,9 @@ To force rebuild from scratch: call manage_index with {"action":"create","path":
|
|
|
2823
3203
|
}
|
|
2824
3204
|
const rerankRrf = 1 / (SEARCH_RERANK_RRF_K + rank);
|
|
2825
3205
|
rerankSlice[idx].fusionScore += SEARCH_RERANK_WEIGHT * rerankRrf;
|
|
2826
|
-
rerankSlice[idx].finalScore = rerankSlice[idx].fusionScore * rerankSlice[idx].pathMultiplier * rerankSlice[idx].changedFilesMultiplier;
|
|
3206
|
+
rerankSlice[idx].finalScore = (rerankSlice[idx].fusionScore + rerankSlice[idx].lexicalScore) * rerankSlice[idx].pathMultiplier * rerankSlice[idx].changedFilesMultiplier;
|
|
2827
3207
|
}
|
|
2828
|
-
|
|
2829
|
-
if (b.finalScore !== a.finalScore)
|
|
2830
|
-
return b.finalScore - a.finalScore;
|
|
2831
|
-
const fileCmp = this.compareNullableStringsAsc(a.result.relativePath, b.result.relativePath);
|
|
2832
|
-
if (fileCmp !== 0)
|
|
2833
|
-
return fileCmp;
|
|
2834
|
-
const startCmp = this.compareNullableNumbersAsc(a.result.startLine, b.result.startLine);
|
|
2835
|
-
if (startCmp !== 0)
|
|
2836
|
-
return startCmp;
|
|
2837
|
-
const labelCmp = this.compareNullableStringsAsc(a.result.symbolLabel, b.result.symbolLabel);
|
|
2838
|
-
if (labelCmp !== 0)
|
|
2839
|
-
return labelCmp;
|
|
2840
|
-
return this.compareNullableStringsAsc(a.result.symbolId, b.result.symbolId);
|
|
2841
|
-
});
|
|
3208
|
+
exactMatchPinningApplied = this.sortSearchCandidates(scored, rerankDecision.exactMatchPinningEnabled, parsedOperators.must.length > 0) || exactMatchPinningApplied;
|
|
2842
3209
|
rerankerApplied = true;
|
|
2843
3210
|
}
|
|
2844
3211
|
catch {
|
|
@@ -2859,6 +3226,18 @@ To force rebuild from scratch: call manage_index with {"action":"create","path":
|
|
|
2859
3226
|
const finalizedSearchWarnings = Array.from(new Set(searchWarnings)).sort();
|
|
2860
3227
|
const debugHintBase = input.debug
|
|
2861
3228
|
? {
|
|
3229
|
+
queryIntent: {
|
|
3230
|
+
classification: queryPlan.intent,
|
|
3231
|
+
confidence: queryPlan.confidence,
|
|
3232
|
+
reasons: [...queryPlan.reasons],
|
|
3233
|
+
lexicalTerms: queryPlan.lexicalTerms.map((term) => term.value),
|
|
3234
|
+
semanticQuery,
|
|
3235
|
+
},
|
|
3236
|
+
retrieval: {
|
|
3237
|
+
mode: queryPlan.retrievalMode,
|
|
3238
|
+
scorePolicyKind: queryPlan.scorePolicyKind,
|
|
3239
|
+
backendScoreKinds: Array.from(backendScoreKinds).sort(),
|
|
3240
|
+
},
|
|
2862
3241
|
passesUsed: Array.from(passesUsed).sort(),
|
|
2863
3242
|
candidateLimit,
|
|
2864
3243
|
mustRetry: {
|
|
@@ -2883,11 +3262,14 @@ To force rebuild from scratch: call manage_index with {"action":"create","path":
|
|
|
2883
3262
|
rerank: {
|
|
2884
3263
|
enabledByPolicy: rerankDecision.enabledByPolicy,
|
|
2885
3264
|
skippedByScopeDocs: rerankDecision.skippedByScopeDocs,
|
|
3265
|
+
skippedByIdentifierIntent: rerankDecision.skippedByIdentifierIntent,
|
|
2886
3266
|
capabilityPresent: rerankDecision.capabilityPresent,
|
|
2887
3267
|
rerankerPresent: rerankDecision.rerankerPresent,
|
|
2888
3268
|
enabled: rerankDecision.enabled,
|
|
2889
3269
|
attempted: rerankerAttempted,
|
|
2890
3270
|
applied: rerankerApplied,
|
|
3271
|
+
exactMatchPinningEnabled: rerankDecision.exactMatchPinningEnabled,
|
|
3272
|
+
exactMatchPinningApplied,
|
|
2891
3273
|
candidatesIn: rerankerCandidatesIn,
|
|
2892
3274
|
candidatesReranked: rerankerCandidatesReranked,
|
|
2893
3275
|
topK: SEARCH_RERANK_TOP_K,
|
|
@@ -2918,10 +3300,14 @@ To force rebuild from scratch: call manage_index with {"action":"create","path":
|
|
|
2918
3300
|
debug: {
|
|
2919
3301
|
baseScore: candidate.baseScore,
|
|
2920
3302
|
fusionScore: candidate.fusionScore,
|
|
3303
|
+
lexicalScore: candidate.lexicalScore,
|
|
2921
3304
|
pathMultiplier: candidate.pathMultiplier,
|
|
2922
3305
|
pathCategory: candidate.pathCategory,
|
|
2923
3306
|
changedFilesMultiplier: candidate.changedFilesMultiplier,
|
|
2924
|
-
matchesMust: candidate.passesMatchedMust
|
|
3307
|
+
matchesMust: candidate.passesMatchedMust,
|
|
3308
|
+
exactLexicalMatch: candidate.exactLexicalMatch,
|
|
3309
|
+
backendScore: candidate.backendScore,
|
|
3310
|
+
backendScoreKind: candidate.backendScoreKind,
|
|
2925
3311
|
}
|
|
2926
3312
|
} : {})
|
|
2927
3313
|
}));
|
|
@@ -2986,23 +3372,7 @@ To force rebuild from scratch: call manage_index with {"action":"create","path":
|
|
|
2986
3372
|
const sidecarReadyForOutline = Boolean(sidecarInfo && sidecarInfo.version === 'v3');
|
|
2987
3373
|
const groupedResults = [];
|
|
2988
3374
|
for (const group of groups.values()) {
|
|
2989
|
-
group.chunks.
|
|
2990
|
-
if (a.passesMatchedMust !== b.passesMatchedMust) {
|
|
2991
|
-
return a.passesMatchedMust ? -1 : 1;
|
|
2992
|
-
}
|
|
2993
|
-
if (b.finalScore !== a.finalScore)
|
|
2994
|
-
return b.finalScore - a.finalScore;
|
|
2995
|
-
const fileCmp = this.compareNullableStringsAsc(a.result.relativePath, b.result.relativePath);
|
|
2996
|
-
if (fileCmp !== 0)
|
|
2997
|
-
return fileCmp;
|
|
2998
|
-
const startCmp = this.compareNullableNumbersAsc(a.result.startLine, b.result.startLine);
|
|
2999
|
-
if (startCmp !== 0)
|
|
3000
|
-
return startCmp;
|
|
3001
|
-
const labelCmp = this.compareNullableStringsAsc(a.result.symbolLabel, b.result.symbolLabel);
|
|
3002
|
-
if (labelCmp !== 0)
|
|
3003
|
-
return labelCmp;
|
|
3004
|
-
return this.compareNullableStringsAsc(a.result.symbolId, b.result.symbolId);
|
|
3005
|
-
});
|
|
3375
|
+
exactMatchPinningApplied = this.sortSearchCandidates(group.chunks, queryPlan.exactMatchPinningEnabled, parsedOperators.must.length > 0) || exactMatchPinningApplied;
|
|
3006
3376
|
const representative = group.chunks[0];
|
|
3007
3377
|
const spanStart = Math.min(...group.chunks.map((c) => c.result.startLine || 0));
|
|
3008
3378
|
const spanEnd = Math.max(...group.chunks.map((c) => c.result.endLine || 0));
|
|
@@ -3037,19 +3407,28 @@ To force rebuild from scratch: call manage_index with {"action":"create","path":
|
|
|
3037
3407
|
callGraphHint,
|
|
3038
3408
|
...(navigationFallback ? { navigationFallback } : {}),
|
|
3039
3409
|
preview: truncateContent(String(representative.result.content || ''), 4000),
|
|
3410
|
+
__exactLexicalMatch: representative.exactLexicalMatch,
|
|
3040
3411
|
...(input.debug ? {
|
|
3041
3412
|
debug: {
|
|
3042
3413
|
representativeChunkCount: group.chunks.length,
|
|
3043
3414
|
pathCategory: representative.pathCategory,
|
|
3044
3415
|
pathMultiplier: representative.pathMultiplier,
|
|
3045
3416
|
topChunkScore: representative.finalScore,
|
|
3417
|
+
lexicalScore: representative.lexicalScore,
|
|
3046
3418
|
changedFilesMultiplier: representative.changedFilesMultiplier,
|
|
3047
|
-
matchesMust: representative.passesMatchedMust
|
|
3419
|
+
matchesMust: representative.passesMatchedMust,
|
|
3420
|
+
exactLexicalMatch: representative.exactLexicalMatch,
|
|
3048
3421
|
}
|
|
3049
3422
|
} : {})
|
|
3050
3423
|
});
|
|
3051
3424
|
}
|
|
3052
|
-
|
|
3425
|
+
const rankedGroupedResults = (queryPlan.referenceSeeking || queryPlan.intent === 'identifier')
|
|
3426
|
+
? this.collapseDuplicateDeclarationGroups(groupedResults)
|
|
3427
|
+
: groupedResults;
|
|
3428
|
+
rankedGroupedResults.sort((a, b) => {
|
|
3429
|
+
if (queryPlan.exactMatchPinningEnabled && a.__exactLexicalMatch !== b.__exactLexicalMatch) {
|
|
3430
|
+
return a.__exactLexicalMatch ? -1 : 1;
|
|
3431
|
+
}
|
|
3053
3432
|
if (b.score !== a.score)
|
|
3054
3433
|
return b.score - a.score;
|
|
3055
3434
|
const fileCmp = a.file.localeCompare(b.file);
|
|
@@ -3063,7 +3442,7 @@ To force rebuild from scratch: call manage_index with {"action":"create","path":
|
|
|
3063
3442
|
return labelCmp;
|
|
3064
3443
|
return this.compareNullableStringsAsc(a.symbolId, b.symbolId);
|
|
3065
3444
|
});
|
|
3066
|
-
const diversityApplied = this.applyGroupDiversity(
|
|
3445
|
+
const diversityApplied = this.applyGroupDiversity(rankedGroupedResults, input.limit, input.groupBy);
|
|
3067
3446
|
const visibleGroupedResults = diversityApplied.selected;
|
|
3068
3447
|
const noiseMitigationHint = this.buildNoiseMitigationHint(effectiveRoot, visibleGroupedResults.map((result) => result.file));
|
|
3069
3448
|
const responseHints = {};
|
|
@@ -3093,7 +3472,7 @@ To force rebuild from scratch: call manage_index with {"action":"create","path":
|
|
|
3093
3472
|
freshnessDecision,
|
|
3094
3473
|
...(finalizedSearchWarnings.length > 0 ? { warnings: finalizedSearchWarnings } : {}),
|
|
3095
3474
|
...(Object.keys(responseHints).length > 0 ? { hints: responseHints } : {}),
|
|
3096
|
-
results: visibleGroupedResults
|
|
3475
|
+
results: visibleGroupedResults.map(({ __exactLexicalMatch: _exactLexicalMatch, ...result }) => result)
|
|
3097
3476
|
};
|
|
3098
3477
|
await this.touchWatchedCodebase(effectiveRoot);
|
|
3099
3478
|
return {
|
|
@@ -33,10 +33,14 @@ export interface SearchChunkResult {
|
|
|
33
33
|
debug?: {
|
|
34
34
|
baseScore: number;
|
|
35
35
|
fusionScore: number;
|
|
36
|
+
lexicalScore: number;
|
|
36
37
|
pathMultiplier: number;
|
|
37
38
|
pathCategory: string;
|
|
38
39
|
changedFilesMultiplier?: number;
|
|
39
40
|
matchesMust?: boolean;
|
|
41
|
+
exactLexicalMatch: boolean;
|
|
42
|
+
backendScore?: number;
|
|
43
|
+
backendScoreKind?: "dense_similarity" | "lexical_rank" | "rrf_fusion" | "unknown";
|
|
40
44
|
};
|
|
41
45
|
}
|
|
42
46
|
export interface SearchGroupResult {
|
|
@@ -59,8 +63,10 @@ export interface SearchGroupResult {
|
|
|
59
63
|
pathCategory: string;
|
|
60
64
|
pathMultiplier: number;
|
|
61
65
|
topChunkScore: number;
|
|
66
|
+
lexicalScore: number;
|
|
62
67
|
changedFilesMultiplier?: number;
|
|
63
68
|
matchesMust?: boolean;
|
|
69
|
+
exactLexicalMatch: boolean;
|
|
64
70
|
};
|
|
65
71
|
}
|
|
66
72
|
export interface SearchNavigationFallbackContext {
|
|
@@ -117,6 +123,18 @@ export interface SearchOperatorSummary {
|
|
|
117
123
|
exclude: string[];
|
|
118
124
|
}
|
|
119
125
|
export interface SearchDebugHint {
|
|
126
|
+
queryIntent: {
|
|
127
|
+
classification: "identifier" | "semantic" | "mixed" | "uncertain";
|
|
128
|
+
confidence: "high" | "medium" | "low";
|
|
129
|
+
reasons: string[];
|
|
130
|
+
lexicalTerms: string[];
|
|
131
|
+
semanticQuery: string;
|
|
132
|
+
};
|
|
133
|
+
retrieval: {
|
|
134
|
+
mode: "dense" | "lexical" | "hybrid";
|
|
135
|
+
scorePolicyKind: "dense_similarity_min" | "topk_only";
|
|
136
|
+
backendScoreKinds: Array<"dense_similarity" | "lexical_rank" | "rrf_fusion" | "unknown">;
|
|
137
|
+
};
|
|
120
138
|
passesUsed: string[];
|
|
121
139
|
candidateLimit: number;
|
|
122
140
|
mustRetry: {
|
|
@@ -156,11 +174,14 @@ export interface SearchDebugHint {
|
|
|
156
174
|
rerank?: {
|
|
157
175
|
enabledByPolicy: boolean;
|
|
158
176
|
skippedByScopeDocs: boolean;
|
|
177
|
+
skippedByIdentifierIntent: boolean;
|
|
159
178
|
capabilityPresent: boolean;
|
|
160
179
|
rerankerPresent: boolean;
|
|
161
180
|
enabled: boolean;
|
|
162
181
|
attempted: boolean;
|
|
163
182
|
applied: boolean;
|
|
183
|
+
exactMatchPinningEnabled: boolean;
|
|
184
|
+
exactMatchPinningApplied: boolean;
|
|
164
185
|
candidatesIn: number;
|
|
165
186
|
candidatesReranked: number;
|
|
166
187
|
errorCode?: "RERANKER_FAILED";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zokizuan/satori-mcp",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.6.0",
|
|
4
4
|
"description": "MCP server for Satori with agent-safe semantic search and indexing",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"ignore": "^7.0.5",
|
|
15
15
|
"zod": "^3.25.55",
|
|
16
16
|
"zod-to-json-schema": "^3.25.1",
|
|
17
|
-
"@zokizuan/satori-core": "1.
|
|
17
|
+
"@zokizuan/satori-core": "1.3.0"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@types/node": "^20.0.0",
|