@neurcode-ai/cli 0.9.21 → 0.9.23
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 +411 -575
- package/dist/commands/ask.js.map +1 -1
- package/package.json +1 -1
package/dist/commands/ask.js
CHANGED
|
@@ -39,14 +39,21 @@ const STOP_WORDS = new Set([
|
|
|
39
39
|
'from', 'your', 'about', 'there', 'their', 'them', 'have', 'does', 'is', 'are', 'was',
|
|
40
40
|
'were', 'any', 'all', 'read', 'tell', 'me', 'its', 'it', 'instead', 'than', 'then',
|
|
41
41
|
'workflow', 'codebase', 'repo', 'repository', 'used', 'use', 'mentioned', 'mention', 'whether',
|
|
42
|
+
'list', 'show', 'like', 'can', 'type', 'types', 'using', 'etc', 'cmd', 'cmds', 'command', 'commands',
|
|
43
|
+
'system', 'platform', 'latest', 'package', 'packages',
|
|
42
44
|
]);
|
|
43
45
|
const LOW_SIGNAL_TERMS = new Set([
|
|
44
46
|
'used', 'use', 'using', 'mentioned', 'mention', 'where', 'tell', 'read', 'check', 'find', 'search',
|
|
45
|
-
'workflow', 'repo', 'repository', 'codebase', 'anywhere',
|
|
47
|
+
'workflow', 'repo', 'repository', 'codebase', 'anywhere', 'can', 'type', 'types', 'list', 'show', 'like',
|
|
48
|
+
'neurcode', 'cli', 'file', 'files', 'path', 'filepath', 'header', 'added', 'add', 'request', 'requests',
|
|
46
49
|
]);
|
|
50
|
+
const GENERIC_OUTPUT_TERMS = new Set(['file', 'files', 'path', 'filepath']);
|
|
47
51
|
function scanFiles(dir, maxFiles = MAX_SCAN_FILES) {
|
|
48
52
|
const files = [];
|
|
49
|
-
const ignoreDirs = new Set([
|
|
53
|
+
const ignoreDirs = new Set([
|
|
54
|
+
'node_modules', '.git', '.next', 'dist', 'build', '.turbo', '.cache', 'coverage',
|
|
55
|
+
'.neurcode', '.vscode', '.pnpm-store', '.npm', '.yarn',
|
|
56
|
+
]);
|
|
50
57
|
const ignoreExts = new Set(['map', 'log', 'lock', 'png', 'jpg', 'jpeg', 'gif', 'ico', 'svg', 'woff', 'woff2', 'ttf', 'eot', 'pdf']);
|
|
51
58
|
const walk = (current) => {
|
|
52
59
|
if (files.length >= maxFiles)
|
|
@@ -95,7 +102,9 @@ function scanFiles(dir, maxFiles = MAX_SCAN_FILES) {
|
|
|
95
102
|
}
|
|
96
103
|
function tokenizeQuestion(question) {
|
|
97
104
|
return (0, plan_cache_1.normalizeIntent)(question)
|
|
105
|
+
.replace(/[^a-z0-9_\-\s]/g, ' ')
|
|
98
106
|
.split(/\s+/)
|
|
107
|
+
.map((token) => token.trim())
|
|
99
108
|
.filter((token) => token.length >= 3 && !STOP_WORDS.has(token));
|
|
100
109
|
}
|
|
101
110
|
function escapeRegExp(input) {
|
|
@@ -105,6 +114,7 @@ function normalizeTerm(raw) {
|
|
|
105
114
|
return raw
|
|
106
115
|
.toLowerCase()
|
|
107
116
|
.replace(/['"`]/g, '')
|
|
117
|
+
.replace(/[^a-z0-9_\-\s]/g, ' ')
|
|
108
118
|
.replace(/\s+/g, ' ')
|
|
109
119
|
.trim();
|
|
110
120
|
}
|
|
@@ -178,6 +188,34 @@ function buildTermMatchers(term, weight) {
|
|
|
178
188
|
push(normalized, `\\b${escapeRegExp(tokens.join('-'))}\\b`);
|
|
179
189
|
return out;
|
|
180
190
|
}
|
|
191
|
+
function expandSearchTerms(terms) {
|
|
192
|
+
const expanded = new Set();
|
|
193
|
+
for (const rawTerm of terms) {
|
|
194
|
+
const term = normalizeTerm(rawTerm);
|
|
195
|
+
if (!term)
|
|
196
|
+
continue;
|
|
197
|
+
expanded.add(term);
|
|
198
|
+
if (term.includes(' ')) {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (term.endsWith('ies') && term.length > 4) {
|
|
202
|
+
expanded.add(`${term.slice(0, -3)}y`);
|
|
203
|
+
}
|
|
204
|
+
if (term.endsWith('s') && term.length > 3) {
|
|
205
|
+
expanded.add(term.slice(0, -1));
|
|
206
|
+
}
|
|
207
|
+
else if (term.length > 3) {
|
|
208
|
+
expanded.add(`${term}s`);
|
|
209
|
+
}
|
|
210
|
+
if (term.endsWith('ing') && term.length > 5) {
|
|
211
|
+
expanded.add(term.slice(0, -3));
|
|
212
|
+
}
|
|
213
|
+
if (term.endsWith('ed') && term.length > 4) {
|
|
214
|
+
expanded.add(term.slice(0, -2));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return [...expanded];
|
|
218
|
+
}
|
|
181
219
|
function buildMatchers(question) {
|
|
182
220
|
const comparisonTerms = extractComparisonTerms(question);
|
|
183
221
|
if (comparisonTerms.length >= 2) {
|
|
@@ -192,10 +230,12 @@ function buildMatchers(question) {
|
|
|
192
230
|
};
|
|
193
231
|
}
|
|
194
232
|
const quoted = extractQuotedTerms(question);
|
|
233
|
+
const identityTerms = extractIdentityTerms(question);
|
|
195
234
|
const keywords = tokenizeQuestion(question).slice(0, 8);
|
|
196
235
|
const quotedSet = new Set(quoted.map((term) => normalizeTerm(term)));
|
|
197
|
-
const
|
|
198
|
-
|
|
236
|
+
const baseTerms = [...new Set([...quoted, ...identityTerms, ...keywords].map(normalizeTerm).filter(Boolean))];
|
|
237
|
+
const filteredTerms = baseTerms.filter((term) => quotedSet.has(term) || !LOW_SIGNAL_TERMS.has(term));
|
|
238
|
+
const terms = expandSearchTerms(filteredTerms.length > 0 ? filteredTerms : baseTerms).filter(Boolean);
|
|
199
239
|
const matchers = terms.flatMap((term) => buildTermMatchers(term, quoted.includes(term) ? 0.9 : 0.55));
|
|
200
240
|
return {
|
|
201
241
|
mode: 'search',
|
|
@@ -216,8 +256,29 @@ function derivePathHints(question) {
|
|
|
216
256
|
}
|
|
217
257
|
if (/\bdashboard|landing|docs|frontend|ui|pricing\b/.test(normalized)) {
|
|
218
258
|
hints.push('web/dashboard/');
|
|
259
|
+
hints.push('docs/');
|
|
260
|
+
hints.push('README.md');
|
|
261
|
+
}
|
|
262
|
+
if (/\bfeature|capabilit|offer|platform\b/.test(normalized)) {
|
|
263
|
+
hints.push('docs/');
|
|
264
|
+
hints.push('README.md');
|
|
265
|
+
}
|
|
266
|
+
if (/\binstall|setup|upgrade|update\b/.test(normalized)) {
|
|
267
|
+
hints.push('README.md');
|
|
268
|
+
hints.push('docs/');
|
|
269
|
+
hints.push('packages/cli/');
|
|
270
|
+
}
|
|
271
|
+
if (/\btenant|tenancy|single|multi|organization|org\b/.test(normalized)) {
|
|
272
|
+
hints.push('services/api/src/lib/');
|
|
273
|
+
hints.push('services/api/src/routes/');
|
|
274
|
+
hints.push('packages/cli/src/');
|
|
275
|
+
}
|
|
276
|
+
if (/\brequest|requests|header|inject|injected\b/.test(normalized)) {
|
|
277
|
+
hints.push('packages/cli/src/api-client.ts');
|
|
278
|
+
hints.push('services/api/src/middleware/');
|
|
219
279
|
}
|
|
220
280
|
if (/(github action|\bci\b)/.test(normalized)) {
|
|
281
|
+
hints.push('.github/workflows/');
|
|
221
282
|
hints.push('packages/action/');
|
|
222
283
|
hints.push('actions/');
|
|
223
284
|
}
|
|
@@ -230,533 +291,240 @@ function normalizeSnippet(line) {
|
|
|
230
291
|
.trim()
|
|
231
292
|
.slice(0, 220);
|
|
232
293
|
}
|
|
233
|
-
function
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
.
|
|
242
|
-
.
|
|
243
|
-
.
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
return true;
|
|
265
|
-
return /\borganization\b/.test(normalizedQuestion) && /\bsingle\b|\bmulti\b/.test(normalizedQuestion);
|
|
266
|
-
}
|
|
267
|
-
function isFeatureOverviewQuestion(normalizedQuestion) {
|
|
268
|
-
return (/\bfeature\b|\bfeatures\b|\bcapability\b|\bcapabilities\b/.test(normalizedQuestion) ||
|
|
269
|
-
/\bwhat all\b|\bwhat does\b|\bwhat can\b|\boffers?\b|\bplatform have\b/.test(normalizedQuestion));
|
|
294
|
+
function addAnchorCandidates(fileTree, candidateSet, pathPriority, normalizedQuestion) {
|
|
295
|
+
const asSet = new Set(fileTree);
|
|
296
|
+
const pinned = [
|
|
297
|
+
'README.md',
|
|
298
|
+
'packages/cli/package.json',
|
|
299
|
+
'packages/cli/src/index.ts',
|
|
300
|
+
'packages/cli/src/api-client.ts',
|
|
301
|
+
'services/api/src/lib/org-context.ts',
|
|
302
|
+
'services/api/src/lib/user-org.ts',
|
|
303
|
+
'docs/cli-commands.md',
|
|
304
|
+
'docs/enterprise-setup.md',
|
|
305
|
+
];
|
|
306
|
+
for (const path of pinned) {
|
|
307
|
+
if (!asSet.has(path))
|
|
308
|
+
continue;
|
|
309
|
+
candidateSet.add(path);
|
|
310
|
+
pathPriority.set(path, Math.max(pathPriority.get(path) || 0, 0.22));
|
|
311
|
+
}
|
|
312
|
+
const docsLikelyUseful = /\b(feature|capabilit|offer|platform|install|setup|tenant|tenancy|architecture)\b/.test(normalizedQuestion);
|
|
313
|
+
if (!docsLikelyUseful)
|
|
314
|
+
return;
|
|
315
|
+
let addedDocs = 0;
|
|
316
|
+
for (const path of fileTree) {
|
|
317
|
+
if (addedDocs >= 24)
|
|
318
|
+
break;
|
|
319
|
+
if (path === 'README.md' || (path.startsWith('docs/') && path.endsWith('.md'))) {
|
|
320
|
+
candidateSet.add(path);
|
|
321
|
+
pathPriority.set(path, Math.max(pathPriority.get(path) || 0, 0.18));
|
|
322
|
+
addedDocs++;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
270
325
|
}
|
|
271
|
-
function
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
if (!(0, fs_1.existsSync)(fullPath))
|
|
326
|
+
function readCliPackageName(cwd) {
|
|
327
|
+
const pkgPath = (0, path_1.join)(cwd, 'packages/cli/package.json');
|
|
328
|
+
if (!(0, fs_1.existsSync)(pkgPath))
|
|
275
329
|
return null;
|
|
276
|
-
let content = '';
|
|
277
330
|
try {
|
|
278
|
-
|
|
331
|
+
const parsed = JSON.parse((0, fs_1.readFileSync)(pkgPath, 'utf-8'));
|
|
332
|
+
if (typeof parsed.name !== 'string' || !parsed.name.trim())
|
|
333
|
+
return null;
|
|
334
|
+
return parsed.name.trim();
|
|
279
335
|
}
|
|
280
336
|
catch {
|
|
281
337
|
return null;
|
|
282
338
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
339
|
+
}
|
|
340
|
+
function extractCommandsFromCliIndex(cwd) {
|
|
341
|
+
const indexPath = (0, path_1.join)(cwd, 'packages/cli/src/index.ts');
|
|
342
|
+
if (!(0, fs_1.existsSync)(indexPath))
|
|
343
|
+
return [];
|
|
344
|
+
let content = '';
|
|
345
|
+
try {
|
|
346
|
+
content = (0, fs_1.readFileSync)(indexPath, 'utf-8');
|
|
347
|
+
}
|
|
348
|
+
catch {
|
|
349
|
+
return [];
|
|
350
|
+
}
|
|
351
|
+
const commandSet = new Set();
|
|
289
352
|
const topByVar = new Map();
|
|
290
|
-
const
|
|
291
|
-
const seenSub = new Set();
|
|
292
|
-
const topRegex = /(?:const\s+([A-Za-z_$][\w$]*)\s*=\s*)?program\s*\r?\n\s*\.command\('([^']+)'\)/g;
|
|
293
|
-
for (const match of content.matchAll(topRegex)) {
|
|
353
|
+
for (const match of content.matchAll(/(?:const\s+([A-Za-z_$][\w$]*)\s*=\s*)?program\s*\r?\n\s*\.command\('([^']+)'\)/g)) {
|
|
294
354
|
const varName = match[1];
|
|
295
|
-
const
|
|
296
|
-
if (!
|
|
297
|
-
continue;
|
|
298
|
-
const commandName = rawCommand.split(/\s+/)[0];
|
|
299
|
-
if (!commandName)
|
|
355
|
+
const top = (match[2] || '').trim().split(/\s+/)[0];
|
|
356
|
+
if (!top)
|
|
300
357
|
continue;
|
|
358
|
+
commandSet.add(`neurcode ${top}`);
|
|
301
359
|
if (varName) {
|
|
302
|
-
topByVar.set(varName,
|
|
303
|
-
}
|
|
304
|
-
if (!seenTop.has(commandName)) {
|
|
305
|
-
seenTop.add(commandName);
|
|
306
|
-
topLevel.push(commandName);
|
|
307
|
-
const baseOffset = match.index ?? 0;
|
|
308
|
-
const commandInMatchOffset = match[0].indexOf(".command('");
|
|
309
|
-
const offset = commandInMatchOffset >= 0 ? baseOffset + commandInMatchOffset : baseOffset;
|
|
310
|
-
const line = lineNumberFromOffset(content, offset);
|
|
311
|
-
citations.push({
|
|
312
|
-
path: indexPath,
|
|
313
|
-
line,
|
|
314
|
-
snippet: normalizeSnippet(lines[line - 1] || `.command('${rawCommand}')`),
|
|
315
|
-
term: 'command',
|
|
316
|
-
});
|
|
360
|
+
topByVar.set(varName, top);
|
|
317
361
|
}
|
|
318
362
|
}
|
|
319
|
-
const
|
|
320
|
-
for (const match of content.matchAll(subRegex)) {
|
|
363
|
+
for (const match of content.matchAll(/([A-Za-z_$][\w$]*)\s*\r?\n\s*\.command\('([^']+)'\)/g)) {
|
|
321
364
|
const parentVar = match[1];
|
|
322
365
|
const parent = topByVar.get(parentVar);
|
|
323
366
|
if (!parent)
|
|
324
367
|
continue;
|
|
325
|
-
const
|
|
326
|
-
if (!
|
|
327
|
-
continue;
|
|
328
|
-
const subcommandName = rawSubcommand.split(/\s+/)[0];
|
|
329
|
-
if (!subcommandName)
|
|
368
|
+
const sub = (match[2] || '').trim().split(/\s+/)[0];
|
|
369
|
+
if (!sub)
|
|
330
370
|
continue;
|
|
331
|
-
|
|
332
|
-
if (seenSub.has(combined))
|
|
333
|
-
continue;
|
|
334
|
-
seenSub.add(combined);
|
|
335
|
-
subcommands.push(combined);
|
|
336
|
-
const baseOffset = match.index ?? 0;
|
|
337
|
-
const commandInMatchOffset = match[0].indexOf(".command('");
|
|
338
|
-
const offset = commandInMatchOffset >= 0 ? baseOffset + commandInMatchOffset : baseOffset;
|
|
339
|
-
const line = lineNumberFromOffset(content, offset);
|
|
340
|
-
citations.push({
|
|
341
|
-
path: indexPath,
|
|
342
|
-
line,
|
|
343
|
-
snippet: normalizeSnippet(lines[line - 1] || `${parentVar}.command('${rawSubcommand}')`),
|
|
344
|
-
term: 'subcommand',
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
if (topLevel.length === 0)
|
|
348
|
-
return null;
|
|
349
|
-
return {
|
|
350
|
-
topLevelCommands: topLevel,
|
|
351
|
-
subcommands,
|
|
352
|
-
citations,
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
function extractInstallCommandInfo(cwd) {
|
|
356
|
-
const pkgPath = 'packages/cli/package.json';
|
|
357
|
-
const fullPath = (0, path_1.join)(cwd, pkgPath);
|
|
358
|
-
if (!(0, fs_1.existsSync)(fullPath))
|
|
359
|
-
return null;
|
|
360
|
-
let raw = '';
|
|
361
|
-
try {
|
|
362
|
-
raw = (0, fs_1.readFileSync)(fullPath, 'utf-8');
|
|
363
|
-
}
|
|
364
|
-
catch {
|
|
365
|
-
return null;
|
|
366
|
-
}
|
|
367
|
-
let packageName = '';
|
|
368
|
-
try {
|
|
369
|
-
const parsed = JSON.parse(raw);
|
|
370
|
-
packageName = typeof parsed.name === 'string' ? parsed.name.trim() : '';
|
|
371
|
-
}
|
|
372
|
-
catch {
|
|
373
|
-
return null;
|
|
374
|
-
}
|
|
375
|
-
if (!packageName)
|
|
376
|
-
return null;
|
|
377
|
-
const lines = raw.split(/\r?\n/);
|
|
378
|
-
const nameLine = lines.findIndex((line) => /"name"\s*:/.test(line));
|
|
379
|
-
const versionLine = lines.findIndex((line) => /"version"\s*:/.test(line));
|
|
380
|
-
const citations = [];
|
|
381
|
-
if (nameLine >= 0) {
|
|
382
|
-
citations.push({
|
|
383
|
-
path: pkgPath,
|
|
384
|
-
line: nameLine + 1,
|
|
385
|
-
snippet: normalizeSnippet(lines[nameLine]),
|
|
386
|
-
term: 'package name',
|
|
387
|
-
});
|
|
371
|
+
commandSet.add(`neurcode ${parent} ${sub}`);
|
|
388
372
|
}
|
|
389
|
-
|
|
390
|
-
citations.push({
|
|
391
|
-
path: pkgPath,
|
|
392
|
-
line: versionLine + 1,
|
|
393
|
-
snippet: normalizeSnippet(lines[versionLine]),
|
|
394
|
-
term: 'version',
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
return {
|
|
398
|
-
packageName,
|
|
399
|
-
citations,
|
|
400
|
-
};
|
|
373
|
+
return [...commandSet].slice(0, 40);
|
|
401
374
|
}
|
|
402
|
-
function
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
question,
|
|
406
|
-
questionNormalized: normalizedQuestion,
|
|
407
|
-
mode: 'search',
|
|
408
|
-
answer: [
|
|
409
|
-
'Use this command to install the latest Neurcode CLI globally:',
|
|
410
|
-
`\`npm install -g ${pkg}@latest\``,
|
|
411
|
-
'',
|
|
412
|
-
`Optional (pnpm): \`pnpm add -g ${pkg}@latest\``,
|
|
413
|
-
'Then verify with: `neurcode --version`',
|
|
414
|
-
].join('\n'),
|
|
415
|
-
findings: [
|
|
416
|
-
`CLI package detected: ${pkg}`,
|
|
417
|
-
'Source of truth: packages/cli/package.json',
|
|
418
|
-
],
|
|
419
|
-
confidence: 'high',
|
|
420
|
-
truth: {
|
|
421
|
-
status: 'grounded',
|
|
422
|
-
score: 0.99,
|
|
423
|
-
reasons: [],
|
|
424
|
-
sourceCitations: installInfo.citations.length,
|
|
425
|
-
sourceFiles: 1,
|
|
426
|
-
minCitationsRequired: 1,
|
|
427
|
-
minFilesRequired: 1,
|
|
428
|
-
},
|
|
429
|
-
citations: installInfo.citations,
|
|
430
|
-
generatedAt: new Date().toISOString(),
|
|
431
|
-
stats: {
|
|
432
|
-
scannedFiles: 1,
|
|
433
|
-
matchedFiles: 1,
|
|
434
|
-
matchedLines: installInfo.citations.length,
|
|
435
|
-
brainCandidates,
|
|
436
|
-
},
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
function buildTenancyAnswer(cwd, question, normalizedQuestion, brainCandidates) {
|
|
440
|
-
const candidatePaths = [
|
|
441
|
-
'README.md',
|
|
442
|
-
'packages/cli/src/api-client.ts',
|
|
443
|
-
'packages/cli/src/utils/state.ts',
|
|
444
|
-
'services/api/src/lib/org-context.ts',
|
|
445
|
-
'services/api/src/routes/logs.ts',
|
|
446
|
-
'services/api/src/routes/metrics.ts',
|
|
447
|
-
'services/api/src/db/index.ts',
|
|
448
|
-
'services/api/src/services/architect-service.ts',
|
|
449
|
-
'services/api/src/lib/user-org.ts',
|
|
450
|
-
];
|
|
451
|
-
const multiPatterns = [
|
|
452
|
-
{ regex: /\bmulti[- ]tenant\b/i, weight: 3, term: 'multi-tenant' },
|
|
453
|
-
{ regex: /\bx-org-id\b/i, weight: 2, term: 'x-org-id' },
|
|
454
|
-
{ regex: /\borganization[_ -]?id\b/i, weight: 1, term: 'organization_id' },
|
|
455
|
-
{ regex: /\bscoped by organization/i, weight: 3, term: 'org-scoped' },
|
|
456
|
-
{ regex: /\bisolated workspaces?\b/i, weight: 2, term: 'isolated workspaces' },
|
|
457
|
-
];
|
|
458
|
-
const singlePatterns = [
|
|
459
|
-
{ regex: /\bsingle[- ]tenant\b/i, weight: 3, term: 'single-tenant' },
|
|
460
|
-
{ regex: /\bsingle-user platform\b/i, weight: 2, term: 'single-user platform' },
|
|
461
|
-
{ regex: /\bno organization management\b/i, weight: 2, term: 'no organization management' },
|
|
462
|
-
];
|
|
463
|
-
const weighted = [];
|
|
464
|
-
let scannedFiles = 0;
|
|
465
|
-
let multiScore = 0;
|
|
466
|
-
let singleScore = 0;
|
|
467
|
-
for (const relPath of candidatePaths) {
|
|
468
|
-
const fullPath = (0, path_1.join)(cwd, relPath);
|
|
469
|
-
if (!(0, fs_1.existsSync)(fullPath))
|
|
470
|
-
continue;
|
|
471
|
-
let content = '';
|
|
472
|
-
try {
|
|
473
|
-
const st = (0, fs_1.statSync)(fullPath);
|
|
474
|
-
if (st.size > MAX_FILE_BYTES)
|
|
475
|
-
continue;
|
|
476
|
-
content = (0, fs_1.readFileSync)(fullPath, 'utf-8');
|
|
477
|
-
}
|
|
478
|
-
catch {
|
|
479
|
-
continue;
|
|
480
|
-
}
|
|
481
|
-
scannedFiles++;
|
|
482
|
-
const lines = content.split(/\r?\n/);
|
|
483
|
-
for (let idx = 0; idx < lines.length; idx++) {
|
|
484
|
-
const line = lines[idx];
|
|
485
|
-
if (!line || line.trim().length === 0)
|
|
486
|
-
continue;
|
|
487
|
-
for (const pattern of multiPatterns) {
|
|
488
|
-
if (!pattern.regex.test(line))
|
|
489
|
-
continue;
|
|
490
|
-
multiScore += pattern.weight;
|
|
491
|
-
weighted.push({
|
|
492
|
-
path: relPath,
|
|
493
|
-
line: idx + 1,
|
|
494
|
-
snippet: normalizeSnippet(line),
|
|
495
|
-
term: pattern.term,
|
|
496
|
-
weight: pattern.weight,
|
|
497
|
-
side: 'multi',
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
|
-
for (const pattern of singlePatterns) {
|
|
501
|
-
if (!pattern.regex.test(line))
|
|
502
|
-
continue;
|
|
503
|
-
singleScore += pattern.weight;
|
|
504
|
-
weighted.push({
|
|
505
|
-
path: relPath,
|
|
506
|
-
line: idx + 1,
|
|
507
|
-
snippet: normalizeSnippet(line),
|
|
508
|
-
term: pattern.term,
|
|
509
|
-
weight: pattern.weight,
|
|
510
|
-
side: 'single',
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
if (weighted.length === 0)
|
|
516
|
-
return null;
|
|
517
|
-
const deduped = new Map();
|
|
518
|
-
for (const citation of weighted) {
|
|
519
|
-
const key = `${citation.path}:${citation.line}:${citation.term || ''}`;
|
|
520
|
-
const existing = deduped.get(key);
|
|
521
|
-
if (!existing || citation.weight > existing.weight) {
|
|
522
|
-
deduped.set(key, citation);
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
const citations = [...deduped.values()].sort((a, b) => b.weight - a.weight);
|
|
526
|
-
const matchedFiles = new Set(citations.map((citation) => citation.path)).size;
|
|
527
|
-
const totalSignals = multiScore + singleScore;
|
|
528
|
-
const delta = Math.abs(multiScore - singleScore);
|
|
529
|
-
let classification;
|
|
530
|
-
if (multiScore >= singleScore + 3)
|
|
531
|
-
classification = 'multi';
|
|
532
|
-
else if (singleScore >= multiScore + 3)
|
|
533
|
-
classification = 'single';
|
|
534
|
-
else
|
|
535
|
-
classification = 'mixed';
|
|
536
|
-
const reasons = [];
|
|
537
|
-
if (classification === 'mixed') {
|
|
538
|
-
reasons.push('Mixed indicators found: both multi-tenant and single-tenant signals are present.');
|
|
539
|
-
}
|
|
540
|
-
if (matchedFiles < 2) {
|
|
541
|
-
reasons.push('Evidence is concentrated in too few files for a strong architecture verdict.');
|
|
542
|
-
}
|
|
543
|
-
const scoreBase = totalSignals > 0 ? delta / totalSignals : 0;
|
|
544
|
-
const score = Math.max(0.45, Math.min(0.98, scoreBase * 0.6 + Math.min(citations.length, 12) / 20 + (matchedFiles >= 2 ? 0.15 : 0.05)));
|
|
545
|
-
const truthStatus = reasons.length > 0 && classification === 'mixed' ? 'insufficient' : 'grounded';
|
|
546
|
-
const truth = {
|
|
547
|
-
status: truthStatus,
|
|
548
|
-
score,
|
|
549
|
-
reasons,
|
|
550
|
-
sourceCitations: citations.length,
|
|
551
|
-
sourceFiles: matchedFiles,
|
|
552
|
-
minCitationsRequired: 2,
|
|
553
|
-
minFilesRequired: 1,
|
|
554
|
-
};
|
|
555
|
-
let answer;
|
|
556
|
-
if (classification === 'multi') {
|
|
557
|
-
answer = 'This codebase is multi-tenant. Core API/CLI flows are organization-scoped (org IDs and `x-org-id` context are enforced).';
|
|
558
|
-
}
|
|
559
|
-
else if (classification === 'single') {
|
|
560
|
-
answer = 'This codebase appears single-tenant based on current source signals.';
|
|
561
|
-
}
|
|
562
|
-
else if (multiScore >= singleScore) {
|
|
563
|
-
answer = 'The repo has mixed signals, but it currently leans multi-tenant. Most runtime paths are organization-scoped, with some legacy single-user wording.';
|
|
564
|
-
}
|
|
565
|
-
else {
|
|
566
|
-
answer = 'The repo has mixed tenancy signals and currently leans single-tenant in wording, though some organization-scoped logic exists.';
|
|
567
|
-
}
|
|
568
|
-
return {
|
|
569
|
-
question,
|
|
570
|
-
questionNormalized: normalizedQuestion,
|
|
571
|
-
mode: 'comparison',
|
|
572
|
-
answer,
|
|
573
|
-
findings: [
|
|
574
|
-
`Tenancy signals: multi=${multiScore}, single=${singleScore}`,
|
|
575
|
-
`Evidence files: ${matchedFiles}`,
|
|
576
|
-
'Key sources include API org-context/auth routes and CLI org-scoping headers.',
|
|
577
|
-
],
|
|
578
|
-
confidence: calibrateConfidence(truth),
|
|
579
|
-
truth: {
|
|
580
|
-
status: truth.status,
|
|
581
|
-
score: Number(truth.score.toFixed(2)),
|
|
582
|
-
reasons: truth.reasons,
|
|
583
|
-
sourceCitations: truth.sourceCitations,
|
|
584
|
-
sourceFiles: truth.sourceFiles,
|
|
585
|
-
minCitationsRequired: truth.minCitationsRequired,
|
|
586
|
-
minFilesRequired: truth.minFilesRequired,
|
|
587
|
-
},
|
|
588
|
-
citations: citations.slice(0, 24).map(({ path, line, snippet, term }) => ({ path, line, snippet, term })),
|
|
589
|
-
generatedAt: new Date().toISOString(),
|
|
590
|
-
stats: {
|
|
591
|
-
scannedFiles,
|
|
592
|
-
matchedFiles,
|
|
593
|
-
matchedLines: citations.length,
|
|
594
|
-
brainCandidates,
|
|
595
|
-
},
|
|
596
|
-
};
|
|
597
|
-
}
|
|
598
|
-
function extractFeatureEntriesFromReadme(cwd) {
|
|
599
|
-
const readmePath = 'README.md';
|
|
600
|
-
const fullPath = (0, path_1.join)(cwd, readmePath);
|
|
601
|
-
if (!(0, fs_1.existsSync)(fullPath))
|
|
375
|
+
function extractFeatureBulletsFromReadme(cwd, limit) {
|
|
376
|
+
const readmePath = (0, path_1.join)(cwd, 'README.md');
|
|
377
|
+
if (!(0, fs_1.existsSync)(readmePath))
|
|
602
378
|
return [];
|
|
603
379
|
let content = '';
|
|
604
380
|
try {
|
|
605
|
-
content = (0, fs_1.readFileSync)(
|
|
381
|
+
content = (0, fs_1.readFileSync)(readmePath, 'utf-8');
|
|
606
382
|
}
|
|
607
383
|
catch {
|
|
608
384
|
return [];
|
|
609
385
|
}
|
|
610
386
|
const lines = content.split(/\r?\n/);
|
|
611
387
|
let start = lines.findIndex((line) => /^##\s+.*features/i.test(line));
|
|
612
|
-
if (start < 0)
|
|
613
|
-
start = lines.findIndex((line) => /^###\s
|
|
614
|
-
}
|
|
388
|
+
if (start < 0)
|
|
389
|
+
start = lines.findIndex((line) => /^###\s+.*features/i.test(line));
|
|
615
390
|
if (start < 0)
|
|
616
391
|
return [];
|
|
617
|
-
|
|
392
|
+
const out = [];
|
|
393
|
+
const seen = new Set();
|
|
618
394
|
for (let i = start + 1; i < lines.length; i++) {
|
|
619
|
-
|
|
620
|
-
|
|
395
|
+
const line = lines[i];
|
|
396
|
+
if (/^##\s+/.test(line))
|
|
621
397
|
break;
|
|
398
|
+
const rich = line.match(/^- \*\*(.+?)\*\*\s*-\s*(.+)$/);
|
|
399
|
+
if (rich?.[1]) {
|
|
400
|
+
const text = `${formatInsightSnippet(rich[1])} - ${formatInsightSnippet(rich[2])}`.trim();
|
|
401
|
+
const key = text.toLowerCase();
|
|
402
|
+
if (key && !seen.has(key)) {
|
|
403
|
+
seen.add(key);
|
|
404
|
+
out.push(text);
|
|
405
|
+
}
|
|
622
406
|
}
|
|
407
|
+
else {
|
|
408
|
+
const simple = line.match(/^- (.+)$/);
|
|
409
|
+
if (!simple?.[1])
|
|
410
|
+
continue;
|
|
411
|
+
const text = formatInsightSnippet(simple[1]);
|
|
412
|
+
const key = text.toLowerCase();
|
|
413
|
+
if (text && !seen.has(key)) {
|
|
414
|
+
seen.add(key);
|
|
415
|
+
out.push(text);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (out.length >= limit)
|
|
419
|
+
break;
|
|
623
420
|
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
421
|
+
return out;
|
|
422
|
+
}
|
|
423
|
+
function extractNeurcodeCommandsFromCitations(citations) {
|
|
424
|
+
const commandSet = new Set();
|
|
425
|
+
for (const citation of citations) {
|
|
426
|
+
const snippet = citation.snippet || '';
|
|
427
|
+
for (const match of snippet.matchAll(/\.command\('([^']+)'\)/g)) {
|
|
428
|
+
const command = (match[1] || '').trim().split(/\s+/)[0];
|
|
429
|
+
if (!command)
|
|
430
|
+
continue;
|
|
431
|
+
commandSet.add(`neurcode ${command}`);
|
|
634
432
|
}
|
|
635
|
-
const
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
description: stripMarkdown(richBullet[2]),
|
|
641
|
-
line: i + 1,
|
|
642
|
-
snippet: normalizeSnippet(line),
|
|
643
|
-
});
|
|
644
|
-
continue;
|
|
433
|
+
for (const match of snippet.matchAll(/`neurcode\s+([^`]+)`/g)) {
|
|
434
|
+
const command = (match[1] || '').trim();
|
|
435
|
+
if (!command)
|
|
436
|
+
continue;
|
|
437
|
+
commandSet.add(`neurcode ${command}`);
|
|
645
438
|
}
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
const text = stripMarkdown(simpleBullet[1]);
|
|
649
|
-
if (!text)
|
|
439
|
+
for (const plainMatch of snippet.matchAll(/\bneurcode\s+([a-z][a-z0-9-]*(?:\s+[a-z][a-z0-9-]*)?)\b/g)) {
|
|
440
|
+
if (!plainMatch?.[1])
|
|
650
441
|
continue;
|
|
651
|
-
|
|
652
|
-
category: currentCategory,
|
|
653
|
-
title: text,
|
|
654
|
-
line: i + 1,
|
|
655
|
-
snippet: normalizeSnippet(line),
|
|
656
|
-
});
|
|
442
|
+
commandSet.add(`neurcode ${plainMatch[1].trim()}`);
|
|
657
443
|
}
|
|
658
444
|
}
|
|
659
|
-
return
|
|
445
|
+
return [...commandSet].slice(0, 20);
|
|
660
446
|
}
|
|
661
|
-
function
|
|
662
|
-
const
|
|
663
|
-
for (const
|
|
664
|
-
const
|
|
665
|
-
const
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
447
|
+
function extractInstallCommandsFromCitations(citations) {
|
|
448
|
+
const installSet = new Set();
|
|
449
|
+
for (const citation of citations) {
|
|
450
|
+
const snippet = citation.snippet || '';
|
|
451
|
+
const matches = snippet.match(/\b(?:npm|pnpm|yarn)\s+(?:install|add)\s+-g\s+[@a-z0-9_.\-/]+(?:@latest)?\b/gi) || [];
|
|
452
|
+
for (const match of matches) {
|
|
453
|
+
installSet.add(match.trim());
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return [...installSet].slice(0, 6);
|
|
457
|
+
}
|
|
458
|
+
function formatInsightSnippet(snippet) {
|
|
459
|
+
return snippet
|
|
460
|
+
.replace(/^[-*]\s+/, '')
|
|
461
|
+
.replace(/^\/\/\s?/, '')
|
|
462
|
+
.replace(/^\/\*\s?/, '')
|
|
463
|
+
.replace(/\*\/$/, '')
|
|
464
|
+
.replace(/^\*\s+/, '')
|
|
465
|
+
.replace(/\s+/g, ' ')
|
|
466
|
+
.trim()
|
|
467
|
+
.slice(0, 180);
|
|
468
|
+
}
|
|
469
|
+
function extractInsightLines(citations, limit) {
|
|
470
|
+
const out = [];
|
|
471
|
+
const seen = new Set();
|
|
472
|
+
for (const citation of citations) {
|
|
473
|
+
const line = formatInsightSnippet(citation.snippet || '');
|
|
474
|
+
if (!line || line.length < 10)
|
|
475
|
+
continue;
|
|
476
|
+
if (/^#+\s/.test(line))
|
|
477
|
+
continue;
|
|
478
|
+
if (/^name:\s+/i.test(line))
|
|
479
|
+
continue;
|
|
480
|
+
const tooNoisy = /[{}[\];=><]/.test(line) && !/\b(multi-tenant|single-tenant|organization|install|command|feature)\b/i.test(line);
|
|
481
|
+
if (tooNoisy)
|
|
482
|
+
continue;
|
|
483
|
+
const key = line.toLowerCase();
|
|
484
|
+
if (seen.has(key))
|
|
485
|
+
continue;
|
|
486
|
+
seen.add(key);
|
|
487
|
+
out.push(line);
|
|
488
|
+
if (out.length >= limit)
|
|
674
489
|
break;
|
|
675
490
|
}
|
|
676
|
-
|
|
677
|
-
path: 'README.md',
|
|
678
|
-
line: entry.line,
|
|
679
|
-
snippet: entry.snippet,
|
|
680
|
-
term: 'feature',
|
|
681
|
-
}));
|
|
682
|
-
return {
|
|
683
|
-
question,
|
|
684
|
-
questionNormalized: normalizedQuestion,
|
|
685
|
-
mode: 'search',
|
|
686
|
-
answer: [
|
|
687
|
-
'Neurcode platform features in this repo include:',
|
|
688
|
-
...categoryLines,
|
|
689
|
-
'',
|
|
690
|
-
'If you want, I can also list this as “feature + exact CLI command” pairs.',
|
|
691
|
-
].join('\n'),
|
|
692
|
-
findings: [
|
|
693
|
-
`Feature entries found: ${entries.length}`,
|
|
694
|
-
`Feature categories found: ${grouped.size}`,
|
|
695
|
-
'Source of truth: README.md',
|
|
696
|
-
],
|
|
697
|
-
confidence: 'high',
|
|
698
|
-
truth: {
|
|
699
|
-
status: 'grounded',
|
|
700
|
-
score: 0.93,
|
|
701
|
-
reasons: [],
|
|
702
|
-
sourceCitations: citations.length,
|
|
703
|
-
sourceFiles: 1,
|
|
704
|
-
minCitationsRequired: 2,
|
|
705
|
-
minFilesRequired: 1,
|
|
706
|
-
},
|
|
707
|
-
citations,
|
|
708
|
-
generatedAt: new Date().toISOString(),
|
|
709
|
-
stats: {
|
|
710
|
-
scannedFiles: 1,
|
|
711
|
-
matchedFiles: 1,
|
|
712
|
-
matchedLines: citations.length,
|
|
713
|
-
brainCandidates,
|
|
714
|
-
},
|
|
715
|
-
};
|
|
491
|
+
return out;
|
|
716
492
|
}
|
|
717
|
-
function
|
|
718
|
-
const
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
citations: catalog.citations,
|
|
746
|
-
generatedAt: new Date().toISOString(),
|
|
747
|
-
stats: {
|
|
748
|
-
scannedFiles: 1,
|
|
749
|
-
matchedFiles: 1,
|
|
750
|
-
matchedLines: catalog.citations.length,
|
|
751
|
-
brainCandidates,
|
|
752
|
-
},
|
|
753
|
-
};
|
|
493
|
+
function isCommandCatalogIntent(normalizedQuestion) {
|
|
494
|
+
const mentionsCommandSurface = /\b(cli|cmd|cmds|command|commands|subcommand|subcommands)\b/.test(normalizedQuestion);
|
|
495
|
+
if (!mentionsCommandSurface)
|
|
496
|
+
return false;
|
|
497
|
+
const listIntent = /\blist\b/.test(normalizedQuestion) ||
|
|
498
|
+
/\bshow\b/.test(normalizedQuestion) ||
|
|
499
|
+
/\bavailable\b/.test(normalizedQuestion) ||
|
|
500
|
+
/\ball commands?\b/.test(normalizedQuestion) ||
|
|
501
|
+
/\bwhich commands?\b/.test(normalizedQuestion) ||
|
|
502
|
+
/\bwhat commands?\b/.test(normalizedQuestion) ||
|
|
503
|
+
/\bwhat can i (type|run)\b/.test(normalizedQuestion) ||
|
|
504
|
+
/\bcan i type\b/.test(normalizedQuestion) ||
|
|
505
|
+
/\bcmds\b/.test(normalizedQuestion);
|
|
506
|
+
if (!listIntent)
|
|
507
|
+
return false;
|
|
508
|
+
const specificIntent = /\bwhere\b/.test(normalizedQuestion) ||
|
|
509
|
+
/\bhow\b/.test(normalizedQuestion) ||
|
|
510
|
+
/\bwhy\b/.test(normalizedQuestion) ||
|
|
511
|
+
/\bwhen\b/.test(normalizedQuestion) ||
|
|
512
|
+
/\bwhich file\b/.test(normalizedQuestion) ||
|
|
513
|
+
/\bin which file\b/.test(normalizedQuestion) ||
|
|
514
|
+
/\bfilepath\b/.test(normalizedQuestion) ||
|
|
515
|
+
/\bfile path\b/.test(normalizedQuestion) ||
|
|
516
|
+
/\binject(?:ed)?\b/.test(normalizedQuestion) ||
|
|
517
|
+
/\bhandle(?:s|d)?\b/.test(normalizedQuestion) ||
|
|
518
|
+
/\bused?\b/.test(normalizedQuestion) ||
|
|
519
|
+
/\bflow\b/.test(normalizedQuestion);
|
|
520
|
+
return !specificIntent;
|
|
754
521
|
}
|
|
755
522
|
function isPrimarySourcePath(filePath) {
|
|
756
523
|
const normalized = filePath.trim().replace(/\\/g, '/').toLowerCase();
|
|
757
524
|
if (!normalized)
|
|
758
525
|
return false;
|
|
759
526
|
if (normalized.startsWith('.neurcode/') ||
|
|
527
|
+
normalized.startsWith('.github/') ||
|
|
760
528
|
normalized.startsWith('.git/') ||
|
|
761
529
|
normalized.startsWith('node_modules/') ||
|
|
762
530
|
normalized.startsWith('dist/') ||
|
|
@@ -796,12 +564,13 @@ function evaluateTruthAssessment(mode, normalizedQuestion, terms, sourceCitation
|
|
|
796
564
|
const broadQuestion = isBroadQuestion(normalizedQuestion);
|
|
797
565
|
const minCitationsRequired = mode === 'comparison' ? 2 : 2;
|
|
798
566
|
let minFilesRequired = mode === 'comparison' ? 2 : 1;
|
|
567
|
+
const sourceCitationCount = sourceCitations.length;
|
|
568
|
+
const sourceFileCount = sourcePerFileCounts.size;
|
|
569
|
+
const hasStrongSingleFileEvidence = sourceFileCount === 1 && sourceCitationCount >= 6;
|
|
799
570
|
if (broadQuestion) {
|
|
800
|
-
minFilesRequired = Math.max(minFilesRequired, 2);
|
|
571
|
+
minFilesRequired = hasStrongSingleFileEvidence ? minFilesRequired : Math.max(minFilesRequired, 2);
|
|
801
572
|
}
|
|
802
573
|
const reasons = [];
|
|
803
|
-
const sourceCitationCount = sourceCitations.length;
|
|
804
|
-
const sourceFileCount = sourcePerFileCounts.size;
|
|
805
574
|
if (sourceCitationCount === 0) {
|
|
806
575
|
reasons.push('No direct source-file evidence was found for the query terms.');
|
|
807
576
|
}
|
|
@@ -831,7 +600,7 @@ function evaluateTruthAssessment(mode, normalizedQuestion, terms, sourceCitation
|
|
|
831
600
|
const highest = Math.max(...sourcePerFileCounts.values());
|
|
832
601
|
dominantShare = highest / sourceCitationCount;
|
|
833
602
|
}
|
|
834
|
-
if (broadQuestion && dominantShare > 0.85 && sourceFileCount < 3) {
|
|
603
|
+
if (broadQuestion && dominantShare > 0.85 && sourceFileCount < 3 && !hasStrongSingleFileEvidence) {
|
|
835
604
|
reasons.push('Evidence is highly concentrated in one file; broader coverage is required for this query.');
|
|
836
605
|
}
|
|
837
606
|
const citationScore = Math.min(1, sourceCitationCount / Math.max(minCitationsRequired, 4));
|
|
@@ -867,17 +636,28 @@ function evaluateTruthAssessment(mode, normalizedQuestion, terms, sourceCitation
|
|
|
867
636
|
minFilesRequired,
|
|
868
637
|
};
|
|
869
638
|
}
|
|
870
|
-
function buildAnswer(mode, question, terms, citations, stats, termCounts, perFileCounts, truth) {
|
|
639
|
+
function buildAnswer(mode, question, terms, citations, stats, termCounts, perFileCounts, truth, context = {}) {
|
|
871
640
|
const confidence = calibrateConfidence(truth);
|
|
872
641
|
const normalizedQuestion = (0, plan_cache_1.normalizeIntent)(question);
|
|
873
642
|
const findings = [];
|
|
874
643
|
let answer = '';
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
644
|
+
const asksCommandCatalog = isCommandCatalogIntent(normalizedQuestion);
|
|
645
|
+
const asksInstall = /\b(install|upgrade|update|latest)\b/.test(normalizedQuestion);
|
|
646
|
+
const asksFeatures = /\b(feature|features|capability|capabilities|offers?)\b/.test(normalizedQuestion);
|
|
647
|
+
const asksTenancy = /\b(tenant|tenancy|single|multi|organization)\b/.test(normalizedQuestion);
|
|
648
|
+
const asksLocation = /\b(where|which file|in which file|filepath|file path|location)\b/.test(normalizedQuestion);
|
|
649
|
+
const asksHow = /\bhow\b/.test(normalizedQuestion);
|
|
650
|
+
const asksOrgRequestInjection = /\b(inject|injected|header|request|requests)\b/.test(normalizedQuestion) &&
|
|
651
|
+
/\b(org|organization)\b/.test(normalizedQuestion);
|
|
652
|
+
const commandMatches = [
|
|
653
|
+
...extractNeurcodeCommandsFromCitations(citations),
|
|
654
|
+
...(context.knownCommands || []),
|
|
655
|
+
].filter((value, index, arr) => arr.indexOf(value) === index);
|
|
656
|
+
const installMatches = extractInstallCommandsFromCitations(citations);
|
|
657
|
+
const insightLines = extractInsightLines(citations, 5);
|
|
658
|
+
const multiSignals = citations.filter((citation) => /\bmulti[- ]tenant|x-org-id|organization[_ -]?id|org-scoped\b/i.test(citation.snippet)).length;
|
|
659
|
+
const singleSignals = citations.filter((citation) => /\bsingle[- ]tenant|single-user\b/i.test(citation.snippet)).length;
|
|
660
|
+
if (mode === 'comparison' && terms.length >= 2) {
|
|
881
661
|
const left = terms[0];
|
|
882
662
|
const right = terms[1];
|
|
883
663
|
const leftCount = termCounts.get(left) || 0;
|
|
@@ -897,14 +677,94 @@ function buildAnswer(mode, question, terms, citations, stats, termCounts, perFil
|
|
|
897
677
|
findings.push(`Compared terms: "${left}" vs "${right}"`);
|
|
898
678
|
findings.push(`Matches by term: ${left}=${leftCount}, ${right}=${rightCount}`);
|
|
899
679
|
}
|
|
900
|
-
else {
|
|
901
|
-
|
|
902
|
-
|
|
680
|
+
else if (citations.length === 0) {
|
|
681
|
+
answer = 'I could not find direct grounded evidence for that in the files I scanned.';
|
|
682
|
+
findings.push('Try adding a module/file hint, for example: "in packages/cli" or "in services/api auth middleware".');
|
|
683
|
+
}
|
|
684
|
+
else if (asksInstall) {
|
|
685
|
+
const manifestInstall = context.cliPackageName ? `npm install -g ${context.cliPackageName}@latest` : null;
|
|
686
|
+
const primaryInstall = manifestInstall || installMatches[0];
|
|
687
|
+
if (!primaryInstall) {
|
|
688
|
+
answer = 'I found CLI references, but not an explicit install command in the matched evidence.';
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
answer = [
|
|
692
|
+
'Here is the install command I found for the CLI:',
|
|
693
|
+
`\`${primaryInstall}\``,
|
|
694
|
+
installMatches.length > 1 ? `Also seen: ${installMatches.slice(1, 3).map((cmd) => `\`${cmd}\``).join(', ')}` : '',
|
|
695
|
+
].filter(Boolean).join('\n');
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
else if (asksFeatures && (context.featureBullets || []).length > 0) {
|
|
699
|
+
const bullets = (context.featureBullets || []).slice(0, 6).map((line) => ` • ${line}`);
|
|
700
|
+
answer = ['Here are the main platform features I could verify from the repo:', ...bullets].join('\n');
|
|
701
|
+
}
|
|
702
|
+
else if (asksCommandCatalog && commandMatches.length > 0) {
|
|
703
|
+
const normalizedCommands = commandMatches
|
|
704
|
+
.filter((command) => /^neurcode\s+[a-z]/.test(command))
|
|
705
|
+
.slice(0, 22);
|
|
706
|
+
const commandBullets = normalizedCommands.map((command) => ` • \`${command}\``);
|
|
707
|
+
answer = ['Here are the CLI commands I could verify from the repo:', ...commandBullets].join('\n');
|
|
708
|
+
}
|
|
709
|
+
else if (asksLocation && citations.length > 0) {
|
|
710
|
+
const focusedCitations = citations.filter((citation) => {
|
|
711
|
+
const term = (citation.term || '').toLowerCase();
|
|
712
|
+
return term.length === 0 || !GENERIC_OUTPUT_TERMS.has(term);
|
|
713
|
+
});
|
|
714
|
+
const locationPool = focusedCitations.length > 0 ? [...focusedCitations] : [...citations];
|
|
715
|
+
if (asksOrgRequestInjection) {
|
|
716
|
+
const score = (citation) => {
|
|
717
|
+
const snippet = citation.snippet.toLowerCase();
|
|
718
|
+
let value = 0;
|
|
719
|
+
if (snippet.includes('x-org-id'))
|
|
720
|
+
value += 4;
|
|
721
|
+
if (snippet.includes('auto-inject') || snippet.includes('inject'))
|
|
722
|
+
value += 2;
|
|
723
|
+
if (snippet.includes('headers[') || snippet.includes('header'))
|
|
724
|
+
value += 2;
|
|
725
|
+
if (snippet.includes('request'))
|
|
726
|
+
value += 1;
|
|
727
|
+
if (citation.path.includes('api-client.ts'))
|
|
728
|
+
value += 2;
|
|
729
|
+
return value;
|
|
730
|
+
};
|
|
731
|
+
locationPool.sort((a, b) => score(b) - score(a));
|
|
732
|
+
}
|
|
733
|
+
const locations = locationPool
|
|
734
|
+
.slice(0, 5)
|
|
735
|
+
.map((citation) => ` • ${citation.path}:${citation.line} — ${formatInsightSnippet(citation.snippet)}`);
|
|
736
|
+
answer = ['I found the relevant references here:', ...locations].join('\n');
|
|
737
|
+
}
|
|
738
|
+
else if (asksHow && insightLines.length > 0) {
|
|
739
|
+
const bullets = insightLines.map((line) => ` • ${line}`);
|
|
740
|
+
answer = ['From the matched code, this is how it works:', ...bullets].join('\n');
|
|
741
|
+
}
|
|
742
|
+
else if (asksTenancy && (multiSignals > 0 || singleSignals > 0)) {
|
|
743
|
+
if (multiSignals >= singleSignals + 2) {
|
|
744
|
+
answer = 'This codebase is multi-tenant, with organization-scoped flows (for example org IDs / `x-org-id` context).';
|
|
745
|
+
}
|
|
746
|
+
else if (singleSignals >= multiSignals + 2) {
|
|
747
|
+
answer = 'This codebase currently looks single-tenant from the scanned evidence.';
|
|
903
748
|
}
|
|
904
749
|
else {
|
|
905
|
-
answer = '
|
|
750
|
+
answer = 'I see mixed tenancy signals, but it leans multi-tenant in current runtime paths.';
|
|
906
751
|
}
|
|
907
752
|
}
|
|
753
|
+
else if (insightLines.length > 0) {
|
|
754
|
+
const bullets = insightLines.map((line) => ` • ${line}`);
|
|
755
|
+
answer = truth.status === 'insufficient'
|
|
756
|
+
? ['I found partial evidence, but not enough for a fully definitive answer yet.', 'What I can confirm so far:', ...bullets].join('\n')
|
|
757
|
+
: ['From this repo, here is what I found:', ...bullets].join('\n');
|
|
758
|
+
}
|
|
759
|
+
else {
|
|
760
|
+
answer = `I found grounded evidence in ${stats.matchedFiles} file(s), but it is mostly low-level implementation detail.`;
|
|
761
|
+
}
|
|
762
|
+
if (asksCommandCatalog && commandMatches.length === 0 && citations.length > 0) {
|
|
763
|
+
findings.push('Command-style question detected, but no command declarations were found in the matched lines.');
|
|
764
|
+
}
|
|
765
|
+
if (asksInstall && installMatches.length === 0 && !context.cliPackageName) {
|
|
766
|
+
findings.push('Install question detected, but no package name could be resolved from packages/cli/package.json.');
|
|
767
|
+
}
|
|
908
768
|
const topFiles = [...perFileCounts.entries()]
|
|
909
769
|
.sort((a, b) => b[1] - a[1])
|
|
910
770
|
.slice(0, 3)
|
|
@@ -913,6 +773,10 @@ function buildAnswer(mode, question, terms, citations, stats, termCounts, perFil
|
|
|
913
773
|
findings.push(`Most relevant files: ${topFiles.join(', ')}`);
|
|
914
774
|
}
|
|
915
775
|
findings.push(`Scanned ${stats.scannedFiles} file(s); matched ${stats.matchedFiles} file(s).`);
|
|
776
|
+
if (truth.status === 'insufficient') {
|
|
777
|
+
findings.push(...truth.reasons);
|
|
778
|
+
findings.push('Add scope hints (module/file/path) to improve precision and grounding coverage.');
|
|
779
|
+
}
|
|
916
780
|
return {
|
|
917
781
|
question,
|
|
918
782
|
questionNormalized: normalizedQuestion,
|
|
@@ -1105,86 +969,13 @@ async function askCommand(question, options = {}) {
|
|
|
1105
969
|
const brainResults = orgId && projectId
|
|
1106
970
|
? (0, brain_context_1.searchBrainContextEntries)(cwd, scope, normalizedQuestion, { limit: 48 })
|
|
1107
971
|
: { entries: [], totalIndexedFiles: 0 };
|
|
972
|
+
const cliPackageName = readCliPackageName(cwd);
|
|
973
|
+
const knownCliCommands = extractCommandsFromCliIndex(cwd);
|
|
974
|
+
const featureBullets = extractFeatureBulletsFromReadme(cwd, 10);
|
|
1108
975
|
if (!options.json && brainResults.entries.length > 0) {
|
|
1109
976
|
const top = brainResults.entries.filter((entry) => entry.score > 0).length;
|
|
1110
977
|
console.log(chalk.dim(`🧠 Brain retrieval: ${top} relevant file summaries from ${brainResults.totalIndexedFiles} indexed files`));
|
|
1111
978
|
}
|
|
1112
|
-
const finalizeSpecialAnswer = (answer, note) => {
|
|
1113
|
-
emitAskResult(answer, {
|
|
1114
|
-
json: options.json,
|
|
1115
|
-
maxCitations,
|
|
1116
|
-
fromPlan: options.fromPlan,
|
|
1117
|
-
verbose: options.verbose,
|
|
1118
|
-
});
|
|
1119
|
-
if (orgId && projectId) {
|
|
1120
|
-
(0, brain_context_1.recordBrainProgressEvent)(cwd, scope, {
|
|
1121
|
-
type: 'ask',
|
|
1122
|
-
note,
|
|
1123
|
-
});
|
|
1124
|
-
}
|
|
1125
|
-
if (shouldUseCache && orgId && projectId) {
|
|
1126
|
-
const questionHash = (0, ask_cache_1.computeAskQuestionHash)({
|
|
1127
|
-
question: normalizedQuestion,
|
|
1128
|
-
contextHash: staticContext.hash,
|
|
1129
|
-
});
|
|
1130
|
-
const key = (0, ask_cache_1.computeAskCacheKey)({
|
|
1131
|
-
schemaVersion: 3,
|
|
1132
|
-
orgId,
|
|
1133
|
-
projectId,
|
|
1134
|
-
repo: repoFingerprint,
|
|
1135
|
-
questionHash,
|
|
1136
|
-
policyVersionHash,
|
|
1137
|
-
neurcodeVersion,
|
|
1138
|
-
});
|
|
1139
|
-
(0, ask_cache_1.writeCachedAsk)(cwd, {
|
|
1140
|
-
key,
|
|
1141
|
-
input: {
|
|
1142
|
-
schemaVersion: 3,
|
|
1143
|
-
orgId,
|
|
1144
|
-
projectId,
|
|
1145
|
-
repo: repoFingerprint,
|
|
1146
|
-
questionHash,
|
|
1147
|
-
policyVersionHash,
|
|
1148
|
-
neurcodeVersion,
|
|
1149
|
-
question: normalizedQuestion,
|
|
1150
|
-
contextHash: staticContext.hash,
|
|
1151
|
-
},
|
|
1152
|
-
output: answer,
|
|
1153
|
-
evidencePaths: answer.citations.map((citation) => citation.path),
|
|
1154
|
-
});
|
|
1155
|
-
}
|
|
1156
|
-
};
|
|
1157
|
-
if (isCliCommandListQuestion(normalizedQuestion)) {
|
|
1158
|
-
const catalog = extractCliCommandCatalog(cwd);
|
|
1159
|
-
if (catalog) {
|
|
1160
|
-
const answer = buildCliCommandAnswer(question, normalizedQuestion, catalog, brainResults.entries.length);
|
|
1161
|
-
finalizeSpecialAnswer(answer, `mode=cli_catalog;truth=${answer.truth.status};score=${answer.truth.score.toFixed(2)};commands=${catalog.topLevelCommands.length}`);
|
|
1162
|
-
return;
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
if (isInstallCliQuestion(normalizedQuestion)) {
|
|
1166
|
-
const installInfo = extractInstallCommandInfo(cwd);
|
|
1167
|
-
if (installInfo) {
|
|
1168
|
-
const answer = buildInstallCommandAnswer(question, normalizedQuestion, installInfo, brainResults.entries.length);
|
|
1169
|
-
finalizeSpecialAnswer(answer, `mode=install_cli;truth=${answer.truth.status};score=${answer.truth.score.toFixed(2)};package=${installInfo.packageName}`);
|
|
1170
|
-
return;
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
if (isTenancyQuestion(normalizedQuestion)) {
|
|
1174
|
-
const answer = buildTenancyAnswer(cwd, question, normalizedQuestion, brainResults.entries.length);
|
|
1175
|
-
if (answer) {
|
|
1176
|
-
finalizeSpecialAnswer(answer, `mode=tenancy;truth=${answer.truth.status};score=${answer.truth.score.toFixed(2)};matched_files=${answer.stats.matchedFiles}`);
|
|
1177
|
-
return;
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
if (isFeatureOverviewQuestion(normalizedQuestion)) {
|
|
1181
|
-
const entries = extractFeatureEntriesFromReadme(cwd);
|
|
1182
|
-
if (entries.length > 0) {
|
|
1183
|
-
const answer = buildFeatureOverviewAnswer(question, normalizedQuestion, entries, brainResults.entries.length);
|
|
1184
|
-
finalizeSpecialAnswer(answer, `mode=feature_overview;truth=${answer.truth.status};score=${answer.truth.score.toFixed(2)};entries=${entries.length}`);
|
|
1185
|
-
return;
|
|
1186
|
-
}
|
|
1187
|
-
}
|
|
1188
979
|
const { mode, terms, matchers } = buildMatchers(question);
|
|
1189
980
|
const pathHints = derivePathHints(question);
|
|
1190
981
|
const mentionsAskCommand = /\bask\b/.test(normalizedQuestion);
|
|
@@ -1198,6 +989,7 @@ async function askCommand(question, options = {}) {
|
|
|
1198
989
|
candidateSet.add(entry.path);
|
|
1199
990
|
pathPriority.set(entry.path, (pathPriority.get(entry.path) || 0) + entry.score);
|
|
1200
991
|
}
|
|
992
|
+
addAnchorCandidates(fileTree, candidateSet, pathPriority, normalizedQuestion);
|
|
1201
993
|
const tokenHints = tokenizeQuestion(question);
|
|
1202
994
|
if (candidateSet.size < 80) {
|
|
1203
995
|
for (const filePath of fileTree) {
|
|
@@ -1207,6 +999,12 @@ async function askCommand(question, options = {}) {
|
|
|
1207
999
|
if (normalizedPath.includes(token))
|
|
1208
1000
|
score += 0.2;
|
|
1209
1001
|
}
|
|
1002
|
+
if (normalizedPath.startsWith('.github/')) {
|
|
1003
|
+
score -= 0.2;
|
|
1004
|
+
}
|
|
1005
|
+
if (normalizedPath.startsWith('scripts/')) {
|
|
1006
|
+
score -= 0.1;
|
|
1007
|
+
}
|
|
1210
1008
|
if (!mentionsAskCommand && (filePath.endsWith('/commands/ask.ts') || filePath.endsWith('/utils/ask-cache.ts'))) {
|
|
1211
1009
|
score -= 0.45;
|
|
1212
1010
|
}
|
|
@@ -1222,8 +1020,8 @@ async function askCommand(question, options = {}) {
|
|
|
1222
1020
|
}
|
|
1223
1021
|
}
|
|
1224
1022
|
}
|
|
1225
|
-
if (candidateSet.size <
|
|
1226
|
-
for (const filePath of fileTree.slice(0,
|
|
1023
|
+
if (candidateSet.size < 40) {
|
|
1024
|
+
for (const filePath of fileTree.slice(0, Math.min(fileTree.length, MAX_SCAN_FILES))) {
|
|
1227
1025
|
candidateSet.add(filePath);
|
|
1228
1026
|
}
|
|
1229
1027
|
}
|
|
@@ -1301,6 +1099,8 @@ async function askCommand(question, options = {}) {
|
|
|
1301
1099
|
}
|
|
1302
1100
|
}
|
|
1303
1101
|
const selectedForOutput = [];
|
|
1102
|
+
const wantsOrgRequestInjection = /\b(inject|injected|header|request|requests)\b/.test(normalizedQuestion) &&
|
|
1103
|
+
/\b(org|organization)\b/.test(normalizedQuestion);
|
|
1304
1104
|
if (mode === 'comparison' && terms.length >= 2) {
|
|
1305
1105
|
for (const term of terms.slice(0, 2)) {
|
|
1306
1106
|
const firstForTerm = sourceEvidence.find((citation) => citation.term === term);
|
|
@@ -1309,6 +1109,38 @@ async function askCommand(question, options = {}) {
|
|
|
1309
1109
|
}
|
|
1310
1110
|
}
|
|
1311
1111
|
}
|
|
1112
|
+
else if (mode === 'search' && terms.length > 0) {
|
|
1113
|
+
const preferredTerms = terms
|
|
1114
|
+
.filter((term) => !LOW_SIGNAL_TERMS.has(term) && term.length >= 4)
|
|
1115
|
+
.slice(0, 8);
|
|
1116
|
+
for (const term of preferredTerms) {
|
|
1117
|
+
const firstForTerm = sourceEvidence.find((citation) => {
|
|
1118
|
+
if (citation.term !== term)
|
|
1119
|
+
return false;
|
|
1120
|
+
const normalized = (citation.term || '').toLowerCase();
|
|
1121
|
+
return !GENERIC_OUTPUT_TERMS.has(normalized);
|
|
1122
|
+
});
|
|
1123
|
+
if (!firstForTerm)
|
|
1124
|
+
continue;
|
|
1125
|
+
if (selectedForOutput.some((existing) => existing.path === firstForTerm.path && existing.line === firstForTerm.line && existing.term === firstForTerm.term)) {
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
1128
|
+
selectedForOutput.push(firstForTerm);
|
|
1129
|
+
if (selectedForOutput.length >= maxCitations)
|
|
1130
|
+
break;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
if (wantsOrgRequestInjection && selectedForOutput.length < maxCitations) {
|
|
1134
|
+
const targeted = sourceEvidence.filter((citation) => /x-org-id|org[_ -]?id|headers?\[|auto-?inject|request/i.test(citation.snippet));
|
|
1135
|
+
for (const citation of targeted) {
|
|
1136
|
+
if (selectedForOutput.length >= maxCitations)
|
|
1137
|
+
break;
|
|
1138
|
+
if (selectedForOutput.some((existing) => existing.path === citation.path && existing.line === citation.line && existing.term === citation.term)) {
|
|
1139
|
+
continue;
|
|
1140
|
+
}
|
|
1141
|
+
selectedForOutput.push(citation);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1312
1144
|
for (const citation of sourceEvidence) {
|
|
1313
1145
|
if (selectedForOutput.length >= maxCitations)
|
|
1314
1146
|
break;
|
|
@@ -1330,7 +1162,11 @@ async function askCommand(question, options = {}) {
|
|
|
1330
1162
|
brainCandidates: brainResults.entries.length,
|
|
1331
1163
|
};
|
|
1332
1164
|
const truth = evaluateTruthAssessment(mode, normalizedQuestion, terms, sourceEvidence, sourcePerFileCounts, sourceTermCounts);
|
|
1333
|
-
const answer = buildAnswer(mode, question, terms, finalCitations, stats, sourceTermCounts, sourcePerFileCounts, truth
|
|
1165
|
+
const answer = buildAnswer(mode, question, terms, finalCitations, stats, sourceTermCounts, sourcePerFileCounts, truth, {
|
|
1166
|
+
cliPackageName,
|
|
1167
|
+
knownCommands: knownCliCommands,
|
|
1168
|
+
featureBullets,
|
|
1169
|
+
});
|
|
1334
1170
|
emitAskResult(answer, {
|
|
1335
1171
|
json: options.json,
|
|
1336
1172
|
maxCitations,
|