@neurcode-ai/cli 0.9.26 → 0.9.27
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/commands/ask.d.ts.map +1 -1
- package/dist/commands/ask.js +1849 -1783
- package/dist/commands/ask.js.map +1 -1
- package/package.json +1 -1
package/dist/commands/ask.js
CHANGED
|
@@ -27,60 +27,269 @@ catch {
|
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
const MAX_SCAN_FILES = (() => {
|
|
30
|
-
const raw = Number(process.env.NEURCODE_ASK_MAX_SCAN_FILES || '
|
|
30
|
+
const raw = Number(process.env.NEURCODE_ASK_MAX_SCAN_FILES || '2200');
|
|
31
31
|
if (!Number.isFinite(raw))
|
|
32
|
-
return
|
|
33
|
-
return Math.max(
|
|
32
|
+
return 2200;
|
|
33
|
+
return Math.max(300, Math.min(Math.trunc(raw), 8000));
|
|
34
34
|
})();
|
|
35
35
|
const MAX_FILE_BYTES = 512 * 1024;
|
|
36
|
-
const MAX_RAW_CITATIONS =
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
const MAX_RAW_CITATIONS = 220;
|
|
37
|
+
const RG_MAX_MATCHES = 3500;
|
|
38
|
+
const FETCH_TIMEOUT_MS = (() => {
|
|
39
|
+
const raw = Number(process.env.NEURCODE_ASK_EXTERNAL_TIMEOUT_MS || '9000');
|
|
40
|
+
if (!Number.isFinite(raw))
|
|
41
|
+
return 9000;
|
|
42
|
+
return Math.max(3000, Math.min(Math.trunc(raw), 30000));
|
|
43
|
+
})();
|
|
44
|
+
const REPO_SCOPE_TERMS = new Set([
|
|
45
|
+
'repo', 'repository', 'codebase', 'file', 'files', 'path', 'paths', 'module', 'modules',
|
|
46
|
+
'function', 'class', 'interface', 'type', 'schema', 'command', 'commands', 'flag', 'option',
|
|
47
|
+
'middleware', 'route', 'service', 'api', 'org', 'organization', 'tenant', 'tenancy',
|
|
48
|
+
'plan', 'verify', 'ask', 'ship', 'apply', 'watch', 'session', 'cache', 'brain', 'diff',
|
|
49
|
+
]);
|
|
50
|
+
const EXTERNAL_WORLD_TERMS = new Set([
|
|
51
|
+
'capital', 'population', 'gdp', 'weather', 'temperature', 'forecast', 'stock', 'price',
|
|
52
|
+
'exchange', 'currency', 'president', 'prime minister', 'news', 'election', 'sports',
|
|
53
|
+
'bitcoin', 'btc', 'ethereum', 'eth', 'usd', 'eur', 'inr', 'jpy',
|
|
54
|
+
'fifa', 'world cup', 'olympics', 'cricket', 'nba', 'nfl',
|
|
42
55
|
]);
|
|
43
56
|
const STOP_WORDS = new Set([
|
|
44
|
-
'the', 'and', 'for', 'with', 'that', 'this', 'what', 'where', 'when', 'which',
|
|
57
|
+
'the', 'and', 'for', 'with', 'that', 'this', 'what', 'where', 'when', 'which',
|
|
45
58
|
'from', 'your', 'about', 'there', 'their', 'them', 'have', 'does', 'is', 'are', 'was',
|
|
46
|
-
'were', 'any', 'all', '
|
|
47
|
-
'
|
|
48
|
-
'
|
|
49
|
-
'system', 'platform', 'latest', 'package', 'packages',
|
|
59
|
+
'were', 'any', 'all', 'tell', 'me', 'its', 'it', 'than', 'then', 'workflow', 'codebase',
|
|
60
|
+
'repo', 'repository', 'used', 'use', 'using', 'list', 'show', 'like', 'can', 'type',
|
|
61
|
+
'types', 'package', 'packages', 'give', 'need', 'please', 'how', 'work', 'works', 'working',
|
|
50
62
|
]);
|
|
51
63
|
const LOW_SIGNAL_TERMS = new Set([
|
|
52
|
-
'used', 'use', 'using', '
|
|
53
|
-
'workflow', 'repo', 'repository', 'codebase', 'anywhere', 'can', 'type', 'types',
|
|
54
|
-
'
|
|
55
|
-
'
|
|
56
|
-
'
|
|
57
|
-
|
|
64
|
+
'used', 'use', 'using', 'where', 'tell', 'read', 'check', 'find', 'search',
|
|
65
|
+
'workflow', 'repo', 'repository', 'codebase', 'anywhere', 'can', 'type', 'types',
|
|
66
|
+
'list', 'show', 'like', 'neurcode', 'cli', 'file', 'files', 'path', 'paths',
|
|
67
|
+
'resolved', 'resolve', 'defined', 'define', 'implemented', 'implement', 'called', 'call',
|
|
68
|
+
'how', 'work', 'works', 'working',
|
|
69
|
+
]);
|
|
70
|
+
const SUBCOMMAND_STOP_TERMS = new Set([
|
|
71
|
+
'command', 'commands', 'subcommand', 'subcommands',
|
|
72
|
+
'option', 'options', 'flag', 'flags',
|
|
73
|
+
'what', 'where', 'when', 'why', 'who', 'which', 'how',
|
|
74
|
+
'does', 'do', 'did', 'can', 'could', 'should', 'would', 'will',
|
|
75
|
+
'work', 'works', 'working', 'flow', 'trace', 'compute', 'computed',
|
|
76
|
+
'implementation', 'implement', 'internals', 'logic', 'behavior', 'behaviour',
|
|
58
77
|
]);
|
|
59
|
-
const GENERIC_OUTPUT_TERMS = new Set(['file', 'files', 'path', 'filepath']);
|
|
60
|
-
const REPO_SCOPE_TERMS = [
|
|
61
|
-
'repo', 'repository', 'codebase', 'neurcode', 'cli', 'command', 'commands', 'ship', 'plan', 'ask', 'verify',
|
|
62
|
-
'apply', 'watch', 'config', 'login', 'logout', 'whoami', 'doctor', 'module', 'file', 'files', 'path',
|
|
63
|
-
'middleware', 'api', 'service', 'services', 'branch', 'commit', 'cache', 'brain', 'org', 'organization',
|
|
64
|
-
'tenant', 'tenancy', 'x-org-id', 'readme', 'docs',
|
|
65
|
-
];
|
|
66
|
-
const EXTERNAL_WORLD_TERMS = [
|
|
67
|
-
'capital', 'exchange rate', 'stock price', 'weather', 'temperature', 'forecast', 'news', 'election',
|
|
68
|
-
'president', 'prime minister', 'population', 'gdp', 'sports score', 'match result', 'bitcoin price',
|
|
69
|
-
'usd', 'inr', 'eur', 'jpy', 'currency',
|
|
70
|
-
];
|
|
71
|
-
const REALTIME_WORLD_TERMS = ['right now', 'currently', 'today', 'tomorrow', 'yesterday', 'live', 'real time'];
|
|
72
78
|
const CLI_COMMAND_NAMES = new Set([
|
|
73
79
|
'check', 'refactor', 'security', 'brain', 'login', 'logout', 'init', 'doctor',
|
|
74
80
|
'whoami', 'config', 'map', 'ask', 'plan', 'ship', 'apply', 'allow', 'watch',
|
|
75
81
|
'session', 'verify', 'prompt', 'revert',
|
|
76
82
|
]);
|
|
83
|
+
function normalizeFilePath(filePath) {
|
|
84
|
+
return filePath.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
85
|
+
}
|
|
86
|
+
function isIgnoredSearchPath(path) {
|
|
87
|
+
const normalized = normalizeFilePath(path).toLowerCase();
|
|
88
|
+
if (!normalized)
|
|
89
|
+
return true;
|
|
90
|
+
if (normalized.startsWith('dist/') ||
|
|
91
|
+
normalized.includes('/dist/') ||
|
|
92
|
+
normalized.startsWith('build/') ||
|
|
93
|
+
normalized.includes('/build/') ||
|
|
94
|
+
normalized.startsWith('out/') ||
|
|
95
|
+
normalized.includes('/out/') ||
|
|
96
|
+
normalized.startsWith('.next/') ||
|
|
97
|
+
normalized.includes('/.next/')) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
if (normalized.includes('.pnpm-store/') ||
|
|
101
|
+
normalized.startsWith('node_modules/') ||
|
|
102
|
+
normalized.includes('/node_modules/') ||
|
|
103
|
+
normalized.startsWith('.git/') ||
|
|
104
|
+
normalized.includes('/.git/') ||
|
|
105
|
+
normalized.startsWith('.neurcode/') ||
|
|
106
|
+
normalized.includes('/.neurcode/') ||
|
|
107
|
+
normalized.includes('/coverage/') ||
|
|
108
|
+
normalized.includes('/.cache/') ||
|
|
109
|
+
normalized.endsWith('.min.js') ||
|
|
110
|
+
normalized.endsWith('.map')) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
function escapeRegExp(input) {
|
|
116
|
+
return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
117
|
+
}
|
|
118
|
+
function normalizeSnippet(line) {
|
|
119
|
+
return line
|
|
120
|
+
.replace(/\t/g, ' ')
|
|
121
|
+
.replace(/\s+/g, ' ')
|
|
122
|
+
.trim()
|
|
123
|
+
.slice(0, 260);
|
|
124
|
+
}
|
|
125
|
+
function buildQueryProfile(searchTerms) {
|
|
126
|
+
const normalizedQuestion = searchTerms.normalizedQuestion;
|
|
127
|
+
const commandFocus = detectCommandFocus(normalizedQuestion);
|
|
128
|
+
return {
|
|
129
|
+
asksLocation: /\b(where|which file|location|defined|implemented|called|computed|resolved)\b/.test(normalizedQuestion),
|
|
130
|
+
asksHow: /\b(how|flow|trace|explain|why)\b/.test(normalizedQuestion),
|
|
131
|
+
asksList: /\b(list|show|which|what)\b/.test(normalizedQuestion),
|
|
132
|
+
asksRegistration: /\b(register|registered|registration|mapped|wired|hooked)\b/.test(normalizedQuestion),
|
|
133
|
+
codeFocused: /\b(command|commands|flag|option|api|middleware|route|service|class|function|interface|type|schema|field|cache|tenant|org|auth|verify|plan|apply|ship)\b/.test(normalizedQuestion),
|
|
134
|
+
commandFocus,
|
|
135
|
+
subcommandFocus: detectSubcommandFocus(normalizedQuestion, commandFocus),
|
|
136
|
+
highSignalSet: new Set(searchTerms.highSignalTerms.map((term) => term.toLowerCase())),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function isLikelyDocumentationPath(path) {
|
|
140
|
+
const normalized = normalizeFilePath(path).toLowerCase();
|
|
141
|
+
if (!normalized)
|
|
142
|
+
return false;
|
|
143
|
+
if (normalized === 'readme.md' || normalized.endsWith('/readme.md'))
|
|
144
|
+
return true;
|
|
145
|
+
if (normalized.startsWith('docs/') || normalized.includes('/docs/'))
|
|
146
|
+
return true;
|
|
147
|
+
if (normalized.includes('/documentation/'))
|
|
148
|
+
return true;
|
|
149
|
+
if (normalized.includes('/sitemap'))
|
|
150
|
+
return true;
|
|
151
|
+
if (normalized.endsWith('.md') || normalized.endsWith('.mdx'))
|
|
152
|
+
return true;
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
function isLikelyCodeSnippet(snippet) {
|
|
156
|
+
const value = snippet.trim();
|
|
157
|
+
if (!value)
|
|
158
|
+
return false;
|
|
159
|
+
if (/^\s*\/[/*]/.test(value))
|
|
160
|
+
return true;
|
|
161
|
+
if (/[{}();=]/.test(value))
|
|
162
|
+
return true;
|
|
163
|
+
if (/\b(import|export|const|let|var|function|class|interface|type|enum|return|await|if|else|switch|case|try|catch|throw)\b/.test(value)) {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
if (/\b[a-zA-Z_][a-zA-Z0-9_]*\s*\(/.test(value))
|
|
167
|
+
return true;
|
|
168
|
+
if (/\.\w+\(/.test(value))
|
|
169
|
+
return true;
|
|
170
|
+
if (/=>/.test(value))
|
|
171
|
+
return true;
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
function isLikelyDocSnippet(snippet) {
|
|
175
|
+
const value = snippet.trim();
|
|
176
|
+
if (!value)
|
|
177
|
+
return false;
|
|
178
|
+
if (/^<\w+/.test(value) || /<\/\w+>/.test(value))
|
|
179
|
+
return true;
|
|
180
|
+
if (/\b(className|href|to: ['"]\/docs\/|#ask-command)\b/.test(value))
|
|
181
|
+
return true;
|
|
182
|
+
if (/^#{1,6}\s/.test(value))
|
|
183
|
+
return true;
|
|
184
|
+
if (/^[-*]\s/.test(value))
|
|
185
|
+
return true;
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
function isPromptExampleSnippet(snippet, normalizedQuestion, highSignalTerms) {
|
|
189
|
+
const snippetLower = snippet.toLowerCase();
|
|
190
|
+
if (!snippetLower)
|
|
191
|
+
return false;
|
|
192
|
+
if (/\bneurcode\s+(ask|plan|verify|ship|apply)\s+["`]/i.test(snippet))
|
|
193
|
+
return true;
|
|
194
|
+
if (snippetLower.includes('?') && /\b(where|what|how|why|which)\b/.test(snippetLower)) {
|
|
195
|
+
const overlaps = highSignalTerms.filter((term) => term && snippetLower.includes(term.toLowerCase())).length;
|
|
196
|
+
if (overlaps >= Math.min(3, Math.max(2, highSignalTerms.length)))
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
const normalizedSnippet = (0, plan_cache_1.normalizeIntent)(snippetLower);
|
|
200
|
+
if (normalizedQuestion.length >= 24 && normalizedSnippet.includes(normalizedQuestion.slice(0, 28))) {
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
function tokenizeQuestion(question) {
|
|
206
|
+
return (0, plan_cache_1.normalizeIntent)(question)
|
|
207
|
+
.replace(/[^a-z0-9_\-\s]/g, ' ')
|
|
208
|
+
.split(/\s+/)
|
|
209
|
+
.map((token) => token.trim())
|
|
210
|
+
.filter((token) => token.length >= 3 && !STOP_WORDS.has(token));
|
|
211
|
+
}
|
|
212
|
+
function extractQuotedPhrases(question) {
|
|
213
|
+
const out = [];
|
|
214
|
+
const seen = new Set();
|
|
215
|
+
const re = /["'`](.{2,100}?)["'`]/g;
|
|
216
|
+
for (const match of question.matchAll(re)) {
|
|
217
|
+
const value = (0, plan_cache_1.normalizeIntent)(match[1] || '').trim();
|
|
218
|
+
if (!value || seen.has(value))
|
|
219
|
+
continue;
|
|
220
|
+
seen.add(value);
|
|
221
|
+
out.push(value);
|
|
222
|
+
}
|
|
223
|
+
return out;
|
|
224
|
+
}
|
|
225
|
+
function extractCodeIdentifiers(question) {
|
|
226
|
+
const matches = question.match(/[A-Za-z_][A-Za-z0-9_\-]{2,}/g) || [];
|
|
227
|
+
const out = [];
|
|
228
|
+
const seen = new Set();
|
|
229
|
+
for (const token of matches) {
|
|
230
|
+
const normalized = token.trim();
|
|
231
|
+
const key = normalized.toLowerCase();
|
|
232
|
+
if (!normalized || STOP_WORDS.has(key))
|
|
233
|
+
continue;
|
|
234
|
+
if (seen.has(key))
|
|
235
|
+
continue;
|
|
236
|
+
seen.add(key);
|
|
237
|
+
out.push(normalized);
|
|
238
|
+
}
|
|
239
|
+
return out.slice(0, 16);
|
|
240
|
+
}
|
|
241
|
+
function buildSearchTerms(question) {
|
|
242
|
+
const normalizedQuestion = (0, plan_cache_1.normalizeIntent)(question);
|
|
243
|
+
const tokens = tokenizeQuestion(question);
|
|
244
|
+
const quotedPhrases = extractQuotedPhrases(question);
|
|
245
|
+
const identifiers = extractCodeIdentifiers(question);
|
|
246
|
+
const highSignalTerms = tokens
|
|
247
|
+
.filter((token) => !LOW_SIGNAL_TERMS.has(token))
|
|
248
|
+
.slice(0, 18);
|
|
249
|
+
const phraseTerms = [];
|
|
250
|
+
for (let i = 0; i < highSignalTerms.length - 1; i++) {
|
|
251
|
+
const phrase = `${highSignalTerms[i]} ${highSignalTerms[i + 1]}`;
|
|
252
|
+
if (phrase.length >= 7)
|
|
253
|
+
phraseTerms.push(phrase);
|
|
254
|
+
if (phraseTerms.length >= 8)
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
const all = [
|
|
258
|
+
...quotedPhrases,
|
|
259
|
+
...identifiers,
|
|
260
|
+
...highSignalTerms,
|
|
261
|
+
...phraseTerms,
|
|
262
|
+
];
|
|
263
|
+
const seen = new Set();
|
|
264
|
+
const rgTerms = [];
|
|
265
|
+
for (const term of all) {
|
|
266
|
+
const normalized = (0, plan_cache_1.normalizeIntent)(term).trim();
|
|
267
|
+
if (!normalized)
|
|
268
|
+
continue;
|
|
269
|
+
if (seen.has(normalized))
|
|
270
|
+
continue;
|
|
271
|
+
seen.add(normalized);
|
|
272
|
+
rgTerms.push(normalized);
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
normalizedQuestion,
|
|
276
|
+
tokens,
|
|
277
|
+
highSignalTerms,
|
|
278
|
+
quotedPhrases,
|
|
279
|
+
identifiers,
|
|
280
|
+
rgTerms: rgTerms.slice(0, 22),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
77
283
|
function scanFiles(dir, maxFiles = MAX_SCAN_FILES) {
|
|
78
284
|
const files = [];
|
|
79
285
|
const ignoreDirs = new Set([
|
|
80
|
-
'node_modules', '.git', '.next', 'dist', 'build', '.turbo', '.cache',
|
|
81
|
-
'.neurcode', '.vscode', '.pnpm-store', '.
|
|
286
|
+
'node_modules', '.git', '.next', 'dist', 'build', '.turbo', '.cache',
|
|
287
|
+
'coverage', '.neurcode', '.vscode', '.pnpm-store', '.yarn', '.npm',
|
|
288
|
+
]);
|
|
289
|
+
const ignoreExts = new Set([
|
|
290
|
+
'map', 'log', 'lock', 'png', 'jpg', 'jpeg', 'gif', 'ico', 'svg',
|
|
291
|
+
'woff', 'woff2', 'ttf', 'eot', 'pdf',
|
|
82
292
|
]);
|
|
83
|
-
const ignoreExts = new Set(['map', 'log', 'lock', 'png', 'jpg', 'jpeg', 'gif', 'ico', 'svg', 'woff', 'woff2', 'ttf', 'eot', 'pdf']);
|
|
84
293
|
const walk = (current) => {
|
|
85
294
|
if (files.length >= maxFiles)
|
|
86
295
|
return;
|
|
@@ -94,10 +303,6 @@ function scanFiles(dir, maxFiles = MAX_SCAN_FILES) {
|
|
|
94
303
|
for (const entry of entries) {
|
|
95
304
|
if (files.length >= maxFiles)
|
|
96
305
|
break;
|
|
97
|
-
if (entry.startsWith('.') && !entry.startsWith('.env')) {
|
|
98
|
-
if (ignoreDirs.has(entry))
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
306
|
const fullPath = (0, path_1.join)(current, entry);
|
|
102
307
|
let st;
|
|
103
308
|
try {
|
|
@@ -109,6 +314,8 @@ function scanFiles(dir, maxFiles = MAX_SCAN_FILES) {
|
|
|
109
314
|
if (st.isDirectory()) {
|
|
110
315
|
if (ignoreDirs.has(entry))
|
|
111
316
|
continue;
|
|
317
|
+
if (entry.startsWith('.') && entry !== '.env')
|
|
318
|
+
continue;
|
|
112
319
|
walk(fullPath);
|
|
113
320
|
continue;
|
|
114
321
|
}
|
|
@@ -119,374 +326,103 @@ function scanFiles(dir, maxFiles = MAX_SCAN_FILES) {
|
|
|
119
326
|
const ext = entry.includes('.') ? entry.split('.').pop()?.toLowerCase() || '' : '';
|
|
120
327
|
if (ext && ignoreExts.has(ext))
|
|
121
328
|
continue;
|
|
122
|
-
const rel = fullPath.slice(dir.length + 1)
|
|
329
|
+
const rel = normalizeFilePath(fullPath.slice(dir.length + 1));
|
|
123
330
|
files.push(rel);
|
|
124
331
|
}
|
|
125
332
|
};
|
|
126
333
|
walk(dir);
|
|
127
334
|
return files.slice(0, maxFiles);
|
|
128
335
|
}
|
|
129
|
-
function
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
138
|
-
}
|
|
139
|
-
function normalizeTerm(raw) {
|
|
140
|
-
const normalized = raw
|
|
141
|
-
.toLowerCase()
|
|
142
|
-
.replace(/['"`]/g, '')
|
|
143
|
-
.replace(/[^a-z0-9_\-\s]/g, ' ')
|
|
144
|
-
.replace(/\s+/g, ' ')
|
|
145
|
-
.trim();
|
|
146
|
-
if (!normalized)
|
|
147
|
-
return '';
|
|
148
|
-
return normalized
|
|
149
|
-
.split(' ')
|
|
150
|
-
.map((token) => token.replace(/^[-_]+|[-_]+$/g, ''))
|
|
151
|
-
.filter(Boolean)
|
|
152
|
-
.join(' ');
|
|
153
|
-
}
|
|
154
|
-
function extractQuotedTerms(question) {
|
|
155
|
-
const seen = new Set();
|
|
156
|
-
const terms = [];
|
|
157
|
-
const re = /["'`](.{2,80}?)["'`]/g;
|
|
158
|
-
for (const match of question.matchAll(re)) {
|
|
159
|
-
const term = normalizeTerm(match[1] || '');
|
|
160
|
-
if (!term || seen.has(term))
|
|
161
|
-
continue;
|
|
162
|
-
seen.add(term);
|
|
163
|
-
terms.push(term);
|
|
164
|
-
}
|
|
165
|
-
return terms;
|
|
166
|
-
}
|
|
167
|
-
function extractIdentityTerms(question) {
|
|
168
|
-
const normalized = (0, plan_cache_1.normalizeIntent)(question);
|
|
169
|
-
const matches = normalized.match(/(?:organization[\s_-]*id|org[\s_-]*id|orgid|organizationid|user[\s_-]*id|userid|userid)/g);
|
|
170
|
-
if (!matches)
|
|
171
|
-
return [];
|
|
172
|
-
const seen = new Set();
|
|
173
|
-
const ordered = [];
|
|
174
|
-
for (const raw of matches) {
|
|
175
|
-
const term = normalizeTerm(raw);
|
|
176
|
-
if (seen.has(term))
|
|
177
|
-
continue;
|
|
178
|
-
seen.add(term);
|
|
179
|
-
ordered.push(term);
|
|
180
|
-
}
|
|
181
|
-
return ordered;
|
|
182
|
-
}
|
|
183
|
-
function extractComparisonTerms(question) {
|
|
184
|
-
const quoted = extractQuotedTerms(question);
|
|
185
|
-
if (quoted.length >= 2)
|
|
186
|
-
return quoted.slice(0, 2);
|
|
187
|
-
const normalized = (0, plan_cache_1.normalizeIntent)(question);
|
|
188
|
-
const hasComparisonJoiner = normalized.includes('instead of') ||
|
|
189
|
-
normalized.includes(' versus ') ||
|
|
190
|
-
normalized.includes(' vs ') ||
|
|
191
|
-
normalized.includes(' compared to ');
|
|
192
|
-
if (!hasComparisonJoiner)
|
|
193
|
-
return [];
|
|
194
|
-
const identities = extractIdentityTerms(question);
|
|
195
|
-
if (identities.length >= 2)
|
|
196
|
-
return identities.slice(0, 2);
|
|
197
|
-
return [];
|
|
198
|
-
}
|
|
199
|
-
function extractPhraseTerms(question, maxTerms = 6) {
|
|
200
|
-
const tokens = (0, plan_cache_1.normalizeIntent)(question)
|
|
201
|
-
.replace(/[^a-z0-9_\-\s]/g, ' ')
|
|
202
|
-
.split(/\s+/)
|
|
203
|
-
.map((token) => token.trim())
|
|
204
|
-
.filter((token) => token.length >= 3 && !STOP_WORDS.has(token) && !LOW_SIGNAL_TERMS.has(token));
|
|
205
|
-
const phrases = [];
|
|
206
|
-
const seen = new Set();
|
|
207
|
-
for (let i = 0; i < tokens.length - 1; i++) {
|
|
208
|
-
const phrase = `${tokens[i]} ${tokens[i + 1]}`;
|
|
209
|
-
if (seen.has(phrase))
|
|
210
|
-
continue;
|
|
211
|
-
seen.add(phrase);
|
|
212
|
-
phrases.push(phrase);
|
|
213
|
-
if (phrases.length >= maxTerms)
|
|
214
|
-
break;
|
|
215
|
-
}
|
|
216
|
-
return phrases;
|
|
217
|
-
}
|
|
218
|
-
function buildTermMatchers(term, weight) {
|
|
219
|
-
const normalized = normalizeTerm(term);
|
|
220
|
-
if (!normalized)
|
|
221
|
-
return [];
|
|
222
|
-
const tokens = normalized.split(/\s+/).filter(Boolean);
|
|
223
|
-
const out = [];
|
|
224
|
-
const push = (label, regexSource) => {
|
|
225
|
-
out.push({
|
|
226
|
-
label,
|
|
227
|
-
regex: new RegExp(regexSource, 'i'),
|
|
228
|
-
weight,
|
|
229
|
-
});
|
|
230
|
-
};
|
|
231
|
-
if (tokens.length === 1) {
|
|
232
|
-
const token = escapeRegExp(tokens[0]);
|
|
233
|
-
push(normalized, `\\b${token}\\b`);
|
|
234
|
-
if (/^[a-z0-9_]+$/.test(tokens[0]) && tokens[0].length >= 5) {
|
|
235
|
-
push(normalized, `\\b${token}[a-z0-9_]*\\b`);
|
|
236
|
-
push(normalized, `\\b[a-z0-9_]*${token}[a-z0-9_]*\\b`);
|
|
237
|
-
}
|
|
238
|
-
return out;
|
|
239
|
-
}
|
|
240
|
-
const tokenChain = tokens.map((t) => escapeRegExp(t)).join('[\\s_-]*');
|
|
241
|
-
push(normalized, `\\b${tokenChain}\\b`);
|
|
242
|
-
push(normalized, `\\b${escapeRegExp(tokens.join(''))}\\b`);
|
|
243
|
-
push(normalized, `\\b${escapeRegExp(tokens.join('_'))}\\b`);
|
|
244
|
-
push(normalized, `\\b${escapeRegExp(tokens.join('-'))}\\b`);
|
|
245
|
-
return out;
|
|
246
|
-
}
|
|
247
|
-
function expandSearchTerms(terms) {
|
|
248
|
-
const expanded = new Set();
|
|
249
|
-
for (const rawTerm of terms) {
|
|
250
|
-
const term = normalizeTerm(rawTerm);
|
|
251
|
-
if (!term)
|
|
336
|
+
function countTermHits(text, terms) {
|
|
337
|
+
if (!text || terms.length === 0)
|
|
338
|
+
return 0;
|
|
339
|
+
let hits = 0;
|
|
340
|
+
const lower = text.toLowerCase();
|
|
341
|
+
for (const term of terms) {
|
|
342
|
+
const normalized = term.toLowerCase().trim();
|
|
343
|
+
if (!normalized)
|
|
252
344
|
continue;
|
|
253
|
-
|
|
254
|
-
|
|
345
|
+
if (normalized.includes(' ')) {
|
|
346
|
+
if (lower.includes(normalized))
|
|
347
|
+
hits += 1;
|
|
255
348
|
continue;
|
|
256
349
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (term.endsWith('s') && term.length > 3) {
|
|
261
|
-
expanded.add(term.slice(0, -1));
|
|
262
|
-
}
|
|
263
|
-
else if (term.length > 3) {
|
|
264
|
-
expanded.add(`${term}s`);
|
|
265
|
-
}
|
|
266
|
-
if (term.endsWith('ing') && term.length > 5) {
|
|
267
|
-
expanded.add(term.slice(0, -3));
|
|
268
|
-
}
|
|
269
|
-
if (term.endsWith('ed') && term.length > 4) {
|
|
270
|
-
expanded.add(term.slice(0, -2));
|
|
271
|
-
}
|
|
272
|
-
if (term.includes('-')) {
|
|
273
|
-
expanded.add(term.replace(/-/g, ' '));
|
|
274
|
-
expanded.add(term.replace(/-/g, ''));
|
|
275
|
-
}
|
|
276
|
-
if (term.endsWith('cache')) {
|
|
277
|
-
expanded.add(`${term}d`);
|
|
278
|
-
}
|
|
279
|
-
if (term.endsWith('cached')) {
|
|
280
|
-
expanded.add(term.slice(0, -1)); // cached -> cache
|
|
281
|
-
}
|
|
282
|
-
if (term.includes('cache') && term.includes('-')) {
|
|
283
|
-
expanded.add(term.replace(/cache\b/, 'cached'));
|
|
284
|
-
expanded.add(term.replace(/cached\b/, 'cache'));
|
|
285
|
-
}
|
|
350
|
+
const pattern = new RegExp(`(?:^|[^a-z0-9])${escapeRegExp(normalized)}(?:$|[^a-z0-9])`, 'i');
|
|
351
|
+
if (pattern.test(lower))
|
|
352
|
+
hits += 1;
|
|
286
353
|
}
|
|
287
|
-
return
|
|
354
|
+
return hits;
|
|
288
355
|
}
|
|
289
|
-
function
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
]
|
|
356
|
+
function classifyQuestionScope(question, terms) {
|
|
357
|
+
const normalized = terms.normalizedQuestion;
|
|
358
|
+
const repoSignal = countTermHits(normalized, [...REPO_SCOPE_TERMS]);
|
|
359
|
+
const externalSignal = countTermHits(normalized, [...EXTERNAL_WORLD_TERMS]);
|
|
360
|
+
const hasCodeLikeSyntax = /[`][^`]+[`]/.test(question) ||
|
|
361
|
+
/\b[a-z0-9_\-/]+\.(ts|tsx|js|jsx|py|go|java|rb|php|cs|json|yml|yaml|toml|sql|md)\b/i.test(question) ||
|
|
362
|
+
/\b[A-Za-z_][A-Za-z0-9_]*\s*\(/.test(question) ||
|
|
363
|
+
/--[a-z0-9-]+/i.test(question);
|
|
364
|
+
const mentionsKnownCommand = [...CLI_COMMAND_NAMES].some((cmd) => new RegExp(`\\b${escapeRegExp(cmd)}\\b`, 'i').test(normalized));
|
|
365
|
+
const asksGlobalFact = /\b(capital|population|gdp|weather|forecast|stock|currency|exchange rate|president|prime minister|who is|who was|who won|what is|where is|when did|world cup|olympics|fifa|nba|nfl|cricket)\b/.test(normalized);
|
|
366
|
+
if ((externalSignal >= 1 || asksGlobalFact) && repoSignal === 0 && !hasCodeLikeSyntax && !mentionsKnownCommand) {
|
|
296
367
|
return {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
matchers,
|
|
368
|
+
kind: 'external',
|
|
369
|
+
reasons: ['Question appears to be outside repository scope.'],
|
|
300
370
|
};
|
|
301
371
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
const phraseTerms = extractPhraseTerms(question);
|
|
305
|
-
const keywords = tokenizeQuestion(question).slice(0, 8);
|
|
306
|
-
const quotedSet = new Set(quoted.map((term) => normalizeTerm(term)));
|
|
307
|
-
const baseTerms = [...new Set([...quoted, ...phraseTerms, ...identityTerms, ...keywords].map(normalizeTerm).filter(Boolean))];
|
|
308
|
-
const filteredTerms = baseTerms.filter((term) => quotedSet.has(term) || !LOW_SIGNAL_TERMS.has(term));
|
|
309
|
-
const terms = expandSearchTerms(filteredTerms.length > 0 ? filteredTerms : baseTerms).filter(Boolean);
|
|
310
|
-
const matchers = terms.flatMap((term) => buildTermMatchers(term, quoted.includes(term) ? 0.9 : 0.55));
|
|
311
|
-
return {
|
|
312
|
-
mode: 'search',
|
|
313
|
-
terms,
|
|
314
|
-
matchers,
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
function derivePathHints(question) {
|
|
318
|
-
const normalized = (0, plan_cache_1.normalizeIntent)(question);
|
|
319
|
-
const hints = [];
|
|
320
|
-
if (/\bcli|command|terminal\b/.test(normalized)) {
|
|
321
|
-
hints.push('packages/cli/src/commands/');
|
|
322
|
-
hints.push('packages/cli/src/');
|
|
323
|
-
hints.push('packages/cli/');
|
|
324
|
-
}
|
|
325
|
-
if (/\bapi|backend|server|route|middleware\b/.test(normalized)) {
|
|
326
|
-
hints.push('services/api/');
|
|
327
|
-
}
|
|
328
|
-
if (/\bdashboard|landing|docs|frontend|ui|pricing\b/.test(normalized)) {
|
|
329
|
-
hints.push('web/dashboard/');
|
|
330
|
-
hints.push('docs/');
|
|
331
|
-
hints.push('README.md');
|
|
332
|
-
}
|
|
333
|
-
if (/\bfeature|capabilit|offer|platform\b/.test(normalized)) {
|
|
334
|
-
hints.push('docs/');
|
|
335
|
-
hints.push('README.md');
|
|
336
|
-
}
|
|
337
|
-
if (/\binstall|setup|upgrade|update\b/.test(normalized)) {
|
|
338
|
-
hints.push('README.md');
|
|
339
|
-
hints.push('docs/');
|
|
340
|
-
hints.push('packages/cli/');
|
|
341
|
-
}
|
|
342
|
-
if (/\btenant|tenancy|single|multi|organization|org\b/.test(normalized)) {
|
|
343
|
-
hints.push('services/api/src/lib/');
|
|
344
|
-
hints.push('services/api/src/routes/');
|
|
345
|
-
hints.push('packages/cli/src/');
|
|
346
|
-
}
|
|
347
|
-
if (/\brequest|requests|header|inject|injected\b/.test(normalized)) {
|
|
348
|
-
hints.push('packages/cli/src/api-client.ts');
|
|
349
|
-
hints.push('services/api/src/middleware/');
|
|
350
|
-
}
|
|
351
|
-
if (/(github action|\bci\b)/.test(normalized)) {
|
|
352
|
-
hints.push('.github/workflows/');
|
|
353
|
-
hints.push('packages/action/');
|
|
354
|
-
hints.push('actions/');
|
|
355
|
-
}
|
|
356
|
-
return [...new Set(hints)];
|
|
357
|
-
}
|
|
358
|
-
function extractCodeLikeIdentifiers(question) {
|
|
359
|
-
const quoted = extractQuotedTerms(question);
|
|
360
|
-
const tokens = question.match(/[A-Za-z_][A-Za-z0-9_]{2,}/g) || [];
|
|
361
|
-
const seen = new Set();
|
|
362
|
-
const output = [];
|
|
363
|
-
for (const raw of [...quoted, ...tokens]) {
|
|
364
|
-
const normalized = raw.trim();
|
|
365
|
-
if (!normalized)
|
|
366
|
-
continue;
|
|
367
|
-
const key = normalized.toLowerCase();
|
|
368
|
-
if (STOP_WORDS.has(key) || LOW_SIGNAL_TERMS.has(key))
|
|
369
|
-
continue;
|
|
370
|
-
if (seen.has(key))
|
|
371
|
-
continue;
|
|
372
|
-
seen.add(key);
|
|
373
|
-
output.push(normalized);
|
|
372
|
+
if (repoSignal > 0 || hasCodeLikeSyntax || mentionsKnownCommand) {
|
|
373
|
+
return { kind: 'repo', reasons: [] };
|
|
374
374
|
}
|
|
375
|
-
return output.slice(0, 8);
|
|
376
|
-
}
|
|
377
|
-
function deriveQuerySignals(question, normalizedQuestion, terms) {
|
|
378
|
-
const asksLocation = /\b(where|which file|in which file|filepath|file path|location)\b/.test(normalizedQuestion);
|
|
379
|
-
const asksHow = /\b(how|flow|trace|walk me through|explain)\b/.test(normalizedQuestion);
|
|
380
|
-
const asksCommandSurface = /\b(command|commands|subcommand|subcommands|flag|flags|option|options)\b/.test(normalizedQuestion);
|
|
381
|
-
const asksSchema = /\b(field|fields|key|keys|schema|interface|input|output|parameter|parameters)\b/.test(normalizedQuestion);
|
|
382
|
-
const asksList = /\b(list|show|which|available)\b/.test(normalizedQuestion) &&
|
|
383
|
-
/\b(command|commands|subcommand|subcommands|files|fields|features)\b/.test(normalizedQuestion);
|
|
384
|
-
const asksDefinition = /\b(defined|implemented|called|computed|resolved)\b/.test(normalizedQuestion);
|
|
385
|
-
const identifiers = extractCodeLikeIdentifiers(question);
|
|
386
|
-
const highSignalTerms = terms.filter((term) => !LOW_SIGNAL_TERMS.has(term) && term.length >= 3);
|
|
387
375
|
return {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
asksList,
|
|
391
|
-
asksDefinition,
|
|
392
|
-
asksCommandSurface,
|
|
393
|
-
asksSchema,
|
|
394
|
-
identifiers,
|
|
395
|
-
highSignalTerms,
|
|
376
|
+
kind: 'ambiguous',
|
|
377
|
+
reasons: ['Question does not clearly reference repository context.'],
|
|
396
378
|
};
|
|
397
379
|
}
|
|
398
|
-
function
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
.filter((term) => !STOP_WORDS.has(term) && !LOW_SIGNAL_TERMS.has(term));
|
|
403
|
-
return expandSearchTerms(normalized).slice(0, 12);
|
|
404
|
-
}
|
|
405
|
-
function looksLikeImportLine(rawLine) {
|
|
406
|
-
const trimmed = rawLine.trim();
|
|
407
|
-
return /^import\s+/.test(trimmed) || /^export\s+\{/.test(trimmed);
|
|
408
|
-
}
|
|
409
|
-
function countQuestionTermHits(normalizedQuestion, terms) {
|
|
410
|
-
let hits = 0;
|
|
411
|
-
const seen = new Set();
|
|
412
|
-
for (const term of terms) {
|
|
413
|
-
const normalizedTerm = term.toLowerCase().trim();
|
|
414
|
-
if (!normalizedTerm || seen.has(normalizedTerm))
|
|
415
|
-
continue;
|
|
416
|
-
seen.add(normalizedTerm);
|
|
417
|
-
if (normalizedTerm.includes(' ')) {
|
|
418
|
-
if (!normalizedQuestion.includes(normalizedTerm))
|
|
419
|
-
continue;
|
|
420
|
-
hits += 1;
|
|
421
|
-
continue;
|
|
422
|
-
}
|
|
423
|
-
const boundaryPattern = new RegExp(`(?:^|[^a-z0-9])${escapeRegExp(normalizedTerm)}(?:$|[^a-z0-9])`);
|
|
424
|
-
if (boundaryPattern.test(normalizedQuestion)) {
|
|
425
|
-
hits += 1;
|
|
426
|
-
}
|
|
380
|
+
function detectCommandFocus(normalizedQuestion) {
|
|
381
|
+
const compound = normalizedQuestion.match(/\b([a-z][a-z0-9-]*)\s+([a-z][a-z0-9-]*)\s+command\b/);
|
|
382
|
+
if (compound?.[1] && CLI_COMMAND_NAMES.has(compound[1])) {
|
|
383
|
+
return compound[1];
|
|
427
384
|
}
|
|
428
|
-
|
|
385
|
+
const direct = normalizedQuestion.match(/\bneurcode\s+([a-z][a-z0-9-]*)\b/);
|
|
386
|
+
if (direct?.[1] && CLI_COMMAND_NAMES.has(direct[1])) {
|
|
387
|
+
return direct[1];
|
|
388
|
+
}
|
|
389
|
+
const singular = normalizedQuestion.match(/\b([a-z][a-z0-9-]*)\s+command\b/);
|
|
390
|
+
if (singular?.[1] && CLI_COMMAND_NAMES.has(singular[1])) {
|
|
391
|
+
return singular[1];
|
|
392
|
+
}
|
|
393
|
+
const mentioned = [...CLI_COMMAND_NAMES].filter((cmd) => new RegExp(`\\b${escapeRegExp(cmd)}\\b`, 'i').test(normalizedQuestion));
|
|
394
|
+
if (mentioned.length === 1)
|
|
395
|
+
return mentioned[0];
|
|
396
|
+
return null;
|
|
429
397
|
}
|
|
430
|
-
function
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
if (looksExternal && repoHits === 0) {
|
|
442
|
-
const reasons = ['This question appears to require external world knowledge rather than repository evidence.'];
|
|
443
|
-
if (hasCurrencyPair) {
|
|
444
|
-
reasons.push('Detected a currency/exchange-rate query, which is outside repo-grounded scope.');
|
|
445
|
-
}
|
|
446
|
-
if (realtimeHits > 0) {
|
|
447
|
-
reasons.push('Detected real-time wording (for example "right now"/"currently"), which is not answerable from local files.');
|
|
448
|
-
}
|
|
449
|
-
return { isOutOfScope: true, reasons };
|
|
450
|
-
}
|
|
451
|
-
return { isOutOfScope: false, reasons: [] };
|
|
398
|
+
function isLikelySubcommandToken(value, options) {
|
|
399
|
+
const token = (0, plan_cache_1.normalizeIntent)(value).trim().toLowerCase();
|
|
400
|
+
if (!token || token.length < 3)
|
|
401
|
+
return false;
|
|
402
|
+
if (!/^[a-z][a-z0-9-]*$/.test(token))
|
|
403
|
+
return false;
|
|
404
|
+
if (!options?.allowKnownCommand && CLI_COMMAND_NAMES.has(token))
|
|
405
|
+
return false;
|
|
406
|
+
if (STOP_WORDS.has(token) || LOW_SIGNAL_TERMS.has(token) || SUBCOMMAND_STOP_TERMS.has(token))
|
|
407
|
+
return false;
|
|
408
|
+
return true;
|
|
452
409
|
}
|
|
453
|
-
function
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
]
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
].
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
'No repo files were scanned to avoid returning misleading answers.',
|
|
470
|
-
],
|
|
471
|
-
confidence: 'low',
|
|
472
|
-
truth: {
|
|
473
|
-
status: 'insufficient',
|
|
474
|
-
score: 0.05,
|
|
475
|
-
reasons: truthReasons,
|
|
476
|
-
sourceCitations: 0,
|
|
477
|
-
sourceFiles: 0,
|
|
478
|
-
minCitationsRequired: 2,
|
|
479
|
-
minFilesRequired: 1,
|
|
480
|
-
},
|
|
481
|
-
citations: [],
|
|
482
|
-
generatedAt: new Date().toISOString(),
|
|
483
|
-
stats: {
|
|
484
|
-
scannedFiles: 0,
|
|
485
|
-
matchedFiles: 0,
|
|
486
|
-
matchedLines: 0,
|
|
487
|
-
brainCandidates: 0,
|
|
488
|
-
},
|
|
489
|
-
};
|
|
410
|
+
function detectSubcommandFocus(normalizedQuestion, commandFocus) {
|
|
411
|
+
if (!commandFocus)
|
|
412
|
+
return null;
|
|
413
|
+
const explicit = normalizedQuestion.match(new RegExp(`\\b${escapeRegExp(commandFocus)}\\s+([a-z][a-z0-9-]*)\\s+command\\b`));
|
|
414
|
+
if (explicit?.[1] && isLikelySubcommandToken(explicit[1], { allowKnownCommand: true })) {
|
|
415
|
+
return explicit[1].toLowerCase();
|
|
416
|
+
}
|
|
417
|
+
const directAfter = normalizedQuestion.match(new RegExp(`\\b${escapeRegExp(commandFocus)}\\s+([a-z][a-z0-9-]*)\\b`));
|
|
418
|
+
if (directAfter?.[1] && isLikelySubcommandToken(directAfter[1])) {
|
|
419
|
+
return directAfter[1].toLowerCase();
|
|
420
|
+
}
|
|
421
|
+
const explicitSubcommand = normalizedQuestion.match(new RegExp(`\\b([a-z][a-z0-9-]*)\\s+subcommand\\b.*\\b${escapeRegExp(commandFocus)}\\b`));
|
|
422
|
+
if (explicitSubcommand?.[1] && isLikelySubcommandToken(explicitSubcommand[1])) {
|
|
423
|
+
return explicitSubcommand[1].toLowerCase();
|
|
424
|
+
}
|
|
425
|
+
return null;
|
|
490
426
|
}
|
|
491
427
|
function parseOwnershipLookbackDays(normalizedQuestion) {
|
|
492
428
|
if (/\bquarter\b/.test(normalizedQuestion))
|
|
@@ -511,10 +447,21 @@ function buildOwnershipDeterministicAnswer(cwd, question, normalizedQuestion) {
|
|
|
511
447
|
const asksOwnership = /\b(who|owner|owners|authored|touched|touch)\b/.test(normalizedQuestion);
|
|
512
448
|
if (!asksOwnership)
|
|
513
449
|
return null;
|
|
450
|
+
const repoSignal = countTermHits(normalizedQuestion, [...REPO_SCOPE_TERMS]);
|
|
451
|
+
const externalSignal = countTermHits(normalizedQuestion, [...EXTERNAL_WORLD_TERMS]);
|
|
452
|
+
const hasCodeLikeSyntax = /[`][^`]+[`]/.test(question) ||
|
|
453
|
+
/\b[a-z0-9_\-/]+\.(ts|tsx|js|jsx|py|go|java|rb|php|cs|json|yml|yaml|toml|sql|md)\b/i.test(question) ||
|
|
454
|
+
/\b[A-Za-z_][A-Za-z0-9_]*\s*\(/.test(question) ||
|
|
455
|
+
/--[a-z0-9-]+/i.test(question);
|
|
456
|
+
const ownershipExternalPattern = /\b(who won|who is|who was|world cup|olympics|fifa|nba|nfl|cricket)\b/.test(normalizedQuestion);
|
|
457
|
+
if ((repoSignal === 0 && !hasCodeLikeSyntax) || ownershipExternalPattern || externalSignal > 0) {
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
514
460
|
const ignoreTerms = new Set([
|
|
515
|
-
'who', 'owner', 'owners', 'authored', 'touched', 'touch', 'last', 'quarter',
|
|
516
|
-
'
|
|
517
|
-
'
|
|
461
|
+
'who', 'owner', 'owners', 'authored', 'touched', 'touch', 'last', 'quarter',
|
|
462
|
+
'recent', 'recently', 'file', 'files', 'module', 'modules', 'repo', 'repository',
|
|
463
|
+
'codebase', 'this', 'that', 'these', 'those', 'for', 'from', 'with', 'about',
|
|
464
|
+
'during', 'before', 'after', 'show', 'list', 'what', 'which', 'where',
|
|
518
465
|
]);
|
|
519
466
|
const focusTerms = (0, plan_cache_1.normalizeIntent)(question)
|
|
520
467
|
.split(/\s+/)
|
|
@@ -543,21 +490,20 @@ function buildOwnershipDeterministicAnswer(cwd, question, normalizedQuestion) {
|
|
|
543
490
|
}
|
|
544
491
|
if (!currentAuthor)
|
|
545
492
|
continue;
|
|
546
|
-
const normalizedPath = line
|
|
547
|
-
if (!normalizedPath ||
|
|
493
|
+
const normalizedPath = normalizeFilePath(line);
|
|
494
|
+
if (!normalizedPath || isIgnoredSearchPath(normalizedPath)) {
|
|
548
495
|
continue;
|
|
549
496
|
}
|
|
550
497
|
if (focusTerms.length > 0 && !focusTerms.some((term) => (0, plan_cache_1.normalizeIntent)(normalizedPath).includes(term))) {
|
|
551
498
|
continue;
|
|
552
499
|
}
|
|
553
500
|
authorTouches.set(currentAuthor, (authorTouches.get(currentAuthor) || 0) + 1);
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
fileTouches.set(normalizedPath,
|
|
501
|
+
const byFile = fileTouches.get(normalizedPath) || new Map();
|
|
502
|
+
byFile.set(currentAuthor, (byFile.get(currentAuthor) || 0) + 1);
|
|
503
|
+
fileTouches.set(normalizedPath, byFile);
|
|
557
504
|
}
|
|
558
|
-
if (authorTouches.size === 0)
|
|
505
|
+
if (authorTouches.size === 0)
|
|
559
506
|
return null;
|
|
560
|
-
}
|
|
561
507
|
const topContributors = [...authorTouches.entries()]
|
|
562
508
|
.sort((a, b) => b[1] - a[1])
|
|
563
509
|
.slice(0, 5)
|
|
@@ -570,10 +516,6 @@ function buildOwnershipDeterministicAnswer(cwd, question, normalizedQuestion) {
|
|
|
570
516
|
})
|
|
571
517
|
.slice(0, 6);
|
|
572
518
|
const targetLabel = focusTerms.length > 0 ? `files matching "${focusTerms.slice(0, 4).join(', ')}"` : 'this repository';
|
|
573
|
-
const answer = [
|
|
574
|
-
`Top contributors for ${targetLabel} in the last ${sinceDays} day(s):`,
|
|
575
|
-
...topContributors.map((entry) => ` • ${entry.author} (${entry.touches} touches)`),
|
|
576
|
-
].join('\n');
|
|
577
519
|
const citations = topFiles.map(([path, owners]) => {
|
|
578
520
|
const summary = [...owners.entries()]
|
|
579
521
|
.sort((a, b) => b[1] - a[1])
|
|
@@ -587,1050 +529,1330 @@ function buildOwnershipDeterministicAnswer(cwd, question, normalizedQuestion) {
|
|
|
587
529
|
snippet: `Recent git touches (last ${sinceDays}d): ${summary}`,
|
|
588
530
|
};
|
|
589
531
|
});
|
|
590
|
-
const sourceFiles = new Set(citations.map((
|
|
591
|
-
const score = Math.min(0.98, 0.65 + Math.min(sourceFiles, 6) * 0.05);
|
|
532
|
+
const sourceFiles = new Set(citations.map((c) => c.path)).size;
|
|
592
533
|
return {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
534
|
+
question,
|
|
535
|
+
questionNormalized: normalizedQuestion,
|
|
536
|
+
mode: 'search',
|
|
537
|
+
answer: [
|
|
538
|
+
`I checked local git history for the last ${sinceDays} day(s).`,
|
|
539
|
+
`Top contributors for ${targetLabel}:`,
|
|
540
|
+
...topContributors.map((entry) => ` • ${entry.author} (${entry.touches} touches)`),
|
|
541
|
+
].join('\n'),
|
|
542
|
+
findings: [
|
|
543
|
+
`Derived from git history over ${sinceDays} day(s).`,
|
|
544
|
+
`Focus: ${targetLabel}.`,
|
|
545
|
+
],
|
|
546
|
+
confidence: topContributors.length >= 2 ? 'high' : 'medium',
|
|
547
|
+
proof: {
|
|
548
|
+
topFiles: [...new Set(citations.map((citation) => citation.path))].slice(0, 5),
|
|
549
|
+
evidenceCount: citations.length,
|
|
550
|
+
coverage: {
|
|
608
551
|
sourceCitations: citations.length,
|
|
609
552
|
sourceFiles,
|
|
610
|
-
minCitationsRequired: 1,
|
|
611
|
-
minFilesRequired: 1,
|
|
612
|
-
},
|
|
613
|
-
citations,
|
|
614
|
-
generatedAt: new Date().toISOString(),
|
|
615
|
-
stats: {
|
|
616
|
-
scannedFiles: 0,
|
|
617
553
|
matchedFiles: sourceFiles,
|
|
618
554
|
matchedLines: citations.length,
|
|
619
|
-
brainCandidates: 0,
|
|
620
555
|
},
|
|
621
556
|
},
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
.replace(/\t/g, ' ')
|
|
641
|
-
.replace(/\s+/g, ' ')
|
|
642
|
-
.trim()
|
|
643
|
-
.slice(0, 220);
|
|
557
|
+
truth: {
|
|
558
|
+
status: 'grounded',
|
|
559
|
+
score: Math.min(0.98, 0.66 + Math.min(sourceFiles, 6) * 0.05),
|
|
560
|
+
reasons: ['Answer is grounded in local git history.'],
|
|
561
|
+
sourceCitations: citations.length,
|
|
562
|
+
sourceFiles,
|
|
563
|
+
minCitationsRequired: 1,
|
|
564
|
+
minFilesRequired: 1,
|
|
565
|
+
},
|
|
566
|
+
citations,
|
|
567
|
+
generatedAt: new Date().toISOString(),
|
|
568
|
+
stats: {
|
|
569
|
+
scannedFiles: 0,
|
|
570
|
+
matchedFiles: sourceFiles,
|
|
571
|
+
matchedLines: citations.length,
|
|
572
|
+
brainCandidates: 0,
|
|
573
|
+
},
|
|
574
|
+
};
|
|
644
575
|
}
|
|
645
|
-
function
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
576
|
+
function buildCommandRegistrationDeterministicAnswer(cwd, question, searchTerms, maxCitations) {
|
|
577
|
+
const profile = buildQueryProfile(searchTerms);
|
|
578
|
+
if (!profile.asksRegistration || !profile.commandFocus)
|
|
579
|
+
return null;
|
|
580
|
+
const commandName = profile.commandFocus;
|
|
581
|
+
const handlerName = `${commandName}Command`;
|
|
582
|
+
const directPattern = `\\.command\\(['"\`]${escapeRegExp(commandName)}['"\`]\\)`;
|
|
583
|
+
const handlerPattern = `\\b${escapeRegExp(handlerName)}\\b`;
|
|
584
|
+
const handlerCallPattern = `\\b(?:await\\s+)?${escapeRegExp(handlerName)}\\s*\\(`;
|
|
585
|
+
const rawMatches = [
|
|
586
|
+
...runRipgrepSearch(cwd, directPattern),
|
|
587
|
+
...runRipgrepSearch(cwd, handlerCallPattern),
|
|
588
|
+
...runRipgrepSearch(cwd, handlerPattern),
|
|
656
589
|
];
|
|
657
|
-
|
|
658
|
-
|
|
590
|
+
if (rawMatches.length === 0)
|
|
591
|
+
return null;
|
|
592
|
+
const dedup = new Map();
|
|
593
|
+
const directRegex = new RegExp(directPattern, 'i');
|
|
594
|
+
const handlerRegex = new RegExp(handlerPattern, 'i');
|
|
595
|
+
const handlerCallRegex = new RegExp(handlerCallPattern, 'i');
|
|
596
|
+
for (const match of rawMatches) {
|
|
597
|
+
if (isIgnoredSearchPath(match.path))
|
|
659
598
|
continue;
|
|
660
|
-
|
|
661
|
-
|
|
599
|
+
if (isLikelyDocumentationPath(match.path))
|
|
600
|
+
continue;
|
|
601
|
+
let score = 0.45;
|
|
602
|
+
if (directRegex.test(match.snippet))
|
|
603
|
+
score += 7.2;
|
|
604
|
+
if (handlerCallRegex.test(match.snippet))
|
|
605
|
+
score += 3.2;
|
|
606
|
+
if (handlerRegex.test(match.snippet))
|
|
607
|
+
score += 1.4;
|
|
608
|
+
const pathLower = match.path.toLowerCase();
|
|
609
|
+
if (pathLower === 'packages/cli/src/index.ts')
|
|
610
|
+
score += 4.6;
|
|
611
|
+
if (pathLower.includes(`/commands/${commandName}.`))
|
|
612
|
+
score += 2.8;
|
|
613
|
+
if (pathLower.includes('/commands/'))
|
|
614
|
+
score += 0.55;
|
|
615
|
+
if (/\.action\(/.test(match.snippet))
|
|
616
|
+
score += 1.1;
|
|
617
|
+
if (/^\s*import\s+/.test(match.snippet))
|
|
618
|
+
score += 0.7;
|
|
619
|
+
if (score <= 0)
|
|
620
|
+
continue;
|
|
621
|
+
const key = `${match.path}:${match.line}`;
|
|
622
|
+
const next = {
|
|
623
|
+
path: match.path,
|
|
624
|
+
line: match.line,
|
|
625
|
+
snippet: match.snippet,
|
|
626
|
+
term: commandName,
|
|
627
|
+
score,
|
|
628
|
+
matchedTerms: [commandName],
|
|
629
|
+
};
|
|
630
|
+
const existing = dedup.get(key);
|
|
631
|
+
if (!existing || next.score > existing.score) {
|
|
632
|
+
dedup.set(key, next);
|
|
633
|
+
}
|
|
662
634
|
}
|
|
663
|
-
const
|
|
664
|
-
if (
|
|
665
|
-
return;
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
635
|
+
const scored = [...dedup.values()].sort((a, b) => b.score - a.score);
|
|
636
|
+
if (scored.length === 0)
|
|
637
|
+
return null;
|
|
638
|
+
const citations = selectTopCitations(scored, Math.min(maxCitations, 10), searchTerms);
|
|
639
|
+
if (citations.length === 0)
|
|
640
|
+
return null;
|
|
641
|
+
const directCitations = citations.filter((citation) => directRegex.test(citation.snippet));
|
|
642
|
+
const sourceFiles = new Set(citations.map((citation) => citation.path)).size;
|
|
643
|
+
const topFiles = [...new Set(citations.map((citation) => citation.path))].slice(0, 5);
|
|
644
|
+
const answerLines = [];
|
|
645
|
+
if (directCitations.length > 0) {
|
|
646
|
+
const first = directCitations[0];
|
|
647
|
+
answerLines.push(`The \`${commandName}\` command is registered at ${first.path}:${first.line}.`);
|
|
648
|
+
answerLines.push('Supporting wiring references:');
|
|
649
|
+
for (const citation of citations.slice(0, 5)) {
|
|
650
|
+
answerLines.push(` • ${citation.path}:${citation.line} — ${normalizeSnippet(citation.snippet)}`);
|
|
674
651
|
}
|
|
675
652
|
}
|
|
653
|
+
else {
|
|
654
|
+
answerLines.push(`I found related wiring for \`${commandName}\`, but no direct \`.command('${commandName}')\` line yet.`);
|
|
655
|
+
answerLines.push('Closest references:');
|
|
656
|
+
for (const citation of citations.slice(0, 5)) {
|
|
657
|
+
answerLines.push(` • ${citation.path}:${citation.line} — ${normalizeSnippet(citation.snippet)}`);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
const truthStatus = directCitations.length > 0 ? 'grounded' : 'insufficient';
|
|
661
|
+
const truthScore = directCitations.length > 0
|
|
662
|
+
? Math.min(0.98, 0.74 + Math.min(citations.length, 6) * 0.03)
|
|
663
|
+
: 0.33;
|
|
664
|
+
return {
|
|
665
|
+
question,
|
|
666
|
+
questionNormalized: searchTerms.normalizedQuestion,
|
|
667
|
+
mode: 'search',
|
|
668
|
+
answer: answerLines.join('\n'),
|
|
669
|
+
findings: [
|
|
670
|
+
`Direct registration hits: ${directCitations.length}.`,
|
|
671
|
+
`Total wiring citations: ${citations.length} across ${sourceFiles} file(s).`,
|
|
672
|
+
],
|
|
673
|
+
confidence: truthStatus === 'grounded' ? 'high' : 'low',
|
|
674
|
+
proof: {
|
|
675
|
+
topFiles,
|
|
676
|
+
evidenceCount: citations.length,
|
|
677
|
+
coverage: {
|
|
678
|
+
sourceCitations: citations.length,
|
|
679
|
+
sourceFiles,
|
|
680
|
+
matchedFiles: sourceFiles,
|
|
681
|
+
matchedLines: citations.length,
|
|
682
|
+
},
|
|
683
|
+
},
|
|
684
|
+
truth: {
|
|
685
|
+
status: truthStatus,
|
|
686
|
+
score: Number(truthScore.toFixed(2)),
|
|
687
|
+
reasons: truthStatus === 'grounded'
|
|
688
|
+
? ['Command registration is grounded in direct command declaration evidence.']
|
|
689
|
+
: [`No direct \`.command('${commandName}')\` declaration was found.`],
|
|
690
|
+
sourceCitations: citations.length,
|
|
691
|
+
sourceFiles,
|
|
692
|
+
minCitationsRequired: 1,
|
|
693
|
+
minFilesRequired: 1,
|
|
694
|
+
},
|
|
695
|
+
citations,
|
|
696
|
+
generatedAt: new Date().toISOString(),
|
|
697
|
+
stats: {
|
|
698
|
+
scannedFiles: 0,
|
|
699
|
+
matchedFiles: sourceFiles,
|
|
700
|
+
matchedLines: citations.length,
|
|
701
|
+
brainCandidates: 0,
|
|
702
|
+
},
|
|
703
|
+
};
|
|
676
704
|
}
|
|
677
|
-
function
|
|
678
|
-
const
|
|
679
|
-
|
|
705
|
+
function buildCommandInventoryDeterministicAnswer(cwd, question, searchTerms, maxCitations) {
|
|
706
|
+
const normalized = searchTerms.normalizedQuestion;
|
|
707
|
+
const asksInventory = /\b(list|show|what|which)\b/.test(normalized) &&
|
|
708
|
+
/\bcommands?\b/.test(normalized) &&
|
|
709
|
+
/\b(neurcode|cli|available|all)\b/.test(normalized);
|
|
710
|
+
if (!asksInventory)
|
|
680
711
|
return null;
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
}
|
|
687
|
-
catch {
|
|
712
|
+
const matches = runRipgrepSearch(cwd, `\\.command\\(['"\`][a-z][a-z0-9-]*['"\`]\\)`)
|
|
713
|
+
.filter((row) => !isIgnoredSearchPath(row.path))
|
|
714
|
+
.filter((row) => !isLikelyDocumentationPath(row.path))
|
|
715
|
+
.filter((row) => normalizeFilePath(row.path) === 'packages/cli/src/index.ts');
|
|
716
|
+
if (matches.length === 0)
|
|
688
717
|
return null;
|
|
718
|
+
const byCommand = new Map();
|
|
719
|
+
for (const row of matches.sort((a, b) => a.path.localeCompare(b.path) || a.line - b.line)) {
|
|
720
|
+
const match = row.snippet.match(/\.command\(['"`]([a-z][a-z0-9-]*)['"`]\)/i);
|
|
721
|
+
if (!match?.[1])
|
|
722
|
+
continue;
|
|
723
|
+
const command = match[1].trim().toLowerCase();
|
|
724
|
+
if (!command || byCommand.has(command))
|
|
725
|
+
continue;
|
|
726
|
+
byCommand.set(command, {
|
|
727
|
+
path: row.path,
|
|
728
|
+
line: row.line,
|
|
729
|
+
term: command,
|
|
730
|
+
snippet: row.snippet,
|
|
731
|
+
});
|
|
689
732
|
}
|
|
733
|
+
const commands = [...byCommand.entries()]
|
|
734
|
+
.sort((a, b) => a[0].localeCompare(b[0]));
|
|
735
|
+
if (commands.length === 0)
|
|
736
|
+
return null;
|
|
737
|
+
const citations = commands
|
|
738
|
+
.slice(0, Math.max(6, Math.min(maxCitations, 30)))
|
|
739
|
+
.map(([, citation]) => citation);
|
|
740
|
+
const sourceFiles = new Set(citations.map((citation) => citation.path)).size;
|
|
741
|
+
const topFiles = [...new Set(citations.map((citation) => citation.path))].slice(0, 5);
|
|
742
|
+
const answerLines = [
|
|
743
|
+
`I found ${commands.length} Neurcode CLI command registrations in this repository.`,
|
|
744
|
+
'Top commands and registration points:',
|
|
745
|
+
...commands.slice(0, 14).map(([command, citation]) => ` • ${command} — ${citation.path}:${citation.line}`),
|
|
746
|
+
'',
|
|
747
|
+
'If you want, ask `where is <command> command registered` for wiring details.',
|
|
748
|
+
];
|
|
749
|
+
return {
|
|
750
|
+
question,
|
|
751
|
+
questionNormalized: normalized,
|
|
752
|
+
mode: 'search',
|
|
753
|
+
answer: answerLines.join('\n'),
|
|
754
|
+
findings: [
|
|
755
|
+
`Detected ${commands.length} command registration(s).`,
|
|
756
|
+
`Evidence spans ${sourceFiles} file(s).`,
|
|
757
|
+
],
|
|
758
|
+
confidence: 'high',
|
|
759
|
+
proof: {
|
|
760
|
+
topFiles,
|
|
761
|
+
evidenceCount: citations.length,
|
|
762
|
+
coverage: {
|
|
763
|
+
sourceCitations: citations.length,
|
|
764
|
+
sourceFiles,
|
|
765
|
+
matchedFiles: sourceFiles,
|
|
766
|
+
matchedLines: citations.length,
|
|
767
|
+
},
|
|
768
|
+
},
|
|
769
|
+
truth: {
|
|
770
|
+
status: 'grounded',
|
|
771
|
+
score: 0.93,
|
|
772
|
+
reasons: ['Command list is grounded in direct `.command(...)` declarations.'],
|
|
773
|
+
sourceCitations: citations.length,
|
|
774
|
+
sourceFiles,
|
|
775
|
+
minCitationsRequired: 1,
|
|
776
|
+
minFilesRequired: 1,
|
|
777
|
+
},
|
|
778
|
+
citations,
|
|
779
|
+
generatedAt: new Date().toISOString(),
|
|
780
|
+
stats: {
|
|
781
|
+
scannedFiles: 0,
|
|
782
|
+
matchedFiles: sourceFiles,
|
|
783
|
+
matchedLines: citations.length,
|
|
784
|
+
brainCandidates: 0,
|
|
785
|
+
},
|
|
786
|
+
};
|
|
690
787
|
}
|
|
691
|
-
function
|
|
692
|
-
const
|
|
693
|
-
if (!(0, fs_1.existsSync)(
|
|
694
|
-
return
|
|
788
|
+
function collectCommandSubcommandBlockEvidence(cwd, commandPath, subcommand, searchTerms, maxCitations) {
|
|
789
|
+
const fullPath = (0, path_1.join)(cwd, commandPath);
|
|
790
|
+
if (!(0, fs_1.existsSync)(fullPath))
|
|
791
|
+
return null;
|
|
695
792
|
let content = '';
|
|
696
793
|
try {
|
|
697
|
-
content = (0, fs_1.readFileSync)(
|
|
794
|
+
content = (0, fs_1.readFileSync)(fullPath, 'utf-8');
|
|
698
795
|
}
|
|
699
796
|
catch {
|
|
700
|
-
return
|
|
797
|
+
return null;
|
|
701
798
|
}
|
|
702
|
-
const
|
|
703
|
-
const
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
799
|
+
const lines = content.split(/\r?\n/);
|
|
800
|
+
const subcommandDeclRegex = new RegExp(`\\.command\\(['"\`]${escapeRegExp(subcommand)}(?:\\s+\\[[^\\]]+\\])?['"\`]\\)`, 'i');
|
|
801
|
+
const anchorIdx = lines.findIndex((line) => subcommandDeclRegex.test(line));
|
|
802
|
+
if (anchorIdx < 0)
|
|
803
|
+
return null;
|
|
804
|
+
let endIdx = lines.length;
|
|
805
|
+
for (let i = anchorIdx + 1; i < lines.length; i++) {
|
|
806
|
+
if (/^\s*\.command\(['"`][a-z][a-z0-9-]*(?:\s+\[[^\]]+\])?['"`]\)/i.test(lines[i])) {
|
|
807
|
+
endIdx = i;
|
|
808
|
+
break;
|
|
712
809
|
}
|
|
713
810
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
811
|
+
const relevantTerms = [...searchTerms.highSignalTerms, ...searchTerms.identifiers]
|
|
812
|
+
.map((term) => (0, plan_cache_1.normalizeIntent)(term))
|
|
813
|
+
.filter((term) => term.length >= 3 && !LOW_SIGNAL_TERMS.has(term) && term !== subcommand)
|
|
814
|
+
.slice(0, 14);
|
|
815
|
+
let focusRegex = null;
|
|
816
|
+
const focusPattern = buildPatternFromTerms(relevantTerms);
|
|
817
|
+
if (focusPattern) {
|
|
818
|
+
try {
|
|
819
|
+
focusRegex = new RegExp(focusPattern, 'i');
|
|
820
|
+
}
|
|
821
|
+
catch {
|
|
822
|
+
focusRegex = null;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
const citations = [];
|
|
826
|
+
const seen = new Set();
|
|
827
|
+
const pushLine = (lineIdx, term) => {
|
|
828
|
+
if (lineIdx < 0 || lineIdx >= lines.length)
|
|
829
|
+
return;
|
|
830
|
+
const snippet = normalizeSnippet(lines[lineIdx] || '');
|
|
831
|
+
if (!snippet)
|
|
832
|
+
return;
|
|
833
|
+
const key = `${commandPath}:${lineIdx + 1}`;
|
|
834
|
+
if (seen.has(key))
|
|
835
|
+
return;
|
|
836
|
+
seen.add(key);
|
|
837
|
+
citations.push({
|
|
838
|
+
path: commandPath,
|
|
839
|
+
line: lineIdx + 1,
|
|
840
|
+
term,
|
|
841
|
+
snippet,
|
|
842
|
+
});
|
|
843
|
+
};
|
|
844
|
+
pushLine(anchorIdx, subcommand);
|
|
845
|
+
for (let i = anchorIdx; i < endIdx; i++) {
|
|
846
|
+
if (citations.length >= maxCitations)
|
|
847
|
+
break;
|
|
848
|
+
const snippet = normalizeSnippet(lines[i] || '');
|
|
849
|
+
if (!snippet)
|
|
718
850
|
continue;
|
|
719
|
-
const
|
|
720
|
-
|
|
851
|
+
const isAction = /\.action\(/.test(snippet);
|
|
852
|
+
const isDescription = /\.description\(/.test(snippet);
|
|
853
|
+
const hasFocus = focusRegex ? focusRegex.test(snippet) : false;
|
|
854
|
+
const hasFlowCall = /\b(?:get|load|read|list|compute|count|find|search|refresh|record|write|print|format|clear|delete|close|set)[A-Za-z0-9_]*\s*\(/.test(snippet);
|
|
855
|
+
const hasStateSignal = /\b(cache|memory|context|scope|stats|entries|bytes|payload|store|index)\b/i.test(snippet);
|
|
856
|
+
const hasOutputSignal = /\bJSON\.stringify\b|\bconsole\.(?:log|warn|error)\b|^\s*return\b/.test(snippet);
|
|
857
|
+
const hasOptionSignal = /\.option\(/.test(snippet);
|
|
858
|
+
if (!isAction && !isDescription && !hasFocus && !hasFlowCall && !hasStateSignal && !hasOutputSignal && !hasOptionSignal) {
|
|
721
859
|
continue;
|
|
722
|
-
|
|
860
|
+
}
|
|
861
|
+
pushLine(i, hasStateSignal ? 'state' : undefined);
|
|
862
|
+
}
|
|
863
|
+
if (!citations.some((citation) => /\.action\(/.test(citation.snippet))) {
|
|
864
|
+
for (let i = anchorIdx; i < endIdx; i++) {
|
|
865
|
+
if (/\.action\(/.test(lines[i] || '')) {
|
|
866
|
+
pushLine(i, 'action');
|
|
867
|
+
break;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
723
870
|
}
|
|
724
|
-
|
|
871
|
+
if (citations.length === 0)
|
|
872
|
+
return null;
|
|
873
|
+
return {
|
|
874
|
+
anchorLine: anchorIdx + 1,
|
|
875
|
+
citations: citations.slice(0, maxCitations),
|
|
876
|
+
};
|
|
725
877
|
}
|
|
726
|
-
function
|
|
727
|
-
const lines = markdown.split(/\r?\n/);
|
|
728
|
-
let start = lines.findIndex((line) => headingPattern.test(line));
|
|
729
|
-
if (start < 0)
|
|
730
|
-
return [];
|
|
878
|
+
function extractCommandOperationNames(citations) {
|
|
731
879
|
const out = [];
|
|
732
880
|
const seen = new Set();
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
}
|
|
746
|
-
else {
|
|
747
|
-
const simple = line.match(/^- (.+)$/);
|
|
748
|
-
if (!simple?.[1])
|
|
881
|
+
const ignored = new Set([
|
|
882
|
+
'if', 'for', 'while', 'switch', 'catch', 'return',
|
|
883
|
+
'async', 'argument',
|
|
884
|
+
'console', 'log', 'warn', 'error', 'JSON', 'Promise',
|
|
885
|
+
'Map', 'Set', 'Array', 'Object', 'String', 'Number', 'Date',
|
|
886
|
+
'command', 'description', 'option', 'action',
|
|
887
|
+
'filter', 'map', 'slice', 'sort', 'join', 'push',
|
|
888
|
+
]);
|
|
889
|
+
for (const citation of citations) {
|
|
890
|
+
for (const match of citation.snippet.matchAll(/\b([A-Za-z_][A-Za-z0-9_]*)\s*\(/g)) {
|
|
891
|
+
const operation = match[1];
|
|
892
|
+
if (!operation || ignored.has(operation))
|
|
749
893
|
continue;
|
|
750
|
-
|
|
751
|
-
if (!text)
|
|
894
|
+
if (operation.length < 3)
|
|
752
895
|
continue;
|
|
753
|
-
const
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
896
|
+
const looksLikeCodeOperation = /[A-Z_]/.test(operation) ||
|
|
897
|
+
/^(get|load|read|list|compute|count|find|search|refresh|record|write|print|format|clear|delete|close|set|diagnose|resolve|build|create|update)/i.test(operation);
|
|
898
|
+
if (!looksLikeCodeOperation)
|
|
899
|
+
continue;
|
|
900
|
+
if (seen.has(operation))
|
|
901
|
+
continue;
|
|
902
|
+
seen.add(operation);
|
|
903
|
+
out.push(operation);
|
|
904
|
+
if (out.length >= 10)
|
|
905
|
+
return out;
|
|
758
906
|
}
|
|
759
|
-
if (out.length >= limit)
|
|
760
|
-
break;
|
|
761
907
|
}
|
|
762
908
|
return out;
|
|
763
909
|
}
|
|
764
|
-
function
|
|
765
|
-
const
|
|
766
|
-
if (!
|
|
910
|
+
function buildCommandSubcommandFlowDeterministicAnswer(cwd, question, searchTerms, maxCitations) {
|
|
911
|
+
const profile = buildQueryProfile(searchTerms);
|
|
912
|
+
if (!profile.commandFocus || !profile.subcommandFocus)
|
|
913
|
+
return null;
|
|
914
|
+
if (profile.asksRegistration || profile.asksList)
|
|
915
|
+
return null;
|
|
916
|
+
const asksFlowLike = profile.asksHow ||
|
|
917
|
+
/\b(flow|trace|internals?|compute|works?|working|steps?|logic|behavior|behaviour)\b/.test(searchTerms.normalizedQuestion);
|
|
918
|
+
if (!asksFlowLike)
|
|
919
|
+
return null;
|
|
920
|
+
const commandName = profile.commandFocus;
|
|
921
|
+
const subcommand = profile.subcommandFocus;
|
|
922
|
+
const commandPath = `packages/cli/src/commands/${commandName}.ts`;
|
|
923
|
+
const blockEvidence = collectCommandSubcommandBlockEvidence(cwd, commandPath, subcommand, searchTerms, Math.min(Math.max(maxCitations, 8), 16));
|
|
924
|
+
if (!blockEvidence || blockEvidence.citations.length === 0)
|
|
925
|
+
return null;
|
|
926
|
+
const registrationCitations = runRipgrepSearch(cwd, `\\.command\\(['"\`]${escapeRegExp(commandName)}['"\`]\\)`)
|
|
927
|
+
.filter((hit) => normalizeFilePath(hit.path) === 'packages/cli/src/index.ts')
|
|
928
|
+
.slice(0, 1)
|
|
929
|
+
.map((hit) => ({
|
|
930
|
+
path: hit.path,
|
|
931
|
+
line: hit.line,
|
|
932
|
+
term: commandName,
|
|
933
|
+
snippet: hit.snippet,
|
|
934
|
+
}));
|
|
935
|
+
const subcommandDeclRegex = new RegExp(`\\.command\\(['"\`]${escapeRegExp(subcommand)}(?:\\s+\\[[^\\]]+\\])?['"\`]\\)`, 'i');
|
|
936
|
+
const scored = new Map();
|
|
937
|
+
for (const citation of registrationCitations) {
|
|
938
|
+
const key = `${citation.path}:${citation.line}`;
|
|
939
|
+
scored.set(key, {
|
|
940
|
+
...citation,
|
|
941
|
+
score: 7.2,
|
|
942
|
+
matchedTerms: [commandName],
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
for (const citation of blockEvidence.citations) {
|
|
946
|
+
const key = `${citation.path}:${citation.line}`;
|
|
947
|
+
let score = 3.8;
|
|
948
|
+
if (subcommandDeclRegex.test(citation.snippet))
|
|
949
|
+
score += 6.2;
|
|
950
|
+
if (/\.action\(/.test(citation.snippet))
|
|
951
|
+
score += 4.2;
|
|
952
|
+
if (/\.description\(/.test(citation.snippet))
|
|
953
|
+
score += 1.0;
|
|
954
|
+
if (/\b(cache|memory|context|scope|stats|entries|bytes|payload|store|index)\b/i.test(citation.snippet)) {
|
|
955
|
+
score += 2.4;
|
|
956
|
+
}
|
|
957
|
+
if (/\b(?:get|load|read|list|compute|count|find|search|refresh|record|write|print|format|clear|delete|close|set)[A-Za-z0-9_]*\s*\(/.test(citation.snippet)) {
|
|
958
|
+
score += 1.8;
|
|
959
|
+
}
|
|
960
|
+
if (/\bJSON\.stringify\b|\bconsole\.(?:log|warn|error)\b|^\s*return\b/.test(citation.snippet)) {
|
|
961
|
+
score += 0.9;
|
|
962
|
+
}
|
|
963
|
+
scored.set(key, {
|
|
964
|
+
...citation,
|
|
965
|
+
score,
|
|
966
|
+
matchedTerms: [commandName, subcommand],
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
const citations = [...scored.values()]
|
|
970
|
+
.sort((a, b) => b.score - a.score)
|
|
971
|
+
.slice(0, Math.min(Math.max(maxCitations, 8), 16))
|
|
972
|
+
.map(({ path, line, snippet, term }) => ({ path, line, snippet, term }));
|
|
973
|
+
if (citations.length === 0)
|
|
974
|
+
return null;
|
|
975
|
+
const hasSubcommandDeclaration = citations.some((citation) => subcommandDeclRegex.test(citation.snippet));
|
|
976
|
+
const hasActionBlock = citations.some((citation) => /\.action\(/.test(citation.snippet));
|
|
977
|
+
if (!hasSubcommandDeclaration)
|
|
978
|
+
return null;
|
|
979
|
+
const sourceFiles = new Set(citations.map((citation) => citation.path)).size;
|
|
980
|
+
const topFiles = [...new Set(citations.map((citation) => citation.path))].slice(0, 5);
|
|
981
|
+
const operations = extractCommandOperationNames(citations.filter((citation) => citation.path === commandPath));
|
|
982
|
+
const answerLines = [
|
|
983
|
+
`Short answer: \`${commandName} ${subcommand}\` is implemented in ${commandPath}:${blockEvidence.anchorLine}.`,
|
|
984
|
+
'',
|
|
985
|
+
'What I verified in code:',
|
|
986
|
+
...citations.slice(0, 8).map((citation) => ` • ${explainEvidenceCitation(citation)}`),
|
|
987
|
+
];
|
|
988
|
+
if (operations.length > 0) {
|
|
989
|
+
answerLines.push('');
|
|
990
|
+
answerLines.push('Key operations in this flow:');
|
|
991
|
+
for (const operation of operations.slice(0, 8)) {
|
|
992
|
+
answerLines.push(` • ${operation}()`);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
answerLines.push('');
|
|
996
|
+
answerLines.push(`If you want, I can trace control flow inside ${commandPath} line-by-line.`);
|
|
997
|
+
const truthScore = Math.min(0.97, 0.73 +
|
|
998
|
+
(hasSubcommandDeclaration ? 0.1 : 0) +
|
|
999
|
+
(hasActionBlock ? 0.08 : 0) +
|
|
1000
|
+
Math.min(citations.length * 0.01, 0.06));
|
|
1001
|
+
return {
|
|
1002
|
+
question,
|
|
1003
|
+
questionNormalized: searchTerms.normalizedQuestion,
|
|
1004
|
+
mode: 'search',
|
|
1005
|
+
answer: answerLines.join('\n'),
|
|
1006
|
+
findings: [
|
|
1007
|
+
`Command focus: ${commandName}, subcommand focus: ${subcommand}.`,
|
|
1008
|
+
`Subcommand block anchor: ${commandPath}:${blockEvidence.anchorLine}.`,
|
|
1009
|
+
`Evidence lines: ${citations.length} across ${sourceFiles} file(s).`,
|
|
1010
|
+
],
|
|
1011
|
+
confidence: truthScore >= 0.9 ? 'high' : 'medium',
|
|
1012
|
+
proof: {
|
|
1013
|
+
topFiles,
|
|
1014
|
+
evidenceCount: citations.length,
|
|
1015
|
+
coverage: {
|
|
1016
|
+
sourceCitations: citations.length,
|
|
1017
|
+
sourceFiles,
|
|
1018
|
+
matchedFiles: sourceFiles,
|
|
1019
|
+
matchedLines: citations.length,
|
|
1020
|
+
},
|
|
1021
|
+
},
|
|
1022
|
+
truth: {
|
|
1023
|
+
status: hasActionBlock ? 'grounded' : 'insufficient',
|
|
1024
|
+
score: Number(truthScore.toFixed(2)),
|
|
1025
|
+
reasons: hasActionBlock
|
|
1026
|
+
? ['Command subcommand flow is grounded in direct command action block evidence.']
|
|
1027
|
+
: ['Subcommand declaration found but action block evidence is limited.'],
|
|
1028
|
+
sourceCitations: citations.length,
|
|
1029
|
+
sourceFiles,
|
|
1030
|
+
minCitationsRequired: 2,
|
|
1031
|
+
minFilesRequired: 1,
|
|
1032
|
+
},
|
|
1033
|
+
citations,
|
|
1034
|
+
generatedAt: new Date().toISOString(),
|
|
1035
|
+
stats: {
|
|
1036
|
+
scannedFiles: 0,
|
|
1037
|
+
matchedFiles: sourceFiles,
|
|
1038
|
+
matchedLines: citations.length,
|
|
1039
|
+
brainCandidates: 0,
|
|
1040
|
+
},
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
function buildAskCacheFlowDeterministicAnswer(cwd, question, searchTerms, maxCitations) {
|
|
1044
|
+
const normalized = searchTerms.normalizedQuestion;
|
|
1045
|
+
const asksAskCacheFlow = /\bask\b/.test(normalized) &&
|
|
1046
|
+
/\bcache\b/.test(normalized) &&
|
|
1047
|
+
/\b(how|flow|work|works|working|exact|steps|internals?|mechanism)\b/.test(normalized);
|
|
1048
|
+
if (!asksAskCacheFlow)
|
|
1049
|
+
return null;
|
|
1050
|
+
const probes = [
|
|
1051
|
+
{ tag: 'hash', pattern: 'computeAskQuestionHash\\(' },
|
|
1052
|
+
{ tag: 'key', pattern: 'computeAskCacheKey\\(' },
|
|
1053
|
+
{ tag: 'exact', pattern: 'readCachedAsk\\(' },
|
|
1054
|
+
{ tag: 'near', pattern: 'findNearCachedAsk\\(' },
|
|
1055
|
+
{ tag: 'drift', pattern: 'getChangedWorkingTreePaths\\(' },
|
|
1056
|
+
{ tag: 'write', pattern: 'writeCachedAsk\\(' },
|
|
1057
|
+
];
|
|
1058
|
+
const raw = [];
|
|
1059
|
+
for (const probe of probes) {
|
|
1060
|
+
const hits = runRipgrepSearch(cwd, probe.pattern);
|
|
1061
|
+
for (const hit of hits) {
|
|
1062
|
+
const normalizedPath = normalizeFilePath(hit.path);
|
|
1063
|
+
if (normalizedPath !== 'packages/cli/src/commands/ask.ts' && normalizedPath !== 'packages/cli/src/utils/ask-cache.ts') {
|
|
1064
|
+
continue;
|
|
1065
|
+
}
|
|
1066
|
+
raw.push({ ...hit, tag: probe.tag });
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
if (raw.length === 0)
|
|
1070
|
+
return null;
|
|
1071
|
+
const scoreByTag = {
|
|
1072
|
+
hash: 2.3,
|
|
1073
|
+
key: 2.0,
|
|
1074
|
+
exact: 4.8,
|
|
1075
|
+
near: 4.2,
|
|
1076
|
+
drift: 2.6,
|
|
1077
|
+
write: 4.6,
|
|
1078
|
+
};
|
|
1079
|
+
const dedup = new Map();
|
|
1080
|
+
for (const hit of raw) {
|
|
1081
|
+
const key = `${hit.path}:${hit.line}`;
|
|
1082
|
+
const score = (scoreByTag[hit.tag] || 0) + (hit.path.endsWith('/commands/ask.ts') ? 1.1 : 0.35);
|
|
1083
|
+
const next = {
|
|
1084
|
+
path: hit.path,
|
|
1085
|
+
line: hit.line,
|
|
1086
|
+
snippet: hit.snippet,
|
|
1087
|
+
term: hit.tag,
|
|
1088
|
+
score,
|
|
1089
|
+
matchedTerms: [hit.tag],
|
|
1090
|
+
};
|
|
1091
|
+
const existing = dedup.get(key);
|
|
1092
|
+
if (!existing || existing.score < next.score) {
|
|
1093
|
+
dedup.set(key, next);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
const scored = [...dedup.values()].sort((a, b) => b.score - a.score);
|
|
1097
|
+
if (scored.length === 0)
|
|
1098
|
+
return null;
|
|
1099
|
+
const citations = selectTopCitations(scored, Math.min(Math.max(maxCitations, 8), 16), searchTerms);
|
|
1100
|
+
if (citations.length === 0)
|
|
1101
|
+
return null;
|
|
1102
|
+
const hasExact = citations.some((citation) => /readCachedAsk\s*\(/.test(citation.snippet));
|
|
1103
|
+
const hasNear = citations.some((citation) => /findNearCachedAsk\s*\(/.test(citation.snippet));
|
|
1104
|
+
const hasWrite = citations.some((citation) => /writeCachedAsk\s*\(/.test(citation.snippet));
|
|
1105
|
+
const hasHash = citations.some((citation) => /computeAskQuestionHash\s*\(|computeAskCacheKey\s*\(/.test(citation.snippet));
|
|
1106
|
+
const hasDrift = citations.some((citation) => /getChangedWorkingTreePaths\s*\(/.test(citation.snippet));
|
|
1107
|
+
const verifiedSteps = [];
|
|
1108
|
+
if (hasHash)
|
|
1109
|
+
verifiedSteps.push('Normalize question + context into deterministic hash and cache key.');
|
|
1110
|
+
if (hasExact)
|
|
1111
|
+
verifiedSteps.push('Attempt exact cache hit first (`readCachedAsk`).');
|
|
1112
|
+
if (hasNear)
|
|
1113
|
+
verifiedSteps.push('On exact miss, attempt semantic near-hit reuse (`findNearCachedAsk`).');
|
|
1114
|
+
if (hasDrift)
|
|
1115
|
+
verifiedSteps.push('Near-hit safety checks include working-tree drift via changed paths.');
|
|
1116
|
+
if (hasWrite)
|
|
1117
|
+
verifiedSteps.push('On fresh retrieval, write the result back for future asks (`writeCachedAsk`).');
|
|
1118
|
+
const sourceFiles = new Set(citations.map((citation) => citation.path)).size;
|
|
1119
|
+
const topFiles = [...new Set(citations.map((citation) => citation.path))].slice(0, 5);
|
|
1120
|
+
const answerLines = [
|
|
1121
|
+
'Short answer: ask cache uses an exact-hit -> near-hit -> fresh-retrieval -> write-back flow.',
|
|
1122
|
+
'',
|
|
1123
|
+
'Verified implementation steps:',
|
|
1124
|
+
...verifiedSteps.map((step, idx) => ` ${idx + 1}. ${step}`),
|
|
1125
|
+
'',
|
|
1126
|
+
'Grounding evidence:',
|
|
1127
|
+
...citations.slice(0, 8).map((citation) => ` • ${citation.path}:${citation.line} — ${normalizeSnippet(citation.snippet)}`),
|
|
1128
|
+
];
|
|
1129
|
+
const truthScore = (hasExact && hasNear && hasWrite) ? 0.94 : 0.66;
|
|
1130
|
+
return {
|
|
1131
|
+
question,
|
|
1132
|
+
questionNormalized: normalized,
|
|
1133
|
+
mode: 'search',
|
|
1134
|
+
answer: answerLines.join('\n'),
|
|
1135
|
+
findings: [
|
|
1136
|
+
`Verified cache-flow steps: ${verifiedSteps.length}.`,
|
|
1137
|
+
`Evidence spans ${sourceFiles} file(s).`,
|
|
1138
|
+
],
|
|
1139
|
+
confidence: truthScore >= 0.9 ? 'high' : 'medium',
|
|
1140
|
+
proof: {
|
|
1141
|
+
topFiles,
|
|
1142
|
+
evidenceCount: citations.length,
|
|
1143
|
+
coverage: {
|
|
1144
|
+
sourceCitations: citations.length,
|
|
1145
|
+
sourceFiles,
|
|
1146
|
+
matchedFiles: sourceFiles,
|
|
1147
|
+
matchedLines: citations.length,
|
|
1148
|
+
},
|
|
1149
|
+
},
|
|
1150
|
+
truth: {
|
|
1151
|
+
status: 'grounded',
|
|
1152
|
+
score: truthScore,
|
|
1153
|
+
reasons: ['Ask cache flow is grounded in direct cache function calls in the CLI implementation.'],
|
|
1154
|
+
sourceCitations: citations.length,
|
|
1155
|
+
sourceFiles,
|
|
1156
|
+
minCitationsRequired: 2,
|
|
1157
|
+
minFilesRequired: 1,
|
|
1158
|
+
},
|
|
1159
|
+
citations,
|
|
1160
|
+
generatedAt: new Date().toISOString(),
|
|
1161
|
+
stats: {
|
|
1162
|
+
scannedFiles: 0,
|
|
1163
|
+
matchedFiles: sourceFiles,
|
|
1164
|
+
matchedLines: citations.length,
|
|
1165
|
+
brainCandidates: 0,
|
|
1166
|
+
},
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
function buildPatternFromTerms(terms) {
|
|
1170
|
+
const parts = terms
|
|
1171
|
+
.map((term) => term.trim())
|
|
1172
|
+
.filter((term) => term.length >= 2)
|
|
1173
|
+
.sort((a, b) => b.length - a.length)
|
|
1174
|
+
.slice(0, 16)
|
|
1175
|
+
.map((term) => {
|
|
1176
|
+
const escaped = escapeRegExp(term);
|
|
1177
|
+
if (/^[a-z0-9_]+$/i.test(term)) {
|
|
1178
|
+
return `\\b${escaped}\\b`;
|
|
1179
|
+
}
|
|
1180
|
+
if (term.includes(' ')) {
|
|
1181
|
+
return escaped.replace(/\\\s+/g, '\\s+');
|
|
1182
|
+
}
|
|
1183
|
+
return escaped;
|
|
1184
|
+
});
|
|
1185
|
+
if (parts.length === 0)
|
|
1186
|
+
return '';
|
|
1187
|
+
return `(?:${parts.join('|')})`;
|
|
1188
|
+
}
|
|
1189
|
+
function parseRgLine(line) {
|
|
1190
|
+
const match = line.match(/^(.*?):(\d+):(.*)$/);
|
|
1191
|
+
if (!match)
|
|
1192
|
+
return null;
|
|
1193
|
+
const rawPath = normalizeFilePath(match[1]);
|
|
1194
|
+
if (isIgnoredSearchPath(rawPath))
|
|
1195
|
+
return null;
|
|
1196
|
+
const lineNumber = Number(match[2]);
|
|
1197
|
+
if (!rawPath || !Number.isFinite(lineNumber) || lineNumber <= 0)
|
|
1198
|
+
return null;
|
|
1199
|
+
return {
|
|
1200
|
+
path: rawPath,
|
|
1201
|
+
line: lineNumber,
|
|
1202
|
+
snippet: normalizeSnippet(match[3] || ''),
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
function runRipgrepSearch(cwd, pattern) {
|
|
1206
|
+
if (!pattern)
|
|
1207
|
+
return [];
|
|
1208
|
+
const args = [
|
|
1209
|
+
'--line-number',
|
|
1210
|
+
'--no-heading',
|
|
1211
|
+
'--color', 'never',
|
|
1212
|
+
'--max-count', String(RG_MAX_MATCHES),
|
|
1213
|
+
'--max-columns', '400',
|
|
1214
|
+
'--smart-case',
|
|
1215
|
+
'--hidden',
|
|
1216
|
+
'--glob', '!**/node_modules/**',
|
|
1217
|
+
'--glob', '!**/.git/**',
|
|
1218
|
+
'--glob', '!**/dist/**',
|
|
1219
|
+
'--glob', '!**/build/**',
|
|
1220
|
+
'--glob', '!**/out/**',
|
|
1221
|
+
'--glob', '!**/.next/**',
|
|
1222
|
+
'--glob', '!**/coverage/**',
|
|
1223
|
+
'--glob', '!**/.neurcode/**',
|
|
1224
|
+
'--glob', '!**/.pnpm-store/**',
|
|
1225
|
+
'--glob', '!*.lock',
|
|
1226
|
+
'--glob', '!*.map',
|
|
1227
|
+
'--',
|
|
1228
|
+
pattern,
|
|
1229
|
+
'.',
|
|
1230
|
+
];
|
|
1231
|
+
const result = (0, child_process_1.spawnSync)('rg', args, {
|
|
1232
|
+
cwd,
|
|
1233
|
+
encoding: 'utf-8',
|
|
1234
|
+
maxBuffer: 1024 * 1024 * 80,
|
|
1235
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1236
|
+
});
|
|
1237
|
+
const status = result.status ?? 1;
|
|
1238
|
+
if (status !== 0 && status !== 1) {
|
|
767
1239
|
return [];
|
|
768
|
-
try {
|
|
769
|
-
const content = (0, fs_1.readFileSync)(readmePath, 'utf-8');
|
|
770
|
-
return extractFeatureBulletsFromMarkdown(content, limit, /^#{2,3}\s+.*(features|capabilities|what it does|highlights|offerings|product surface|platform surface|overview)/i);
|
|
771
1240
|
}
|
|
772
|
-
|
|
1241
|
+
const stdout = result.stdout || '';
|
|
1242
|
+
if (!stdout.trim())
|
|
773
1243
|
return [];
|
|
1244
|
+
const out = [];
|
|
1245
|
+
for (const raw of stdout.split(/\r?\n/)) {
|
|
1246
|
+
const parsed = parseRgLine(raw.trim());
|
|
1247
|
+
if (!parsed)
|
|
1248
|
+
continue;
|
|
1249
|
+
out.push(parsed);
|
|
1250
|
+
if (out.length >= RG_MAX_MATCHES)
|
|
1251
|
+
break;
|
|
774
1252
|
}
|
|
1253
|
+
return out;
|
|
775
1254
|
}
|
|
776
|
-
function
|
|
777
|
-
const docsDir = (0, path_1.join)(cwd, 'docs');
|
|
778
|
-
if (!(0, fs_1.existsSync)(docsDir))
|
|
779
|
-
return [];
|
|
1255
|
+
function fallbackScanMatches(cwd, fileTree, pattern, maxMatches = 1200) {
|
|
780
1256
|
const out = [];
|
|
781
|
-
|
|
782
|
-
|
|
1257
|
+
if (!pattern)
|
|
1258
|
+
return out;
|
|
1259
|
+
let regex;
|
|
783
1260
|
try {
|
|
784
|
-
|
|
1261
|
+
regex = new RegExp(pattern, 'i');
|
|
785
1262
|
}
|
|
786
1263
|
catch {
|
|
787
|
-
return
|
|
1264
|
+
return out;
|
|
788
1265
|
}
|
|
789
|
-
for (const
|
|
790
|
-
if (out.length >=
|
|
1266
|
+
for (const filePath of fileTree) {
|
|
1267
|
+
if (out.length >= maxMatches)
|
|
791
1268
|
break;
|
|
792
|
-
|
|
1269
|
+
if (isIgnoredSearchPath(filePath))
|
|
1270
|
+
continue;
|
|
1271
|
+
const fullPath = (0, path_1.join)(cwd, filePath);
|
|
793
1272
|
let content = '';
|
|
794
1273
|
try {
|
|
1274
|
+
const st = (0, fs_1.statSync)(fullPath);
|
|
1275
|
+
if (st.size > MAX_FILE_BYTES)
|
|
1276
|
+
continue;
|
|
795
1277
|
content = (0, fs_1.readFileSync)(fullPath, 'utf-8');
|
|
796
1278
|
}
|
|
797
1279
|
catch {
|
|
798
1280
|
continue;
|
|
799
1281
|
}
|
|
800
|
-
const
|
|
801
|
-
for (
|
|
802
|
-
const
|
|
803
|
-
if (
|
|
1282
|
+
const lines = content.split(/\r?\n/);
|
|
1283
|
+
for (let idx = 0; idx < lines.length; idx++) {
|
|
1284
|
+
const line = lines[idx];
|
|
1285
|
+
if (!line)
|
|
804
1286
|
continue;
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
1287
|
+
if (!regex.test(line))
|
|
1288
|
+
continue;
|
|
1289
|
+
out.push({
|
|
1290
|
+
path: filePath,
|
|
1291
|
+
line: idx + 1,
|
|
1292
|
+
snippet: normalizeSnippet(line),
|
|
1293
|
+
});
|
|
1294
|
+
if (out.length >= maxMatches)
|
|
808
1295
|
break;
|
|
809
1296
|
}
|
|
810
1297
|
}
|
|
811
1298
|
return out;
|
|
812
1299
|
}
|
|
813
|
-
function
|
|
814
|
-
const
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
const
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
const
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
1300
|
+
function collectRepoEvidence(cwd, fileTree, searchTerms, pathBoostScores) {
|
|
1301
|
+
const pattern = buildPatternFromTerms(searchTerms.rgTerms);
|
|
1302
|
+
const fromRg = runRipgrepSearch(cwd, pattern);
|
|
1303
|
+
const rawMatches = fromRg.length > 0 ? fromRg : fallbackScanMatches(cwd, fileTree, pattern);
|
|
1304
|
+
const profile = buildQueryProfile(searchTerms);
|
|
1305
|
+
const asksLocation = profile.asksLocation;
|
|
1306
|
+
const asksHow = profile.asksHow;
|
|
1307
|
+
const asksList = profile.asksList;
|
|
1308
|
+
const asksRegistration = profile.asksRegistration;
|
|
1309
|
+
const commandFocus = profile.commandFocus;
|
|
1310
|
+
const subcommandFocus = profile.subcommandFocus;
|
|
1311
|
+
const subcommandDeclRegex = subcommandFocus
|
|
1312
|
+
? new RegExp(`\\.command\\(['"\`]${escapeRegExp(subcommandFocus)}(?:\\s+\\[[^\\]]+\\])?['"\`]\\)`, 'i')
|
|
1313
|
+
: null;
|
|
1314
|
+
const codeFocused = profile.codeFocused;
|
|
1315
|
+
const matchedTerms = new Set();
|
|
1316
|
+
const dedup = new Map();
|
|
1317
|
+
for (const match of rawMatches) {
|
|
1318
|
+
if (isIgnoredSearchPath(match.path))
|
|
1319
|
+
continue;
|
|
1320
|
+
const pathLower = match.path.toLowerCase();
|
|
1321
|
+
const snippetLower = (match.snippet || '').toLowerCase();
|
|
1322
|
+
const isDocPath = isLikelyDocumentationPath(match.path);
|
|
1323
|
+
const docSnippet = isLikelyDocSnippet(match.snippet);
|
|
1324
|
+
const codeSnippet = isLikelyCodeSnippet(match.snippet);
|
|
1325
|
+
const promptExample = isPromptExampleSnippet(match.snippet, searchTerms.normalizedQuestion, searchTerms.highSignalTerms);
|
|
1326
|
+
if (asksLocation && codeFocused && (pathLower.endsWith('.md') || pathLower.endsWith('.txt'))) {
|
|
1327
|
+
continue;
|
|
830
1328
|
}
|
|
831
|
-
|
|
832
|
-
const
|
|
833
|
-
if (
|
|
834
|
-
|
|
835
|
-
|
|
1329
|
+
const termHits = searchTerms.rgTerms.filter((term) => {
|
|
1330
|
+
const normalized = term.toLowerCase();
|
|
1331
|
+
if (normalized.includes(' '))
|
|
1332
|
+
return snippetLower.includes(normalized);
|
|
1333
|
+
const pattern = new RegExp(`(?:^|[^a-z0-9])${escapeRegExp(normalized)}(?:$|[^a-z0-9])`, 'i');
|
|
1334
|
+
return pattern.test(snippetLower);
|
|
1335
|
+
});
|
|
1336
|
+
const highSignalHits = searchTerms.highSignalTerms.filter((term) => {
|
|
1337
|
+
const normalized = term.toLowerCase();
|
|
1338
|
+
if (normalized.includes(' '))
|
|
1339
|
+
return snippetLower.includes(normalized);
|
|
1340
|
+
const pattern = new RegExp(`(?:^|[^a-z0-9])${escapeRegExp(normalized)}(?:$|[^a-z0-9])`, 'i');
|
|
1341
|
+
return pattern.test(snippetLower);
|
|
1342
|
+
});
|
|
1343
|
+
const identifierHits = searchTerms.identifiers.filter((identifier) => new RegExp(`\\b${escapeRegExp(identifier)}\\b`, 'i').test(match.snippet));
|
|
1344
|
+
const pathHits = searchTerms.highSignalTerms.filter((term) => pathLower.includes(term)).length;
|
|
1345
|
+
const quotedHits = searchTerms.quotedPhrases.filter((phrase) => snippetLower.includes(phrase.toLowerCase())).length;
|
|
1346
|
+
let score = 0;
|
|
1347
|
+
score += termHits.length * 1.15;
|
|
1348
|
+
score += highSignalHits.length * 1.65;
|
|
1349
|
+
score += identifierHits.length * 2.05;
|
|
1350
|
+
score += pathHits * (asksLocation ? 1.95 : 1.1);
|
|
1351
|
+
score += quotedHits * 2.1;
|
|
1352
|
+
if (codeFocused && codeSnippet) {
|
|
1353
|
+
score += 0.95;
|
|
836
1354
|
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
continue;
|
|
840
|
-
commandSet.add(`neurcode ${plainMatch[1].trim()}`);
|
|
1355
|
+
if (codeFocused && !codeSnippet) {
|
|
1356
|
+
score -= 1.35;
|
|
841
1357
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
}
|
|
845
|
-
function extractCommandFocus(normalizedQuestion) {
|
|
846
|
-
const toKnownCommand = (candidate) => {
|
|
847
|
-
if (!candidate)
|
|
848
|
-
return null;
|
|
849
|
-
const normalized = candidate.toLowerCase().trim();
|
|
850
|
-
return CLI_COMMAND_NAMES.has(normalized) ? normalized : null;
|
|
851
|
-
};
|
|
852
|
-
const direct = normalizedQuestion.match(/\bneurcode\s+([a-z][a-z0-9-]*)\b/);
|
|
853
|
-
const directCommand = toKnownCommand(direct?.[1] || null);
|
|
854
|
-
if (directCommand)
|
|
855
|
-
return directCommand;
|
|
856
|
-
const scoped = normalizedQuestion.match(/\b(?:under|for|within|inside|in)\s+(?:neurcode\s+)?([a-z][a-z0-9-]*)\b/);
|
|
857
|
-
const scopedCommand = toKnownCommand(scoped?.[1] || null);
|
|
858
|
-
if (scopedCommand)
|
|
859
|
-
return scopedCommand;
|
|
860
|
-
const commandPattern = normalizedQuestion.match(/\b([a-z][a-z0-9-]*)\s+command\b/);
|
|
861
|
-
const commandPhrase = toKnownCommand(commandPattern?.[1] || null);
|
|
862
|
-
if (commandPhrase)
|
|
863
|
-
return commandPhrase;
|
|
864
|
-
const mentionedCommands = [...CLI_COMMAND_NAMES].filter((command) => new RegExp(`\\b${escapeRegExp(command)}\\b`, 'i').test(normalizedQuestion));
|
|
865
|
-
if (mentionedCommands.length !== 1)
|
|
866
|
-
return null;
|
|
867
|
-
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);
|
|
868
|
-
if (hasImplementationSignals) {
|
|
869
|
-
return mentionedCommands[0];
|
|
870
|
-
}
|
|
871
|
-
return null;
|
|
872
|
-
}
|
|
873
|
-
function extractInstallCommandsFromCitations(citations) {
|
|
874
|
-
const installSet = new Set();
|
|
875
|
-
for (const citation of citations) {
|
|
876
|
-
const snippet = citation.snippet || '';
|
|
877
|
-
const matches = snippet.match(/\b(?:npm|pnpm|yarn)\s+(?:install|add)\s+-g\s+[@a-z0-9_.\-/]+(?:@latest)?\b/gi) || [];
|
|
878
|
-
for (const match of matches) {
|
|
879
|
-
installSet.add(match.trim());
|
|
1358
|
+
if (docSnippet) {
|
|
1359
|
+
score -= codeFocused ? 1.9 : 0.6;
|
|
880
1360
|
}
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
}
|
|
884
|
-
function formatInsightSnippet(snippet) {
|
|
885
|
-
return snippet
|
|
886
|
-
.replace(/^[-*]\s+/, '')
|
|
887
|
-
.replace(/^\/\/\s?/, '')
|
|
888
|
-
.replace(/^\/\*\s?/, '')
|
|
889
|
-
.replace(/\*\/$/, '')
|
|
890
|
-
.replace(/^\*\s+/, '')
|
|
891
|
-
.replace(/\s+/g, ' ')
|
|
892
|
-
.trim()
|
|
893
|
-
.slice(0, 180);
|
|
894
|
-
}
|
|
895
|
-
function countTermHitsInText(text, terms) {
|
|
896
|
-
if (!text || terms.length === 0)
|
|
897
|
-
return 0;
|
|
898
|
-
let hits = 0;
|
|
899
|
-
const isCompactText = !/\s/.test(text);
|
|
900
|
-
const compactText = isCompactText ? text.replace(/[^a-z0-9]/g, '') : '';
|
|
901
|
-
for (const term of terms) {
|
|
902
|
-
const normalized = term.toLowerCase().trim();
|
|
903
|
-
if (!normalized)
|
|
904
|
-
continue;
|
|
905
|
-
if (normalized.includes(' ')) {
|
|
906
|
-
if (text.includes(normalized))
|
|
907
|
-
hits += 1;
|
|
908
|
-
continue;
|
|
1361
|
+
if (isDocPath && codeFocused) {
|
|
1362
|
+
score -= asksLocation ? 3.7 : 2.2;
|
|
909
1363
|
}
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
hits += 1;
|
|
913
|
-
continue;
|
|
1364
|
+
if (promptExample) {
|
|
1365
|
+
score -= codeFocused ? 4.4 : 2.2;
|
|
914
1366
|
}
|
|
915
|
-
if (
|
|
916
|
-
|
|
917
|
-
if (compactTerm.length >= 3 && compactText.includes(compactTerm)) {
|
|
918
|
-
hits += 1;
|
|
919
|
-
}
|
|
1367
|
+
if (/\b(?:export\s+)?(?:function|class|const|interface|type|enum)\b/.test(match.snippet)) {
|
|
1368
|
+
score += 0.85;
|
|
920
1369
|
}
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
}
|
|
924
|
-
function findInterfaceFieldBlock(lines, startLine) {
|
|
925
|
-
const startIndex = Math.max(0, startLine - 1);
|
|
926
|
-
const lookbackStart = Math.max(0, startIndex - 14);
|
|
927
|
-
const lookaheadEnd = Math.min(lines.length - 1, startIndex + 10);
|
|
928
|
-
let headerIndex = -1;
|
|
929
|
-
let interfaceName = '';
|
|
930
|
-
for (let i = startIndex; i >= lookbackStart; i--) {
|
|
931
|
-
const match = lines[i].match(/^\s*(?:export\s+)?interface\s+([A-Za-z_][A-Za-z0-9_]*)\b/);
|
|
932
|
-
if (!match?.[1])
|
|
933
|
-
continue;
|
|
934
|
-
headerIndex = i;
|
|
935
|
-
interfaceName = match[1];
|
|
936
|
-
break;
|
|
937
|
-
}
|
|
938
|
-
if (headerIndex < 0) {
|
|
939
|
-
for (let i = startIndex; i <= lookaheadEnd; i++) {
|
|
940
|
-
const match = lines[i].match(/^\s*(?:export\s+)?interface\s+([A-Za-z_][A-Za-z0-9_]*)\b/);
|
|
941
|
-
if (!match?.[1])
|
|
942
|
-
continue;
|
|
943
|
-
headerIndex = i;
|
|
944
|
-
interfaceName = match[1];
|
|
945
|
-
break;
|
|
1370
|
+
if (asksLocation && /\b(?:function|class|interface|type)\b/.test(match.snippet)) {
|
|
1371
|
+
score += 0.75;
|
|
946
1372
|
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
const line = lines[i];
|
|
956
|
-
if (!opened) {
|
|
957
|
-
if (line.includes('{')) {
|
|
958
|
-
opened = true;
|
|
959
|
-
depth = 1;
|
|
960
|
-
}
|
|
961
|
-
continue;
|
|
1373
|
+
if (asksHow && /\b(if|else|return|await|for|while|switch|try|catch)\b/.test(match.snippet)) {
|
|
1374
|
+
score += 0.55;
|
|
1375
|
+
}
|
|
1376
|
+
if (asksList && /\.command\(|\.option\(|\bneurcode\s+[a-z]/i.test(match.snippet)) {
|
|
1377
|
+
score += 0.7;
|
|
1378
|
+
}
|
|
1379
|
+
if (asksRegistration && /(?:^|[^a-z])(?:register|registered|registration)(?:$|[^a-z])/i.test(match.snippet)) {
|
|
1380
|
+
score += 0.8;
|
|
962
1381
|
}
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1382
|
+
if (asksRegistration && /\.command\(|program\.command\(/i.test(match.snippet)) {
|
|
1383
|
+
score += 2.3;
|
|
1384
|
+
}
|
|
1385
|
+
if (asksLocation && profile.highSignalSet.has('middleware') && pathLower.includes('/middleware/')) {
|
|
1386
|
+
score += 1.9;
|
|
1387
|
+
}
|
|
1388
|
+
if (asksLocation && profile.highSignalSet.has('middleware') && !pathLower.includes('middleware')) {
|
|
1389
|
+
score -= 1.7;
|
|
1390
|
+
}
|
|
1391
|
+
if (asksLocation && profile.highSignalSet.has('auth') && /(?:^|\/)auth(?:[-_.]|\/|\.|$)/.test(pathLower)) {
|
|
1392
|
+
score += 1.25;
|
|
1393
|
+
}
|
|
1394
|
+
if (asksLocation && profile.highSignalSet.has('orgid') && /\borgid\b|organizationid/i.test(match.snippet)) {
|
|
1395
|
+
score += 1.45;
|
|
1396
|
+
}
|
|
1397
|
+
if (commandFocus) {
|
|
1398
|
+
if (new RegExp(`\\.command\\(['"\`]${escapeRegExp(commandFocus)}['"\`]\\)`, 'i').test(match.snippet)) {
|
|
1399
|
+
score += 6.2;
|
|
1400
|
+
}
|
|
1401
|
+
if (asksRegistration && pathLower === 'packages/cli/src/index.ts') {
|
|
1402
|
+
score += 2.8;
|
|
1403
|
+
}
|
|
1404
|
+
if (pathLower.includes(`/commands/${commandFocus}.`)) {
|
|
1405
|
+
score += asksHow ? 3.2 : 1.8;
|
|
1406
|
+
if (subcommandDeclRegex && subcommandDeclRegex.test(match.snippet)) {
|
|
1407
|
+
score += 4.1;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
else if (pathLower.includes('/commands/')) {
|
|
1411
|
+
score -= asksHow ? 1.85 : 0.45;
|
|
1412
|
+
}
|
|
1413
|
+
if (asksHow && pathLower.includes('/commands/') && !pathLower.includes(`/commands/${commandFocus}.`) && pathLower !== 'packages/cli/src/index.ts') {
|
|
1414
|
+
score -= 1.05;
|
|
1415
|
+
}
|
|
1416
|
+
if (commandFocus !== 'ask' && pathLower.endsWith('/commands/ask.ts')) {
|
|
1417
|
+
score -= 3.2;
|
|
969
1418
|
}
|
|
970
1419
|
}
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1420
|
+
if (codeFocused && (pathLower.endsWith('.md') || pathLower.startsWith('docs/'))) {
|
|
1421
|
+
score -= 1.35;
|
|
1422
|
+
}
|
|
1423
|
+
if (codeFocused && (pathLower.endsWith('.txt') || pathLower.includes('audit'))) {
|
|
1424
|
+
score -= 1.8;
|
|
1425
|
+
}
|
|
1426
|
+
if (codeFocused && /(?:^|\/)(pnpm-lock\.yaml|package-lock\.json|yarn\.lock)$/i.test(pathLower)) {
|
|
1427
|
+
score -= 4.5;
|
|
1428
|
+
}
|
|
1429
|
+
if (codeFocused && /\bneurcode\s+(ask|plan|verify|ship)\s+["`]/i.test(match.snippet)) {
|
|
1430
|
+
score -= 2.4;
|
|
1431
|
+
}
|
|
1432
|
+
if (codeFocused && /\?/.test(match.snippet) && /\b(neurcode|ask|plan)\b/i.test(match.snippet)) {
|
|
1433
|
+
score -= 1.1;
|
|
1434
|
+
}
|
|
1435
|
+
if (codeFocused && /\bnew Set\(\[/.test(match.snippet)) {
|
|
1436
|
+
score -= 1.1;
|
|
1437
|
+
}
|
|
1438
|
+
if (codeFocused && /\\b\([^)]*\|[^)]*\)/.test(match.snippet) && /\.test\(/.test(match.snippet)) {
|
|
1439
|
+
score -= 1.45;
|
|
1440
|
+
}
|
|
1441
|
+
if (pathLower.includes('/commands/')) {
|
|
1442
|
+
score += 0.15;
|
|
1443
|
+
}
|
|
1444
|
+
if (asksRegistration && pathLower.endsWith('/commands/ask.ts')) {
|
|
1445
|
+
score -= 2.4;
|
|
1446
|
+
}
|
|
1447
|
+
if (asksLocation && codeFocused && pathLower.endsWith('/commands/ask.ts')) {
|
|
1448
|
+
score -= 2.9;
|
|
1449
|
+
}
|
|
1450
|
+
const boost = pathBoostScores.get(match.path) || 0;
|
|
1451
|
+
if (boost > 0) {
|
|
1452
|
+
const cappedBoost = codeFocused && isDocPath
|
|
1453
|
+
? Math.min(boost, 0.06)
|
|
1454
|
+
: Math.min(boost, 0.45);
|
|
1455
|
+
score += cappedBoost;
|
|
1456
|
+
}
|
|
1457
|
+
if (score <= 0)
|
|
1458
|
+
continue;
|
|
1459
|
+
for (const term of highSignalHits) {
|
|
1460
|
+
matchedTerms.add(term);
|
|
1461
|
+
}
|
|
1462
|
+
const dominantTerm = highSignalHits[0] ||
|
|
1463
|
+
identifierHits[0] ||
|
|
1464
|
+
termHits[0] ||
|
|
1465
|
+
'';
|
|
1466
|
+
const key = `${match.path}:${match.line}`;
|
|
1467
|
+
const next = {
|
|
1468
|
+
path: match.path,
|
|
1469
|
+
line: match.line,
|
|
1470
|
+
snippet: match.snippet,
|
|
1471
|
+
term: dominantTerm || undefined,
|
|
1472
|
+
score,
|
|
1473
|
+
matchedTerms: [...new Set([...highSignalHits, ...identifierHits, ...termHits])],
|
|
1474
|
+
};
|
|
1475
|
+
const existing = dedup.get(key);
|
|
1476
|
+
if (!existing || existing.score < next.score) {
|
|
1477
|
+
dedup.set(key, next);
|
|
976
1478
|
}
|
|
977
|
-
if (depth <= 0)
|
|
978
|
-
break;
|
|
979
1479
|
}
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1480
|
+
const scoredCitations = [...dedup.values()]
|
|
1481
|
+
.sort((a, b) => b.score - a.score)
|
|
1482
|
+
.slice(0, MAX_RAW_CITATIONS);
|
|
1483
|
+
return {
|
|
1484
|
+
scoredCitations,
|
|
1485
|
+
matchedTerms,
|
|
1486
|
+
scannedFiles: fileTree.length,
|
|
1487
|
+
};
|
|
983
1488
|
}
|
|
984
|
-
function
|
|
985
|
-
if (
|
|
1489
|
+
function selectTopCitations(scored, maxCitations, searchTerms) {
|
|
1490
|
+
if (scored.length === 0)
|
|
986
1491
|
return [];
|
|
987
|
-
const
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
const
|
|
1003
|
-
if (
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
const
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1492
|
+
const profile = buildQueryProfile(searchTerms);
|
|
1493
|
+
const asksLocationCode = profile.asksLocation && profile.codeFocused;
|
|
1494
|
+
const structuralTerms = new Set([
|
|
1495
|
+
'middleware', 'route', 'routes', 'service', 'services', 'controller', 'controllers',
|
|
1496
|
+
'command', 'commands', 'schema', 'model', 'models', 'db', 'database', 'api', 'auth',
|
|
1497
|
+
'cache', 'plan', 'verify', 'ship', 'apply', 'watch',
|
|
1498
|
+
]);
|
|
1499
|
+
const sorted = [...scored].sort((a, b) => b.score - a.score);
|
|
1500
|
+
const topScore = sorted[0]?.score || 0;
|
|
1501
|
+
const scoreFloor = asksLocationCode ? topScore * 0.45 : topScore * 0.18;
|
|
1502
|
+
let candidates = sorted.filter((citation) => citation.score >= scoreFloor);
|
|
1503
|
+
if (candidates.length === 0) {
|
|
1504
|
+
candidates = sorted.slice(0, maxCitations * 3);
|
|
1505
|
+
}
|
|
1506
|
+
if (asksLocationCode) {
|
|
1507
|
+
const anchorTerms = searchTerms.highSignalTerms.filter((term) => structuralTerms.has(term.toLowerCase()));
|
|
1508
|
+
if (anchorTerms.length > 0) {
|
|
1509
|
+
const anchored = candidates.filter((citation) => {
|
|
1510
|
+
const pathLower = citation.path.toLowerCase();
|
|
1511
|
+
return anchorTerms.some((term) => pathLower.includes(term.toLowerCase()));
|
|
1512
|
+
});
|
|
1513
|
+
if (anchored.length >= Math.min(2, maxCitations)) {
|
|
1514
|
+
candidates = anchored;
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
const strict = candidates.filter((citation) => !isLikelyDocumentationPath(citation.path) &&
|
|
1518
|
+
!isLikelyDocSnippet(citation.snippet) &&
|
|
1519
|
+
!isPromptExampleSnippet(citation.snippet, searchTerms.normalizedQuestion, searchTerms.highSignalTerms) &&
|
|
1520
|
+
isLikelyCodeSnippet(citation.snippet));
|
|
1521
|
+
if (strict.length >= Math.min(maxCitations, 3)) {
|
|
1522
|
+
candidates = strict;
|
|
1523
|
+
}
|
|
1524
|
+
else {
|
|
1525
|
+
const merged = [];
|
|
1526
|
+
const seen = new Set();
|
|
1527
|
+
for (const citation of [...strict, ...candidates]) {
|
|
1528
|
+
const key = `${citation.path}:${citation.line}`;
|
|
1529
|
+
if (seen.has(key))
|
|
1530
|
+
continue;
|
|
1531
|
+
seen.add(key);
|
|
1532
|
+
merged.push(citation);
|
|
1533
|
+
}
|
|
1534
|
+
candidates = merged;
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
if (profile.codeFocused && !asksLocationCode) {
|
|
1538
|
+
const identifierAnchors = searchTerms.identifiers
|
|
1539
|
+
.map((term) => (0, plan_cache_1.normalizeIntent)(term))
|
|
1540
|
+
.filter((term) => term.length >= 3);
|
|
1541
|
+
const termAnchors = searchTerms.highSignalTerms
|
|
1542
|
+
.map((term) => term.toLowerCase().trim())
|
|
1543
|
+
.filter((term) => term.length >= 3 && !LOW_SIGNAL_TERMS.has(term));
|
|
1544
|
+
const anchors = [...new Set([...identifierAnchors, ...termAnchors])].slice(0, 8);
|
|
1545
|
+
if (anchors.length > 0) {
|
|
1546
|
+
const anchored = candidates.filter((citation) => {
|
|
1547
|
+
const pathLower = citation.path.toLowerCase();
|
|
1548
|
+
const snippetLower = citation.snippet.toLowerCase();
|
|
1549
|
+
return anchors.some((term) => {
|
|
1550
|
+
if (pathLower.includes(term))
|
|
1551
|
+
return true;
|
|
1552
|
+
const pattern = new RegExp(`(?:^|[^a-z0-9])${escapeRegExp(term)}(?:$|[^a-z0-9])`, 'i');
|
|
1553
|
+
return pattern.test(snippetLower);
|
|
1554
|
+
});
|
|
1555
|
+
});
|
|
1556
|
+
if (anchored.length >= Math.min(maxCitations, 4)) {
|
|
1557
|
+
candidates = anchored;
|
|
1558
|
+
}
|
|
1029
1559
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1560
|
+
}
|
|
1561
|
+
if (profile.commandFocus) {
|
|
1562
|
+
const commandNeedle = `/commands/${profile.commandFocus}.`;
|
|
1563
|
+
const commandAnchored = candidates.filter((citation) => {
|
|
1564
|
+
const pathLower = citation.path.toLowerCase();
|
|
1565
|
+
return pathLower.includes(commandNeedle) || pathLower === 'packages/cli/src/index.ts';
|
|
1034
1566
|
});
|
|
1567
|
+
if (commandAnchored.length >= Math.min(maxCitations, 3)) {
|
|
1568
|
+
candidates = commandAnchored;
|
|
1569
|
+
}
|
|
1035
1570
|
}
|
|
1036
|
-
const
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
const strictExactFieldRequest = queryTermSet.has('exact') &&
|
|
1042
|
-
(queryTermSet.has('field') || queryTermSet.has('fields'));
|
|
1043
|
-
if (strictExactFieldRequest) {
|
|
1044
|
-
minScore = Math.max(minScore, topScore - 0.5);
|
|
1045
|
-
}
|
|
1046
|
-
return ranked
|
|
1047
|
-
.filter((item) => item.score >= minScore)
|
|
1048
|
-
.slice(0, 6)
|
|
1049
|
-
.map((item) => item.summary);
|
|
1050
|
-
}
|
|
1051
|
-
function extractInsightLines(citations, limit) {
|
|
1052
|
-
const out = [];
|
|
1053
|
-
const seen = new Set();
|
|
1054
|
-
for (const citation of citations) {
|
|
1055
|
-
const line = formatInsightSnippet(citation.snippet || '');
|
|
1056
|
-
if (!line || line.length < 10)
|
|
1057
|
-
continue;
|
|
1058
|
-
if (/^#+\s/.test(line))
|
|
1059
|
-
continue;
|
|
1060
|
-
if (/^name:\s+/i.test(line))
|
|
1061
|
-
continue;
|
|
1062
|
-
const tooNoisy = /[{}[\];=><]/.test(line) && !/\b(multi-tenant|single-tenant|organization|install|command|feature)\b/i.test(line);
|
|
1063
|
-
if (tooNoisy)
|
|
1064
|
-
continue;
|
|
1065
|
-
const key = line.toLowerCase();
|
|
1066
|
-
if (seen.has(key))
|
|
1067
|
-
continue;
|
|
1068
|
-
seen.add(key);
|
|
1069
|
-
out.push(line);
|
|
1070
|
-
if (out.length >= limit)
|
|
1071
|
-
break;
|
|
1571
|
+
const byFile = new Map();
|
|
1572
|
+
for (const citation of candidates) {
|
|
1573
|
+
const bucket = byFile.get(citation.path) || [];
|
|
1574
|
+
bucket.push(citation);
|
|
1575
|
+
byFile.set(citation.path, bucket);
|
|
1072
1576
|
}
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
function isCommandCatalogIntent(normalizedQuestion) {
|
|
1076
|
-
const mentionsCommandSurface = /\b(cli|cmd|cmds|command|commands|subcommand|subcommands)\b/.test(normalizedQuestion);
|
|
1077
|
-
if (!mentionsCommandSurface)
|
|
1078
|
-
return false;
|
|
1079
|
-
const listIntent = /\blist\b/.test(normalizedQuestion) ||
|
|
1080
|
-
/\bshow\b/.test(normalizedQuestion) ||
|
|
1081
|
-
/\bavailable\b/.test(normalizedQuestion) ||
|
|
1082
|
-
/\ball commands?\b/.test(normalizedQuestion) ||
|
|
1083
|
-
/\bwhich commands\b/.test(normalizedQuestion) ||
|
|
1084
|
-
/\bwhat commands\b/.test(normalizedQuestion) ||
|
|
1085
|
-
/\bwhat can i (type|run)\b/.test(normalizedQuestion) ||
|
|
1086
|
-
/\bcan i type\b/.test(normalizedQuestion) ||
|
|
1087
|
-
/\bcmds\b/.test(normalizedQuestion);
|
|
1088
|
-
if (!listIntent)
|
|
1089
|
-
return false;
|
|
1090
|
-
const specificIntent = /\bwhere\b/.test(normalizedQuestion) ||
|
|
1091
|
-
/\bhow\b/.test(normalizedQuestion) ||
|
|
1092
|
-
/\bwhy\b/.test(normalizedQuestion) ||
|
|
1093
|
-
/\bwhen\b/.test(normalizedQuestion) ||
|
|
1094
|
-
/\bwhich file\b/.test(normalizedQuestion) ||
|
|
1095
|
-
/\bin which file\b/.test(normalizedQuestion) ||
|
|
1096
|
-
/\bfilepath\b/.test(normalizedQuestion) ||
|
|
1097
|
-
/\bfile path\b/.test(normalizedQuestion) ||
|
|
1098
|
-
/\binject(?:ed)?\b/.test(normalizedQuestion) ||
|
|
1099
|
-
/\bhandle(?:s|d)?\b/.test(normalizedQuestion) ||
|
|
1100
|
-
/\bused?\b/.test(normalizedQuestion) ||
|
|
1101
|
-
/\bflow\b/.test(normalizedQuestion);
|
|
1102
|
-
return !specificIntent;
|
|
1103
|
-
}
|
|
1104
|
-
function isPrimarySourcePath(filePath) {
|
|
1105
|
-
const normalized = filePath.trim().replace(/\\/g, '/').toLowerCase();
|
|
1106
|
-
if (!normalized)
|
|
1107
|
-
return false;
|
|
1108
|
-
if (normalized.startsWith('.neurcode/') ||
|
|
1109
|
-
normalized.startsWith('.github/') ||
|
|
1110
|
-
normalized.startsWith('.git/') ||
|
|
1111
|
-
normalized.startsWith('node_modules/') ||
|
|
1112
|
-
normalized.startsWith('dist/') ||
|
|
1113
|
-
normalized.startsWith('build/') ||
|
|
1114
|
-
normalized.startsWith('coverage/')) {
|
|
1115
|
-
return false;
|
|
1577
|
+
for (const list of byFile.values()) {
|
|
1578
|
+
list.sort((a, b) => b.score - a.score);
|
|
1116
1579
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1580
|
+
const selected = [];
|
|
1581
|
+
// Pass 1: strongest line per file.
|
|
1582
|
+
for (const [_, list] of [...byFile.entries()].sort((a, b) => b[1][0].score - a[1][0].score)) {
|
|
1583
|
+
if (selected.length >= maxCitations)
|
|
1584
|
+
break;
|
|
1585
|
+
selected.push(list.shift());
|
|
1119
1586
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1587
|
+
// Pass 2: fill remaining by global score.
|
|
1588
|
+
const remainder = [...byFile.values()].flat().sort((a, b) => b.score - a.score);
|
|
1589
|
+
for (const citation of remainder) {
|
|
1590
|
+
if (selected.length >= maxCitations)
|
|
1591
|
+
break;
|
|
1592
|
+
selected.push(citation);
|
|
1124
1593
|
}
|
|
1125
|
-
return
|
|
1594
|
+
return selected.map(({ path, line, snippet, term }) => ({ path, line, snippet, term }));
|
|
1126
1595
|
}
|
|
1127
|
-
function
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
return
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
const
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
const
|
|
1150
|
-
const
|
|
1151
|
-
|
|
1152
|
-
if (broadQuestion) {
|
|
1153
|
-
minFilesRequired = hasStrongSingleFileEvidence ? minFilesRequired : Math.max(minFilesRequired, 2);
|
|
1154
|
-
}
|
|
1596
|
+
function evaluateTruth(normalizedQuestion, highSignalTerms, citations) {
|
|
1597
|
+
const sourceCitations = citations.length;
|
|
1598
|
+
const sourceFiles = new Set(citations.map((c) => c.path)).size;
|
|
1599
|
+
const asksLocation = /\b(where|which file|location|defined|implemented|called|computed|resolved)\b/.test(normalizedQuestion);
|
|
1600
|
+
const asksList = /\b(list|all|available|commands|files|features)\b/.test(normalizedQuestion);
|
|
1601
|
+
const commandFocus = detectCommandFocus(normalizedQuestion);
|
|
1602
|
+
const subcommandFocus = detectSubcommandFocus(normalizedQuestion, commandFocus);
|
|
1603
|
+
const minCitationsRequired = asksLocation ? 1 : asksList ? 3 : 2;
|
|
1604
|
+
const minFilesRequired = asksLocation ? 1 : 2;
|
|
1605
|
+
const matchedHighSignalTerms = highSignalTerms.filter((term) => {
|
|
1606
|
+
const normalized = term.toLowerCase();
|
|
1607
|
+
return citations.some((citation) => {
|
|
1608
|
+
const haystack = `${citation.path} ${citation.snippet}`.toLowerCase();
|
|
1609
|
+
if (normalized.includes(' '))
|
|
1610
|
+
return haystack.includes(normalized);
|
|
1611
|
+
const pattern = new RegExp(`(?:^|[^a-z0-9])${escapeRegExp(normalized)}(?:$|[^a-z0-9])`, 'i');
|
|
1612
|
+
return pattern.test(haystack);
|
|
1613
|
+
});
|
|
1614
|
+
});
|
|
1615
|
+
const coverage = highSignalTerms.length === 0
|
|
1616
|
+
? (sourceCitations > 0 ? 1 : 0)
|
|
1617
|
+
: matchedHighSignalTerms.length / highSignalTerms.length;
|
|
1618
|
+
const citationScore = Math.min(1, sourceCitations / Math.max(minCitationsRequired, 3));
|
|
1619
|
+
const fileScore = Math.min(1, sourceFiles / Math.max(minFilesRequired, 2));
|
|
1620
|
+
let score = Math.max(0, Math.min(1, citationScore * 0.45 + fileScore * 0.35 + coverage * 0.2));
|
|
1155
1621
|
const reasons = [];
|
|
1156
|
-
if (
|
|
1157
|
-
reasons.push(
|
|
1158
|
-
}
|
|
1159
|
-
if (
|
|
1160
|
-
reasons.push(`
|
|
1161
|
-
}
|
|
1162
|
-
if (
|
|
1163
|
-
reasons.push(
|
|
1164
|
-
}
|
|
1165
|
-
if (
|
|
1166
|
-
const
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1622
|
+
if (sourceCitations < minCitationsRequired) {
|
|
1623
|
+
reasons.push(`Only ${sourceCitations} citation(s) found (minimum ${minCitationsRequired} expected).`);
|
|
1624
|
+
}
|
|
1625
|
+
if (sourceFiles < minFilesRequired) {
|
|
1626
|
+
reasons.push(`Evidence spans ${sourceFiles} file(s) (minimum ${minFilesRequired} expected).`);
|
|
1627
|
+
}
|
|
1628
|
+
if (highSignalTerms.length > 0 && coverage < 0.4) {
|
|
1629
|
+
reasons.push('Important query terms were not well represented in matched evidence.');
|
|
1630
|
+
}
|
|
1631
|
+
if (commandFocus) {
|
|
1632
|
+
const commandNeedle = `/commands/${commandFocus}.`;
|
|
1633
|
+
const commandFileCitations = citations.filter((citation) => citation.path.toLowerCase().includes(commandNeedle));
|
|
1634
|
+
const registrationHits = citations.filter((citation) => citation.path.toLowerCase() === 'packages/cli/src/index.ts' &&
|
|
1635
|
+
new RegExp(`\\.command\\(['"\`]${escapeRegExp(commandFocus)}['"\`]\\)`, 'i').test(citation.snippet));
|
|
1636
|
+
const anchoredCount = commandFileCitations.length + registrationHits.length;
|
|
1637
|
+
const anchoredRatio = sourceCitations > 0 ? anchoredCount / sourceCitations : 0;
|
|
1638
|
+
if (anchoredCount === 0) {
|
|
1639
|
+
reasons.push(`No direct evidence found in ${commandNeedle} or command registration wiring.`);
|
|
1640
|
+
}
|
|
1641
|
+
else if (sourceCitations >= 2 && anchoredRatio < 0.35) {
|
|
1642
|
+
reasons.push('Top evidence is weakly anchored to the referenced command implementation.');
|
|
1643
|
+
}
|
|
1644
|
+
if (subcommandFocus) {
|
|
1645
|
+
const subcommandDeclRegex = new RegExp(`\\.command\\(['"\`]${escapeRegExp(subcommandFocus)}(?:\\s+\\[[^\\]]+\\])?['"\`]\\)`, 'i');
|
|
1646
|
+
const hasSubcommandDeclaration = citations.some((citation) => citation.path.toLowerCase().includes(commandNeedle) &&
|
|
1647
|
+
subcommandDeclRegex.test(citation.snippet));
|
|
1648
|
+
if (!hasSubcommandDeclaration) {
|
|
1649
|
+
reasons.push(`No direct \`${subcommandFocus}\` subcommand declaration found in ${commandNeedle}.`);
|
|
1177
1650
|
}
|
|
1178
1651
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
if (sourceCitationCount > 0) {
|
|
1182
|
-
const highest = Math.max(...sourcePerFileCounts.values());
|
|
1183
|
-
dominantShare = highest / sourceCitationCount;
|
|
1184
|
-
}
|
|
1185
|
-
if (broadQuestion && dominantShare > 0.85 && sourceFileCount < 3 && !hasStrongSingleFileEvidence) {
|
|
1186
|
-
reasons.push('Evidence is highly concentrated in one file; broader coverage is required for this query.');
|
|
1187
|
-
}
|
|
1188
|
-
const citationScore = Math.min(1, sourceCitationCount / Math.max(minCitationsRequired, 4));
|
|
1189
|
-
const fileScore = Math.min(1, sourceFileCount / Math.max(minFilesRequired, 3));
|
|
1190
|
-
let termCoverageScore = 0;
|
|
1191
|
-
if (mode === 'comparison' && terms.length >= 2) {
|
|
1192
|
-
termCoverageScore = terms.filter((term) => (sourceTermCounts.get(term) || 0) > 0).length / terms.length;
|
|
1193
|
-
}
|
|
1194
|
-
else if (mode === 'search') {
|
|
1195
|
-
const highSignalTerms = terms.filter((term) => !LOW_SIGNAL_TERMS.has(term));
|
|
1196
|
-
if (highSignalTerms.length === 0) {
|
|
1197
|
-
termCoverageScore = sourceCitationCount > 0 ? 1 : 0;
|
|
1652
|
+
if (anchoredRatio < 0.35) {
|
|
1653
|
+
score *= 0.68;
|
|
1198
1654
|
}
|
|
1199
|
-
else {
|
|
1200
|
-
|
|
1655
|
+
else if (anchoredRatio < 0.55) {
|
|
1656
|
+
score *= 0.86;
|
|
1201
1657
|
}
|
|
1202
1658
|
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
if (!Number.isFinite(score))
|
|
1206
|
-
score = 0;
|
|
1207
|
-
score = Math.max(0, Math.min(score, 1));
|
|
1208
|
-
if (score < 0.42 && reasons.length === 0) {
|
|
1209
|
-
reasons.push('Evidence quality score is below the confidence threshold.');
|
|
1659
|
+
if (score < 0.4 && reasons.length === 0) {
|
|
1660
|
+
reasons.push('Evidence quality is below the confidence threshold.');
|
|
1210
1661
|
}
|
|
1211
1662
|
return {
|
|
1212
|
-
status: reasons.length
|
|
1663
|
+
status: reasons.length === 0 ? 'grounded' : 'insufficient',
|
|
1213
1664
|
score,
|
|
1214
1665
|
reasons,
|
|
1215
|
-
sourceCitations
|
|
1216
|
-
sourceFiles
|
|
1666
|
+
sourceCitations,
|
|
1667
|
+
sourceFiles,
|
|
1217
1668
|
minCitationsRequired,
|
|
1218
1669
|
minFilesRequired,
|
|
1219
1670
|
};
|
|
1220
1671
|
}
|
|
1221
|
-
function
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
const
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
const citationRelevanceScore = (citation) => {
|
|
1273
|
-
const snippetLower = (citation.snippet || '').toLowerCase();
|
|
1274
|
-
let score = countTermHitsInText(snippetLower, highSignalAnswerTerms);
|
|
1275
|
-
score += countTermHitsInText(citation.path.toLowerCase(), highSignalAnswerTerms) * 0.5;
|
|
1276
|
-
const pathAnchorHits = countTermHitsInText(citation.path.toLowerCase(), pathAnchorTerms);
|
|
1277
|
-
score += pathAnchorHits * 0.9;
|
|
1278
|
-
if (asksLocation && pathAnchorTerms.length > 0 && pathAnchorHits === 0) {
|
|
1279
|
-
score -= 0.35;
|
|
1280
|
-
}
|
|
1281
|
-
const anchorHits = countTermHitsInText(snippetLower, anchorTerms);
|
|
1282
|
-
score += anchorHits * 1.4;
|
|
1283
|
-
if ((asksLocation || asksHow) && anchorTerms.length > 0 && anchorHits === 0) {
|
|
1284
|
-
score -= 0.45;
|
|
1285
|
-
}
|
|
1286
|
-
if (queriedFlags.length > 0) {
|
|
1287
|
-
const flagHits = queriedFlags.filter((flag) => snippetLower.includes(flag)).length;
|
|
1288
|
-
score += flagHits * 2;
|
|
1289
|
-
if (asksLocation && flagHits === 0)
|
|
1290
|
-
score -= 0.4;
|
|
1291
|
-
if (/\.option\(/i.test(snippetLower) && flagHits > 0)
|
|
1292
|
-
score += 2.5;
|
|
1293
|
-
}
|
|
1294
|
-
if (asksCommandSurface) {
|
|
1295
|
-
if (/\.command\(|\bcommand\(/i.test(snippetLower))
|
|
1296
|
-
score += 2;
|
|
1297
|
-
if (/\.option\(|--[a-z0-9-]+/i.test(snippetLower))
|
|
1298
|
-
score += 1.4;
|
|
1299
|
-
if (citation.path.includes('/commands/') || citation.path.endsWith('/index.ts'))
|
|
1300
|
-
score += 0.6;
|
|
1301
|
-
}
|
|
1302
|
-
if (commandFocus) {
|
|
1303
|
-
if (citation.path.includes(`/commands/${commandFocus}.`)) {
|
|
1304
|
-
score += 2.1;
|
|
1305
|
-
}
|
|
1306
|
-
else if ((asksLocation || asksHow) && citation.path.includes('/commands/')) {
|
|
1307
|
-
score -= 1.0;
|
|
1308
|
-
}
|
|
1309
|
-
if (citation.path.endsWith('/index.ts') &&
|
|
1310
|
-
new RegExp(`\\.command\\('${escapeRegExp(commandFocus)}'\\)`, 'i').test(citation.snippet)) {
|
|
1311
|
-
score += 1.4;
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
if ((asksLocation || asksHow) && looksLikeImportLine(citation.snippet)) {
|
|
1315
|
-
score -= 0.8;
|
|
1316
|
-
}
|
|
1317
|
-
if ((asksLocation || asksHow) && /^\s*[?:]?\s*['"`].+['"`][,;]?\s*$/i.test(citation.snippet)) {
|
|
1318
|
-
score -= 0.9;
|
|
1319
|
-
}
|
|
1320
|
-
if (asksLocation && /console\.log\(/i.test(citation.snippet)) {
|
|
1321
|
-
score -= 1.0;
|
|
1322
|
-
}
|
|
1323
|
-
if (asksDecision) {
|
|
1324
|
-
if (/\b(verdict|exitcode|policydecision)\b/i.test(snippetLower) || /\bif\s*\(|\?\s*'[^']+'\s*:\s*'[^']+'/i.test(citation.snippet)) {
|
|
1325
|
-
score += 1.1;
|
|
1326
|
-
}
|
|
1327
|
-
if (/(log|roi|estimat|time saved|badge|banner)/i.test(snippetLower)) {
|
|
1328
|
-
score -= 0.9;
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
return score;
|
|
1332
|
-
};
|
|
1333
|
-
const multiSignals = citations.filter((citation) => /\bmulti[- ]tenant|x-org-id|organization[_ -]?id|org-scoped\b/i.test(citation.snippet)).length;
|
|
1334
|
-
const singleSignals = citations.filter((citation) => /\bsingle[- ]tenant|single-user\b/i.test(citation.snippet)).length;
|
|
1335
|
-
if (mode === 'comparison' && terms.length >= 2) {
|
|
1336
|
-
const left = terms[0];
|
|
1337
|
-
const right = terms[1];
|
|
1338
|
-
const leftCount = termCounts.get(left) || 0;
|
|
1339
|
-
const rightCount = termCounts.get(right) || 0;
|
|
1340
|
-
if (leftCount > 0 && rightCount > 0) {
|
|
1341
|
-
answer = `Found both "${left}" (${leftCount}) and "${right}" (${rightCount}) references in the scanned scope.`;
|
|
1342
|
-
}
|
|
1343
|
-
else if (leftCount > 0) {
|
|
1344
|
-
answer = `Found "${left}" references (${leftCount}) and no direct "${right}" references in the scanned scope.`;
|
|
1345
|
-
}
|
|
1346
|
-
else if (rightCount > 0) {
|
|
1347
|
-
answer = `No direct "${left}" references found; "${right}" appears ${rightCount} time(s) in the scanned scope.`;
|
|
1348
|
-
}
|
|
1349
|
-
else {
|
|
1350
|
-
answer = `No direct references to "${left}" or "${right}" were found in the scanned scope.`;
|
|
1351
|
-
}
|
|
1352
|
-
findings.push(`Compared terms: "${left}" vs "${right}"`);
|
|
1353
|
-
findings.push(`Matches by term: ${left}=${leftCount}, ${right}=${rightCount}`);
|
|
1672
|
+
function calibrateConfidence(truth) {
|
|
1673
|
+
if (truth.status === 'insufficient')
|
|
1674
|
+
return 'low';
|
|
1675
|
+
if (truth.score >= 0.78)
|
|
1676
|
+
return 'high';
|
|
1677
|
+
if (truth.score >= 0.55)
|
|
1678
|
+
return 'medium';
|
|
1679
|
+
return 'low';
|
|
1680
|
+
}
|
|
1681
|
+
function isPrimarySourcePath(path) {
|
|
1682
|
+
const normalized = path.toLowerCase();
|
|
1683
|
+
if (isIgnoredSearchPath(normalized))
|
|
1684
|
+
return false;
|
|
1685
|
+
if (isLikelyDocumentationPath(normalized))
|
|
1686
|
+
return false;
|
|
1687
|
+
if (normalized === 'license' || normalized.startsWith('license.'))
|
|
1688
|
+
return false;
|
|
1689
|
+
if (normalized.endsWith('/license') || normalized.includes('/license.'))
|
|
1690
|
+
return false;
|
|
1691
|
+
if (normalized.startsWith('changelog') || normalized.includes('/changelog'))
|
|
1692
|
+
return false;
|
|
1693
|
+
if (normalized.endsWith('pnpm-lock.yaml'))
|
|
1694
|
+
return false;
|
|
1695
|
+
if (normalized.endsWith('package-lock.json'))
|
|
1696
|
+
return false;
|
|
1697
|
+
if (normalized.endsWith('yarn.lock'))
|
|
1698
|
+
return false;
|
|
1699
|
+
if (normalized.endsWith('.md'))
|
|
1700
|
+
return false;
|
|
1701
|
+
if (normalized.endsWith('.txt'))
|
|
1702
|
+
return false;
|
|
1703
|
+
if (normalized === 'readme.md')
|
|
1704
|
+
return false;
|
|
1705
|
+
if (normalized.startsWith('docs/'))
|
|
1706
|
+
return false;
|
|
1707
|
+
if (normalized.includes('/__tests__/'))
|
|
1708
|
+
return false;
|
|
1709
|
+
if (normalized.includes('.test.') || normalized.includes('.spec.'))
|
|
1710
|
+
return false;
|
|
1711
|
+
return true;
|
|
1712
|
+
}
|
|
1713
|
+
function formatCitationLocation(citation) {
|
|
1714
|
+
return `${citation.path}:${citation.line}`;
|
|
1715
|
+
}
|
|
1716
|
+
function explainEvidenceCitation(citation) {
|
|
1717
|
+
const location = formatCitationLocation(citation);
|
|
1718
|
+
const snippet = normalizeSnippet(citation.snippet);
|
|
1719
|
+
if (!snippet)
|
|
1720
|
+
return `${location}`;
|
|
1721
|
+
if (/\.command\(/.test(snippet)) {
|
|
1722
|
+
return `${location} registers CLI wiring: ${snippet}`;
|
|
1354
1723
|
}
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
findings.push('Try adding a module/file hint, for example: "in packages/cli" or "in services/api auth middleware".');
|
|
1724
|
+
if (/^\s*import\b/.test(snippet)) {
|
|
1725
|
+
return `${location} connects module dependency: ${snippet}`;
|
|
1358
1726
|
}
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
const primaryInstall = manifestInstall || installMatches[0];
|
|
1362
|
-
if (!primaryInstall) {
|
|
1363
|
-
answer = 'I found CLI references, but not an explicit install command in the matched evidence.';
|
|
1364
|
-
}
|
|
1365
|
-
else {
|
|
1366
|
-
answer = [
|
|
1367
|
-
'Here is the install command I found for the CLI:',
|
|
1368
|
-
`\`${primaryInstall}\``,
|
|
1369
|
-
installMatches.length > 1 ? `Also seen: ${installMatches.slice(1, 3).map((cmd) => `\`${cmd}\``).join(', ')}` : '',
|
|
1370
|
-
].filter(Boolean).join('\n');
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
else if (asksFeatures && (context.featureBullets || []).length > 0) {
|
|
1374
|
-
const bullets = (context.featureBullets || []).slice(0, 6).map((line) => ` • ${line}`);
|
|
1375
|
-
answer = ['Here are the main platform features I could verify from the repo:', ...bullets].join('\n');
|
|
1376
|
-
}
|
|
1377
|
-
else if (asksFeatures) {
|
|
1378
|
-
const inferredFeatures = [];
|
|
1379
|
-
const signalText = citations.map((citation) => `${citation.path} ${citation.snippet}`.toLowerCase());
|
|
1380
|
-
const hasSignal = (patterns) => {
|
|
1381
|
-
return signalText.some((line) => patterns.some((pattern) => pattern.test(line)));
|
|
1382
|
-
};
|
|
1383
|
-
const preferredCommands = [
|
|
1384
|
-
'neurcode ask',
|
|
1385
|
-
'neurcode plan',
|
|
1386
|
-
'neurcode apply',
|
|
1387
|
-
'neurcode verify',
|
|
1388
|
-
'neurcode ship',
|
|
1389
|
-
'neurcode watch',
|
|
1390
|
-
'neurcode session',
|
|
1391
|
-
'neurcode revert',
|
|
1392
|
-
'neurcode check',
|
|
1393
|
-
'neurcode login',
|
|
1394
|
-
'neurcode config',
|
|
1395
|
-
'neurcode init',
|
|
1396
|
-
];
|
|
1397
|
-
const primaryCommands = [
|
|
1398
|
-
...preferredCommands.filter((command) => commandMatches.includes(command)),
|
|
1399
|
-
...commandMatches.filter((command) => !preferredCommands.includes(command)),
|
|
1400
|
-
]
|
|
1401
|
-
.filter((command) => !command.startsWith('neurcode session ') && !command.startsWith('neurcode revert '))
|
|
1402
|
-
.slice(0, 12);
|
|
1403
|
-
if (primaryCommands.length > 0) {
|
|
1404
|
-
inferredFeatures.push(`CLI workflows: ${primaryCommands.join(', ')}`);
|
|
1405
|
-
}
|
|
1406
|
-
if (hasSignal([/\bverify\b/, /\bpolicy\b/, /\bscope[_ -]?guard\b/, /\bgovernance\b/])) {
|
|
1407
|
-
inferredFeatures.push('Governance and policy verification gates.');
|
|
1408
|
-
}
|
|
1409
|
-
if (hasSignal([/\bship\b/, /\bmerge confidence\b/, /\bauto-remediation\b/])) {
|
|
1410
|
-
inferredFeatures.push('Ship workflow with automated remediation and merge confidence scoring.');
|
|
1411
|
-
}
|
|
1412
|
-
if (hasSignal([/\bask\b/, /\bcitation\b/, /\bgrounded\b/, /\bbrain\b/])) {
|
|
1413
|
-
inferredFeatures.push('Repository-grounded Q&A with cached context retrieval.');
|
|
1414
|
-
}
|
|
1415
|
-
if (hasSignal([/\bsession\b/, /\brevert\b/, /\bhistory\b/, /\btime machine\b/])) {
|
|
1416
|
-
inferredFeatures.push('Session/history tracking with revert support.');
|
|
1417
|
-
}
|
|
1418
|
-
if (hasSignal([/\bx-org-id\b/, /\bmulti[- ]tenant\b/, /\borganization[_ -]?id\b/])) {
|
|
1419
|
-
inferredFeatures.push('Multi-tenant organization-scoped isolation and identity context.');
|
|
1420
|
-
}
|
|
1421
|
-
if (inferredFeatures.length > 0) {
|
|
1422
|
-
answer = ['Based on repository evidence, these platform capabilities are available:', ...inferredFeatures.map((line) => ` • ${line}`)].join('\n');
|
|
1423
|
-
}
|
|
1424
|
-
else {
|
|
1425
|
-
answer = 'I found code evidence, but not enough explicit feature documentation to give a precise feature list yet.';
|
|
1426
|
-
}
|
|
1727
|
+
if (/\b(orgid|organizationid|userid|token|auth)\b/i.test(snippet) && /=/.test(snippet)) {
|
|
1728
|
+
return `${location} sets auth/org context: ${snippet}`;
|
|
1427
1729
|
}
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
const schemaFieldSummaries = buildSchemaFieldSummaries(citations, lineCache, terms);
|
|
1431
|
-
if (schemaFieldSummaries.length > 0) {
|
|
1432
|
-
const bullets = schemaFieldSummaries.map((line) => ` • ${line}`);
|
|
1433
|
-
answer = ['From the matched code, these fields/signatures are relevant:', ...bullets].join('\n');
|
|
1434
|
-
}
|
|
1435
|
-
else {
|
|
1436
|
-
const schemaLines = citations
|
|
1437
|
-
.map((citation) => formatInsightSnippet(citation.snippet))
|
|
1438
|
-
.filter((line) => /^export\s+interface\b/i.test(line) ||
|
|
1439
|
-
/^export\s+type\b/i.test(line) ||
|
|
1440
|
-
(/^[A-Za-z_][A-Za-z0-9_?]*\s*:\s*[^=,]+;?$/.test(line) &&
|
|
1441
|
-
!line.includes('{') &&
|
|
1442
|
-
!line.includes('=>')));
|
|
1443
|
-
const selected = [...new Set(schemaLines)].slice(0, 8);
|
|
1444
|
-
if (selected.length > 0) {
|
|
1445
|
-
const bullets = selected.map((line) => ` • ${line}`);
|
|
1446
|
-
answer = ['From the matched code, these fields/signatures are relevant:', ...bullets].join('\n');
|
|
1447
|
-
}
|
|
1448
|
-
else {
|
|
1449
|
-
answer = 'I found cache/schema-related code, but not enough direct field declarations in the top evidence.';
|
|
1450
|
-
}
|
|
1451
|
-
}
|
|
1730
|
+
if (/\breturn\b/.test(snippet)) {
|
|
1731
|
+
return `${location} returns runtime behavior: ${snippet}`;
|
|
1452
1732
|
}
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
const locationPool = focusedCitations.length > 0 ? [...focusedCitations] : [...citations];
|
|
1499
|
-
locationPool.sort((a, b) => citationRelevanceScore(b) - citationRelevanceScore(a));
|
|
1500
|
-
if (asksOrgRequestInjection) {
|
|
1501
|
-
const score = (citation) => {
|
|
1502
|
-
const snippet = citation.snippet.toLowerCase();
|
|
1503
|
-
let value = 0;
|
|
1504
|
-
if (snippet.includes('x-org-id'))
|
|
1505
|
-
value += 4;
|
|
1506
|
-
if (snippet.includes('auto-inject') || snippet.includes('inject'))
|
|
1507
|
-
value += 2;
|
|
1508
|
-
if (snippet.includes('headers[') || snippet.includes('header'))
|
|
1509
|
-
value += 2;
|
|
1510
|
-
if (snippet.includes('request'))
|
|
1511
|
-
value += 1;
|
|
1512
|
-
if (citation.path.includes('api-client.ts'))
|
|
1513
|
-
value += 2;
|
|
1514
|
-
return value;
|
|
1515
|
-
};
|
|
1516
|
-
locationPool.sort((a, b) => score(b) - score(a));
|
|
1517
|
-
}
|
|
1518
|
-
const prefersImplementationLines = /\b(defined|implemented|called|computed|resolved|lookup)\b/.test(normalizedQuestion);
|
|
1519
|
-
const queriedFlags = (question.match(/--[a-z0-9-]+/gi) || []).map((flag) => flag.toLowerCase());
|
|
1520
|
-
const declarationLike = prefersImplementationLines
|
|
1521
|
-
? locationPool.filter((citation) => /\b(?:export\s+)?(?:function|const|let|var|class|interface|type)\b/i.test(citation.snippet))
|
|
1733
|
+
if (/\bawait\b/.test(snippet)) {
|
|
1734
|
+
return `${location} performs async flow step: ${snippet}`;
|
|
1735
|
+
}
|
|
1736
|
+
return `${location} — ${snippet}`;
|
|
1737
|
+
}
|
|
1738
|
+
function collectUniquePaths(citations, limit, skipFirst) {
|
|
1739
|
+
const out = [];
|
|
1740
|
+
const seen = new Set();
|
|
1741
|
+
const start = skipFirst ? 1 : 0;
|
|
1742
|
+
for (let i = start; i < citations.length; i++) {
|
|
1743
|
+
const path = citations[i]?.path;
|
|
1744
|
+
if (!path || seen.has(path))
|
|
1745
|
+
continue;
|
|
1746
|
+
seen.add(path);
|
|
1747
|
+
out.push(path);
|
|
1748
|
+
if (out.length >= limit)
|
|
1749
|
+
break;
|
|
1750
|
+
}
|
|
1751
|
+
return out;
|
|
1752
|
+
}
|
|
1753
|
+
function buildRepoAnswerPayload(question, searchTerms, citations, stats, truth) {
|
|
1754
|
+
const profile = buildQueryProfile(searchTerms);
|
|
1755
|
+
const asksLocation = profile.asksLocation;
|
|
1756
|
+
const asksHow = profile.asksHow;
|
|
1757
|
+
const asksList = profile.asksList;
|
|
1758
|
+
const primary = citations[0];
|
|
1759
|
+
const primaryLocation = primary ? formatCitationLocation(primary) : null;
|
|
1760
|
+
const flowPaths = collectUniquePaths(citations, 4, false);
|
|
1761
|
+
const relatedPaths = collectUniquePaths(citations, 4, true);
|
|
1762
|
+
const evidenceLines = citations
|
|
1763
|
+
.slice(0, 6)
|
|
1764
|
+
.map((citation) => ` • ${explainEvidenceCitation(citation)}`);
|
|
1765
|
+
let answer = '';
|
|
1766
|
+
if (citations.length === 0) {
|
|
1767
|
+
answer = [
|
|
1768
|
+
'Short answer: I do not have enough direct repository evidence for this yet.',
|
|
1769
|
+
'',
|
|
1770
|
+
'What will improve accuracy:',
|
|
1771
|
+
' • Add a folder/file hint (for example: `in packages/cli/src/commands`).',
|
|
1772
|
+
' • Mention exact identifiers (function/class/flag names) if you have them.',
|
|
1773
|
+
].join('\n');
|
|
1774
|
+
}
|
|
1775
|
+
else if (asksLocation) {
|
|
1776
|
+
const relatedContext = relatedPaths.length > 0
|
|
1777
|
+
? ['', 'Related context worth checking:', ...relatedPaths.map((path) => ` • ${path}`)]
|
|
1522
1778
|
: [];
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
if (queriedFlags.length > 0) {
|
|
1540
|
-
const snippetLower = citation.snippet.toLowerCase();
|
|
1541
|
-
const matchedFlagCount = queriedFlags.filter((flag) => snippetLower.includes(flag)).length;
|
|
1542
|
-
value += matchedFlagCount * 2;
|
|
1543
|
-
if (/\.option\(/i.test(citation.snippet) && matchedFlagCount > 0)
|
|
1544
|
-
value += 2;
|
|
1545
|
-
}
|
|
1546
|
-
return value;
|
|
1547
|
-
};
|
|
1548
|
-
return score(b) - score(a);
|
|
1549
|
-
})
|
|
1550
|
-
: orderedLocationPool;
|
|
1551
|
-
const locations = commandFocusedLocationPool
|
|
1552
|
-
.slice(0, 5)
|
|
1553
|
-
.map((citation) => ` • ${citation.path}:${citation.line} — ${formatInsightSnippet(citation.snippet)}`);
|
|
1554
|
-
answer = ['I found the relevant references here:', ...locations].join('\n');
|
|
1555
|
-
}
|
|
1556
|
-
else if (asksHow && insightLines.length > 0) {
|
|
1557
|
-
const focusedInsights = [...citations]
|
|
1558
|
-
.sort((a, b) => citationRelevanceScore(b) - citationRelevanceScore(a))
|
|
1559
|
-
.filter((citation) => citationRelevanceScore(citation) >= 1)
|
|
1560
|
-
.map((citation) => formatInsightSnippet(citation.snippet))
|
|
1561
|
-
.filter((line) => !/^const\s+[A-Za-z_$][\w$]*\s*=\s*\/.+\/[a-z]*\.test\(/i.test(line))
|
|
1562
|
-
.filter((line) => !/^[A-Za-z_$][\w$]*,\s*$/.test(line))
|
|
1563
|
-
.filter((line) => !/^['"`].+['"`],?\s*$/.test(line));
|
|
1564
|
-
const decisionFilteredInsights = asksDecision
|
|
1565
|
-
? focusedInsights.filter((line) => !/(log|roi|estimat|time saved|badge|banner|record[a-z]*event|telemetry|metric)/i.test(line))
|
|
1566
|
-
: focusedInsights;
|
|
1567
|
-
const decisionCoreInsights = asksDecision
|
|
1568
|
-
? decisionFilteredInsights.filter((line) => /\b(verdict|policydecision|exitcode|pass|fail|warn)\b/i.test(line) ||
|
|
1569
|
-
/\?\s*'[^']+'\s*:\s*'[^']+'/.test(line))
|
|
1779
|
+
answer = [
|
|
1780
|
+
`Short answer: ${primaryLocation} is the strongest direct location match.`,
|
|
1781
|
+
'',
|
|
1782
|
+
'What I verified in code:',
|
|
1783
|
+
...evidenceLines,
|
|
1784
|
+
...relatedContext,
|
|
1785
|
+
'',
|
|
1786
|
+
`If you want, I can trace upstream callers and downstream usage from ${primaryLocation}.`,
|
|
1787
|
+
].join('\n');
|
|
1788
|
+
}
|
|
1789
|
+
else if (asksHow) {
|
|
1790
|
+
const flowSummary = flowPaths.length > 1
|
|
1791
|
+
? flowPaths.join(' -> ')
|
|
1792
|
+
: flowPaths[0] || (primaryLocation || 'the top match');
|
|
1793
|
+
const relatedContext = relatedPaths.length > 0
|
|
1794
|
+
? ['', 'Related context:', ...relatedPaths.map((path) => ` • ${path}`)]
|
|
1570
1795
|
: [];
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
else {
|
|
1589
|
-
answer = 'I see mixed tenancy signals, but it leans multi-tenant in current runtime paths.';
|
|
1590
|
-
}
|
|
1591
|
-
}
|
|
1592
|
-
else if (insightLines.length > 0) {
|
|
1593
|
-
const bullets = insightLines.map((line) => ` • ${line}`);
|
|
1594
|
-
answer = truth.status === 'insufficient'
|
|
1595
|
-
? ['I found partial evidence, but not enough for a fully definitive answer yet.', 'What I can confirm so far:', ...bullets].join('\n')
|
|
1596
|
-
: ['From this repo, here is what I found:', ...bullets].join('\n');
|
|
1796
|
+
answer = [
|
|
1797
|
+
`Short answer: the implementation flow is centered around ${flowSummary}.`,
|
|
1798
|
+
'',
|
|
1799
|
+
'Evidence-backed breakdown:',
|
|
1800
|
+
...evidenceLines,
|
|
1801
|
+
...relatedContext,
|
|
1802
|
+
].join('\n');
|
|
1803
|
+
}
|
|
1804
|
+
else if (asksList) {
|
|
1805
|
+
answer = [
|
|
1806
|
+
`Short answer: I verified ${truth.sourceCitations} evidence line(s) across ${truth.sourceFiles} file(s).`,
|
|
1807
|
+
'',
|
|
1808
|
+
'Most relevant items:',
|
|
1809
|
+
...evidenceLines,
|
|
1810
|
+
'',
|
|
1811
|
+
'If you want, I can rank these by execution order next.',
|
|
1812
|
+
].join('\n');
|
|
1597
1813
|
}
|
|
1598
1814
|
else {
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1815
|
+
const corroborating = relatedPaths.slice(0, 2);
|
|
1816
|
+
const corroboratingText = corroborating.length > 0 ? corroborating.join(', ') : 'nearby modules';
|
|
1817
|
+
answer = [
|
|
1818
|
+
`Short answer: ${primaryLocation} is the strongest anchor, corroborated by ${corroboratingText}.`,
|
|
1819
|
+
'',
|
|
1820
|
+
'What I can confirm directly from the codebase:',
|
|
1821
|
+
...evidenceLines,
|
|
1822
|
+
].join('\n');
|
|
1823
|
+
}
|
|
1824
|
+
const findings = [
|
|
1825
|
+
`Matched ${stats.matchedLines} evidence line(s) across ${stats.matchedFiles} primary source file(s).`,
|
|
1826
|
+
];
|
|
1827
|
+
if (primaryLocation) {
|
|
1828
|
+
findings.push(`Primary evidence anchor: ${primaryLocation}`);
|
|
1603
1829
|
}
|
|
1604
|
-
if (
|
|
1605
|
-
findings.push(
|
|
1830
|
+
if (flowPaths.length > 1) {
|
|
1831
|
+
findings.push(`Inferred file flow: ${flowPaths.join(' -> ')}`);
|
|
1606
1832
|
}
|
|
1607
|
-
|
|
1608
|
-
.
|
|
1609
|
-
.slice(0, 3)
|
|
1610
|
-
.map(([file, count]) => `${file} (${count})`);
|
|
1611
|
-
if (topFiles.length > 0) {
|
|
1612
|
-
findings.push(`Most relevant files: ${topFiles.join(', ')}`);
|
|
1833
|
+
if (searchTerms.highSignalTerms.length > 0) {
|
|
1834
|
+
findings.push(`High-signal terms used: ${searchTerms.highSignalTerms.slice(0, 8).join(', ')}`);
|
|
1613
1835
|
}
|
|
1614
|
-
findings.push(`Scanned ${stats.scannedFiles} file(s); matched ${stats.matchedFiles} file(s).`);
|
|
1615
1836
|
if (truth.status === 'insufficient') {
|
|
1616
1837
|
findings.push(...truth.reasons);
|
|
1617
|
-
findings.push('Add
|
|
1838
|
+
findings.push('Add a tighter module/file hint to improve grounding precision.');
|
|
1618
1839
|
}
|
|
1840
|
+
const topFiles = [...citations.reduce((acc, citation) => {
|
|
1841
|
+
acc.set(citation.path, (acc.get(citation.path) || 0) + 1);
|
|
1842
|
+
return acc;
|
|
1843
|
+
}, new Map())]
|
|
1844
|
+
.sort((a, b) => b[1] - a[1])
|
|
1845
|
+
.map(([path]) => path)
|
|
1846
|
+
.slice(0, 5);
|
|
1619
1847
|
return {
|
|
1620
1848
|
question,
|
|
1621
|
-
questionNormalized: normalizedQuestion,
|
|
1622
|
-
mode,
|
|
1849
|
+
questionNormalized: searchTerms.normalizedQuestion,
|
|
1850
|
+
mode: 'search',
|
|
1623
1851
|
answer,
|
|
1624
1852
|
findings,
|
|
1625
|
-
confidence,
|
|
1853
|
+
confidence: calibrateConfidence(truth),
|
|
1626
1854
|
proof: {
|
|
1627
|
-
topFiles
|
|
1628
|
-
acc.set(citation.path, (acc.get(citation.path) || 0) + 1);
|
|
1629
|
-
return acc;
|
|
1630
|
-
}, new Map()).entries()]
|
|
1631
|
-
.sort((a, b) => b[1] - a[1])
|
|
1632
|
-
.slice(0, 5)
|
|
1633
|
-
.map(([path]) => path),
|
|
1855
|
+
topFiles,
|
|
1634
1856
|
evidenceCount: citations.length,
|
|
1635
1857
|
coverage: {
|
|
1636
1858
|
sourceCitations: truth.sourceCitations,
|
|
@@ -1653,6 +1875,168 @@ function buildAnswer(mode, question, terms, citations, stats, termCounts, perFil
|
|
|
1653
1875
|
stats,
|
|
1654
1876
|
};
|
|
1655
1877
|
}
|
|
1878
|
+
function stripHtml(value) {
|
|
1879
|
+
return value
|
|
1880
|
+
.replace(/<[^>]*>/g, ' ')
|
|
1881
|
+
.replace(/"/g, '"')
|
|
1882
|
+
.replace(/'/g, "'")
|
|
1883
|
+
.replace(/&/g, '&')
|
|
1884
|
+
.replace(/\s+/g, ' ')
|
|
1885
|
+
.trim();
|
|
1886
|
+
}
|
|
1887
|
+
const EXTERNAL_LLM_BASE_URL = (process.env.NEURCODE_ASK_EXTERNAL_BASE_URL || 'https://api.deepinfra.com/v1/openai').replace(/\/+$/, '');
|
|
1888
|
+
const EXTERNAL_LLM_MODEL = process.env.NEURCODE_ASK_EXTERNAL_MODEL || 'deepseek-ai/DeepSeek-V3.2';
|
|
1889
|
+
const EXTERNAL_LLM_KEY_ENV_NAMES = [
|
|
1890
|
+
'DEEPINFRA_API_KEY',
|
|
1891
|
+
'NEURCODE_DEEPINFRA_API_KEY',
|
|
1892
|
+
'DEEPSEEK_API_KEY',
|
|
1893
|
+
'NEURCODE_DEEPSEEK_API_KEY',
|
|
1894
|
+
];
|
|
1895
|
+
function resolveExternalLlmApiKey() {
|
|
1896
|
+
for (const envName of EXTERNAL_LLM_KEY_ENV_NAMES) {
|
|
1897
|
+
const value = process.env[envName];
|
|
1898
|
+
if (value && value.trim())
|
|
1899
|
+
return value.trim();
|
|
1900
|
+
}
|
|
1901
|
+
return null;
|
|
1902
|
+
}
|
|
1903
|
+
function inferExternalAnswerConfidence(text) {
|
|
1904
|
+
const normalized = (0, plan_cache_1.normalizeIntent)(text);
|
|
1905
|
+
if (!normalized)
|
|
1906
|
+
return 'low';
|
|
1907
|
+
if (/\b(i am not sure|i'm not sure|unsure|unknown|cannot verify|can't verify|not certain|might|may|possibly|likely|probably)\b/.test(normalized)) {
|
|
1908
|
+
return 'low';
|
|
1909
|
+
}
|
|
1910
|
+
if (text.split(/\s+/).length <= 28)
|
|
1911
|
+
return 'high';
|
|
1912
|
+
return 'medium';
|
|
1913
|
+
}
|
|
1914
|
+
async function fetchExternalLlmAnswer(question) {
|
|
1915
|
+
if (process.env.NEURCODE_ASK_DISABLE_EXTERNAL_WEB === '1') {
|
|
1916
|
+
return null;
|
|
1917
|
+
}
|
|
1918
|
+
const apiKey = resolveExternalLlmApiKey();
|
|
1919
|
+
if (!apiKey)
|
|
1920
|
+
return null;
|
|
1921
|
+
const controller = new AbortController();
|
|
1922
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
1923
|
+
try {
|
|
1924
|
+
const response = await fetch(`${EXTERNAL_LLM_BASE_URL}/chat/completions`, {
|
|
1925
|
+
method: 'POST',
|
|
1926
|
+
signal: controller.signal,
|
|
1927
|
+
headers: {
|
|
1928
|
+
'Content-Type': 'application/json',
|
|
1929
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
1930
|
+
},
|
|
1931
|
+
body: JSON.stringify({
|
|
1932
|
+
model: EXTERNAL_LLM_MODEL,
|
|
1933
|
+
messages: [
|
|
1934
|
+
{
|
|
1935
|
+
role: 'system',
|
|
1936
|
+
content: [
|
|
1937
|
+
'You answer non-codebase factual questions for a CLI assistant.',
|
|
1938
|
+
'Return a direct factual answer in at most 2 short sentences.',
|
|
1939
|
+
'If uncertain, explicitly say you are not sure instead of guessing.',
|
|
1940
|
+
'Do not include markdown or code fences.',
|
|
1941
|
+
].join(' '),
|
|
1942
|
+
},
|
|
1943
|
+
{
|
|
1944
|
+
role: 'user',
|
|
1945
|
+
content: question,
|
|
1946
|
+
},
|
|
1947
|
+
],
|
|
1948
|
+
temperature: 0.1,
|
|
1949
|
+
max_tokens: 220,
|
|
1950
|
+
}),
|
|
1951
|
+
});
|
|
1952
|
+
if (!response.ok)
|
|
1953
|
+
return null;
|
|
1954
|
+
const payload = await response.json();
|
|
1955
|
+
const content = payload.choices?.[0]?.message?.content;
|
|
1956
|
+
if (typeof content !== 'string')
|
|
1957
|
+
return null;
|
|
1958
|
+
const text = stripHtml(content).replace(/^short answer:\s*/i, '').trim();
|
|
1959
|
+
if (!text)
|
|
1960
|
+
return null;
|
|
1961
|
+
return {
|
|
1962
|
+
text,
|
|
1963
|
+
source: `${EXTERNAL_LLM_MODEL} via DeepInfra`,
|
|
1964
|
+
confidence: inferExternalAnswerConfidence(text),
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1967
|
+
catch {
|
|
1968
|
+
return null;
|
|
1969
|
+
}
|
|
1970
|
+
finally {
|
|
1971
|
+
clearTimeout(timer);
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
async function buildExternalAnswerPayload(question, normalizedQuestion, reasons) {
|
|
1975
|
+
const external = await fetchExternalLlmAnswer(question);
|
|
1976
|
+
const hasExternalLlmKey = Boolean(resolveExternalLlmApiKey());
|
|
1977
|
+
const shortAnswer = external?.text
|
|
1978
|
+
? `Short answer: ${external.text}`
|
|
1979
|
+
: hasExternalLlmKey
|
|
1980
|
+
? 'Short answer: I could not get a reliable external response right now.'
|
|
1981
|
+
: 'Short answer: I could not answer this external question because DeepSeek is not configured (set DEEPINFRA_API_KEY).';
|
|
1982
|
+
const sourceNote = external?.source
|
|
1983
|
+
? `Source used: ${external.source}`
|
|
1984
|
+
: hasExternalLlmKey
|
|
1985
|
+
? 'DeepSeek request failed or returned no answer.'
|
|
1986
|
+
: 'DeepSeek not configured. Set DEEPINFRA_API_KEY to enable external free-flow answers.';
|
|
1987
|
+
const truthScore = external
|
|
1988
|
+
? external.confidence === 'high'
|
|
1989
|
+
? 0.42
|
|
1990
|
+
: external.confidence === 'medium'
|
|
1991
|
+
? 0.34
|
|
1992
|
+
: 0.24
|
|
1993
|
+
: 0.08;
|
|
1994
|
+
return {
|
|
1995
|
+
question,
|
|
1996
|
+
questionNormalized: normalizedQuestion,
|
|
1997
|
+
mode: 'search',
|
|
1998
|
+
answer: [
|
|
1999
|
+
shortAnswer,
|
|
2000
|
+
'',
|
|
2001
|
+
'I am strongest on repository questions. Come back with a codebase question and I will answer with file/line citations.',
|
|
2002
|
+
].join('\n'),
|
|
2003
|
+
findings: [
|
|
2004
|
+
'Question appears to be outside repository scope.',
|
|
2005
|
+
sourceNote,
|
|
2006
|
+
],
|
|
2007
|
+
confidence: external ? (external.confidence === 'high' ? 'medium' : 'low') : 'low',
|
|
2008
|
+
proof: {
|
|
2009
|
+
topFiles: [],
|
|
2010
|
+
evidenceCount: 0,
|
|
2011
|
+
coverage: {
|
|
2012
|
+
sourceCitations: 0,
|
|
2013
|
+
sourceFiles: 0,
|
|
2014
|
+
matchedFiles: 0,
|
|
2015
|
+
matchedLines: 0,
|
|
2016
|
+
},
|
|
2017
|
+
},
|
|
2018
|
+
truth: {
|
|
2019
|
+
status: 'insufficient',
|
|
2020
|
+
score: Number(truthScore.toFixed(2)),
|
|
2021
|
+
reasons: [
|
|
2022
|
+
'Answer is not grounded in repository files.',
|
|
2023
|
+
...reasons,
|
|
2024
|
+
],
|
|
2025
|
+
sourceCitations: 0,
|
|
2026
|
+
sourceFiles: 0,
|
|
2027
|
+
minCitationsRequired: 2,
|
|
2028
|
+
minFilesRequired: 1,
|
|
2029
|
+
},
|
|
2030
|
+
citations: [],
|
|
2031
|
+
generatedAt: new Date().toISOString(),
|
|
2032
|
+
stats: {
|
|
2033
|
+
scannedFiles: 0,
|
|
2034
|
+
matchedFiles: 0,
|
|
2035
|
+
matchedLines: 0,
|
|
2036
|
+
brainCandidates: 0,
|
|
2037
|
+
},
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
1656
2040
|
function emitAskResult(result, options) {
|
|
1657
2041
|
if (options.json) {
|
|
1658
2042
|
console.log(JSON.stringify({
|
|
@@ -1679,66 +2063,49 @@ function emitAskResult(result, options) {
|
|
|
1679
2063
|
console.log(chalk.yellow(result.answer));
|
|
1680
2064
|
}
|
|
1681
2065
|
const showProof = options.proof === true;
|
|
1682
|
-
|
|
2066
|
+
const showVerbose = options.verbose === true;
|
|
2067
|
+
if (!showProof && !showVerbose) {
|
|
1683
2068
|
console.log(chalk.dim(`\nConfidence: ${result.confidence.toUpperCase()}`));
|
|
1684
|
-
if (result.truth.status === 'insufficient' && result.
|
|
2069
|
+
if (result.truth.status === 'insufficient' && result.truth.reasons.length > 0) {
|
|
1685
2070
|
console.log(chalk.yellow('\nWhy confidence is limited:'));
|
|
1686
2071
|
for (const reason of result.truth.reasons.slice(0, 2)) {
|
|
1687
2072
|
console.log(chalk.yellow(` • ${reason}`));
|
|
1688
2073
|
}
|
|
1689
|
-
console.log(chalk.dim('\nTip: add `--proof` for
|
|
2074
|
+
console.log(chalk.dim('\nTip: add `--proof` for concise evidence or `--verbose` for full evidence output.'));
|
|
1690
2075
|
}
|
|
1691
2076
|
return;
|
|
1692
2077
|
}
|
|
1693
|
-
if (showProof
|
|
1694
|
-
|
|
1695
|
-
if (proof) {
|
|
2078
|
+
if (showProof) {
|
|
2079
|
+
if (result.proof) {
|
|
1696
2080
|
console.log(chalk.bold.white('\nProof:'));
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
console.log(chalk.cyan(` • Top files: ${topFiles.join(', ')}`));
|
|
1700
|
-
}
|
|
1701
|
-
console.log(chalk.cyan(` • Evidence: citations=${proof.coverage.sourceCitations}, source_files=${proof.coverage.sourceFiles}, matched_lines=${proof.coverage.matchedLines}`));
|
|
1702
|
-
}
|
|
1703
|
-
const tenancyQuestion = /\b(tenant|tenancy|single|multi|organization|org)\b/.test(result.questionNormalized);
|
|
1704
|
-
let citations = result.citations.slice(0, Math.min(options.maxCitations, 12));
|
|
1705
|
-
if (tenancyQuestion) {
|
|
1706
|
-
const focused = citations.filter((citation) => /\b(multi[- ]tenant|x-org-id|organization[_ -]?id|org[_ -]?id)\b/i.test(citation.snippet));
|
|
1707
|
-
if (focused.length >= 3) {
|
|
1708
|
-
citations = focused;
|
|
1709
|
-
}
|
|
1710
|
-
if (/multi-tenant/i.test(result.answer)) {
|
|
1711
|
-
citations = citations.filter((citation) => !/\bsingle-user platform\b/i.test(citation.snippet));
|
|
2081
|
+
if (result.proof.topFiles.length > 0) {
|
|
2082
|
+
console.log(chalk.cyan(` • Top files: ${result.proof.topFiles.slice(0, 5).join(', ')}`));
|
|
1712
2083
|
}
|
|
2084
|
+
console.log(chalk.cyan(` • Coverage: citations=${result.proof.coverage.sourceCitations}, files=${result.proof.coverage.sourceFiles}, matched_lines=${result.proof.coverage.matchedLines}`));
|
|
1713
2085
|
}
|
|
1714
|
-
|
|
1715
|
-
if (
|
|
2086
|
+
const proofCitations = result.citations.slice(0, Math.min(options.maxCitations, 6));
|
|
2087
|
+
if (proofCitations.length > 0) {
|
|
1716
2088
|
console.log(chalk.bold.white('\nKey Evidence:'));
|
|
1717
|
-
|
|
2089
|
+
proofCitations.forEach((citation, idx) => {
|
|
1718
2090
|
console.log(chalk.dim(` ${idx + 1}. ${citation.path}:${citation.line} ${citation.snippet}`));
|
|
1719
2091
|
});
|
|
1720
2092
|
}
|
|
1721
|
-
const truthLabel = result.truth.status === 'grounded' ? chalk.green('GROUNDED') : chalk.yellow('INSUFFICIENT');
|
|
1722
|
-
console.log(chalk.dim(`\nTruth Mode: ${truthLabel} (score=${result.truth.score.toFixed(2)}, source_citations=${result.truth.sourceCitations}, source_files=${result.truth.sourceFiles})`));
|
|
1723
|
-
console.log(chalk.dim(`Confidence: ${result.confidence.toUpperCase()} | scanned=${result.stats.scannedFiles} matched=${result.stats.matchedFiles}`));
|
|
1724
|
-
return;
|
|
1725
2093
|
}
|
|
1726
|
-
if (
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
2094
|
+
if (showVerbose) {
|
|
2095
|
+
if (result.findings.length > 0) {
|
|
2096
|
+
console.log(chalk.bold.white('\nFindings:'));
|
|
2097
|
+
for (const finding of result.findings) {
|
|
2098
|
+
console.log(chalk.cyan(` • ${finding}`));
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
const verboseCitations = result.citations.slice(0, options.maxCitations);
|
|
2102
|
+
if (verboseCitations.length > 0) {
|
|
2103
|
+
console.log(chalk.bold.white('\nEvidence:'));
|
|
2104
|
+
verboseCitations.forEach((citation, idx) => {
|
|
2105
|
+
const prefix = citation.term ? `${citation.term} ` : '';
|
|
2106
|
+
console.log(chalk.dim(` ${idx + 1}. ${citation.path}:${citation.line} ${prefix}${citation.snippet}`));
|
|
2107
|
+
});
|
|
1730
2108
|
}
|
|
1731
|
-
}
|
|
1732
|
-
const citations = result.citations.slice(0, options.maxCitations);
|
|
1733
|
-
if (citations.length > 0) {
|
|
1734
|
-
console.log(chalk.bold.white('\nEvidence:'));
|
|
1735
|
-
citations.forEach((citation, idx) => {
|
|
1736
|
-
const prefix = citation.term ? `${citation.term} ` : '';
|
|
1737
|
-
console.log(chalk.dim(` ${idx + 1}. ${citation.path}:${citation.line} ${prefix}${citation.snippet}`));
|
|
1738
|
-
});
|
|
1739
|
-
}
|
|
1740
|
-
else {
|
|
1741
|
-
console.log(chalk.yellow('\nEvidence: no direct matching lines found.'));
|
|
1742
2109
|
}
|
|
1743
2110
|
const truthLabel = result.truth.status === 'grounded' ? chalk.green('GROUNDED') : chalk.yellow('INSUFFICIENT');
|
|
1744
2111
|
console.log(chalk.dim(`\nTruth Mode: ${truthLabel} (score=${result.truth.score.toFixed(2)}, source_citations=${result.truth.sourceCitations}, source_files=${result.truth.sourceFiles})`));
|
|
@@ -1759,13 +2126,96 @@ async function askCommand(question, options = {}) {
|
|
|
1759
2126
|
const scope = { orgId: orgId || null, projectId: projectId || null };
|
|
1760
2127
|
const maxCitations = Math.max(3, Math.min(options.maxCitations || 12, 30));
|
|
1761
2128
|
const shouldUseCache = options.cache !== false && process.env.NEURCODE_ASK_NO_CACHE !== '1';
|
|
1762
|
-
const
|
|
1763
|
-
const
|
|
1764
|
-
if (
|
|
1765
|
-
|
|
1766
|
-
|
|
2129
|
+
const searchTerms = buildSearchTerms(question);
|
|
2130
|
+
const ownershipAnswer = buildOwnershipDeterministicAnswer(cwd, question, searchTerms.normalizedQuestion);
|
|
2131
|
+
if (ownershipAnswer) {
|
|
2132
|
+
emitAskResult(ownershipAnswer, {
|
|
2133
|
+
json: options.json,
|
|
2134
|
+
maxCitations,
|
|
2135
|
+
fromPlan: options.fromPlan,
|
|
2136
|
+
verbose: options.verbose,
|
|
2137
|
+
proof: options.proof,
|
|
2138
|
+
});
|
|
2139
|
+
if (orgId && projectId) {
|
|
2140
|
+
(0, brain_context_1.recordBrainProgressEvent)(cwd, scope, {
|
|
2141
|
+
type: 'ask',
|
|
2142
|
+
note: `mode=deterministic;reason=ownership_git_history;truth=${ownershipAnswer.truth.status};score=${ownershipAnswer.truth.score.toFixed(2)}`,
|
|
2143
|
+
});
|
|
1767
2144
|
}
|
|
1768
|
-
|
|
2145
|
+
return;
|
|
2146
|
+
}
|
|
2147
|
+
const scopeAssessment = classifyQuestionScope(question, searchTerms);
|
|
2148
|
+
if (scopeAssessment.kind === 'external') {
|
|
2149
|
+
const externalPayload = await buildExternalAnswerPayload(question, searchTerms.normalizedQuestion, scopeAssessment.reasons);
|
|
2150
|
+
emitAskResult(externalPayload, {
|
|
2151
|
+
json: options.json,
|
|
2152
|
+
maxCitations,
|
|
2153
|
+
fromPlan: options.fromPlan,
|
|
2154
|
+
verbose: options.verbose,
|
|
2155
|
+
proof: options.proof,
|
|
2156
|
+
});
|
|
2157
|
+
if (orgId && projectId) {
|
|
2158
|
+
(0, brain_context_1.recordBrainProgressEvent)(cwd, scope, {
|
|
2159
|
+
type: 'ask',
|
|
2160
|
+
note: `mode=external;truth=${externalPayload.truth.status};score=${externalPayload.truth.score.toFixed(2)}`,
|
|
2161
|
+
});
|
|
2162
|
+
}
|
|
2163
|
+
return;
|
|
2164
|
+
}
|
|
2165
|
+
const registrationAnswer = buildCommandRegistrationDeterministicAnswer(cwd, question, searchTerms, maxCitations);
|
|
2166
|
+
if (registrationAnswer) {
|
|
2167
|
+
emitAskResult(registrationAnswer, {
|
|
2168
|
+
json: options.json,
|
|
2169
|
+
maxCitations,
|
|
2170
|
+
fromPlan: options.fromPlan,
|
|
2171
|
+
verbose: options.verbose,
|
|
2172
|
+
proof: options.proof,
|
|
2173
|
+
});
|
|
2174
|
+
if (orgId && projectId) {
|
|
2175
|
+
(0, brain_context_1.recordBrainProgressEvent)(cwd, scope, {
|
|
2176
|
+
type: 'ask',
|
|
2177
|
+
note: `mode=deterministic;reason=command_registration;truth=${registrationAnswer.truth.status};score=${registrationAnswer.truth.score.toFixed(2)}`,
|
|
2178
|
+
});
|
|
2179
|
+
}
|
|
2180
|
+
return;
|
|
2181
|
+
}
|
|
2182
|
+
const commandInventoryAnswer = buildCommandInventoryDeterministicAnswer(cwd, question, searchTerms, maxCitations);
|
|
2183
|
+
if (commandInventoryAnswer) {
|
|
2184
|
+
emitAskResult(commandInventoryAnswer, {
|
|
2185
|
+
json: options.json,
|
|
2186
|
+
maxCitations,
|
|
2187
|
+
fromPlan: options.fromPlan,
|
|
2188
|
+
verbose: options.verbose,
|
|
2189
|
+
proof: options.proof,
|
|
2190
|
+
});
|
|
2191
|
+
if (orgId && projectId) {
|
|
2192
|
+
(0, brain_context_1.recordBrainProgressEvent)(cwd, scope, {
|
|
2193
|
+
type: 'ask',
|
|
2194
|
+
note: `mode=deterministic;reason=command_inventory;truth=${commandInventoryAnswer.truth.status};score=${commandInventoryAnswer.truth.score.toFixed(2)}`,
|
|
2195
|
+
});
|
|
2196
|
+
}
|
|
2197
|
+
return;
|
|
2198
|
+
}
|
|
2199
|
+
const commandSubcommandFlowAnswer = buildCommandSubcommandFlowDeterministicAnswer(cwd, question, searchTerms, maxCitations);
|
|
2200
|
+
if (commandSubcommandFlowAnswer) {
|
|
2201
|
+
emitAskResult(commandSubcommandFlowAnswer, {
|
|
2202
|
+
json: options.json,
|
|
2203
|
+
maxCitations,
|
|
2204
|
+
fromPlan: options.fromPlan,
|
|
2205
|
+
verbose: options.verbose,
|
|
2206
|
+
proof: options.proof,
|
|
2207
|
+
});
|
|
2208
|
+
if (orgId && projectId) {
|
|
2209
|
+
(0, brain_context_1.recordBrainProgressEvent)(cwd, scope, {
|
|
2210
|
+
type: 'ask',
|
|
2211
|
+
note: `mode=deterministic;reason=command_subcommand_flow;truth=${commandSubcommandFlowAnswer.truth.status};score=${commandSubcommandFlowAnswer.truth.score.toFixed(2)}`,
|
|
2212
|
+
});
|
|
2213
|
+
}
|
|
2214
|
+
return;
|
|
2215
|
+
}
|
|
2216
|
+
const askCacheFlowAnswer = buildAskCacheFlowDeterministicAnswer(cwd, question, searchTerms, maxCitations);
|
|
2217
|
+
if (askCacheFlowAnswer) {
|
|
2218
|
+
emitAskResult(askCacheFlowAnswer, {
|
|
1769
2219
|
json: options.json,
|
|
1770
2220
|
maxCitations,
|
|
1771
2221
|
fromPlan: options.fromPlan,
|
|
@@ -1775,7 +2225,7 @@ async function askCommand(question, options = {}) {
|
|
|
1775
2225
|
if (orgId && projectId) {
|
|
1776
2226
|
(0, brain_context_1.recordBrainProgressEvent)(cwd, scope, {
|
|
1777
2227
|
type: 'ask',
|
|
1778
|
-
note: `mode=deterministic;reason
|
|
2228
|
+
note: `mode=deterministic;reason=ask_cache_flow;truth=${askCacheFlowAnswer.truth.status};score=${askCacheFlowAnswer.truth.score.toFixed(2)}`,
|
|
1779
2229
|
});
|
|
1780
2230
|
}
|
|
1781
2231
|
return;
|
|
@@ -1802,12 +2252,12 @@ async function askCommand(question, options = {}) {
|
|
|
1802
2252
|
});
|
|
1803
2253
|
}
|
|
1804
2254
|
catch {
|
|
1805
|
-
//
|
|
2255
|
+
// Non-blocking.
|
|
1806
2256
|
}
|
|
1807
2257
|
}
|
|
1808
2258
|
if (shouldUseCache && orgId && projectId) {
|
|
1809
2259
|
const questionHash = (0, ask_cache_1.computeAskQuestionHash)({
|
|
1810
|
-
question: normalizedQuestion,
|
|
2260
|
+
question: searchTerms.normalizedQuestion,
|
|
1811
2261
|
contextHash: staticContext.hash,
|
|
1812
2262
|
});
|
|
1813
2263
|
const exactKey = (0, ask_cache_1.computeAskCacheKey)({
|
|
@@ -1824,7 +2274,7 @@ async function askCommand(question, options = {}) {
|
|
|
1824
2274
|
const exactOutput = {
|
|
1825
2275
|
...exact.output,
|
|
1826
2276
|
question,
|
|
1827
|
-
questionNormalized: normalizedQuestion,
|
|
2277
|
+
questionNormalized: searchTerms.normalizedQuestion,
|
|
1828
2278
|
};
|
|
1829
2279
|
emitAskResult(exactOutput, {
|
|
1830
2280
|
json: options.json,
|
|
@@ -1844,18 +2294,18 @@ async function askCommand(question, options = {}) {
|
|
|
1844
2294
|
orgId,
|
|
1845
2295
|
projectId,
|
|
1846
2296
|
repo: repoFingerprint,
|
|
1847
|
-
question: normalizedQuestion,
|
|
2297
|
+
question: searchTerms.normalizedQuestion,
|
|
1848
2298
|
policyVersionHash,
|
|
1849
2299
|
neurcodeVersion,
|
|
1850
2300
|
contextHash: staticContext.hash,
|
|
1851
2301
|
changedPaths: (0, ask_cache_1.getChangedWorkingTreePaths)(cwd),
|
|
1852
|
-
minSimilarity: 0.
|
|
2302
|
+
minSimilarity: 0.72,
|
|
1853
2303
|
});
|
|
1854
2304
|
if (near) {
|
|
1855
2305
|
const nearOutput = {
|
|
1856
2306
|
...near.entry.output,
|
|
1857
2307
|
question,
|
|
1858
|
-
questionNormalized: normalizedQuestion,
|
|
2308
|
+
questionNormalized: searchTerms.normalizedQuestion,
|
|
1859
2309
|
};
|
|
1860
2310
|
const reasonText = near.reason === 'safe_repo_drift_similar_question'
|
|
1861
2311
|
? 'Using near-cached answer (safe repo drift)'
|
|
@@ -1879,408 +2329,24 @@ async function askCommand(question, options = {}) {
|
|
|
1879
2329
|
console.log(chalk.dim(`🧠 Asking repo context in ${cwd}...`));
|
|
1880
2330
|
}
|
|
1881
2331
|
const brainResults = orgId && projectId
|
|
1882
|
-
? (0, brain_context_1.searchBrainContextEntries)(cwd, scope, normalizedQuestion, { limit:
|
|
2332
|
+
? (0, brain_context_1.searchBrainContextEntries)(cwd, scope, searchTerms.normalizedQuestion, { limit: 64 })
|
|
1883
2333
|
: { entries: [], totalIndexedFiles: 0 };
|
|
1884
|
-
const
|
|
1885
|
-
const knownCliCommands = extractCommandsFromCliIndex(cwd);
|
|
1886
|
-
const featureBullets = extractFeatureBulletsForRepo(cwd, 10);
|
|
1887
|
-
if (!options.json && brainResults.entries.length > 0) {
|
|
1888
|
-
const top = brainResults.entries.filter((entry) => entry.score > 0).length;
|
|
1889
|
-
console.log(chalk.dim(`🧠 Brain retrieval: ${top} relevant file summaries from ${brainResults.totalIndexedFiles} indexed files`));
|
|
1890
|
-
}
|
|
1891
|
-
const { mode, terms, matchers } = buildMatchers(question);
|
|
1892
|
-
const pathHints = derivePathHints(question);
|
|
1893
|
-
const mentionsAskCommand = /\bask\b/.test(normalizedQuestion);
|
|
1894
|
-
const asksQuestionAnsweringInternals = /\b(question|questions|answer|answers|cache|brain|citation|grounded)\b/.test(normalizedQuestion);
|
|
1895
|
-
if (matchers.length === 0) {
|
|
1896
|
-
console.error(chalk.red('❌ Could not derive useful search terms from the question.'));
|
|
1897
|
-
process.exit(1);
|
|
1898
|
-
}
|
|
1899
|
-
const candidateSet = new Set();
|
|
1900
|
-
const pathPriority = new Map();
|
|
2334
|
+
const pathBoostScores = new Map();
|
|
1901
2335
|
for (const entry of brainResults.entries) {
|
|
1902
|
-
|
|
1903
|
-
pathPriority.set(entry.path, (pathPriority.get(entry.path) || 0) + entry.score);
|
|
1904
|
-
}
|
|
1905
|
-
addAnchorCandidates(fileTree, candidateSet, pathPriority, normalizedQuestion);
|
|
1906
|
-
const tokenHints = tokenizeQuestion(question);
|
|
1907
|
-
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) &&
|
|
1908
|
-
!/\b(feature|features|install|setup|readme|docs|documentation)\b/.test(normalizedQuestion);
|
|
1909
|
-
if (candidateSet.size < 80) {
|
|
1910
|
-
for (const filePath of fileTree) {
|
|
1911
|
-
const normalizedPath = filePath.toLowerCase();
|
|
1912
|
-
let score = pathPriority.get(filePath) || 0;
|
|
1913
|
-
for (const token of tokenHints) {
|
|
1914
|
-
if (normalizedPath.includes(token))
|
|
1915
|
-
score += 0.2;
|
|
1916
|
-
}
|
|
1917
|
-
if (normalizedPath.startsWith('.github/')) {
|
|
1918
|
-
score -= 0.2;
|
|
1919
|
-
}
|
|
1920
|
-
if (normalizedPath.startsWith('scripts/')) {
|
|
1921
|
-
score -= 0.1;
|
|
1922
|
-
}
|
|
1923
|
-
if (codeFocusedQuestion && (filePath === 'README.md' || normalizedPath.startsWith('docs/'))) {
|
|
1924
|
-
score -= 0.35;
|
|
1925
|
-
}
|
|
1926
|
-
if (!mentionsAskCommand &&
|
|
1927
|
-
!asksQuestionAnsweringInternals &&
|
|
1928
|
-
(filePath.endsWith('/commands/ask.ts') || filePath.endsWith('/utils/ask-cache.ts'))) {
|
|
1929
|
-
score -= 0.45;
|
|
1930
|
-
}
|
|
1931
|
-
if (pathHints.some((hint) => normalizedPath.startsWith(hint))) {
|
|
1932
|
-
score += 0.45;
|
|
1933
|
-
}
|
|
1934
|
-
else if (pathHints.length > 0) {
|
|
1935
|
-
score -= 0.08;
|
|
1936
|
-
}
|
|
1937
|
-
if (/\b(feature|features|capability|capabilities|offers?|platform)\b/.test(normalizedQuestion)) {
|
|
1938
|
-
if (filePath === 'README.md' || normalizedPath.startsWith('docs/')) {
|
|
1939
|
-
score += 0.55;
|
|
1940
|
-
}
|
|
1941
|
-
if (normalizedPath.startsWith('web/') || normalizedPath.startsWith('packages/dashboard/')) {
|
|
1942
|
-
score -= 0.25;
|
|
1943
|
-
}
|
|
1944
|
-
}
|
|
1945
|
-
if (score > 0) {
|
|
1946
|
-
candidateSet.add(filePath);
|
|
1947
|
-
pathPriority.set(filePath, score);
|
|
1948
|
-
}
|
|
1949
|
-
}
|
|
1950
|
-
}
|
|
1951
|
-
if (candidateSet.size < 40) {
|
|
1952
|
-
for (const filePath of fileTree.slice(0, Math.min(fileTree.length, MAX_SCAN_FILES))) {
|
|
1953
|
-
candidateSet.add(filePath);
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
const selfReferenceFiles = new Set([
|
|
1957
|
-
'packages/cli/src/commands/ask.ts',
|
|
1958
|
-
'packages/cli/src/utils/ask-cache.ts',
|
|
1959
|
-
]);
|
|
1960
|
-
const prioritized = [...candidateSet]
|
|
1961
|
-
.filter((filePath) => (0, fs_1.existsSync)((0, path_1.join)(cwd, filePath)))
|
|
1962
|
-
.filter((filePath) => mentionsAskCommand || !selfReferenceFiles.has(filePath))
|
|
1963
|
-
.sort((a, b) => (pathPriority.get(b) || 0) - (pathPriority.get(a) || 0));
|
|
1964
|
-
const hinted = pathHints.length > 0
|
|
1965
|
-
? prioritized.filter((filePath) => pathHints.some((hint) => filePath.startsWith(hint)))
|
|
1966
|
-
: [];
|
|
1967
|
-
const nonHinted = pathHints.length > 0
|
|
1968
|
-
? prioritized.filter((filePath) => !pathHints.some((hint) => filePath.startsWith(hint)))
|
|
1969
|
-
: prioritized;
|
|
1970
|
-
const candidates = [...hinted, ...nonHinted].slice(0, MAX_SCAN_FILES);
|
|
1971
|
-
const querySignals = deriveQuerySignals(question, normalizedQuestion, terms);
|
|
1972
|
-
const commandFocusQuery = extractCommandFocus(normalizedQuestion);
|
|
1973
|
-
const anchorTerms = extractAnchorTerms(question);
|
|
1974
|
-
const hasTemporalIntent = /\b(before|after|during|when)\b/.test(normalizedQuestion);
|
|
1975
|
-
const highSignalSet = new Set(querySignals.highSignalTerms);
|
|
1976
|
-
const rawFileMatches = new Map();
|
|
1977
|
-
const termFileHits = new Map();
|
|
1978
|
-
let scannedFiles = 0;
|
|
1979
|
-
for (const filePath of candidates) {
|
|
1980
|
-
const fullPath = (0, path_1.join)(cwd, filePath);
|
|
1981
|
-
let content = '';
|
|
1982
|
-
try {
|
|
1983
|
-
const st = (0, fs_1.statSync)(fullPath);
|
|
1984
|
-
if (st.size > MAX_FILE_BYTES)
|
|
1985
|
-
continue;
|
|
1986
|
-
content = (0, fs_1.readFileSync)(fullPath, 'utf-8');
|
|
1987
|
-
}
|
|
1988
|
-
catch {
|
|
1989
|
-
continue;
|
|
1990
|
-
}
|
|
1991
|
-
scannedFiles++;
|
|
1992
|
-
const lines = content.split(/\r?\n/);
|
|
1993
|
-
const lineTerms = new Map();
|
|
1994
|
-
const matchedTerms = new Set();
|
|
1995
|
-
for (let idx = 0; idx < lines.length; idx++) {
|
|
1996
|
-
const rawLine = lines[idx];
|
|
1997
|
-
if (!rawLine || rawLine.trim().length === 0)
|
|
1998
|
-
continue;
|
|
1999
|
-
for (const matcher of matchers) {
|
|
2000
|
-
if (!matcher.regex.test(rawLine))
|
|
2001
|
-
continue;
|
|
2002
|
-
if (!lineTerms.has(idx))
|
|
2003
|
-
lineTerms.set(idx, new Set());
|
|
2004
|
-
lineTerms.get(idx).add(matcher.label);
|
|
2005
|
-
matchedTerms.add(matcher.label);
|
|
2006
|
-
}
|
|
2007
|
-
}
|
|
2008
|
-
if (lineTerms.size === 0)
|
|
2009
|
-
continue;
|
|
2010
|
-
rawFileMatches.set(filePath, {
|
|
2011
|
-
lines,
|
|
2012
|
-
lineTerms,
|
|
2013
|
-
pathScore: pathPriority.get(filePath) || 0,
|
|
2014
|
-
hintBoost: pathHints.some((hint) => filePath.startsWith(hint)) ? 0.25 : 0,
|
|
2015
|
-
});
|
|
2016
|
-
for (const term of matchedTerms) {
|
|
2017
|
-
termFileHits.set(term, (termFileHits.get(term) || 0) + 1);
|
|
2018
|
-
}
|
|
2019
|
-
}
|
|
2020
|
-
const matchedFileTotal = Math.max(rawFileMatches.size, 1);
|
|
2021
|
-
const termWeight = new Map();
|
|
2022
|
-
for (const term of terms) {
|
|
2023
|
-
const docFreq = termFileHits.get(term) || 0;
|
|
2024
|
-
const idfWeight = Math.log((matchedFileTotal + 1) / (docFreq + 1)) + 0.25;
|
|
2025
|
-
const highSignalBoost = highSignalSet.has(term) ? 0.35 : 0;
|
|
2026
|
-
termWeight.set(term, Math.max(0.08, idfWeight + highSignalBoost));
|
|
2027
|
-
}
|
|
2028
|
-
const citations = [];
|
|
2029
|
-
for (const [filePath, fileData] of rawFileMatches.entries()) {
|
|
2030
|
-
const { lines, lineTerms, pathScore, hintBoost } = fileData;
|
|
2031
|
-
for (const [lineIdx, directTerms] of lineTerms.entries()) {
|
|
2032
|
-
if (citations.length >= MAX_RAW_CITATIONS)
|
|
2033
|
-
break;
|
|
2034
|
-
const rawLine = lines[lineIdx] || '';
|
|
2035
|
-
const snippet = normalizeSnippet(rawLine);
|
|
2036
|
-
if (!snippet)
|
|
2037
|
-
continue;
|
|
2038
|
-
const windowTerms = new Set();
|
|
2039
|
-
const contextRadius = querySignals.asksLocation || querySignals.asksDefinition ? 6 : 2;
|
|
2040
|
-
for (let i = Math.max(0, lineIdx - contextRadius); i <= Math.min(lines.length - 1, lineIdx + contextRadius); i++) {
|
|
2041
|
-
const termsAtLine = lineTerms.get(i);
|
|
2042
|
-
if (!termsAtLine)
|
|
2043
|
-
continue;
|
|
2044
|
-
for (const term of termsAtLine)
|
|
2045
|
-
windowTerms.add(term);
|
|
2046
|
-
}
|
|
2047
|
-
const highSignalWindow = [...windowTerms].filter((term) => highSignalSet.has(term));
|
|
2048
|
-
let score = pathScore + hintBoost;
|
|
2049
|
-
for (const term of windowTerms) {
|
|
2050
|
-
score += termWeight.get(term) || 0.1;
|
|
2051
|
-
}
|
|
2052
|
-
score += highSignalWindow.length * 0.7;
|
|
2053
|
-
score += windowTerms.size * 0.12;
|
|
2054
|
-
if (querySignals.highSignalTerms.length > 0 && highSignalWindow.length === 0) {
|
|
2055
|
-
score -= 0.65;
|
|
2056
|
-
}
|
|
2057
|
-
if ((querySignals.asksLocation || querySignals.asksDefinition) && querySignals.highSignalTerms.length >= 2 && highSignalWindow.length < 2) {
|
|
2058
|
-
score -= 0.9;
|
|
2059
|
-
}
|
|
2060
|
-
const anchorHits = countTermHitsInText(rawLine.toLowerCase(), anchorTerms);
|
|
2061
|
-
score += anchorHits * 1.2;
|
|
2062
|
-
if ((querySignals.asksLocation || querySignals.asksDefinition) && anchorTerms.length > 0 && anchorHits === 0) {
|
|
2063
|
-
score -= 0.6;
|
|
2064
|
-
}
|
|
2065
|
-
if (querySignals.asksLocation || querySignals.asksDefinition) {
|
|
2066
|
-
if (looksLikeImportLine(rawLine))
|
|
2067
|
-
score -= 0.75;
|
|
2068
|
-
if (/\b(?:export\s+)?(?:function|const|let|var|class|interface|type)\b/.test(rawLine) &&
|
|
2069
|
-
countTermHitsInText(rawLine.toLowerCase(), querySignals.highSignalTerms) >= 1) {
|
|
2070
|
-
score += 0.9;
|
|
2071
|
-
}
|
|
2072
|
-
if (querySignals.asksDefinition && /console\.log\(/.test(rawLine)) {
|
|
2073
|
-
score -= 0.6;
|
|
2074
|
-
}
|
|
2075
|
-
for (const identifier of querySignals.identifiers) {
|
|
2076
|
-
const escaped = escapeRegExp(identifier);
|
|
2077
|
-
if (new RegExp(`\\b${escaped}\\s*\\(`, 'i').test(rawLine)) {
|
|
2078
|
-
score += 0.95;
|
|
2079
|
-
}
|
|
2080
|
-
if (new RegExp(`\\b(?:function|const|let|var|class|interface|type)\\s+${escaped}\\b`, 'i').test(rawLine)) {
|
|
2081
|
-
score += 0.8;
|
|
2082
|
-
}
|
|
2083
|
-
}
|
|
2084
|
-
if (hasTemporalIntent) {
|
|
2085
|
-
if (/\b[A-Za-z_][A-Za-z0-9_]*\s*\(/.test(rawLine))
|
|
2086
|
-
score += 0.75;
|
|
2087
|
-
if (/^\s*export\s+(interface|type)\b/.test(rawLine))
|
|
2088
|
-
score -= 0.9;
|
|
2089
|
-
if (/^\s*const\s+[A-Z0-9_]+\s*=/.test(rawLine))
|
|
2090
|
-
score -= 0.7;
|
|
2091
|
-
}
|
|
2092
|
-
}
|
|
2093
|
-
if (querySignals.asksHow && /\b(if|else|return|await|for|while|switch)\b/.test(rawLine)) {
|
|
2094
|
-
score += 0.2;
|
|
2095
|
-
}
|
|
2096
|
-
if (querySignals.asksHow) {
|
|
2097
|
-
const trimmed = rawLine.trim();
|
|
2098
|
-
if (trimmed.startsWith('//') || trimmed.startsWith('*'))
|
|
2099
|
-
score -= 0.45;
|
|
2100
|
-
if (/\.description\(/.test(rawLine))
|
|
2101
|
-
score -= 0.4;
|
|
2102
|
-
}
|
|
2103
|
-
if (querySignals.asksSchema) {
|
|
2104
|
-
if (/^\s*export\s+interface\s+/i.test(rawLine) || /^\s*export\s+type\s+/i.test(rawLine))
|
|
2105
|
-
score += 1.1;
|
|
2106
|
-
if (/^\s*[A-Za-z_][A-Za-z0-9_?]*\s*:\s*/.test(rawLine))
|
|
2107
|
-
score += 0.8;
|
|
2108
|
-
if (filePath.endsWith('.md'))
|
|
2109
|
-
score -= 0.5;
|
|
2110
|
-
}
|
|
2111
|
-
if (rawLine.length > 180 && rawLine.includes(',') && /['"`].*['"`].*['"`]/.test(rawLine)) {
|
|
2112
|
-
score -= 0.35;
|
|
2113
|
-
}
|
|
2114
|
-
if ((querySignals.asksLocation || querySignals.asksDefinition) && /Usage:\s*neurcode/i.test(rawLine)) {
|
|
2115
|
-
score -= 0.6;
|
|
2116
|
-
}
|
|
2117
|
-
if ((querySignals.asksLocation || querySignals.asksDefinition) && /^\s*type:\s*['"`][a-z0-9_-]+['"`],?\s*$/i.test(rawLine)) {
|
|
2118
|
-
score -= 0.7;
|
|
2119
|
-
}
|
|
2120
|
-
if (querySignals.asksCommandSurface) {
|
|
2121
|
-
if (/\.command\(|\bcommand\(/i.test(rawLine))
|
|
2122
|
-
score += 1.2;
|
|
2123
|
-
if (/\.option\(|--[a-z0-9-]+/i.test(rawLine))
|
|
2124
|
-
score += 0.8;
|
|
2125
|
-
if (filePath.endsWith('/index.ts') || filePath.includes('/commands/'))
|
|
2126
|
-
score += 0.35;
|
|
2127
|
-
}
|
|
2128
|
-
if (commandFocusQuery) {
|
|
2129
|
-
if (filePath.includes(`/commands/${commandFocusQuery}.`)) {
|
|
2130
|
-
score += 1.4;
|
|
2131
|
-
}
|
|
2132
|
-
else if ((querySignals.asksHow || querySignals.asksLocation) && filePath.includes('/commands/')) {
|
|
2133
|
-
score -= 1.2;
|
|
2134
|
-
}
|
|
2135
|
-
if (filePath.endsWith('/index.ts') &&
|
|
2136
|
-
new RegExp(`\\.command\\('${escapeRegExp(commandFocusQuery)}'\\)`, 'i').test(rawLine)) {
|
|
2137
|
-
score += 1.1;
|
|
2138
|
-
}
|
|
2139
|
-
}
|
|
2140
|
-
const dominantTerm = [...directTerms].sort((a, b) => (termWeight.get(b) || 0) - (termWeight.get(a) || 0))[0] ||
|
|
2141
|
-
[...windowTerms].sort((a, b) => (termWeight.get(b) || 0) - (termWeight.get(a) || 0))[0] ||
|
|
2142
|
-
'';
|
|
2143
|
-
if (score <= 0)
|
|
2144
|
-
continue;
|
|
2145
|
-
citations.push({
|
|
2146
|
-
path: filePath,
|
|
2147
|
-
line: lineIdx + 1,
|
|
2148
|
-
snippet,
|
|
2149
|
-
term: dominantTerm,
|
|
2150
|
-
weight: score,
|
|
2151
|
-
});
|
|
2152
|
-
}
|
|
2153
|
-
}
|
|
2154
|
-
citations.sort((a, b) => b.weight - a.weight);
|
|
2155
|
-
const sourceEvidence = citations.filter((citation) => isPrimarySourcePath(citation.path));
|
|
2156
|
-
const sourcePerFileCounts = new Map();
|
|
2157
|
-
const sourceTermCounts = new Map();
|
|
2158
|
-
for (const citation of sourceEvidence) {
|
|
2159
|
-
sourcePerFileCounts.set(citation.path, (sourcePerFileCounts.get(citation.path) || 0) + 1);
|
|
2160
|
-
if (citation.term) {
|
|
2161
|
-
sourceTermCounts.set(citation.term, (sourceTermCounts.get(citation.term) || 0) + 1);
|
|
2162
|
-
}
|
|
2163
|
-
}
|
|
2164
|
-
const selectedForOutput = [];
|
|
2165
|
-
const selectedFileCounts = new Map();
|
|
2166
|
-
const pushSelected = (citation) => {
|
|
2167
|
-
if (selectedForOutput.length >= maxCitations)
|
|
2168
|
-
return false;
|
|
2169
|
-
if (selectedForOutput.some((existing) => existing.path === citation.path && existing.line === citation.line && existing.term === citation.term)) {
|
|
2170
|
-
return false;
|
|
2171
|
-
}
|
|
2172
|
-
if (querySignals.asksLocation || querySignals.asksDefinition) {
|
|
2173
|
-
const perFile = selectedFileCounts.get(citation.path) || 0;
|
|
2174
|
-
const distinctFiles = selectedFileCounts.size;
|
|
2175
|
-
const targetDistinct = Math.min(3, sourcePerFileCounts.size);
|
|
2176
|
-
if (perFile >= 3 && distinctFiles < targetDistinct) {
|
|
2177
|
-
return false;
|
|
2178
|
-
}
|
|
2179
|
-
}
|
|
2180
|
-
selectedForOutput.push(citation);
|
|
2181
|
-
selectedFileCounts.set(citation.path, (selectedFileCounts.get(citation.path) || 0) + 1);
|
|
2182
|
-
return true;
|
|
2183
|
-
};
|
|
2184
|
-
if (mode === 'search' && querySignals.asksSchema) {
|
|
2185
|
-
const schemaAffinity = (citation) => {
|
|
2186
|
-
const text = citation.snippet.toLowerCase();
|
|
2187
|
-
let affinity = 0;
|
|
2188
|
-
for (const term of querySignals.highSignalTerms) {
|
|
2189
|
-
const normalized = term.toLowerCase();
|
|
2190
|
-
const compact = normalized.replace(/\s+/g, '');
|
|
2191
|
-
if (text.includes(normalized))
|
|
2192
|
-
affinity += 1;
|
|
2193
|
-
else if (text.includes(compact))
|
|
2194
|
-
affinity += 0.85;
|
|
2195
|
-
}
|
|
2196
|
-
return affinity;
|
|
2197
|
-
};
|
|
2198
|
-
const schemaPreferred = sourceEvidence
|
|
2199
|
-
.filter((citation) => /^export\s+interface\b/i.test(citation.snippet) ||
|
|
2200
|
-
/^export\s+type\b/i.test(citation.snippet) ||
|
|
2201
|
-
/^[A-Za-z_][A-Za-z0-9_?]*\s*:\s*[^=,]+;?$/.test(citation.snippet))
|
|
2202
|
-
.sort((a, b) => {
|
|
2203
|
-
const affinityDiff = schemaAffinity(b) - schemaAffinity(a);
|
|
2204
|
-
if (affinityDiff !== 0)
|
|
2205
|
-
return affinityDiff;
|
|
2206
|
-
return b.weight - a.weight;
|
|
2207
|
-
});
|
|
2208
|
-
for (const citation of schemaPreferred) {
|
|
2209
|
-
if (selectedForOutput.length >= maxCitations)
|
|
2210
|
-
break;
|
|
2211
|
-
pushSelected(citation);
|
|
2212
|
-
}
|
|
2336
|
+
pathBoostScores.set(entry.path, Math.max(pathBoostScores.get(entry.path) || 0, entry.score));
|
|
2213
2337
|
}
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
if (firstForTerm) {
|
|
2218
|
-
pushSelected(firstForTerm);
|
|
2219
|
-
}
|
|
2220
|
-
}
|
|
2221
|
-
}
|
|
2222
|
-
else if (mode === 'search' && terms.length > 0) {
|
|
2223
|
-
const discriminativeHighSignalTerms = querySignals.highSignalTerms.filter((term) => {
|
|
2224
|
-
const docFreq = termFileHits.get(term) || 0;
|
|
2225
|
-
return docFreq / matchedFileTotal <= 0.65;
|
|
2226
|
-
});
|
|
2227
|
-
const preferredTerms = discriminativeHighSignalTerms.length > 0
|
|
2228
|
-
? discriminativeHighSignalTerms.slice(0, 8)
|
|
2229
|
-
: querySignals.highSignalTerms.length > 0
|
|
2230
|
-
? querySignals.highSignalTerms.slice(0, 8)
|
|
2231
|
-
: terms
|
|
2232
|
-
.filter((term) => !LOW_SIGNAL_TERMS.has(term) && term.length >= 4)
|
|
2233
|
-
.slice(0, 8);
|
|
2234
|
-
for (const term of preferredTerms) {
|
|
2235
|
-
const firstForTerm = sourceEvidence.find((citation) => {
|
|
2236
|
-
if (citation.term !== term)
|
|
2237
|
-
return false;
|
|
2238
|
-
const normalized = (citation.term || '').toLowerCase();
|
|
2239
|
-
return !GENERIC_OUTPUT_TERMS.has(normalized);
|
|
2240
|
-
});
|
|
2241
|
-
if (!firstForTerm)
|
|
2242
|
-
continue;
|
|
2243
|
-
pushSelected(firstForTerm);
|
|
2244
|
-
if (selectedForOutput.length >= maxCitations)
|
|
2245
|
-
break;
|
|
2246
|
-
}
|
|
2247
|
-
}
|
|
2248
|
-
for (const citation of sourceEvidence) {
|
|
2249
|
-
if (selectedForOutput.length >= maxCitations)
|
|
2250
|
-
break;
|
|
2251
|
-
if (commandFocusQuery &&
|
|
2252
|
-
(querySignals.asksLocation || querySignals.asksHow) &&
|
|
2253
|
-
selectedForOutput.some((item) => item.path.includes(`/commands/${commandFocusQuery}.`)) &&
|
|
2254
|
-
citation.path.includes('/commands/') &&
|
|
2255
|
-
!citation.path.includes(`/commands/${commandFocusQuery}.`)) {
|
|
2256
|
-
continue;
|
|
2257
|
-
}
|
|
2258
|
-
pushSelected(citation);
|
|
2259
|
-
}
|
|
2260
|
-
const finalCitations = selectedForOutput.map(({ path, line, snippet, term }) => ({
|
|
2261
|
-
path,
|
|
2262
|
-
line,
|
|
2263
|
-
snippet,
|
|
2264
|
-
term,
|
|
2265
|
-
}));
|
|
2338
|
+
const evidence = collectRepoEvidence(cwd, fileTree, searchTerms, pathBoostScores);
|
|
2339
|
+
const sourceEvidence = evidence.scoredCitations.filter((citation) => isPrimarySourcePath(citation.path));
|
|
2340
|
+
const finalCitations = selectTopCitations(sourceEvidence, maxCitations, searchTerms);
|
|
2266
2341
|
const stats = {
|
|
2267
|
-
scannedFiles,
|
|
2268
|
-
matchedFiles:
|
|
2342
|
+
scannedFiles: evidence.scannedFiles,
|
|
2343
|
+
matchedFiles: new Set(sourceEvidence.map((citation) => citation.path)).size,
|
|
2269
2344
|
matchedLines: sourceEvidence.length,
|
|
2270
2345
|
brainCandidates: brainResults.entries.length,
|
|
2271
2346
|
};
|
|
2272
|
-
const
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
}
|
|
2276
|
-
const truth = evaluateTruthAssessment(mode, normalizedQuestion, terms, sourceEvidence, sourcePerFileCounts, sourceTermCounts);
|
|
2277
|
-
const answer = buildAnswer(mode, question, terms, finalCitations, stats, sourceTermCounts, sourcePerFileCounts, truth, {
|
|
2278
|
-
cliPackageName,
|
|
2279
|
-
knownCommands: knownCliCommands,
|
|
2280
|
-
featureBullets,
|
|
2281
|
-
lineCache: lineCacheForAnswer,
|
|
2282
|
-
});
|
|
2283
|
-
emitAskResult(answer, {
|
|
2347
|
+
const truth = evaluateTruth(searchTerms.normalizedQuestion, searchTerms.highSignalTerms, finalCitations);
|
|
2348
|
+
const payload = buildRepoAnswerPayload(question, searchTerms, finalCitations, stats, truth);
|
|
2349
|
+
emitAskResult(payload, {
|
|
2284
2350
|
json: options.json,
|
|
2285
2351
|
maxCitations,
|
|
2286
2352
|
fromPlan: options.fromPlan,
|
|
@@ -2290,12 +2356,12 @@ async function askCommand(question, options = {}) {
|
|
|
2290
2356
|
if (orgId && projectId) {
|
|
2291
2357
|
(0, brain_context_1.recordBrainProgressEvent)(cwd, scope, {
|
|
2292
2358
|
type: 'ask',
|
|
2293
|
-
note: `mode
|
|
2359
|
+
note: `mode=retrieval;truth=${truth.status};score=${truth.score.toFixed(2)};matched_files=${stats.matchedFiles};matched_lines=${stats.matchedLines}`,
|
|
2294
2360
|
});
|
|
2295
2361
|
}
|
|
2296
2362
|
if (shouldUseCache && orgId && projectId) {
|
|
2297
2363
|
const questionHash = (0, ask_cache_1.computeAskQuestionHash)({
|
|
2298
|
-
question: normalizedQuestion,
|
|
2364
|
+
question: searchTerms.normalizedQuestion,
|
|
2299
2365
|
contextHash: staticContext.hash,
|
|
2300
2366
|
});
|
|
2301
2367
|
const key = (0, ask_cache_1.computeAskCacheKey)({
|
|
@@ -2317,10 +2383,10 @@ async function askCommand(question, options = {}) {
|
|
|
2317
2383
|
questionHash,
|
|
2318
2384
|
policyVersionHash,
|
|
2319
2385
|
neurcodeVersion,
|
|
2320
|
-
question: normalizedQuestion,
|
|
2386
|
+
question: searchTerms.normalizedQuestion,
|
|
2321
2387
|
contextHash: staticContext.hash,
|
|
2322
2388
|
},
|
|
2323
|
-
output:
|
|
2389
|
+
output: payload,
|
|
2324
2390
|
evidencePaths: finalCitations.map((citation) => citation.path),
|
|
2325
2391
|
});
|
|
2326
2392
|
}
|