codebase-context 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -90
- package/dist/analyzers/angular/index.d.ts.map +1 -1
- package/dist/analyzers/angular/index.js +85 -39
- package/dist/analyzers/angular/index.js.map +1 -1
- package/dist/analyzers/generic/index.d.ts.map +1 -1
- package/dist/analyzers/generic/index.js +5 -4
- package/dist/analyzers/generic/index.js.map +1 -1
- package/dist/cli-formatters.d.ts +47 -0
- package/dist/cli-formatters.d.ts.map +1 -0
- package/dist/cli-formatters.js +803 -0
- package/dist/cli-formatters.js.map +1 -0
- package/dist/cli-memory.d.ts +5 -0
- package/dist/cli-memory.d.ts.map +1 -0
- package/dist/cli-memory.js +218 -0
- package/dist/cli-memory.js.map +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +168 -178
- package/dist/cli.js.map +1 -1
- package/dist/core/auto-refresh.d.ts +16 -0
- package/dist/core/auto-refresh.d.ts.map +1 -0
- package/dist/core/auto-refresh.js +25 -0
- package/dist/core/auto-refresh.js.map +1 -0
- package/dist/core/file-watcher.d.ts +15 -0
- package/dist/core/file-watcher.d.ts.map +1 -0
- package/dist/core/file-watcher.js +59 -0
- package/dist/core/file-watcher.js.map +1 -0
- package/dist/core/index-meta.d.ts +3 -0
- package/dist/core/index-meta.d.ts.map +1 -1
- package/dist/core/index-meta.js +9 -1
- package/dist/core/index-meta.js.map +1 -1
- package/dist/core/indexer.d.ts.map +1 -1
- package/dist/core/indexer.js +74 -15
- package/dist/core/indexer.js.map +1 -1
- package/dist/core/reranker.d.ts.map +1 -1
- package/dist/core/reranker.js +3 -0
- package/dist/core/reranker.js.map +1 -1
- package/dist/core/search-quality.js +2 -2
- package/dist/core/search-quality.js.map +1 -1
- package/dist/core/search.d.ts.map +1 -1
- package/dist/core/search.js +20 -7
- package/dist/core/search.js.map +1 -1
- package/dist/core/symbol-references.d.ts +2 -3
- package/dist/core/symbol-references.d.ts.map +1 -1
- package/dist/core/symbol-references.js +111 -16
- package/dist/core/symbol-references.js.map +1 -1
- package/dist/embeddings/index.d.ts +8 -0
- package/dist/embeddings/index.d.ts.map +1 -1
- package/dist/embeddings/index.js +17 -2
- package/dist/embeddings/index.js.map +1 -1
- package/dist/embeddings/openai.d.ts +1 -1
- package/dist/embeddings/openai.d.ts.map +1 -1
- package/dist/embeddings/openai.js +3 -1
- package/dist/embeddings/openai.js.map +1 -1
- package/dist/embeddings/transformers.d.ts +6 -0
- package/dist/embeddings/transformers.d.ts.map +1 -1
- package/dist/embeddings/transformers.js +12 -5
- package/dist/embeddings/transformers.js.map +1 -1
- package/dist/embeddings/types.d.ts +1 -0
- package/dist/embeddings/types.d.ts.map +1 -1
- package/dist/embeddings/types.js +7 -1
- package/dist/embeddings/types.js.map +1 -1
- package/dist/grammars/manifest.d.ts.map +1 -1
- package/dist/grammars/manifest.js +2 -1
- package/dist/grammars/manifest.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +46 -3
- package/dist/index.js.map +1 -1
- package/dist/patterns/semantics.d.ts +2 -1
- package/dist/patterns/semantics.d.ts.map +1 -1
- package/dist/patterns/semantics.js +0 -2
- package/dist/patterns/semantics.js.map +1 -1
- package/dist/storage/index.d.ts +4 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +2 -2
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/lancedb.d.ts +2 -0
- package/dist/storage/lancedb.d.ts.map +1 -1
- package/dist/storage/lancedb.js +20 -4
- package/dist/storage/lancedb.js.map +1 -1
- package/dist/storage/types.d.ts +4 -1
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/storage/types.js.map +1 -1
- package/dist/tools/detect-circular-dependencies.d.ts.map +1 -1
- package/dist/tools/detect-circular-dependencies.js.map +1 -1
- package/dist/tools/get-team-patterns.d.ts.map +1 -1
- package/dist/tools/get-team-patterns.js +30 -14
- package/dist/tools/get-team-patterns.js.map +1 -1
- package/dist/tools/search-codebase.d.ts.map +1 -1
- package/dist/tools/search-codebase.js +296 -189
- package/dist/tools/search-codebase.js.map +1 -1
- package/dist/tools/types.d.ts +193 -1
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/types/index.d.ts +73 -11
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +0 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/language-detection.d.ts.map +1 -1
- package/dist/utils/language-detection.js +6 -1
- package/dist/utils/language-detection.js.map +1 -1
- package/dist/utils/tree-sitter.d.ts +11 -0
- package/dist/utils/tree-sitter.d.ts.map +1 -1
- package/dist/utils/tree-sitter.js +111 -0
- package/dist/utils/tree-sitter.js.map +1 -1
- package/dist/utils/usage-tracker.d.ts +30 -40
- package/dist/utils/usage-tracker.d.ts.map +1 -1
- package/dist/utils/usage-tracker.js +66 -8
- package/dist/utils/usage-tracker.js.map +1 -1
- package/docs/capabilities.md +22 -8
- package/docs/cli.md +196 -0
- package/grammars/tree-sitter-kotlin.wasm +0 -0
- package/package.json +6 -4
- package/dist/tools/get-component-usage.d.ts +0 -5
- package/dist/tools/get-component-usage.d.ts.map +0 -1
- package/dist/tools/get-component-usage.js +0 -83
- package/dist/tools/get-component-usage.js.map +0 -1
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
1
|
import { promises as fs } from 'fs';
|
|
3
2
|
import path from 'path';
|
|
4
3
|
import { CodebaseSearcher } from '../core/search.js';
|
|
@@ -115,7 +114,7 @@ export async function handle(args, ctx) {
|
|
|
115
114
|
}
|
|
116
115
|
const searcher = new CodebaseSearcher(ctx.rootPath);
|
|
117
116
|
let results;
|
|
118
|
-
const searchProfile = intent && ['explore', 'edit', 'refactor', 'migrate'].includes(intent) ? intent : 'explore';
|
|
117
|
+
const searchProfile = (intent && ['explore', 'edit', 'refactor', 'migrate'].includes(intent) ? intent : 'explore');
|
|
119
118
|
try {
|
|
120
119
|
results = await searcher.search(queryStr, limit || 5, filters, {
|
|
121
120
|
profile: searchProfile
|
|
@@ -180,7 +179,10 @@ export async function handle(args, ctx) {
|
|
|
180
179
|
let intelligence = null;
|
|
181
180
|
try {
|
|
182
181
|
const intelligenceContent = await fs.readFile(ctx.paths.intelligence, 'utf-8');
|
|
183
|
-
|
|
182
|
+
const parsed = JSON.parse(intelligenceContent);
|
|
183
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
184
|
+
intelligence = parsed;
|
|
185
|
+
}
|
|
184
186
|
}
|
|
185
187
|
catch {
|
|
186
188
|
/* graceful degradation — intelligence file may not exist yet */
|
|
@@ -190,7 +192,10 @@ export async function handle(args, ctx) {
|
|
|
190
192
|
try {
|
|
191
193
|
const relationshipsPath = path.join(path.dirname(ctx.paths.intelligence), RELATIONSHIPS_FILENAME);
|
|
192
194
|
const relationshipsContent = await fs.readFile(relationshipsPath, 'utf-8');
|
|
193
|
-
|
|
195
|
+
const parsed = JSON.parse(relationshipsContent);
|
|
196
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
197
|
+
relationships = parsed;
|
|
198
|
+
}
|
|
194
199
|
}
|
|
195
200
|
catch {
|
|
196
201
|
/* graceful degradation — relationships sidecar may not exist yet */
|
|
@@ -205,6 +210,29 @@ export async function handle(args, ctx) {
|
|
|
205
210
|
}
|
|
206
211
|
return null;
|
|
207
212
|
}
|
|
213
|
+
function getImportDetailsGraph() {
|
|
214
|
+
if (relationships?.graph?.importDetails) {
|
|
215
|
+
return relationships.graph.importDetails;
|
|
216
|
+
}
|
|
217
|
+
const internalDetails = intelligence?.internalFileGraph?.importDetails;
|
|
218
|
+
if (internalDetails) {
|
|
219
|
+
return internalDetails;
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
function normalizeGraphPath(filePath) {
|
|
224
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
225
|
+
if (path.isAbsolute(filePath)) {
|
|
226
|
+
const rel = path.relative(ctx.rootPath, filePath).replace(/\\/g, '/');
|
|
227
|
+
if (rel && !rel.startsWith('..')) {
|
|
228
|
+
return rel;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return normalized.replace(/^\.\//, '');
|
|
232
|
+
}
|
|
233
|
+
function pathsMatch(a, b) {
|
|
234
|
+
return a === b || a.endsWith(b) || b.endsWith(a);
|
|
235
|
+
}
|
|
208
236
|
function computeIndexConfidence() {
|
|
209
237
|
let confidence = 'stale';
|
|
210
238
|
if (intelligence?.generatedAt) {
|
|
@@ -219,20 +247,78 @@ export async function handle(args, ctx) {
|
|
|
219
247
|
}
|
|
220
248
|
return confidence;
|
|
221
249
|
}
|
|
222
|
-
|
|
250
|
+
function findImportDetail(details, importer, imported) {
|
|
251
|
+
if (!details)
|
|
252
|
+
return null;
|
|
253
|
+
const edges = details[importer];
|
|
254
|
+
if (!edges)
|
|
255
|
+
return null;
|
|
256
|
+
if (edges[imported])
|
|
257
|
+
return edges[imported];
|
|
258
|
+
let bestKey = null;
|
|
259
|
+
for (const depKey of Object.keys(edges)) {
|
|
260
|
+
if (!pathsMatch(depKey, imported))
|
|
261
|
+
continue;
|
|
262
|
+
if (!bestKey || depKey.length > bestKey.length) {
|
|
263
|
+
bestKey = depKey;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return bestKey ? edges[bestKey] : null;
|
|
267
|
+
}
|
|
268
|
+
// Impact breadth estimate from the import graph (used for risk assessment).
|
|
269
|
+
// 2-hop: direct importers (hop 1) + importers of importers (hop 2).
|
|
223
270
|
function computeImpactCandidates(resultPaths) {
|
|
224
|
-
const impactCandidates = [];
|
|
225
271
|
const allImports = getImportsGraph();
|
|
226
272
|
if (!allImports)
|
|
227
|
-
return
|
|
273
|
+
return [];
|
|
274
|
+
const importDetails = getImportDetailsGraph();
|
|
275
|
+
const reverseImportsLocal = new Map();
|
|
228
276
|
for (const [file, deps] of Object.entries(allImports)) {
|
|
229
|
-
|
|
230
|
-
if (!
|
|
231
|
-
|
|
277
|
+
for (const dep of deps) {
|
|
278
|
+
if (!reverseImportsLocal.has(dep))
|
|
279
|
+
reverseImportsLocal.set(dep, []);
|
|
280
|
+
reverseImportsLocal.get(dep).push(file);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const targets = resultPaths.map((rp) => normalizeGraphPath(rp));
|
|
284
|
+
const candidates = new Map();
|
|
285
|
+
const addCandidate = (file, hop, line) => {
|
|
286
|
+
const existing = candidates.get(file);
|
|
287
|
+
if (existing) {
|
|
288
|
+
if (existing.hop <= hop)
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
candidates.set(file, { file, hop, ...(line ? { line } : {}) });
|
|
292
|
+
};
|
|
293
|
+
const collectImporters = (target) => {
|
|
294
|
+
const matches = [];
|
|
295
|
+
for (const [dep, importers] of reverseImportsLocal) {
|
|
296
|
+
if (!pathsMatch(dep, target))
|
|
297
|
+
continue;
|
|
298
|
+
for (const importer of importers) {
|
|
299
|
+
matches.push({ importer, detail: findImportDetail(importDetails, importer, dep) });
|
|
232
300
|
}
|
|
233
301
|
}
|
|
302
|
+
return matches;
|
|
303
|
+
};
|
|
304
|
+
// Hop 1
|
|
305
|
+
const hop1Files = [];
|
|
306
|
+
for (const target of targets) {
|
|
307
|
+
for (const { importer, detail } of collectImporters(target)) {
|
|
308
|
+
addCandidate(importer, 1, detail?.line);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
for (const candidate of candidates.values()) {
|
|
312
|
+
if (candidate.hop === 1)
|
|
313
|
+
hop1Files.push(candidate.file);
|
|
314
|
+
}
|
|
315
|
+
// Hop 2
|
|
316
|
+
for (const mid of hop1Files) {
|
|
317
|
+
for (const { importer, detail } of collectImporters(mid)) {
|
|
318
|
+
addCandidate(importer, 2, detail?.line);
|
|
319
|
+
}
|
|
234
320
|
}
|
|
235
|
-
return
|
|
321
|
+
return Array.from(candidates.values()).slice(0, 20);
|
|
236
322
|
}
|
|
237
323
|
// Build reverse import map from relationships sidecar (preferred) or intelligence graph
|
|
238
324
|
const reverseImports = new Map();
|
|
@@ -304,7 +390,7 @@ export async function handle(args, ctx) {
|
|
|
304
390
|
}
|
|
305
391
|
return output;
|
|
306
392
|
}
|
|
307
|
-
const searchQuality = assessSearchQuality(
|
|
393
|
+
const searchQuality = assessSearchQuality(queryStr, results);
|
|
308
394
|
// Always-on edit preflight (lite): do not require intent and keep payload small.
|
|
309
395
|
let editPreflight = undefined;
|
|
310
396
|
if (intelligence && (!intent || intent === 'explore')) {
|
|
@@ -353,193 +439,206 @@ export async function handle(args, ctx) {
|
|
|
353
439
|
// Compose preflight card for edit/refactor/migrate intents
|
|
354
440
|
let preflight = undefined;
|
|
355
441
|
const preflightIntents = ['edit', 'refactor', 'migrate'];
|
|
356
|
-
if (intent && preflightIntents.includes(intent)
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
}
|
|
377
|
-
// Also-detected patterns that are Declining = avoid
|
|
378
|
-
if (data.alsoDetected) {
|
|
379
|
-
for (const alt of data.alsoDetected) {
|
|
380
|
-
if (alt.trend === 'Declining') {
|
|
381
|
-
avoidPatternsList.push({
|
|
382
|
-
pattern: alt.name,
|
|
442
|
+
if (intent && preflightIntents.includes(intent)) {
|
|
443
|
+
if (!intelligence) {
|
|
444
|
+
preflight = {
|
|
445
|
+
ready: false,
|
|
446
|
+
nextAction: 'Run a full index rebuild to generate pattern intelligence before editing.'
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
try {
|
|
451
|
+
// --- Avoid / Prefer patterns ---
|
|
452
|
+
const avoidPatternsList = [];
|
|
453
|
+
const preferredPatternsList = [];
|
|
454
|
+
const patterns = intelligence.patterns || {};
|
|
455
|
+
for (const [category, data] of Object.entries(patterns)) {
|
|
456
|
+
// Primary pattern = preferred if Rising or Stable
|
|
457
|
+
if (data.primary) {
|
|
458
|
+
const p = data.primary;
|
|
459
|
+
if (p.trend === 'Rising' || p.trend === 'Stable') {
|
|
460
|
+
preferredPatternsList.push({
|
|
461
|
+
pattern: p.name,
|
|
383
462
|
category,
|
|
384
|
-
adoption:
|
|
385
|
-
trend:
|
|
386
|
-
guidance:
|
|
463
|
+
adoption: p.frequency,
|
|
464
|
+
trend: p.trend,
|
|
465
|
+
guidance: p.guidance,
|
|
466
|
+
...(p.canonicalExample && { example: p.canonicalExample.file })
|
|
387
467
|
});
|
|
388
468
|
}
|
|
389
469
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
470
|
+
// Also-detected patterns that are Declining = avoid
|
|
471
|
+
if (data.alsoDetected) {
|
|
472
|
+
for (const alt of data.alsoDetected) {
|
|
473
|
+
if (alt.trend === 'Declining') {
|
|
474
|
+
avoidPatternsList.push({
|
|
475
|
+
pattern: alt.name,
|
|
476
|
+
category,
|
|
477
|
+
adoption: alt.frequency,
|
|
478
|
+
trend: 'Declining',
|
|
479
|
+
guidance: alt.guidance
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
}
|
|
401
483
|
}
|
|
402
484
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
485
|
+
// --- Impact candidates (files importing the result files) ---
|
|
486
|
+
const resultPaths = results.map((r) => r.filePath);
|
|
487
|
+
const impactCandidates = computeImpactCandidates(resultPaths);
|
|
488
|
+
// PREF-02: Compute impact coverage (callers of result files that appear in results)
|
|
489
|
+
const callerFiles = resultPaths.flatMap((p) => {
|
|
490
|
+
const importers = [];
|
|
491
|
+
for (const [dep, importerList] of reverseImports) {
|
|
492
|
+
if (dep.endsWith(p) || p.endsWith(dep)) {
|
|
493
|
+
importers.push(...importerList);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return importers;
|
|
497
|
+
});
|
|
498
|
+
const uniqueCallers = new Set(callerFiles);
|
|
499
|
+
const callersCovered = Array.from(uniqueCallers).filter((f) => resultPaths.some((rp) => f.endsWith(rp) || rp.endsWith(f))).length;
|
|
500
|
+
const callersTotal = uniqueCallers.size;
|
|
501
|
+
const impactCoverage = callersTotal > 0 ? { covered: callersCovered, total: callersTotal } : undefined;
|
|
502
|
+
// --- Risk level (based on circular deps + impact breadth) ---
|
|
503
|
+
//TODO: Review this risk level calculation
|
|
504
|
+
let _riskLevel = 'low';
|
|
505
|
+
let cycleCount = 0;
|
|
506
|
+
const graphDataSource = relationships?.graph || intelligence?.internalFileGraph;
|
|
507
|
+
if (graphDataSource) {
|
|
508
|
+
try {
|
|
509
|
+
const graph = InternalFileGraph.fromJSON(graphDataSource, ctx.rootPath);
|
|
510
|
+
// Use directory prefixes as scope (not full file paths)
|
|
511
|
+
// findCycles(scope) filters files by startsWith, so a full path would only match itself
|
|
512
|
+
const scopes = new Set(resultPaths.map((rp) => {
|
|
513
|
+
const lastSlash = rp.lastIndexOf('/');
|
|
514
|
+
return lastSlash > 0 ? rp.substring(0, lastSlash + 1) : rp;
|
|
515
|
+
}));
|
|
516
|
+
for (const scope of scopes) {
|
|
517
|
+
const cycles = graph.findCycles(scope);
|
|
518
|
+
cycleCount += cycles.length;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
catch {
|
|
522
|
+
// Graph reconstruction failed — skip cycle check
|
|
426
523
|
}
|
|
427
524
|
}
|
|
428
|
-
|
|
429
|
-
|
|
525
|
+
if (cycleCount > 0 || impactCandidates.length > 10) {
|
|
526
|
+
_riskLevel = 'high';
|
|
430
527
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
.slice(0,
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
});
|
|
528
|
+
else if (impactCandidates.length > 3) {
|
|
529
|
+
_riskLevel = 'medium';
|
|
530
|
+
}
|
|
531
|
+
// --- Golden files (exemplar code) ---
|
|
532
|
+
const goldenFiles = (intelligence.goldenFiles ?? [])
|
|
533
|
+
.slice(0, 3)
|
|
534
|
+
.map((g) => ({
|
|
535
|
+
file: g.file,
|
|
536
|
+
score: g.score
|
|
537
|
+
}));
|
|
538
|
+
// --- Confidence (index freshness) ---
|
|
539
|
+
// TODO: Review this confidence calculation
|
|
540
|
+
//const confidence = computeIndexConfidence();
|
|
541
|
+
// --- Failure memories (1.5x relevance boost) ---
|
|
542
|
+
const failureWarnings = relatedMemories
|
|
543
|
+
.filter((m) => m.type === 'failure' && !m.stale)
|
|
544
|
+
.map((m) => ({
|
|
545
|
+
memory: m.memory,
|
|
546
|
+
reason: m.reason,
|
|
547
|
+
confidence: m.effectiveConfidence
|
|
548
|
+
}))
|
|
549
|
+
.slice(0, 3);
|
|
550
|
+
const preferredPatternsForOutput = preferredPatternsList.slice(0, 5);
|
|
551
|
+
const avoidPatternsForOutput = avoidPatternsList.slice(0, 5);
|
|
552
|
+
// --- Pattern conflicts (split decisions within categories) ---
|
|
553
|
+
const patternConflicts = [];
|
|
554
|
+
const hasUnitTestFramework = Boolean(patterns.unitTestFramework?.primary);
|
|
555
|
+
for (const [cat, data] of Object.entries(patterns)) {
|
|
556
|
+
if (shouldSkipLegacyTestingFrameworkCategory(cat, patterns))
|
|
557
|
+
continue;
|
|
558
|
+
if (!shouldIncludePatternConflictCategory(cat, queryStr))
|
|
559
|
+
continue;
|
|
560
|
+
if (!data.primary || !data.alsoDetected?.length)
|
|
561
|
+
continue;
|
|
562
|
+
const primaryFreq = parseFloat(data.primary.frequency) || 100;
|
|
563
|
+
if (primaryFreq >= 80)
|
|
564
|
+
continue;
|
|
565
|
+
for (const alt of data.alsoDetected) {
|
|
566
|
+
const altFreq = parseFloat(alt.frequency) || 0;
|
|
567
|
+
if (altFreq >= 20) {
|
|
568
|
+
if (isComplementaryPatternConflict(cat, data.primary.name, alt.name))
|
|
569
|
+
continue;
|
|
570
|
+
if (hasUnitTestFramework && cat === 'testingFramework')
|
|
571
|
+
continue;
|
|
572
|
+
patternConflicts.push({
|
|
573
|
+
category: cat,
|
|
574
|
+
primary: { name: data.primary.name, adoption: data.primary.frequency },
|
|
575
|
+
alternative: { name: alt.name, adoption: alt.frequency }
|
|
576
|
+
});
|
|
577
|
+
}
|
|
482
578
|
}
|
|
483
579
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
};
|
|
497
|
-
// Add nextAction if not ready
|
|
498
|
-
if (!decisionCard.ready && evidenceLock.nextAction) {
|
|
499
|
-
decisionCard.nextAction = evidenceLock.nextAction;
|
|
500
|
-
}
|
|
501
|
-
// Add warnings from failure memories (capped at 3)
|
|
502
|
-
if (failureWarnings.length > 0) {
|
|
503
|
-
decisionCard.warnings = failureWarnings.slice(0, 3).map((w) => w.memory);
|
|
504
|
-
}
|
|
505
|
-
// Add patterns (do/avoid, capped at 3 each, with adoption %)
|
|
506
|
-
const doPatterns = preferredPatternsForOutput
|
|
507
|
-
.slice(0, 3)
|
|
508
|
-
.map((p) => `${p.pattern} — ${p.adoption ? ` ${p.adoption}% adoption` : ''}`);
|
|
509
|
-
const avoidPatterns = avoidPatternsForOutput
|
|
510
|
-
.slice(0, 3)
|
|
511
|
-
.map((p) => `${p.pattern} — ${p.adoption ? ` ${p.adoption}% adoption` : ''} (declining)`);
|
|
512
|
-
if (doPatterns.length > 0 || avoidPatterns.length > 0) {
|
|
513
|
-
decisionCard.patterns = {
|
|
514
|
-
...(doPatterns.length > 0 && { do: doPatterns }),
|
|
515
|
-
...(avoidPatterns.length > 0 && { avoid: avoidPatterns })
|
|
580
|
+
const evidenceLock = buildEvidenceLock({
|
|
581
|
+
results,
|
|
582
|
+
preferredPatterns: preferredPatternsForOutput,
|
|
583
|
+
relatedMemories,
|
|
584
|
+
failureWarnings,
|
|
585
|
+
patternConflicts,
|
|
586
|
+
searchQualityStatus: searchQuality.status,
|
|
587
|
+
impactCoverage
|
|
588
|
+
});
|
|
589
|
+
// Build clean decision card (PREF-01 to PREF-04)
|
|
590
|
+
const decisionCard = {
|
|
591
|
+
ready: evidenceLock.readyToEdit
|
|
516
592
|
};
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
decisionCard.bestExample = `${goldenFiles[0].file}`;
|
|
521
|
-
}
|
|
522
|
-
// Add impact (coverage + top 3 files)
|
|
523
|
-
if (impactCoverage || impactCandidates.length > 0) {
|
|
524
|
-
const impactObj = {};
|
|
525
|
-
if (impactCoverage) {
|
|
526
|
-
impactObj.coverage = `${impactCoverage.covered}/${impactCoverage.total} callers in results`;
|
|
593
|
+
// Add nextAction if not ready
|
|
594
|
+
if (!decisionCard.ready && evidenceLock.nextAction) {
|
|
595
|
+
decisionCard.nextAction = evidenceLock.nextAction;
|
|
527
596
|
}
|
|
528
|
-
|
|
529
|
-
|
|
597
|
+
// Add warnings from failure memories (capped at 3)
|
|
598
|
+
if (failureWarnings.length > 0) {
|
|
599
|
+
decisionCard.warnings = failureWarnings.slice(0, 3).map((w) => w.memory);
|
|
530
600
|
}
|
|
531
|
-
|
|
532
|
-
|
|
601
|
+
// Add patterns (do/avoid, capped at 3 each, with adoption %)
|
|
602
|
+
const doPatterns = preferredPatternsForOutput
|
|
603
|
+
.slice(0, 3)
|
|
604
|
+
.map((p) => `${p.pattern} — ${p.adoption ? `${p.adoption} adoption` : ''}`);
|
|
605
|
+
const avoidPatterns = avoidPatternsForOutput
|
|
606
|
+
.slice(0, 3)
|
|
607
|
+
.map((p) => `${p.pattern} — ${p.adoption ? `${p.adoption} adoption` : ''} (declining)`);
|
|
608
|
+
if (doPatterns.length > 0 || avoidPatterns.length > 0) {
|
|
609
|
+
decisionCard.patterns = {
|
|
610
|
+
...(doPatterns.length > 0 && { do: doPatterns }),
|
|
611
|
+
...(avoidPatterns.length > 0 && { avoid: avoidPatterns })
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
// Add bestExample (top 1 golden file)
|
|
615
|
+
if (goldenFiles.length > 0) {
|
|
616
|
+
decisionCard.bestExample = `${goldenFiles[0].file}`;
|
|
617
|
+
}
|
|
618
|
+
// Add impact (coverage + top 3 files)
|
|
619
|
+
if (impactCoverage || impactCandidates.length > 0) {
|
|
620
|
+
const impactObj = {};
|
|
621
|
+
if (impactCoverage) {
|
|
622
|
+
impactObj.coverage = `${impactCoverage.covered}/${impactCoverage.total} callers in results`;
|
|
623
|
+
}
|
|
624
|
+
if (impactCandidates.length > 0) {
|
|
625
|
+
const top = impactCandidates.slice(0, 3);
|
|
626
|
+
impactObj.files = top.map((candidate) => candidate.file);
|
|
627
|
+
impactObj.details = top;
|
|
628
|
+
}
|
|
629
|
+
if (Object.keys(impactObj).length > 0) {
|
|
630
|
+
decisionCard.impact = impactObj;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
// Add whatWouldHelp from evidenceLock
|
|
634
|
+
if (evidenceLock.whatWouldHelp && evidenceLock.whatWouldHelp.length > 0) {
|
|
635
|
+
decisionCard.whatWouldHelp = evidenceLock.whatWouldHelp;
|
|
533
636
|
}
|
|
637
|
+
preflight = decisionCard;
|
|
534
638
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
decisionCard.whatWouldHelp = evidenceLock.whatWouldHelp;
|
|
639
|
+
catch {
|
|
640
|
+
// Preflight construction failed — skip preflight, don't fail the search
|
|
538
641
|
}
|
|
539
|
-
preflight = decisionCard;
|
|
540
|
-
}
|
|
541
|
-
catch {
|
|
542
|
-
// Preflight construction failed — skip preflight, don't fail the search
|
|
543
642
|
}
|
|
544
643
|
}
|
|
545
644
|
// For edit/refactor/migrate: return clean decision card.
|
|
@@ -581,14 +680,20 @@ export async function handle(args, ctx) {
|
|
|
581
680
|
}
|
|
582
681
|
return null;
|
|
583
682
|
}
|
|
584
|
-
function
|
|
683
|
+
function formatSnippetFallbackHeader(filePath, startLine) {
|
|
684
|
+
const rel = path.relative(ctx.rootPath, filePath).replace(/\\/g, '/');
|
|
685
|
+
const displayPath = rel && !rel.startsWith('..') && !path.isAbsolute(rel) ? rel : path.basename(filePath);
|
|
686
|
+
return `${displayPath}:${startLine}`;
|
|
687
|
+
}
|
|
688
|
+
function enrichSnippetWithScope(snippet, metadata, filePath, startLine) {
|
|
585
689
|
if (!snippet)
|
|
586
690
|
return undefined;
|
|
587
|
-
const
|
|
588
|
-
if (
|
|
589
|
-
return
|
|
691
|
+
const cleanedSnippet = snippet.replace(/^\r?\n+/, '');
|
|
692
|
+
if (cleanedSnippet.startsWith('//')) {
|
|
693
|
+
return cleanedSnippet;
|
|
590
694
|
}
|
|
591
|
-
|
|
695
|
+
const scopeHeader = buildScopeHeader(metadata) ?? formatSnippetFallbackHeader(filePath, startLine);
|
|
696
|
+
return `// ${scopeHeader}\n${cleanedSnippet}`;
|
|
592
697
|
}
|
|
593
698
|
return {
|
|
594
699
|
content: [
|
|
@@ -608,13 +713,15 @@ export async function handle(args, ctx) {
|
|
|
608
713
|
results: results.map((r) => {
|
|
609
714
|
const relationshipsAndHints = buildRelationshipHints(r);
|
|
610
715
|
const enrichedSnippet = includeSnippets
|
|
611
|
-
? enrichSnippetWithScope(r.snippet, r.metadata)
|
|
716
|
+
? enrichSnippetWithScope(r.snippet, r.metadata, r.filePath, r.startLine)
|
|
612
717
|
: undefined;
|
|
613
718
|
return {
|
|
614
719
|
file: `${r.filePath}:${r.startLine}-${r.endLine}`,
|
|
615
720
|
summary: r.summary,
|
|
616
721
|
score: Math.round(r.score * 100) / 100,
|
|
617
|
-
...(r.componentType &&
|
|
722
|
+
...(r.componentType &&
|
|
723
|
+
r.layer &&
|
|
724
|
+
r.layer !== 'unknown' && { type: `${r.componentType}:${r.layer}` }),
|
|
618
725
|
...(r.trend && r.trend !== 'Stable' && { trend: r.trend }),
|
|
619
726
|
...(r.patternWarning && { patternWarning: r.patternWarning }),
|
|
620
727
|
...(relationshipsAndHints.relationships && {
|