@neurcode-ai/cli 0.9.23 → 0.9.25
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/README.md +48 -0
- package/dist/commands/ask.d.ts.map +1 -1
- package/dist/commands/ask.js +875 -60
- package/dist/commands/ask.js.map +1 -1
- package/package.json +1 -1
package/dist/commands/ask.js
CHANGED
|
@@ -25,7 +25,12 @@ catch {
|
|
|
25
25
|
white: (str) => str,
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
const MAX_SCAN_FILES =
|
|
28
|
+
const MAX_SCAN_FILES = (() => {
|
|
29
|
+
const raw = Number(process.env.NEURCODE_ASK_MAX_SCAN_FILES || '900');
|
|
30
|
+
if (!Number.isFinite(raw))
|
|
31
|
+
return 900;
|
|
32
|
+
return Math.max(120, Math.min(Math.trunc(raw), 2000));
|
|
33
|
+
})();
|
|
29
34
|
const MAX_FILE_BYTES = 512 * 1024;
|
|
30
35
|
const MAX_RAW_CITATIONS = 120;
|
|
31
36
|
const PRIMARY_SOURCE_EXTENSIONS = new Set([
|
|
@@ -45,9 +50,29 @@ const STOP_WORDS = new Set([
|
|
|
45
50
|
const LOW_SIGNAL_TERMS = new Set([
|
|
46
51
|
'used', 'use', 'using', 'mentioned', 'mention', 'where', 'tell', 'read', 'check', 'find', 'search',
|
|
47
52
|
'workflow', 'repo', 'repository', 'codebase', 'anywhere', 'can', 'type', 'types', 'list', 'show', 'like',
|
|
48
|
-
'neurcode', 'cli', 'file', 'files', 'path', 'filepath', 'header', 'added', 'add', 'request', 'requests',
|
|
53
|
+
'neurcode', 'cli', 'ask', 'file', 'files', 'path', 'filepath', 'header', 'added', 'add', 'request', 'requests',
|
|
54
|
+
'flag', 'flags', 'option', 'options',
|
|
55
|
+
'defined', 'define', 'implemented', 'implement', 'called', 'call', 'computed', 'compute', 'resolved', 'resolve',
|
|
56
|
+
'lookup', 'lookups', 'decide', 'decides',
|
|
49
57
|
]);
|
|
50
58
|
const GENERIC_OUTPUT_TERMS = new Set(['file', 'files', 'path', 'filepath']);
|
|
59
|
+
const REPO_SCOPE_TERMS = [
|
|
60
|
+
'repo', 'repository', 'codebase', 'neurcode', 'cli', 'command', 'commands', 'ship', 'plan', 'ask', 'verify',
|
|
61
|
+
'apply', 'watch', 'config', 'login', 'logout', 'whoami', 'doctor', 'module', 'file', 'files', 'path',
|
|
62
|
+
'middleware', 'api', 'service', 'services', 'branch', 'commit', 'cache', 'brain', 'org', 'organization',
|
|
63
|
+
'tenant', 'tenancy', 'x-org-id', 'readme', 'docs',
|
|
64
|
+
];
|
|
65
|
+
const EXTERNAL_WORLD_TERMS = [
|
|
66
|
+
'capital', 'exchange rate', 'stock price', 'weather', 'temperature', 'forecast', 'news', 'election',
|
|
67
|
+
'president', 'prime minister', 'population', 'gdp', 'sports score', 'match result', 'bitcoin price',
|
|
68
|
+
'usd', 'inr', 'eur', 'jpy', 'currency',
|
|
69
|
+
];
|
|
70
|
+
const REALTIME_WORLD_TERMS = ['right now', 'currently', 'today', 'tomorrow', 'yesterday', 'live', 'real time'];
|
|
71
|
+
const CLI_COMMAND_NAMES = new Set([
|
|
72
|
+
'check', 'refactor', 'security', 'brain', 'login', 'logout', 'init', 'doctor',
|
|
73
|
+
'whoami', 'config', 'map', 'ask', 'plan', 'ship', 'apply', 'allow', 'watch',
|
|
74
|
+
'session', 'verify', 'prompt', 'revert',
|
|
75
|
+
]);
|
|
51
76
|
function scanFiles(dir, maxFiles = MAX_SCAN_FILES) {
|
|
52
77
|
const files = [];
|
|
53
78
|
const ignoreDirs = new Set([
|
|
@@ -111,12 +136,19 @@ function escapeRegExp(input) {
|
|
|
111
136
|
return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
112
137
|
}
|
|
113
138
|
function normalizeTerm(raw) {
|
|
114
|
-
|
|
139
|
+
const normalized = raw
|
|
115
140
|
.toLowerCase()
|
|
116
141
|
.replace(/['"`]/g, '')
|
|
117
142
|
.replace(/[^a-z0-9_\-\s]/g, ' ')
|
|
118
143
|
.replace(/\s+/g, ' ')
|
|
119
144
|
.trim();
|
|
145
|
+
if (!normalized)
|
|
146
|
+
return '';
|
|
147
|
+
return normalized
|
|
148
|
+
.split(' ')
|
|
149
|
+
.map((token) => token.replace(/^[-_]+|[-_]+$/g, ''))
|
|
150
|
+
.filter(Boolean)
|
|
151
|
+
.join(' ');
|
|
120
152
|
}
|
|
121
153
|
function extractQuotedTerms(question) {
|
|
122
154
|
const seen = new Set();
|
|
@@ -163,6 +195,25 @@ function extractComparisonTerms(question) {
|
|
|
163
195
|
return identities.slice(0, 2);
|
|
164
196
|
return [];
|
|
165
197
|
}
|
|
198
|
+
function extractPhraseTerms(question, maxTerms = 6) {
|
|
199
|
+
const tokens = (0, plan_cache_1.normalizeIntent)(question)
|
|
200
|
+
.replace(/[^a-z0-9_\-\s]/g, ' ')
|
|
201
|
+
.split(/\s+/)
|
|
202
|
+
.map((token) => token.trim())
|
|
203
|
+
.filter((token) => token.length >= 3 && !STOP_WORDS.has(token) && !LOW_SIGNAL_TERMS.has(token));
|
|
204
|
+
const phrases = [];
|
|
205
|
+
const seen = new Set();
|
|
206
|
+
for (let i = 0; i < tokens.length - 1; i++) {
|
|
207
|
+
const phrase = `${tokens[i]} ${tokens[i + 1]}`;
|
|
208
|
+
if (seen.has(phrase))
|
|
209
|
+
continue;
|
|
210
|
+
seen.add(phrase);
|
|
211
|
+
phrases.push(phrase);
|
|
212
|
+
if (phrases.length >= maxTerms)
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
return phrases;
|
|
216
|
+
}
|
|
166
217
|
function buildTermMatchers(term, weight) {
|
|
167
218
|
const normalized = normalizeTerm(term);
|
|
168
219
|
if (!normalized)
|
|
@@ -179,6 +230,10 @@ function buildTermMatchers(term, weight) {
|
|
|
179
230
|
if (tokens.length === 1) {
|
|
180
231
|
const token = escapeRegExp(tokens[0]);
|
|
181
232
|
push(normalized, `\\b${token}\\b`);
|
|
233
|
+
if (/^[a-z0-9_]+$/.test(tokens[0]) && tokens[0].length >= 5) {
|
|
234
|
+
push(normalized, `\\b${token}[a-z0-9_]*\\b`);
|
|
235
|
+
push(normalized, `\\b[a-z0-9_]*${token}[a-z0-9_]*\\b`);
|
|
236
|
+
}
|
|
182
237
|
return out;
|
|
183
238
|
}
|
|
184
239
|
const tokenChain = tokens.map((t) => escapeRegExp(t)).join('[\\s_-]*');
|
|
@@ -213,6 +268,20 @@ function expandSearchTerms(terms) {
|
|
|
213
268
|
if (term.endsWith('ed') && term.length > 4) {
|
|
214
269
|
expanded.add(term.slice(0, -2));
|
|
215
270
|
}
|
|
271
|
+
if (term.includes('-')) {
|
|
272
|
+
expanded.add(term.replace(/-/g, ' '));
|
|
273
|
+
expanded.add(term.replace(/-/g, ''));
|
|
274
|
+
}
|
|
275
|
+
if (term.endsWith('cache')) {
|
|
276
|
+
expanded.add(`${term}d`);
|
|
277
|
+
}
|
|
278
|
+
if (term.endsWith('cached')) {
|
|
279
|
+
expanded.add(term.slice(0, -1)); // cached -> cache
|
|
280
|
+
}
|
|
281
|
+
if (term.includes('cache') && term.includes('-')) {
|
|
282
|
+
expanded.add(term.replace(/cache\b/, 'cached'));
|
|
283
|
+
expanded.add(term.replace(/cached\b/, 'cache'));
|
|
284
|
+
}
|
|
216
285
|
}
|
|
217
286
|
return [...expanded];
|
|
218
287
|
}
|
|
@@ -231,9 +300,10 @@ function buildMatchers(question) {
|
|
|
231
300
|
}
|
|
232
301
|
const quoted = extractQuotedTerms(question);
|
|
233
302
|
const identityTerms = extractIdentityTerms(question);
|
|
303
|
+
const phraseTerms = extractPhraseTerms(question);
|
|
234
304
|
const keywords = tokenizeQuestion(question).slice(0, 8);
|
|
235
305
|
const quotedSet = new Set(quoted.map((term) => normalizeTerm(term)));
|
|
236
|
-
const baseTerms = [...new Set([...quoted, ...identityTerms, ...keywords].map(normalizeTerm).filter(Boolean))];
|
|
306
|
+
const baseTerms = [...new Set([...quoted, ...phraseTerms, ...identityTerms, ...keywords].map(normalizeTerm).filter(Boolean))];
|
|
237
307
|
const filteredTerms = baseTerms.filter((term) => quotedSet.has(term) || !LOW_SIGNAL_TERMS.has(term));
|
|
238
308
|
const terms = expandSearchTerms(filteredTerms.length > 0 ? filteredTerms : baseTerms).filter(Boolean);
|
|
239
309
|
const matchers = terms.flatMap((term) => buildTermMatchers(term, quoted.includes(term) ? 0.9 : 0.55));
|
|
@@ -284,6 +354,149 @@ function derivePathHints(question) {
|
|
|
284
354
|
}
|
|
285
355
|
return [...new Set(hints)];
|
|
286
356
|
}
|
|
357
|
+
function extractCodeLikeIdentifiers(question) {
|
|
358
|
+
const quoted = extractQuotedTerms(question);
|
|
359
|
+
const tokens = question.match(/[A-Za-z_][A-Za-z0-9_]{2,}/g) || [];
|
|
360
|
+
const seen = new Set();
|
|
361
|
+
const output = [];
|
|
362
|
+
for (const raw of [...quoted, ...tokens]) {
|
|
363
|
+
const normalized = raw.trim();
|
|
364
|
+
if (!normalized)
|
|
365
|
+
continue;
|
|
366
|
+
const key = normalized.toLowerCase();
|
|
367
|
+
if (STOP_WORDS.has(key) || LOW_SIGNAL_TERMS.has(key))
|
|
368
|
+
continue;
|
|
369
|
+
if (seen.has(key))
|
|
370
|
+
continue;
|
|
371
|
+
seen.add(key);
|
|
372
|
+
output.push(normalized);
|
|
373
|
+
}
|
|
374
|
+
return output.slice(0, 8);
|
|
375
|
+
}
|
|
376
|
+
function deriveQuerySignals(question, normalizedQuestion, terms) {
|
|
377
|
+
const asksLocation = /\b(where|which file|in which file|filepath|file path|location)\b/.test(normalizedQuestion);
|
|
378
|
+
const asksHow = /\b(how|flow|trace|walk me through|explain)\b/.test(normalizedQuestion);
|
|
379
|
+
const asksCommandSurface = /\b(command|commands|subcommand|subcommands|flag|flags|option|options)\b/.test(normalizedQuestion);
|
|
380
|
+
const asksSchema = /\b(field|fields|key|keys|schema|interface|input|output|parameter|parameters)\b/.test(normalizedQuestion);
|
|
381
|
+
const asksList = /\b(list|show|which|available)\b/.test(normalizedQuestion) &&
|
|
382
|
+
/\b(command|commands|subcommand|subcommands|files|fields|features)\b/.test(normalizedQuestion);
|
|
383
|
+
const asksDefinition = /\b(defined|implemented|called|computed|resolved)\b/.test(normalizedQuestion);
|
|
384
|
+
const identifiers = extractCodeLikeIdentifiers(question);
|
|
385
|
+
const highSignalTerms = terms.filter((term) => !LOW_SIGNAL_TERMS.has(term) && term.length >= 3);
|
|
386
|
+
return {
|
|
387
|
+
asksLocation,
|
|
388
|
+
asksHow,
|
|
389
|
+
asksList,
|
|
390
|
+
asksDefinition,
|
|
391
|
+
asksCommandSurface,
|
|
392
|
+
asksSchema,
|
|
393
|
+
identifiers,
|
|
394
|
+
highSignalTerms,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
function extractAnchorTerms(question) {
|
|
398
|
+
const quoted = extractQuotedTerms(question);
|
|
399
|
+
const special = (question.match(/[A-Za-z0-9_-]{4,}/g) || []).filter((token) => /[_-]/.test(token) || /[A-Z]/.test(token));
|
|
400
|
+
const normalized = [...new Set([...quoted, ...special].map(normalizeTerm).filter(Boolean))]
|
|
401
|
+
.filter((term) => !STOP_WORDS.has(term) && !LOW_SIGNAL_TERMS.has(term));
|
|
402
|
+
return expandSearchTerms(normalized).slice(0, 12);
|
|
403
|
+
}
|
|
404
|
+
function looksLikeImportLine(rawLine) {
|
|
405
|
+
const trimmed = rawLine.trim();
|
|
406
|
+
return /^import\s+/.test(trimmed) || /^export\s+\{/.test(trimmed);
|
|
407
|
+
}
|
|
408
|
+
function countQuestionTermHits(normalizedQuestion, terms) {
|
|
409
|
+
let hits = 0;
|
|
410
|
+
const seen = new Set();
|
|
411
|
+
for (const term of terms) {
|
|
412
|
+
const normalizedTerm = term.toLowerCase().trim();
|
|
413
|
+
if (!normalizedTerm || seen.has(normalizedTerm))
|
|
414
|
+
continue;
|
|
415
|
+
seen.add(normalizedTerm);
|
|
416
|
+
if (normalizedTerm.includes(' ')) {
|
|
417
|
+
if (!normalizedQuestion.includes(normalizedTerm))
|
|
418
|
+
continue;
|
|
419
|
+
hits += 1;
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
const boundaryPattern = new RegExp(`(?:^|[^a-z0-9])${escapeRegExp(normalizedTerm)}(?:$|[^a-z0-9])`);
|
|
423
|
+
if (boundaryPattern.test(normalizedQuestion)) {
|
|
424
|
+
hits += 1;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return hits;
|
|
428
|
+
}
|
|
429
|
+
function assessQuestionScope(normalizedQuestion) {
|
|
430
|
+
const repoHits = countQuestionTermHits(normalizedQuestion, REPO_SCOPE_TERMS);
|
|
431
|
+
const externalHits = countQuestionTermHits(normalizedQuestion, EXTERNAL_WORLD_TERMS);
|
|
432
|
+
const realtimeHits = countQuestionTermHits(normalizedQuestion, REALTIME_WORLD_TERMS);
|
|
433
|
+
const currencyPairPattern = /\b[a-z]{3}\s*(?:to|\/)\s*[a-z]{3}\b/;
|
|
434
|
+
const hasCurrencyPair = currencyPairPattern.test(normalizedQuestion);
|
|
435
|
+
const hasWorldEntity = /\b(france|india|usa|united states|china|germany|europe|asia|africa)\b/.test(normalizedQuestion);
|
|
436
|
+
const looksExternal = hasCurrencyPair ||
|
|
437
|
+
externalHits > 0 ||
|
|
438
|
+
(hasWorldEntity && /\bcapital|population|gdp|president|prime minister\b/.test(normalizedQuestion)) ||
|
|
439
|
+
(realtimeHits > 0 && /\bexchange rate|weather|stock|price|news|capital|population\b/.test(normalizedQuestion));
|
|
440
|
+
if (looksExternal && repoHits === 0) {
|
|
441
|
+
const reasons = ['This question appears to require external world knowledge rather than repository evidence.'];
|
|
442
|
+
if (hasCurrencyPair) {
|
|
443
|
+
reasons.push('Detected a currency/exchange-rate query, which is outside repo-grounded scope.');
|
|
444
|
+
}
|
|
445
|
+
if (realtimeHits > 0) {
|
|
446
|
+
reasons.push('Detected real-time wording (for example "right now"/"currently"), which is not answerable from local files.');
|
|
447
|
+
}
|
|
448
|
+
return { isOutOfScope: true, reasons };
|
|
449
|
+
}
|
|
450
|
+
return { isOutOfScope: false, reasons: [] };
|
|
451
|
+
}
|
|
452
|
+
function buildOutOfScopeAnswerPayload(question, normalizedQuestion, reasons) {
|
|
453
|
+
const truthReasons = [
|
|
454
|
+
'Neurcode Ask is repository-grounded and does not use web/external knowledge.',
|
|
455
|
+
...reasons,
|
|
456
|
+
];
|
|
457
|
+
return {
|
|
458
|
+
question,
|
|
459
|
+
questionNormalized: normalizedQuestion,
|
|
460
|
+
mode: 'search',
|
|
461
|
+
answer: [
|
|
462
|
+
'I can only answer from this repository.',
|
|
463
|
+
'This question looks external/realtime, so I cannot answer it from repo evidence.',
|
|
464
|
+
'Try a repo-scoped variant, for example: `Where is org id injected in CLI requests?`',
|
|
465
|
+
].join('\n'),
|
|
466
|
+
findings: [
|
|
467
|
+
'Scope guard triggered: external/non-repo query.',
|
|
468
|
+
'No repo files were scanned to avoid returning misleading answers.',
|
|
469
|
+
],
|
|
470
|
+
confidence: 'low',
|
|
471
|
+
truth: {
|
|
472
|
+
status: 'insufficient',
|
|
473
|
+
score: 0.05,
|
|
474
|
+
reasons: truthReasons,
|
|
475
|
+
sourceCitations: 0,
|
|
476
|
+
sourceFiles: 0,
|
|
477
|
+
minCitationsRequired: 2,
|
|
478
|
+
minFilesRequired: 1,
|
|
479
|
+
},
|
|
480
|
+
citations: [],
|
|
481
|
+
generatedAt: new Date().toISOString(),
|
|
482
|
+
stats: {
|
|
483
|
+
scannedFiles: 0,
|
|
484
|
+
matchedFiles: 0,
|
|
485
|
+
matchedLines: 0,
|
|
486
|
+
brainCandidates: 0,
|
|
487
|
+
},
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
function tryBuildDeterministicAnswer(_cwd, question, normalizedQuestion) {
|
|
491
|
+
const scope = assessQuestionScope(normalizedQuestion);
|
|
492
|
+
if (scope.isOutOfScope) {
|
|
493
|
+
return {
|
|
494
|
+
payload: buildOutOfScopeAnswerPayload(question, normalizedQuestion, scope.reasons),
|
|
495
|
+
reason: 'out_of_scope',
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
287
500
|
function normalizeSnippet(line) {
|
|
288
501
|
return line
|
|
289
502
|
.replace(/\t/g, ' ')
|
|
@@ -444,6 +657,34 @@ function extractNeurcodeCommandsFromCitations(citations) {
|
|
|
444
657
|
}
|
|
445
658
|
return [...commandSet].slice(0, 20);
|
|
446
659
|
}
|
|
660
|
+
function extractCommandFocus(normalizedQuestion) {
|
|
661
|
+
const toKnownCommand = (candidate) => {
|
|
662
|
+
if (!candidate)
|
|
663
|
+
return null;
|
|
664
|
+
const normalized = candidate.toLowerCase().trim();
|
|
665
|
+
return CLI_COMMAND_NAMES.has(normalized) ? normalized : null;
|
|
666
|
+
};
|
|
667
|
+
const direct = normalizedQuestion.match(/\bneurcode\s+([a-z][a-z0-9-]*)\b/);
|
|
668
|
+
const directCommand = toKnownCommand(direct?.[1] || null);
|
|
669
|
+
if (directCommand)
|
|
670
|
+
return directCommand;
|
|
671
|
+
const scoped = normalizedQuestion.match(/\b(?:under|for|within|inside|in)\s+(?:neurcode\s+)?([a-z][a-z0-9-]*)\b/);
|
|
672
|
+
const scopedCommand = toKnownCommand(scoped?.[1] || null);
|
|
673
|
+
if (scopedCommand)
|
|
674
|
+
return scopedCommand;
|
|
675
|
+
const commandPattern = normalizedQuestion.match(/\b([a-z][a-z0-9-]*)\s+command\b/);
|
|
676
|
+
const commandPhrase = toKnownCommand(commandPattern?.[1] || null);
|
|
677
|
+
if (commandPhrase)
|
|
678
|
+
return commandPhrase;
|
|
679
|
+
const mentionedCommands = [...CLI_COMMAND_NAMES].filter((command) => new RegExp(`\\b${escapeRegExp(command)}\\b`, 'i').test(normalizedQuestion));
|
|
680
|
+
if (mentionedCommands.length !== 1)
|
|
681
|
+
return null;
|
|
682
|
+
const hasImplementationSignals = /\b(where|how|defined|implemented|called|computed|resolved|lookup|cache|flag|option|verdict|pass|fail|schema|field|fields|key|keys)\b/.test(normalizedQuestion);
|
|
683
|
+
if (hasImplementationSignals) {
|
|
684
|
+
return mentionedCommands[0];
|
|
685
|
+
}
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
447
688
|
function extractInstallCommandsFromCitations(citations) {
|
|
448
689
|
const installSet = new Set();
|
|
449
690
|
for (const citation of citations) {
|
|
@@ -466,6 +707,162 @@ function formatInsightSnippet(snippet) {
|
|
|
466
707
|
.trim()
|
|
467
708
|
.slice(0, 180);
|
|
468
709
|
}
|
|
710
|
+
function countTermHitsInText(text, terms) {
|
|
711
|
+
if (!text || terms.length === 0)
|
|
712
|
+
return 0;
|
|
713
|
+
let hits = 0;
|
|
714
|
+
const isCompactText = !/\s/.test(text);
|
|
715
|
+
const compactText = isCompactText ? text.replace(/[^a-z0-9]/g, '') : '';
|
|
716
|
+
for (const term of terms) {
|
|
717
|
+
const normalized = term.toLowerCase().trim();
|
|
718
|
+
if (!normalized)
|
|
719
|
+
continue;
|
|
720
|
+
if (normalized.includes(' ')) {
|
|
721
|
+
if (text.includes(normalized))
|
|
722
|
+
hits += 1;
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
const pattern = new RegExp(`(?:^|[^a-z0-9])${escapeRegExp(normalized)}(?:$|[^a-z0-9])`, 'i');
|
|
726
|
+
if (pattern.test(text)) {
|
|
727
|
+
hits += 1;
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
if (isCompactText) {
|
|
731
|
+
const compactTerm = normalized.replace(/[^a-z0-9]/g, '');
|
|
732
|
+
if (compactTerm.length >= 3 && compactText.includes(compactTerm)) {
|
|
733
|
+
hits += 1;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
return hits;
|
|
738
|
+
}
|
|
739
|
+
function findInterfaceFieldBlock(lines, startLine) {
|
|
740
|
+
const startIndex = Math.max(0, startLine - 1);
|
|
741
|
+
const lookbackStart = Math.max(0, startIndex - 14);
|
|
742
|
+
const lookaheadEnd = Math.min(lines.length - 1, startIndex + 10);
|
|
743
|
+
let headerIndex = -1;
|
|
744
|
+
let interfaceName = '';
|
|
745
|
+
for (let i = startIndex; i >= lookbackStart; i--) {
|
|
746
|
+
const match = lines[i].match(/^\s*(?:export\s+)?interface\s+([A-Za-z_][A-Za-z0-9_]*)\b/);
|
|
747
|
+
if (!match?.[1])
|
|
748
|
+
continue;
|
|
749
|
+
headerIndex = i;
|
|
750
|
+
interfaceName = match[1];
|
|
751
|
+
break;
|
|
752
|
+
}
|
|
753
|
+
if (headerIndex < 0) {
|
|
754
|
+
for (let i = startIndex; i <= lookaheadEnd; i++) {
|
|
755
|
+
const match = lines[i].match(/^\s*(?:export\s+)?interface\s+([A-Za-z_][A-Za-z0-9_]*)\b/);
|
|
756
|
+
if (!match?.[1])
|
|
757
|
+
continue;
|
|
758
|
+
headerIndex = i;
|
|
759
|
+
interfaceName = match[1];
|
|
760
|
+
break;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
if (headerIndex < 0 || !interfaceName)
|
|
764
|
+
return null;
|
|
765
|
+
let opened = false;
|
|
766
|
+
let depth = 0;
|
|
767
|
+
const fields = [];
|
|
768
|
+
const seen = new Set();
|
|
769
|
+
for (let i = headerIndex; i < Math.min(lines.length, headerIndex + 120); i++) {
|
|
770
|
+
const line = lines[i];
|
|
771
|
+
if (!opened) {
|
|
772
|
+
if (line.includes('{')) {
|
|
773
|
+
opened = true;
|
|
774
|
+
depth = 1;
|
|
775
|
+
}
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
const fieldMatch = line.match(/^\s*([A-Za-z_][A-Za-z0-9_?]*)\s*:/);
|
|
779
|
+
if (fieldMatch?.[1]) {
|
|
780
|
+
const field = fieldMatch[1].replace(/\?$/, '');
|
|
781
|
+
if (!seen.has(field)) {
|
|
782
|
+
seen.add(field);
|
|
783
|
+
fields.push(field);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
for (const ch of line) {
|
|
787
|
+
if (ch === '{')
|
|
788
|
+
depth += 1;
|
|
789
|
+
if (ch === '}')
|
|
790
|
+
depth -= 1;
|
|
791
|
+
}
|
|
792
|
+
if (depth <= 0)
|
|
793
|
+
break;
|
|
794
|
+
}
|
|
795
|
+
if (fields.length === 0)
|
|
796
|
+
return null;
|
|
797
|
+
return { interfaceName, fields };
|
|
798
|
+
}
|
|
799
|
+
function splitIdentifierTokens(value) {
|
|
800
|
+
if (!value)
|
|
801
|
+
return [];
|
|
802
|
+
const tokens = value
|
|
803
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
804
|
+
.replace(/[_\-\s]+/g, ' ')
|
|
805
|
+
.toLowerCase()
|
|
806
|
+
.split(/\s+/)
|
|
807
|
+
.map((token) => token.trim())
|
|
808
|
+
.filter((token) => token.length >= 2);
|
|
809
|
+
return [...new Set(tokens)];
|
|
810
|
+
}
|
|
811
|
+
function buildSchemaFieldSummaries(citations, lineCache, terms) {
|
|
812
|
+
const scored = [];
|
|
813
|
+
const seen = new Set();
|
|
814
|
+
const highSignalTerms = terms.filter((term) => !LOW_SIGNAL_TERMS.has(term));
|
|
815
|
+
const queryTermSet = new Set(highSignalTerms.map((term) => term.toLowerCase()));
|
|
816
|
+
for (const citation of citations) {
|
|
817
|
+
const lines = lineCache.get(citation.path);
|
|
818
|
+
if (!lines || lines.length === 0)
|
|
819
|
+
continue;
|
|
820
|
+
const parsed = findInterfaceFieldBlock(lines, citation.line);
|
|
821
|
+
if (!parsed)
|
|
822
|
+
continue;
|
|
823
|
+
const interfaceKey = `${citation.path}:${parsed.interfaceName}`;
|
|
824
|
+
if (seen.has(interfaceKey))
|
|
825
|
+
continue;
|
|
826
|
+
const ifaceLower = parsed.interfaceName.toLowerCase();
|
|
827
|
+
const ifaceTokens = splitIdentifierTokens(parsed.interfaceName);
|
|
828
|
+
const ifaceText = `${ifaceLower} ${ifaceTokens.join(' ')}`.trim();
|
|
829
|
+
const fieldText = parsed.fields.join(' ').toLowerCase();
|
|
830
|
+
const nameHits = countTermHitsInText(ifaceText, highSignalTerms);
|
|
831
|
+
const fieldHits = countTermHitsInText(fieldText, highSignalTerms);
|
|
832
|
+
let relevanceScore = nameHits * 2 + fieldHits;
|
|
833
|
+
if (queryTermSet.has('plan') && ifaceText.includes('plan'))
|
|
834
|
+
relevanceScore += 0.8;
|
|
835
|
+
if (queryTermSet.has('cache') && ifaceText.includes('cache'))
|
|
836
|
+
relevanceScore += 0.8;
|
|
837
|
+
if (queryTermSet.has('key') && ifaceText.includes('key'))
|
|
838
|
+
relevanceScore += 1.2;
|
|
839
|
+
if ((queryTermSet.has('field') || queryTermSet.has('fields')) && parsed.fields.length > 0) {
|
|
840
|
+
relevanceScore += Math.min(1, parsed.fields.length / 8);
|
|
841
|
+
}
|
|
842
|
+
if (highSignalTerms.length > 0 && relevanceScore === 0) {
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
845
|
+
seen.add(interfaceKey);
|
|
846
|
+
scored.push({
|
|
847
|
+
score: relevanceScore,
|
|
848
|
+
summary: `${parsed.interfaceName}: ${parsed.fields.slice(0, 12).join(', ')}`,
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
const ranked = scored.sort((a, b) => b.score - a.score);
|
|
852
|
+
if (ranked.length === 0)
|
|
853
|
+
return [];
|
|
854
|
+
const topScore = ranked[0].score;
|
|
855
|
+
let minScore = topScore >= 2 ? topScore * 0.6 : 0;
|
|
856
|
+
const strictExactFieldRequest = queryTermSet.has('exact') &&
|
|
857
|
+
(queryTermSet.has('field') || queryTermSet.has('fields'));
|
|
858
|
+
if (strictExactFieldRequest) {
|
|
859
|
+
minScore = Math.max(minScore, topScore - 0.5);
|
|
860
|
+
}
|
|
861
|
+
return ranked
|
|
862
|
+
.filter((item) => item.score >= minScore)
|
|
863
|
+
.slice(0, 6)
|
|
864
|
+
.map((item) => item.summary);
|
|
865
|
+
}
|
|
469
866
|
function extractInsightLines(citations, limit) {
|
|
470
867
|
const out = [];
|
|
471
868
|
const seen = new Set();
|
|
@@ -498,8 +895,8 @@ function isCommandCatalogIntent(normalizedQuestion) {
|
|
|
498
895
|
/\bshow\b/.test(normalizedQuestion) ||
|
|
499
896
|
/\bavailable\b/.test(normalizedQuestion) ||
|
|
500
897
|
/\ball commands?\b/.test(normalizedQuestion) ||
|
|
501
|
-
/\bwhich commands
|
|
502
|
-
/\bwhat commands
|
|
898
|
+
/\bwhich commands\b/.test(normalizedQuestion) ||
|
|
899
|
+
/\bwhat commands\b/.test(normalizedQuestion) ||
|
|
503
900
|
/\bwhat can i (type|run)\b/.test(normalizedQuestion) ||
|
|
504
901
|
/\bcan i type\b/.test(normalizedQuestion) ||
|
|
505
902
|
/\bcmds\b/.test(normalizedQuestion);
|
|
@@ -644,17 +1041,110 @@ function buildAnswer(mode, question, terms, citations, stats, termCounts, perFil
|
|
|
644
1041
|
const asksCommandCatalog = isCommandCatalogIntent(normalizedQuestion);
|
|
645
1042
|
const asksInstall = /\b(install|upgrade|update|latest)\b/.test(normalizedQuestion);
|
|
646
1043
|
const asksFeatures = /\b(feature|features|capability|capabilities|offers?)\b/.test(normalizedQuestion);
|
|
1044
|
+
const asksSchema = /\b(field|fields|key|keys|schema|interface|input|output|parameter|parameters)\b/.test(normalizedQuestion);
|
|
647
1045
|
const asksTenancy = /\b(tenant|tenancy|single|multi|organization)\b/.test(normalizedQuestion);
|
|
648
|
-
const asksLocation = /\b(where|which file|in which file|filepath|file path|location)\b/.test(normalizedQuestion);
|
|
1046
|
+
const asksLocation = /\b(where|which file|in which file|filepath|file path|location|defined|implemented|called|computed|resolved)\b/.test(normalizedQuestion);
|
|
649
1047
|
const asksHow = /\bhow\b/.test(normalizedQuestion);
|
|
1048
|
+
const asksDecision = /\b(decide|decision|verdict|pass|fail)\b/.test(normalizedQuestion);
|
|
1049
|
+
const asksCommandSurface = /\b(command|commands|subcommand|subcommands|flag|flags|option|options)\b/.test(normalizedQuestion);
|
|
1050
|
+
const asksSingleCommandLookup = /\b(what|which)\s+command\b/.test(normalizedQuestion) &&
|
|
1051
|
+
!/\b(commands|subcommands)\b/.test(normalizedQuestion);
|
|
650
1052
|
const asksOrgRequestInjection = /\b(inject|injected|header|request|requests)\b/.test(normalizedQuestion) &&
|
|
651
1053
|
/\b(org|organization)\b/.test(normalizedQuestion);
|
|
1054
|
+
const extractedCommandMatches = extractNeurcodeCommandsFromCitations(citations);
|
|
1055
|
+
const knownCommands = context.knownCommands || [];
|
|
1056
|
+
const knownCommandSet = new Set(knownCommands);
|
|
1057
|
+
const knownRoots = new Set(knownCommands.map((command) => command.split(/\s+/).slice(0, 2).join(' ')));
|
|
1058
|
+
const filteredExtractedMatches = knownCommands.length > 0
|
|
1059
|
+
? extractedCommandMatches.filter((command) => {
|
|
1060
|
+
if (knownCommandSet.has(command))
|
|
1061
|
+
return true;
|
|
1062
|
+
const root = command.split(/\s+/).slice(0, 2).join(' ');
|
|
1063
|
+
return knownRoots.has(root);
|
|
1064
|
+
})
|
|
1065
|
+
: extractedCommandMatches;
|
|
652
1066
|
const commandMatches = [
|
|
653
|
-
...
|
|
654
|
-
...
|
|
1067
|
+
...knownCommands,
|
|
1068
|
+
...filteredExtractedMatches,
|
|
655
1069
|
].filter((value, index, arr) => arr.indexOf(value) === index);
|
|
1070
|
+
const commandFocus = extractCommandFocus(normalizedQuestion);
|
|
656
1071
|
const installMatches = extractInstallCommandsFromCitations(citations);
|
|
657
1072
|
const insightLines = extractInsightLines(citations, 5);
|
|
1073
|
+
const highSignalAnswerTerms = terms.filter((term) => !LOW_SIGNAL_TERMS.has(term) && term.length >= 3).slice(0, 10);
|
|
1074
|
+
const anchorTerms = extractAnchorTerms(question);
|
|
1075
|
+
const queriedFlags = (question.match(/--[a-z0-9-]+/gi) || []).map((flag) => flag.toLowerCase());
|
|
1076
|
+
const pathAnchorTerms = (0, plan_cache_1.normalizeIntent)(question)
|
|
1077
|
+
.replace(/[^a-z0-9_\-\s]/g, ' ')
|
|
1078
|
+
.split(/\s+/)
|
|
1079
|
+
.map((token) => token.trim())
|
|
1080
|
+
.filter((token) => token.length >= 3 &&
|
|
1081
|
+
!STOP_WORDS.has(token) &&
|
|
1082
|
+
![
|
|
1083
|
+
'where', 'which', 'file', 'files', 'path', 'paths', 'location',
|
|
1084
|
+
'defined', 'implemented', 'called', 'computed', 'resolved',
|
|
1085
|
+
].includes(token))
|
|
1086
|
+
.slice(0, 10);
|
|
1087
|
+
const citationRelevanceScore = (citation) => {
|
|
1088
|
+
const snippetLower = (citation.snippet || '').toLowerCase();
|
|
1089
|
+
let score = countTermHitsInText(snippetLower, highSignalAnswerTerms);
|
|
1090
|
+
score += countTermHitsInText(citation.path.toLowerCase(), highSignalAnswerTerms) * 0.5;
|
|
1091
|
+
const pathAnchorHits = countTermHitsInText(citation.path.toLowerCase(), pathAnchorTerms);
|
|
1092
|
+
score += pathAnchorHits * 0.9;
|
|
1093
|
+
if (asksLocation && pathAnchorTerms.length > 0 && pathAnchorHits === 0) {
|
|
1094
|
+
score -= 0.35;
|
|
1095
|
+
}
|
|
1096
|
+
const anchorHits = countTermHitsInText(snippetLower, anchorTerms);
|
|
1097
|
+
score += anchorHits * 1.4;
|
|
1098
|
+
if ((asksLocation || asksHow) && anchorTerms.length > 0 && anchorHits === 0) {
|
|
1099
|
+
score -= 0.45;
|
|
1100
|
+
}
|
|
1101
|
+
if (queriedFlags.length > 0) {
|
|
1102
|
+
const flagHits = queriedFlags.filter((flag) => snippetLower.includes(flag)).length;
|
|
1103
|
+
score += flagHits * 2;
|
|
1104
|
+
if (asksLocation && flagHits === 0)
|
|
1105
|
+
score -= 0.4;
|
|
1106
|
+
if (/\.option\(/i.test(snippetLower) && flagHits > 0)
|
|
1107
|
+
score += 2.5;
|
|
1108
|
+
}
|
|
1109
|
+
if (asksCommandSurface) {
|
|
1110
|
+
if (/\.command\(|\bcommand\(/i.test(snippetLower))
|
|
1111
|
+
score += 2;
|
|
1112
|
+
if (/\.option\(|--[a-z0-9-]+/i.test(snippetLower))
|
|
1113
|
+
score += 1.4;
|
|
1114
|
+
if (citation.path.includes('/commands/') || citation.path.endsWith('/index.ts'))
|
|
1115
|
+
score += 0.6;
|
|
1116
|
+
}
|
|
1117
|
+
if (commandFocus) {
|
|
1118
|
+
if (citation.path.includes(`/commands/${commandFocus}.`)) {
|
|
1119
|
+
score += 2.1;
|
|
1120
|
+
}
|
|
1121
|
+
else if ((asksLocation || asksHow) && citation.path.includes('/commands/')) {
|
|
1122
|
+
score -= 1.0;
|
|
1123
|
+
}
|
|
1124
|
+
if (citation.path.endsWith('/index.ts') &&
|
|
1125
|
+
new RegExp(`\\.command\\('${escapeRegExp(commandFocus)}'\\)`, 'i').test(citation.snippet)) {
|
|
1126
|
+
score += 1.4;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
if ((asksLocation || asksHow) && looksLikeImportLine(citation.snippet)) {
|
|
1130
|
+
score -= 0.8;
|
|
1131
|
+
}
|
|
1132
|
+
if ((asksLocation || asksHow) && /^\s*[?:]?\s*['"`].+['"`][,;]?\s*$/i.test(citation.snippet)) {
|
|
1133
|
+
score -= 0.9;
|
|
1134
|
+
}
|
|
1135
|
+
if (asksLocation && /console\.log\(/i.test(citation.snippet)) {
|
|
1136
|
+
score -= 1.0;
|
|
1137
|
+
}
|
|
1138
|
+
if (asksDecision) {
|
|
1139
|
+
if (/\b(verdict|exitcode|policydecision)\b/i.test(snippetLower) || /\bif\s*\(|\?\s*'[^']+'\s*:\s*'[^']+'/i.test(citation.snippet)) {
|
|
1140
|
+
score += 1.1;
|
|
1141
|
+
}
|
|
1142
|
+
if (/(log|roi|estimat|time saved|badge|banner)/i.test(snippetLower)) {
|
|
1143
|
+
score -= 0.9;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
return score;
|
|
1147
|
+
};
|
|
658
1148
|
const multiSignals = citations.filter((citation) => /\bmulti[- ]tenant|x-org-id|organization[_ -]?id|org-scoped\b/i.test(citation.snippet)).length;
|
|
659
1149
|
const singleSignals = citations.filter((citation) => /\bsingle[- ]tenant|single-user\b/i.test(citation.snippet)).length;
|
|
660
1150
|
if (mode === 'comparison' && terms.length >= 2) {
|
|
@@ -699,8 +1189,66 @@ function buildAnswer(mode, question, terms, citations, stats, termCounts, perFil
|
|
|
699
1189
|
const bullets = (context.featureBullets || []).slice(0, 6).map((line) => ` • ${line}`);
|
|
700
1190
|
answer = ['Here are the main platform features I could verify from the repo:', ...bullets].join('\n');
|
|
701
1191
|
}
|
|
1192
|
+
else if (asksSchema && citations.length > 0) {
|
|
1193
|
+
const lineCache = context.lineCache || new Map();
|
|
1194
|
+
const schemaFieldSummaries = buildSchemaFieldSummaries(citations, lineCache, terms);
|
|
1195
|
+
if (schemaFieldSummaries.length > 0) {
|
|
1196
|
+
const bullets = schemaFieldSummaries.map((line) => ` • ${line}`);
|
|
1197
|
+
answer = ['From the matched code, these fields/signatures are relevant:', ...bullets].join('\n');
|
|
1198
|
+
}
|
|
1199
|
+
else {
|
|
1200
|
+
const schemaLines = citations
|
|
1201
|
+
.map((citation) => formatInsightSnippet(citation.snippet))
|
|
1202
|
+
.filter((line) => /^export\s+interface\b/i.test(line) ||
|
|
1203
|
+
/^export\s+type\b/i.test(line) ||
|
|
1204
|
+
(/^[A-Za-z_][A-Za-z0-9_?]*\s*:\s*[^=,]+;?$/.test(line) &&
|
|
1205
|
+
!line.includes('{') &&
|
|
1206
|
+
!line.includes('=>')));
|
|
1207
|
+
const selected = [...new Set(schemaLines)].slice(0, 8);
|
|
1208
|
+
if (selected.length > 0) {
|
|
1209
|
+
const bullets = selected.map((line) => ` • ${line}`);
|
|
1210
|
+
answer = ['From the matched code, these fields/signatures are relevant:', ...bullets].join('\n');
|
|
1211
|
+
}
|
|
1212
|
+
else {
|
|
1213
|
+
answer = 'I found cache/schema-related code, but not enough direct field declarations in the top evidence.';
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
else if (asksSingleCommandLookup && commandMatches.length > 0) {
|
|
1218
|
+
const commandQueryTerms = [...new Set(tokenizeQuestion(question).flatMap((term) => {
|
|
1219
|
+
const out = [term.toLowerCase()];
|
|
1220
|
+
if (term.endsWith('s') && term.length > 4)
|
|
1221
|
+
out.push(term.slice(0, -1).toLowerCase());
|
|
1222
|
+
if (term.endsWith('ing') && term.length > 5)
|
|
1223
|
+
out.push(term.slice(0, -3).toLowerCase());
|
|
1224
|
+
if (term.endsWith('ed') && term.length > 4)
|
|
1225
|
+
out.push(term.slice(0, -2).toLowerCase());
|
|
1226
|
+
return out;
|
|
1227
|
+
}))];
|
|
1228
|
+
const scored = commandMatches
|
|
1229
|
+
.map((command) => {
|
|
1230
|
+
const normalized = command.toLowerCase();
|
|
1231
|
+
let score = countTermHitsInText(normalized, commandQueryTerms);
|
|
1232
|
+
if (/\bend(s|ed|ing)?\b/.test(normalizedQuestion) && /\bend\b/.test(normalized))
|
|
1233
|
+
score += 0.8;
|
|
1234
|
+
if (commandFocus &&
|
|
1235
|
+
(normalized === `neurcode ${commandFocus}` || normalized.startsWith(`neurcode ${commandFocus} `))) {
|
|
1236
|
+
score += 1.2;
|
|
1237
|
+
}
|
|
1238
|
+
return { command, score };
|
|
1239
|
+
})
|
|
1240
|
+
.sort((a, b) => b.score - a.score);
|
|
1241
|
+
const selected = (scored.filter((item) => item.score > 0).slice(0, 3).map((item) => item.command));
|
|
1242
|
+
const top = selected.length > 0 ? selected : [scored[0].command];
|
|
1243
|
+
answer = top.length === 1
|
|
1244
|
+
? `Use \`${top[0]}\`.`
|
|
1245
|
+
: ['Most relevant commands:', ...top.map((command) => ` • \`${command}\``)].join('\n');
|
|
1246
|
+
}
|
|
702
1247
|
else if (asksCommandCatalog && commandMatches.length > 0) {
|
|
703
|
-
const
|
|
1248
|
+
const scopedCommands = commandFocus
|
|
1249
|
+
? commandMatches.filter((command) => command === `neurcode ${commandFocus}` || command.startsWith(`neurcode ${commandFocus} `))
|
|
1250
|
+
: commandMatches;
|
|
1251
|
+
const normalizedCommands = (scopedCommands.length > 0 ? scopedCommands : commandMatches)
|
|
704
1252
|
.filter((command) => /^neurcode\s+[a-z]/.test(command))
|
|
705
1253
|
.slice(0, 22);
|
|
706
1254
|
const commandBullets = normalizedCommands.map((command) => ` • \`${command}\``);
|
|
@@ -712,6 +1260,7 @@ function buildAnswer(mode, question, terms, citations, stats, termCounts, perFil
|
|
|
712
1260
|
return term.length === 0 || !GENERIC_OUTPUT_TERMS.has(term);
|
|
713
1261
|
});
|
|
714
1262
|
const locationPool = focusedCitations.length > 0 ? [...focusedCitations] : [...citations];
|
|
1263
|
+
locationPool.sort((a, b) => citationRelevanceScore(b) - citationRelevanceScore(a));
|
|
715
1264
|
if (asksOrgRequestInjection) {
|
|
716
1265
|
const score = (citation) => {
|
|
717
1266
|
const snippet = citation.snippet.toLowerCase();
|
|
@@ -730,13 +1279,67 @@ function buildAnswer(mode, question, terms, citations, stats, termCounts, perFil
|
|
|
730
1279
|
};
|
|
731
1280
|
locationPool.sort((a, b) => score(b) - score(a));
|
|
732
1281
|
}
|
|
733
|
-
const
|
|
1282
|
+
const prefersImplementationLines = /\b(defined|implemented|called|computed|resolved|lookup)\b/.test(normalizedQuestion);
|
|
1283
|
+
const queriedFlags = (question.match(/--[a-z0-9-]+/gi) || []).map((flag) => flag.toLowerCase());
|
|
1284
|
+
const declarationLike = prefersImplementationLines
|
|
1285
|
+
? locationPool.filter((citation) => /\b(?:export\s+)?(?:function|const|let|var|class|interface|type)\b/i.test(citation.snippet))
|
|
1286
|
+
: [];
|
|
1287
|
+
const orderedLocationPool = declarationLike.length >= 2
|
|
1288
|
+
? [...declarationLike, ...locationPool.filter((citation) => !declarationLike.includes(citation))]
|
|
1289
|
+
: locationPool;
|
|
1290
|
+
const commandFocusedLocationPool = commandFocus
|
|
1291
|
+
? [...orderedLocationPool].sort((a, b) => {
|
|
1292
|
+
const score = (citation) => {
|
|
1293
|
+
let value = 0;
|
|
1294
|
+
if (citation.path.includes(`/commands/${commandFocus}.`))
|
|
1295
|
+
value += 3;
|
|
1296
|
+
if (citation.path.endsWith('/index.ts') &&
|
|
1297
|
+
new RegExp(`\\.command\\('${escapeRegExp(commandFocus)}'\\)`, 'i').test(citation.snippet)) {
|
|
1298
|
+
value += 2;
|
|
1299
|
+
}
|
|
1300
|
+
if (citation.path.includes('/commands/') && !citation.path.includes(`/commands/${commandFocus}.`)) {
|
|
1301
|
+
value -= 0.5;
|
|
1302
|
+
}
|
|
1303
|
+
if (queriedFlags.length > 0) {
|
|
1304
|
+
const snippetLower = citation.snippet.toLowerCase();
|
|
1305
|
+
const matchedFlagCount = queriedFlags.filter((flag) => snippetLower.includes(flag)).length;
|
|
1306
|
+
value += matchedFlagCount * 2;
|
|
1307
|
+
if (/\.option\(/i.test(citation.snippet) && matchedFlagCount > 0)
|
|
1308
|
+
value += 2;
|
|
1309
|
+
}
|
|
1310
|
+
return value;
|
|
1311
|
+
};
|
|
1312
|
+
return score(b) - score(a);
|
|
1313
|
+
})
|
|
1314
|
+
: orderedLocationPool;
|
|
1315
|
+
const locations = commandFocusedLocationPool
|
|
734
1316
|
.slice(0, 5)
|
|
735
1317
|
.map((citation) => ` • ${citation.path}:${citation.line} — ${formatInsightSnippet(citation.snippet)}`);
|
|
736
1318
|
answer = ['I found the relevant references here:', ...locations].join('\n');
|
|
737
1319
|
}
|
|
738
1320
|
else if (asksHow && insightLines.length > 0) {
|
|
739
|
-
const
|
|
1321
|
+
const focusedInsights = [...citations]
|
|
1322
|
+
.sort((a, b) => citationRelevanceScore(b) - citationRelevanceScore(a))
|
|
1323
|
+
.filter((citation) => citationRelevanceScore(citation) >= 1)
|
|
1324
|
+
.map((citation) => formatInsightSnippet(citation.snippet))
|
|
1325
|
+
.filter((line) => !/^const\s+[A-Za-z_$][\w$]*\s*=\s*\/.+\/[a-z]*\.test\(/i.test(line))
|
|
1326
|
+
.filter((line) => !/^[A-Za-z_$][\w$]*,\s*$/.test(line))
|
|
1327
|
+
.filter((line) => !/^['"`].+['"`],?\s*$/.test(line));
|
|
1328
|
+
const decisionFilteredInsights = asksDecision
|
|
1329
|
+
? focusedInsights.filter((line) => !/(log|roi|estimat|time saved|badge|banner|record[a-z]*event|telemetry|metric)/i.test(line))
|
|
1330
|
+
: focusedInsights;
|
|
1331
|
+
const decisionCoreInsights = asksDecision
|
|
1332
|
+
? decisionFilteredInsights.filter((line) => /\b(verdict|policydecision|exitcode|pass|fail|warn)\b/i.test(line) ||
|
|
1333
|
+
/\?\s*'[^']+'\s*:\s*'[^']+'/.test(line))
|
|
1334
|
+
: [];
|
|
1335
|
+
const selectedInsights = decisionCoreInsights.length > 0
|
|
1336
|
+
? [...new Set(decisionCoreInsights)].slice(0, 5)
|
|
1337
|
+
: decisionFilteredInsights.length > 0
|
|
1338
|
+
? [...new Set(decisionFilteredInsights)].slice(0, 5)
|
|
1339
|
+
: focusedInsights.length > 0
|
|
1340
|
+
? [...new Set(focusedInsights)].slice(0, 5)
|
|
1341
|
+
: insightLines;
|
|
1342
|
+
const bullets = selectedInsights.map((line) => ` • ${line}`);
|
|
740
1343
|
answer = ['From the matched code, this is how it works:', ...bullets].join('\n');
|
|
741
1344
|
}
|
|
742
1345
|
else if (asksTenancy && (multiSignals > 0 || singleSignals > 0)) {
|
|
@@ -870,6 +1473,25 @@ async function askCommand(question, options = {}) {
|
|
|
870
1473
|
const maxCitations = Math.max(3, Math.min(options.maxCitations || 12, 30));
|
|
871
1474
|
const shouldUseCache = options.cache !== false && process.env.NEURCODE_ASK_NO_CACHE !== '1';
|
|
872
1475
|
const normalizedQuestion = (0, plan_cache_1.normalizeIntent)(question);
|
|
1476
|
+
const deterministic = tryBuildDeterministicAnswer(cwd, question, normalizedQuestion);
|
|
1477
|
+
if (deterministic) {
|
|
1478
|
+
if (!options.json) {
|
|
1479
|
+
console.log(chalk.dim(`🧠 Asking repo context in ${cwd}...`));
|
|
1480
|
+
}
|
|
1481
|
+
emitAskResult(deterministic.payload, {
|
|
1482
|
+
json: options.json,
|
|
1483
|
+
maxCitations,
|
|
1484
|
+
fromPlan: options.fromPlan,
|
|
1485
|
+
verbose: options.verbose,
|
|
1486
|
+
});
|
|
1487
|
+
if (orgId && projectId) {
|
|
1488
|
+
(0, brain_context_1.recordBrainProgressEvent)(cwd, scope, {
|
|
1489
|
+
type: 'ask',
|
|
1490
|
+
note: `mode=deterministic;reason=${deterministic.reason};truth=${deterministic.payload.truth.status};score=${deterministic.payload.truth.score.toFixed(2)}`,
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
873
1495
|
if (process.stdout.isTTY && !process.env.CI) {
|
|
874
1496
|
(0, neurcode_context_1.ensureDefaultLocalContextFile)(cwd);
|
|
875
1497
|
}
|
|
@@ -979,6 +1601,7 @@ async function askCommand(question, options = {}) {
|
|
|
979
1601
|
const { mode, terms, matchers } = buildMatchers(question);
|
|
980
1602
|
const pathHints = derivePathHints(question);
|
|
981
1603
|
const mentionsAskCommand = /\bask\b/.test(normalizedQuestion);
|
|
1604
|
+
const asksQuestionAnsweringInternals = /\b(question|questions|answer|answers|cache|brain|citation|grounded)\b/.test(normalizedQuestion);
|
|
982
1605
|
if (matchers.length === 0) {
|
|
983
1606
|
console.error(chalk.red('❌ Could not derive useful search terms from the question.'));
|
|
984
1607
|
process.exit(1);
|
|
@@ -991,6 +1614,8 @@ async function askCommand(question, options = {}) {
|
|
|
991
1614
|
}
|
|
992
1615
|
addAnchorCandidates(fileTree, candidateSet, pathPriority, normalizedQuestion);
|
|
993
1616
|
const tokenHints = tokenizeQuestion(question);
|
|
1617
|
+
const codeFocusedQuestion = /\b(how|where|which file|defined|implemented|called|computed|resolved|function|class|api|command|flow|trace|why|field|fields|key|keys|schema|interface|parameter|parameters)\b/.test(normalizedQuestion) &&
|
|
1618
|
+
!/\b(feature|features|install|setup|readme|docs|documentation)\b/.test(normalizedQuestion);
|
|
994
1619
|
if (candidateSet.size < 80) {
|
|
995
1620
|
for (const filePath of fileTree) {
|
|
996
1621
|
const normalizedPath = filePath.toLowerCase();
|
|
@@ -1005,7 +1630,12 @@ async function askCommand(question, options = {}) {
|
|
|
1005
1630
|
if (normalizedPath.startsWith('scripts/')) {
|
|
1006
1631
|
score -= 0.1;
|
|
1007
1632
|
}
|
|
1008
|
-
if (
|
|
1633
|
+
if (codeFocusedQuestion && (filePath === 'README.md' || normalizedPath.startsWith('docs/'))) {
|
|
1634
|
+
score -= 0.35;
|
|
1635
|
+
}
|
|
1636
|
+
if (!mentionsAskCommand &&
|
|
1637
|
+
!asksQuestionAnsweringInternals &&
|
|
1638
|
+
(filePath.endsWith('/commands/ask.ts') || filePath.endsWith('/utils/ask-cache.ts'))) {
|
|
1009
1639
|
score -= 0.45;
|
|
1010
1640
|
}
|
|
1011
1641
|
if (pathHints.some((hint) => normalizedPath.startsWith(hint))) {
|
|
@@ -1040,11 +1670,15 @@ async function askCommand(question, options = {}) {
|
|
|
1040
1670
|
? prioritized.filter((filePath) => !pathHints.some((hint) => filePath.startsWith(hint)))
|
|
1041
1671
|
: prioritized;
|
|
1042
1672
|
const candidates = [...hinted, ...nonHinted].slice(0, MAX_SCAN_FILES);
|
|
1043
|
-
const
|
|
1673
|
+
const querySignals = deriveQuerySignals(question, normalizedQuestion, terms);
|
|
1674
|
+
const commandFocusQuery = extractCommandFocus(normalizedQuestion);
|
|
1675
|
+
const anchorTerms = extractAnchorTerms(question);
|
|
1676
|
+
const hasTemporalIntent = /\b(before|after|during|when)\b/.test(normalizedQuestion);
|
|
1677
|
+
const highSignalSet = new Set(querySignals.highSignalTerms);
|
|
1678
|
+
const rawFileMatches = new Map();
|
|
1679
|
+
const termFileHits = new Map();
|
|
1044
1680
|
let scannedFiles = 0;
|
|
1045
1681
|
for (const filePath of candidates) {
|
|
1046
|
-
if (citations.length >= MAX_RAW_CITATIONS)
|
|
1047
|
-
break;
|
|
1048
1682
|
const fullPath = (0, path_1.join)(cwd, filePath);
|
|
1049
1683
|
let content = '';
|
|
1050
1684
|
try {
|
|
@@ -1058,34 +1692,165 @@ async function askCommand(question, options = {}) {
|
|
|
1058
1692
|
}
|
|
1059
1693
|
scannedFiles++;
|
|
1060
1694
|
const lines = content.split(/\r?\n/);
|
|
1695
|
+
const lineTerms = new Map();
|
|
1696
|
+
const matchedTerms = new Set();
|
|
1061
1697
|
for (let idx = 0; idx < lines.length; idx++) {
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
const line = lines[idx];
|
|
1065
|
-
if (!line || line.trim().length === 0)
|
|
1698
|
+
const rawLine = lines[idx];
|
|
1699
|
+
if (!rawLine || rawLine.trim().length === 0)
|
|
1066
1700
|
continue;
|
|
1067
1701
|
for (const matcher of matchers) {
|
|
1068
|
-
if (!matcher.regex.test(
|
|
1069
|
-
continue;
|
|
1070
|
-
const snippet = normalizeSnippet(line);
|
|
1071
|
-
if (!snippet)
|
|
1702
|
+
if (!matcher.regex.test(rawLine))
|
|
1072
1703
|
continue;
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1704
|
+
if (!lineTerms.has(idx))
|
|
1705
|
+
lineTerms.set(idx, new Set());
|
|
1706
|
+
lineTerms.get(idx).add(matcher.label);
|
|
1707
|
+
matchedTerms.add(matcher.label);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
if (lineTerms.size === 0)
|
|
1711
|
+
continue;
|
|
1712
|
+
rawFileMatches.set(filePath, {
|
|
1713
|
+
lines,
|
|
1714
|
+
lineTerms,
|
|
1715
|
+
pathScore: pathPriority.get(filePath) || 0,
|
|
1716
|
+
hintBoost: pathHints.some((hint) => filePath.startsWith(hint)) ? 0.25 : 0,
|
|
1717
|
+
});
|
|
1718
|
+
for (const term of matchedTerms) {
|
|
1719
|
+
termFileHits.set(term, (termFileHits.get(term) || 0) + 1);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
const matchedFileTotal = Math.max(rawFileMatches.size, 1);
|
|
1723
|
+
const termWeight = new Map();
|
|
1724
|
+
for (const term of terms) {
|
|
1725
|
+
const docFreq = termFileHits.get(term) || 0;
|
|
1726
|
+
const idfWeight = Math.log((matchedFileTotal + 1) / (docFreq + 1)) + 0.25;
|
|
1727
|
+
const highSignalBoost = highSignalSet.has(term) ? 0.35 : 0;
|
|
1728
|
+
termWeight.set(term, Math.max(0.08, idfWeight + highSignalBoost));
|
|
1729
|
+
}
|
|
1730
|
+
const citations = [];
|
|
1731
|
+
for (const [filePath, fileData] of rawFileMatches.entries()) {
|
|
1732
|
+
const { lines, lineTerms, pathScore, hintBoost } = fileData;
|
|
1733
|
+
for (const [lineIdx, directTerms] of lineTerms.entries()) {
|
|
1734
|
+
if (citations.length >= MAX_RAW_CITATIONS)
|
|
1735
|
+
break;
|
|
1736
|
+
const rawLine = lines[lineIdx] || '';
|
|
1737
|
+
const snippet = normalizeSnippet(rawLine);
|
|
1738
|
+
if (!snippet)
|
|
1739
|
+
continue;
|
|
1740
|
+
const windowTerms = new Set();
|
|
1741
|
+
const contextRadius = querySignals.asksLocation || querySignals.asksDefinition ? 6 : 2;
|
|
1742
|
+
for (let i = Math.max(0, lineIdx - contextRadius); i <= Math.min(lines.length - 1, lineIdx + contextRadius); i++) {
|
|
1743
|
+
const termsAtLine = lineTerms.get(i);
|
|
1744
|
+
if (!termsAtLine)
|
|
1084
1745
|
continue;
|
|
1746
|
+
for (const term of termsAtLine)
|
|
1747
|
+
windowTerms.add(term);
|
|
1748
|
+
}
|
|
1749
|
+
const highSignalWindow = [...windowTerms].filter((term) => highSignalSet.has(term));
|
|
1750
|
+
let score = pathScore + hintBoost;
|
|
1751
|
+
for (const term of windowTerms) {
|
|
1752
|
+
score += termWeight.get(term) || 0.1;
|
|
1753
|
+
}
|
|
1754
|
+
score += highSignalWindow.length * 0.7;
|
|
1755
|
+
score += windowTerms.size * 0.12;
|
|
1756
|
+
if (querySignals.highSignalTerms.length > 0 && highSignalWindow.length === 0) {
|
|
1757
|
+
score -= 0.65;
|
|
1758
|
+
}
|
|
1759
|
+
if ((querySignals.asksLocation || querySignals.asksDefinition) && querySignals.highSignalTerms.length >= 2 && highSignalWindow.length < 2) {
|
|
1760
|
+
score -= 0.9;
|
|
1761
|
+
}
|
|
1762
|
+
const anchorHits = countTermHitsInText(rawLine.toLowerCase(), anchorTerms);
|
|
1763
|
+
score += anchorHits * 1.2;
|
|
1764
|
+
if ((querySignals.asksLocation || querySignals.asksDefinition) && anchorTerms.length > 0 && anchorHits === 0) {
|
|
1765
|
+
score -= 0.6;
|
|
1766
|
+
}
|
|
1767
|
+
if (querySignals.asksLocation || querySignals.asksDefinition) {
|
|
1768
|
+
if (looksLikeImportLine(rawLine))
|
|
1769
|
+
score -= 0.75;
|
|
1770
|
+
if (/\b(?:export\s+)?(?:function|const|let|var|class|interface|type)\b/.test(rawLine) &&
|
|
1771
|
+
countTermHitsInText(rawLine.toLowerCase(), querySignals.highSignalTerms) >= 1) {
|
|
1772
|
+
score += 0.9;
|
|
1773
|
+
}
|
|
1774
|
+
if (querySignals.asksDefinition && /console\.log\(/.test(rawLine)) {
|
|
1775
|
+
score -= 0.6;
|
|
1776
|
+
}
|
|
1777
|
+
for (const identifier of querySignals.identifiers) {
|
|
1778
|
+
const escaped = escapeRegExp(identifier);
|
|
1779
|
+
if (new RegExp(`\\b${escaped}\\s*\\(`, 'i').test(rawLine)) {
|
|
1780
|
+
score += 0.95;
|
|
1781
|
+
}
|
|
1782
|
+
if (new RegExp(`\\b(?:function|const|let|var|class|interface|type)\\s+${escaped}\\b`, 'i').test(rawLine)) {
|
|
1783
|
+
score += 0.8;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
if (hasTemporalIntent) {
|
|
1787
|
+
if (/\b[A-Za-z_][A-Za-z0-9_]*\s*\(/.test(rawLine))
|
|
1788
|
+
score += 0.75;
|
|
1789
|
+
if (/^\s*export\s+(interface|type)\b/.test(rawLine))
|
|
1790
|
+
score -= 0.9;
|
|
1791
|
+
if (/^\s*const\s+[A-Z0-9_]+\s*=/.test(rawLine))
|
|
1792
|
+
score -= 0.7;
|
|
1085
1793
|
}
|
|
1086
|
-
citations.push(evidence);
|
|
1087
|
-
break;
|
|
1088
1794
|
}
|
|
1795
|
+
if (querySignals.asksHow && /\b(if|else|return|await|for|while|switch)\b/.test(rawLine)) {
|
|
1796
|
+
score += 0.2;
|
|
1797
|
+
}
|
|
1798
|
+
if (querySignals.asksHow) {
|
|
1799
|
+
const trimmed = rawLine.trim();
|
|
1800
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('*'))
|
|
1801
|
+
score -= 0.45;
|
|
1802
|
+
if (/\.description\(/.test(rawLine))
|
|
1803
|
+
score -= 0.4;
|
|
1804
|
+
}
|
|
1805
|
+
if (querySignals.asksSchema) {
|
|
1806
|
+
if (/^\s*export\s+interface\s+/i.test(rawLine) || /^\s*export\s+type\s+/i.test(rawLine))
|
|
1807
|
+
score += 1.1;
|
|
1808
|
+
if (/^\s*[A-Za-z_][A-Za-z0-9_?]*\s*:\s*/.test(rawLine))
|
|
1809
|
+
score += 0.8;
|
|
1810
|
+
if (filePath.endsWith('.md'))
|
|
1811
|
+
score -= 0.5;
|
|
1812
|
+
}
|
|
1813
|
+
if (rawLine.length > 180 && rawLine.includes(',') && /['"`].*['"`].*['"`]/.test(rawLine)) {
|
|
1814
|
+
score -= 0.35;
|
|
1815
|
+
}
|
|
1816
|
+
if ((querySignals.asksLocation || querySignals.asksDefinition) && /Usage:\s*neurcode/i.test(rawLine)) {
|
|
1817
|
+
score -= 0.6;
|
|
1818
|
+
}
|
|
1819
|
+
if ((querySignals.asksLocation || querySignals.asksDefinition) && /^\s*type:\s*['"`][a-z0-9_-]+['"`],?\s*$/i.test(rawLine)) {
|
|
1820
|
+
score -= 0.7;
|
|
1821
|
+
}
|
|
1822
|
+
if (querySignals.asksCommandSurface) {
|
|
1823
|
+
if (/\.command\(|\bcommand\(/i.test(rawLine))
|
|
1824
|
+
score += 1.2;
|
|
1825
|
+
if (/\.option\(|--[a-z0-9-]+/i.test(rawLine))
|
|
1826
|
+
score += 0.8;
|
|
1827
|
+
if (filePath.endsWith('/index.ts') || filePath.includes('/commands/'))
|
|
1828
|
+
score += 0.35;
|
|
1829
|
+
}
|
|
1830
|
+
if (commandFocusQuery) {
|
|
1831
|
+
if (filePath.includes(`/commands/${commandFocusQuery}.`)) {
|
|
1832
|
+
score += 1.4;
|
|
1833
|
+
}
|
|
1834
|
+
else if ((querySignals.asksHow || querySignals.asksLocation) && filePath.includes('/commands/')) {
|
|
1835
|
+
score -= 1.2;
|
|
1836
|
+
}
|
|
1837
|
+
if (filePath.endsWith('/index.ts') &&
|
|
1838
|
+
new RegExp(`\\.command\\('${escapeRegExp(commandFocusQuery)}'\\)`, 'i').test(rawLine)) {
|
|
1839
|
+
score += 1.1;
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
const dominantTerm = [...directTerms].sort((a, b) => (termWeight.get(b) || 0) - (termWeight.get(a) || 0))[0] ||
|
|
1843
|
+
[...windowTerms].sort((a, b) => (termWeight.get(b) || 0) - (termWeight.get(a) || 0))[0] ||
|
|
1844
|
+
'';
|
|
1845
|
+
if (score <= 0)
|
|
1846
|
+
continue;
|
|
1847
|
+
citations.push({
|
|
1848
|
+
path: filePath,
|
|
1849
|
+
line: lineIdx + 1,
|
|
1850
|
+
snippet,
|
|
1851
|
+
term: dominantTerm,
|
|
1852
|
+
weight: score,
|
|
1853
|
+
});
|
|
1089
1854
|
}
|
|
1090
1855
|
}
|
|
1091
1856
|
citations.sort((a, b) => b.weight - a.weight);
|
|
@@ -1099,20 +1864,75 @@ async function askCommand(question, options = {}) {
|
|
|
1099
1864
|
}
|
|
1100
1865
|
}
|
|
1101
1866
|
const selectedForOutput = [];
|
|
1102
|
-
const
|
|
1103
|
-
|
|
1867
|
+
const selectedFileCounts = new Map();
|
|
1868
|
+
const pushSelected = (citation) => {
|
|
1869
|
+
if (selectedForOutput.length >= maxCitations)
|
|
1870
|
+
return false;
|
|
1871
|
+
if (selectedForOutput.some((existing) => existing.path === citation.path && existing.line === citation.line && existing.term === citation.term)) {
|
|
1872
|
+
return false;
|
|
1873
|
+
}
|
|
1874
|
+
if (querySignals.asksLocation || querySignals.asksDefinition) {
|
|
1875
|
+
const perFile = selectedFileCounts.get(citation.path) || 0;
|
|
1876
|
+
const distinctFiles = selectedFileCounts.size;
|
|
1877
|
+
const targetDistinct = Math.min(3, sourcePerFileCounts.size);
|
|
1878
|
+
if (perFile >= 3 && distinctFiles < targetDistinct) {
|
|
1879
|
+
return false;
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
selectedForOutput.push(citation);
|
|
1883
|
+
selectedFileCounts.set(citation.path, (selectedFileCounts.get(citation.path) || 0) + 1);
|
|
1884
|
+
return true;
|
|
1885
|
+
};
|
|
1886
|
+
if (mode === 'search' && querySignals.asksSchema) {
|
|
1887
|
+
const schemaAffinity = (citation) => {
|
|
1888
|
+
const text = citation.snippet.toLowerCase();
|
|
1889
|
+
let affinity = 0;
|
|
1890
|
+
for (const term of querySignals.highSignalTerms) {
|
|
1891
|
+
const normalized = term.toLowerCase();
|
|
1892
|
+
const compact = normalized.replace(/\s+/g, '');
|
|
1893
|
+
if (text.includes(normalized))
|
|
1894
|
+
affinity += 1;
|
|
1895
|
+
else if (text.includes(compact))
|
|
1896
|
+
affinity += 0.85;
|
|
1897
|
+
}
|
|
1898
|
+
return affinity;
|
|
1899
|
+
};
|
|
1900
|
+
const schemaPreferred = sourceEvidence
|
|
1901
|
+
.filter((citation) => /^export\s+interface\b/i.test(citation.snippet) ||
|
|
1902
|
+
/^export\s+type\b/i.test(citation.snippet) ||
|
|
1903
|
+
/^[A-Za-z_][A-Za-z0-9_?]*\s*:\s*[^=,]+;?$/.test(citation.snippet))
|
|
1904
|
+
.sort((a, b) => {
|
|
1905
|
+
const affinityDiff = schemaAffinity(b) - schemaAffinity(a);
|
|
1906
|
+
if (affinityDiff !== 0)
|
|
1907
|
+
return affinityDiff;
|
|
1908
|
+
return b.weight - a.weight;
|
|
1909
|
+
});
|
|
1910
|
+
for (const citation of schemaPreferred) {
|
|
1911
|
+
if (selectedForOutput.length >= maxCitations)
|
|
1912
|
+
break;
|
|
1913
|
+
pushSelected(citation);
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1104
1916
|
if (mode === 'comparison' && terms.length >= 2) {
|
|
1105
1917
|
for (const term of terms.slice(0, 2)) {
|
|
1106
1918
|
const firstForTerm = sourceEvidence.find((citation) => citation.term === term);
|
|
1107
1919
|
if (firstForTerm) {
|
|
1108
|
-
|
|
1920
|
+
pushSelected(firstForTerm);
|
|
1109
1921
|
}
|
|
1110
1922
|
}
|
|
1111
1923
|
}
|
|
1112
1924
|
else if (mode === 'search' && terms.length > 0) {
|
|
1113
|
-
const
|
|
1114
|
-
|
|
1115
|
-
.
|
|
1925
|
+
const discriminativeHighSignalTerms = querySignals.highSignalTerms.filter((term) => {
|
|
1926
|
+
const docFreq = termFileHits.get(term) || 0;
|
|
1927
|
+
return docFreq / matchedFileTotal <= 0.65;
|
|
1928
|
+
});
|
|
1929
|
+
const preferredTerms = discriminativeHighSignalTerms.length > 0
|
|
1930
|
+
? discriminativeHighSignalTerms.slice(0, 8)
|
|
1931
|
+
: querySignals.highSignalTerms.length > 0
|
|
1932
|
+
? querySignals.highSignalTerms.slice(0, 8)
|
|
1933
|
+
: terms
|
|
1934
|
+
.filter((term) => !LOW_SIGNAL_TERMS.has(term) && term.length >= 4)
|
|
1935
|
+
.slice(0, 8);
|
|
1116
1936
|
for (const term of preferredTerms) {
|
|
1117
1937
|
const firstForTerm = sourceEvidence.find((citation) => {
|
|
1118
1938
|
if (citation.term !== term)
|
|
@@ -1122,32 +1942,22 @@ async function askCommand(question, options = {}) {
|
|
|
1122
1942
|
});
|
|
1123
1943
|
if (!firstForTerm)
|
|
1124
1944
|
continue;
|
|
1125
|
-
|
|
1126
|
-
continue;
|
|
1127
|
-
}
|
|
1128
|
-
selectedForOutput.push(firstForTerm);
|
|
1945
|
+
pushSelected(firstForTerm);
|
|
1129
1946
|
if (selectedForOutput.length >= maxCitations)
|
|
1130
1947
|
break;
|
|
1131
1948
|
}
|
|
1132
1949
|
}
|
|
1133
|
-
if (wantsOrgRequestInjection && selectedForOutput.length < maxCitations) {
|
|
1134
|
-
const targeted = sourceEvidence.filter((citation) => /x-org-id|org[_ -]?id|headers?\[|auto-?inject|request/i.test(citation.snippet));
|
|
1135
|
-
for (const citation of targeted) {
|
|
1136
|
-
if (selectedForOutput.length >= maxCitations)
|
|
1137
|
-
break;
|
|
1138
|
-
if (selectedForOutput.some((existing) => existing.path === citation.path && existing.line === citation.line && existing.term === citation.term)) {
|
|
1139
|
-
continue;
|
|
1140
|
-
}
|
|
1141
|
-
selectedForOutput.push(citation);
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
1950
|
for (const citation of sourceEvidence) {
|
|
1145
1951
|
if (selectedForOutput.length >= maxCitations)
|
|
1146
1952
|
break;
|
|
1147
|
-
if (
|
|
1953
|
+
if (commandFocusQuery &&
|
|
1954
|
+
(querySignals.asksLocation || querySignals.asksHow) &&
|
|
1955
|
+
selectedForOutput.some((item) => item.path.includes(`/commands/${commandFocusQuery}.`)) &&
|
|
1956
|
+
citation.path.includes('/commands/') &&
|
|
1957
|
+
!citation.path.includes(`/commands/${commandFocusQuery}.`)) {
|
|
1148
1958
|
continue;
|
|
1149
1959
|
}
|
|
1150
|
-
|
|
1960
|
+
pushSelected(citation);
|
|
1151
1961
|
}
|
|
1152
1962
|
const finalCitations = selectedForOutput.map(({ path, line, snippet, term }) => ({
|
|
1153
1963
|
path,
|
|
@@ -1161,11 +1971,16 @@ async function askCommand(question, options = {}) {
|
|
|
1161
1971
|
matchedLines: sourceEvidence.length,
|
|
1162
1972
|
brainCandidates: brainResults.entries.length,
|
|
1163
1973
|
};
|
|
1974
|
+
const lineCacheForAnswer = new Map();
|
|
1975
|
+
for (const [filePath, data] of rawFileMatches.entries()) {
|
|
1976
|
+
lineCacheForAnswer.set(filePath, data.lines);
|
|
1977
|
+
}
|
|
1164
1978
|
const truth = evaluateTruthAssessment(mode, normalizedQuestion, terms, sourceEvidence, sourcePerFileCounts, sourceTermCounts);
|
|
1165
1979
|
const answer = buildAnswer(mode, question, terms, finalCitations, stats, sourceTermCounts, sourcePerFileCounts, truth, {
|
|
1166
1980
|
cliPackageName,
|
|
1167
1981
|
knownCommands: knownCliCommands,
|
|
1168
1982
|
featureBullets,
|
|
1983
|
+
lineCache: lineCacheForAnswer,
|
|
1169
1984
|
});
|
|
1170
1985
|
emitAskResult(answer, {
|
|
1171
1986
|
json: options.json,
|