codebase-context 1.6.0 → 1.6.2
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/LICENSE +21 -21
- package/README.md +282 -292
- package/dist/analyzers/angular/index.d.ts +4 -0
- package/dist/analyzers/angular/index.d.ts.map +1 -1
- package/dist/analyzers/angular/index.js +44 -8
- package/dist/analyzers/angular/index.js.map +1 -1
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +113 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/indexer.d.ts +0 -5
- package/dist/core/indexer.d.ts.map +1 -1
- package/dist/core/indexer.js +13 -47
- package/dist/core/indexer.js.map +1 -1
- package/dist/core/reranker.js +2 -2
- package/dist/core/search.js +5 -5
- package/dist/core/search.js.map +1 -1
- package/dist/embeddings/transformers.js +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +257 -118
- package/dist/index.js.map +1 -1
- package/dist/memory/store.d.ts +3 -0
- package/dist/memory/store.d.ts.map +1 -1
- package/dist/memory/store.js +9 -0
- package/dist/memory/store.js.map +1 -1
- package/dist/patterns/semantics.d.ts +6 -0
- package/dist/patterns/semantics.d.ts.map +1 -1
- package/dist/patterns/semantics.js +22 -6
- package/dist/patterns/semantics.js.map +1 -1
- package/dist/preflight/evidence-lock.d.ts +2 -0
- package/dist/preflight/evidence-lock.d.ts.map +1 -1
- package/dist/preflight/evidence-lock.js +17 -3
- package/dist/preflight/evidence-lock.js.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/dependency-detection.d.ts +2 -1
- package/dist/utils/dependency-detection.d.ts.map +1 -1
- package/dist/utils/dependency-detection.js.map +1 -1
- package/dist/utils/usage-tracker.js +2 -2
- package/dist/utils/usage-tracker.js.map +1 -1
- package/docs/capabilities.md +92 -0
- package/package.json +157 -122
package/dist/index.js
CHANGED
|
@@ -16,10 +16,10 @@ import { analyzerRegistry } from './core/analyzer-registry.js';
|
|
|
16
16
|
import { AngularAnalyzer } from './analyzers/angular/index.js';
|
|
17
17
|
import { GenericAnalyzer } from './analyzers/generic/index.js';
|
|
18
18
|
import { InternalFileGraph } from './utils/usage-tracker.js';
|
|
19
|
-
import { getFileCommitDates } from './utils/git-dates.js';
|
|
20
19
|
import { IndexCorruptedError } from './errors/index.js';
|
|
21
20
|
import { CODEBASE_CONTEXT_DIRNAME, MEMORY_FILENAME, INTELLIGENCE_FILENAME, KEYWORD_INDEX_FILENAME, VECTOR_DB_DIRNAME } from './constants/codebase-context.js';
|
|
22
21
|
import { appendMemoryFile, readMemoriesFile, filterMemories, applyUnfilteredLimit, withConfidence } from './memory/store.js';
|
|
22
|
+
import { handleMemoryCli } from './cli.js';
|
|
23
23
|
import { parseGitLogLineToMemory } from './memory/git-memory.js';
|
|
24
24
|
import { buildEvidenceLock } from './preflight/evidence-lock.js';
|
|
25
25
|
import { shouldIncludePatternConflictCategory } from './preflight/query-scope.js';
|
|
@@ -132,11 +132,8 @@ const server = new Server({
|
|
|
132
132
|
const TOOLS = [
|
|
133
133
|
{
|
|
134
134
|
name: 'search_codebase',
|
|
135
|
-
description: 'Search the indexed codebase
|
|
136
|
-
'
|
|
137
|
-
'When intent is "edit", "refactor", or "migrate", returns a preflight card with risk level, ' +
|
|
138
|
-
'patterns to use/avoid, impact candidates, related memories, and an evidence lock score — all in one call. ' +
|
|
139
|
-
'Use the returned filePath with other tools to read complete file contents.',
|
|
135
|
+
description: 'Search the indexed codebase. Returns ranked results and a searchQuality confidence summary. ' +
|
|
136
|
+
'IMPORTANT: Pass the intent="edit"|"refactor"|"migrate" to get preflight: edit readiness check with evidence gating.',
|
|
140
137
|
inputSchema: {
|
|
141
138
|
type: 'object',
|
|
142
139
|
properties: {
|
|
@@ -147,15 +144,18 @@ const TOOLS = [
|
|
|
147
144
|
intent: {
|
|
148
145
|
type: 'string',
|
|
149
146
|
enum: ['explore', 'edit', 'refactor', 'migrate'],
|
|
150
|
-
description: '
|
|
151
|
-
'Use "edit", "refactor", or "migrate" to get a preflight card with risk assessment, ' +
|
|
152
|
-
'patterns to prefer/avoid, affected files, relevant team memories, and ready-to-edit evidence checks.'
|
|
147
|
+
description: 'Optional. Use "edit", "refactor", or "migrate" to get the full preflight card before making changes.'
|
|
153
148
|
},
|
|
154
149
|
limit: {
|
|
155
150
|
type: 'number',
|
|
156
151
|
description: 'Maximum number of results to return (default: 5)',
|
|
157
152
|
default: 5
|
|
158
153
|
},
|
|
154
|
+
includeSnippets: {
|
|
155
|
+
type: 'boolean',
|
|
156
|
+
description: 'Include code snippets in results (default: false). If you need code, prefer read_file instead.',
|
|
157
|
+
default: false
|
|
158
|
+
},
|
|
159
159
|
filters: {
|
|
160
160
|
type: 'object',
|
|
161
161
|
description: 'Optional filters',
|
|
@@ -237,8 +237,7 @@ const TOOLS = [
|
|
|
237
237
|
type: 'string',
|
|
238
238
|
description: 'Filter by category (naming, structure, patterns, testing)'
|
|
239
239
|
}
|
|
240
|
-
}
|
|
241
|
-
required: ['query']
|
|
240
|
+
}
|
|
242
241
|
}
|
|
243
242
|
},
|
|
244
243
|
{
|
|
@@ -260,7 +259,7 @@ const TOOLS = [
|
|
|
260
259
|
name: 'get_component_usage',
|
|
261
260
|
description: 'Find WHERE a library or component is used in the codebase. ' +
|
|
262
261
|
"This is 'Find Usages' - returns all files that import a given package/module. " +
|
|
263
|
-
"Example: get_component_usage('@mycompany/utils')
|
|
262
|
+
"Example: get_component_usage('@mycompany/utils') -> shows all files using it.",
|
|
264
263
|
inputSchema: {
|
|
265
264
|
type: 'object',
|
|
266
265
|
properties: {
|
|
@@ -289,24 +288,24 @@ const TOOLS = [
|
|
|
289
288
|
},
|
|
290
289
|
{
|
|
291
290
|
name: 'remember',
|
|
292
|
-
description: '
|
|
291
|
+
description: 'CALL IMMEDIATELY when user explicitly asks to remember/record something.\n\n' +
|
|
293
292
|
'USER TRIGGERS:\n' +
|
|
294
|
-
'
|
|
295
|
-
'
|
|
296
|
-
'
|
|
297
|
-
'
|
|
293
|
+
'- "Remember this: [X]"\n' +
|
|
294
|
+
'- "Record this: [Y]"\n' +
|
|
295
|
+
'- "Save this for next time: [Z]"\n\n' +
|
|
296
|
+
'DO NOT call unless user explicitly requests it.\n\n' +
|
|
298
297
|
'HOW TO WRITE:\n' +
|
|
299
|
-
'
|
|
300
|
-
'
|
|
301
|
-
'
|
|
302
|
-
'
|
|
298
|
+
'- ONE convention per memory (if user lists 5 things, call this 5 times)\n' +
|
|
299
|
+
'- memory: 5-10 words (the specific rule)\n' +
|
|
300
|
+
'- reason: 1 sentence (why it matters)\n' +
|
|
301
|
+
'- Skip: one-time features, code examples, essays',
|
|
303
302
|
inputSchema: {
|
|
304
303
|
type: 'object',
|
|
305
304
|
properties: {
|
|
306
305
|
type: {
|
|
307
306
|
type: 'string',
|
|
308
307
|
enum: ['convention', 'decision', 'gotcha', 'failure'],
|
|
309
|
-
description: 'Type of memory being recorded. Use "failure" for things that were tried and failed
|
|
308
|
+
description: 'Type of memory being recorded. Use "failure" for things that were tried and failed - ' +
|
|
310
309
|
'prevents repeating the same mistakes.'
|
|
311
310
|
},
|
|
312
311
|
category: {
|
|
@@ -376,7 +375,7 @@ async function generateCodebaseContext() {
|
|
|
376
375
|
const lines = [];
|
|
377
376
|
lines.push('# Codebase Intelligence');
|
|
378
377
|
lines.push('');
|
|
379
|
-
lines.push('
|
|
378
|
+
lines.push('WARNING: This is what YOUR codebase actually uses, not generic recommendations.');
|
|
380
379
|
lines.push('These are FACTS from analyzing your code, not best practices from the internet.');
|
|
381
380
|
lines.push('');
|
|
382
381
|
// Library usage - sorted by count
|
|
@@ -400,7 +399,7 @@ async function generateCodebaseContext() {
|
|
|
400
399
|
lines.push('');
|
|
401
400
|
lines.push('These path aliases map to internal project code:');
|
|
402
401
|
for (const [alias, paths] of Object.entries(intelligence.tsconfigPaths)) {
|
|
403
|
-
lines.push(`- \`${alias}\`
|
|
402
|
+
lines.push(`- \`${alias}\` -> ${paths.join(', ')}`);
|
|
404
403
|
}
|
|
405
404
|
lines.push('');
|
|
406
405
|
}
|
|
@@ -429,8 +428,8 @@ async function generateCodebaseContext() {
|
|
|
429
428
|
.trim()
|
|
430
429
|
.replace(/^./, (str) => str.toUpperCase());
|
|
431
430
|
lines.push(`### ${categoryName}: **${primary.name}** (${primary.frequency}) + **${secondary.name}** (${secondary.frequency})`);
|
|
432
|
-
lines.push('
|
|
433
|
-
lines.push('
|
|
431
|
+
lines.push(' -> Computed and effect are complementary Signals primitives and are commonly used together.');
|
|
432
|
+
lines.push(' -> Treat this as balanced usage, not a hard split decision.');
|
|
434
433
|
lines.push('');
|
|
435
434
|
continue;
|
|
436
435
|
}
|
|
@@ -442,26 +441,26 @@ async function generateCodebaseContext() {
|
|
|
442
441
|
.replace(/^./, (str) => str.toUpperCase());
|
|
443
442
|
if (percentage === 100) {
|
|
444
443
|
lines.push(`### ${categoryName}: **${primary.name}** (${primary.frequency} - unanimous)`);
|
|
445
|
-
lines.push(`
|
|
444
|
+
lines.push(` -> Your codebase is 100% consistent - ALWAYS use ${primary.name}`);
|
|
446
445
|
}
|
|
447
446
|
else if (percentage >= 80) {
|
|
448
447
|
lines.push(`### ${categoryName}: **${primary.name}** (${primary.frequency} - strong consensus)`);
|
|
449
|
-
lines.push(`
|
|
448
|
+
lines.push(` -> Your team strongly prefers ${primary.name}`);
|
|
450
449
|
if (alternatives.length) {
|
|
451
450
|
const alt = alternatives[0];
|
|
452
|
-
lines.push(`
|
|
451
|
+
lines.push(` -> Minority pattern: ${alt.name} (${alt.frequency}) - avoid for new code`);
|
|
453
452
|
}
|
|
454
453
|
}
|
|
455
454
|
else if (percentage >= 60) {
|
|
456
455
|
lines.push(`### ${categoryName}: **${primary.name}** (${primary.frequency} - majority)`);
|
|
457
|
-
lines.push(`
|
|
456
|
+
lines.push(` -> Most code uses ${primary.name}, but not unanimous`);
|
|
458
457
|
if (alternatives.length) {
|
|
459
|
-
lines.push(`
|
|
458
|
+
lines.push(` -> Also detected: ${alternatives[0].name} (${alternatives[0].frequency})`);
|
|
460
459
|
}
|
|
461
460
|
}
|
|
462
461
|
else {
|
|
463
462
|
// Split decision
|
|
464
|
-
lines.push(`### ${categoryName}:
|
|
463
|
+
lines.push(`### ${categoryName}: WARNING: NO TEAM CONSENSUS`);
|
|
465
464
|
lines.push(` Your codebase is split between multiple approaches:`);
|
|
466
465
|
lines.push(` - ${primary.name} (${primary.frequency})`);
|
|
467
466
|
if (alternatives.length) {
|
|
@@ -469,7 +468,7 @@ async function generateCodebaseContext() {
|
|
|
469
468
|
lines.push(` - ${alt.name} (${alt.frequency})`);
|
|
470
469
|
}
|
|
471
470
|
}
|
|
472
|
-
lines.push(`
|
|
471
|
+
lines.push(` -> ASK the team which approach to use for new features`);
|
|
473
472
|
}
|
|
474
473
|
lines.push('');
|
|
475
474
|
}
|
|
@@ -593,7 +592,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
593
592
|
try {
|
|
594
593
|
switch (name) {
|
|
595
594
|
case 'search_codebase': {
|
|
596
|
-
const { query, limit, filters, intent } = args;
|
|
595
|
+
const { query, limit, filters, intent, includeSnippets } = args;
|
|
596
|
+
const queryStr = typeof query === 'string' ? query.trim() : '';
|
|
597
|
+
if (!queryStr) {
|
|
598
|
+
return {
|
|
599
|
+
content: [
|
|
600
|
+
{
|
|
601
|
+
type: 'text',
|
|
602
|
+
text: JSON.stringify({
|
|
603
|
+
status: 'error',
|
|
604
|
+
errorCode: 'invalid_params',
|
|
605
|
+
message: "Invalid params: 'query' is required and must be a non-empty string.",
|
|
606
|
+
hint: "Provide a query like 'how are routes configured' or 'AlbumApiService'."
|
|
607
|
+
}, null, 2)
|
|
608
|
+
}
|
|
609
|
+
],
|
|
610
|
+
isError: true
|
|
611
|
+
};
|
|
612
|
+
}
|
|
597
613
|
if (indexState.status === 'indexing') {
|
|
598
614
|
return {
|
|
599
615
|
content: [
|
|
@@ -627,7 +643,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
627
643
|
? intent
|
|
628
644
|
: 'explore';
|
|
629
645
|
try {
|
|
630
|
-
results = await searcher.search(
|
|
646
|
+
results = await searcher.search(queryStr, limit || 5, filters, {
|
|
631
647
|
profile: searchProfile
|
|
632
648
|
});
|
|
633
649
|
}
|
|
@@ -639,7 +655,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
639
655
|
console.error('[Auto-Heal] Success. Retrying search...');
|
|
640
656
|
const freshSearcher = new CodebaseSearcher(ROOT_PATH);
|
|
641
657
|
try {
|
|
642
|
-
results = await freshSearcher.search(
|
|
658
|
+
results = await freshSearcher.search(queryStr, limit || 5, filters, {
|
|
643
659
|
profile: searchProfile
|
|
644
660
|
});
|
|
645
661
|
}
|
|
@@ -679,7 +695,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
679
695
|
// Load memories for keyword matching, enriched with confidence
|
|
680
696
|
const allMemories = await readMemoriesFile(PATHS.memory);
|
|
681
697
|
const allMemoriesWithConf = withConfidence(allMemories);
|
|
682
|
-
const queryTerms =
|
|
698
|
+
const queryTerms = queryStr.toLowerCase().split(/\s+/).filter(Boolean);
|
|
683
699
|
const relatedMemories = allMemoriesWithConf
|
|
684
700
|
.filter((m) => {
|
|
685
701
|
const searchText = `${m.memory} ${m.reason}`.toLowerCase();
|
|
@@ -695,6 +711,35 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
695
711
|
catch {
|
|
696
712
|
/* graceful degradation — intelligence file may not exist yet */
|
|
697
713
|
}
|
|
714
|
+
function computeIndexConfidence() {
|
|
715
|
+
let confidence = 'stale';
|
|
716
|
+
if (intelligence?.generatedAt) {
|
|
717
|
+
const indexAge = Date.now() - new Date(intelligence.generatedAt).getTime();
|
|
718
|
+
const hoursOld = indexAge / (1000 * 60 * 60);
|
|
719
|
+
if (hoursOld < 24) {
|
|
720
|
+
confidence = 'fresh';
|
|
721
|
+
}
|
|
722
|
+
else if (hoursOld < 168) {
|
|
723
|
+
confidence = 'aging';
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
return confidence;
|
|
727
|
+
}
|
|
728
|
+
// Cheap impact breadth estimate from the import graph (used for risk assessment).
|
|
729
|
+
function computeImpactCandidates(resultPaths) {
|
|
730
|
+
const impactCandidates = [];
|
|
731
|
+
if (!intelligence?.internalFileGraph?.imports)
|
|
732
|
+
return impactCandidates;
|
|
733
|
+
const allImports = intelligence.internalFileGraph.imports;
|
|
734
|
+
for (const [file, deps] of Object.entries(allImports)) {
|
|
735
|
+
if (deps.some((dep) => resultPaths.some((rp) => dep.endsWith(rp) || rp.endsWith(dep)))) {
|
|
736
|
+
if (!resultPaths.some((rp) => file.endsWith(rp) || rp.endsWith(file))) {
|
|
737
|
+
impactCandidates.push(file);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
return impactCandidates;
|
|
742
|
+
}
|
|
698
743
|
// Build reverse import map from intelligence graph
|
|
699
744
|
const reverseImports = new Map();
|
|
700
745
|
if (intelligence?.internalFileGraph?.imports) {
|
|
@@ -706,14 +751,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
706
751
|
}
|
|
707
752
|
}
|
|
708
753
|
}
|
|
709
|
-
// Load git dates for lastModified enrichment
|
|
710
|
-
let gitDates = null;
|
|
711
|
-
try {
|
|
712
|
-
gitDates = await getFileCommitDates(ROOT_PATH);
|
|
713
|
-
}
|
|
714
|
-
catch {
|
|
715
|
-
/* not a git repo */
|
|
716
|
-
}
|
|
717
754
|
// Enrich a search result with relationship data
|
|
718
755
|
function enrichResult(r) {
|
|
719
756
|
const rPath = r.filePath;
|
|
@@ -745,31 +782,62 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
745
782
|
}
|
|
746
783
|
}
|
|
747
784
|
}
|
|
748
|
-
// lastModified: from git dates
|
|
749
|
-
let lastModified;
|
|
750
|
-
if (gitDates) {
|
|
751
|
-
// Try matching by relative path (git dates use repo-relative forward-slash paths)
|
|
752
|
-
const relPath = path.relative(ROOT_PATH, rPath).replace(/\\/g, '/');
|
|
753
|
-
const date = gitDates.get(relPath);
|
|
754
|
-
if (date) {
|
|
755
|
-
lastModified = date.toISOString();
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
785
|
// Only return if we have at least one piece of data
|
|
759
|
-
if (importedBy.length === 0 &&
|
|
760
|
-
imports.length === 0 &&
|
|
761
|
-
testedIn.length === 0 &&
|
|
762
|
-
!lastModified) {
|
|
786
|
+
if (importedBy.length === 0 && imports.length === 0 && testedIn.length === 0) {
|
|
763
787
|
return undefined;
|
|
764
788
|
}
|
|
765
789
|
return {
|
|
766
790
|
...(importedBy.length > 0 && { importedBy }),
|
|
767
791
|
...(imports.length > 0 && { imports }),
|
|
768
|
-
...(testedIn.length > 0 && { testedIn })
|
|
769
|
-
...(lastModified && { lastModified })
|
|
792
|
+
...(testedIn.length > 0 && { testedIn })
|
|
770
793
|
};
|
|
771
794
|
}
|
|
772
795
|
const searchQuality = assessSearchQuality(query, results);
|
|
796
|
+
// Always-on edit preflight (lite): do not require intent and keep payload small.
|
|
797
|
+
let editPreflight = undefined;
|
|
798
|
+
if (intelligence && (!intent || intent === 'explore')) {
|
|
799
|
+
try {
|
|
800
|
+
const resultPaths = results.map((r) => r.filePath);
|
|
801
|
+
const impactCandidates = computeImpactCandidates(resultPaths);
|
|
802
|
+
let riskLevel = 'low';
|
|
803
|
+
if (impactCandidates.length > 10) {
|
|
804
|
+
riskLevel = 'high';
|
|
805
|
+
}
|
|
806
|
+
else if (impactCandidates.length > 3) {
|
|
807
|
+
riskLevel = 'medium';
|
|
808
|
+
}
|
|
809
|
+
// Use existing pattern intelligence for evidenceLock scoring, but keep the output payload lite.
|
|
810
|
+
const preferredPatternsForEvidence = [];
|
|
811
|
+
const patterns = intelligence.patterns || {};
|
|
812
|
+
for (const [_, data] of Object.entries(patterns)) {
|
|
813
|
+
if (data.primary) {
|
|
814
|
+
const p = data.primary;
|
|
815
|
+
if (p.trend === 'Rising' || p.trend === 'Stable') {
|
|
816
|
+
preferredPatternsForEvidence.push({
|
|
817
|
+
pattern: p.name,
|
|
818
|
+
...(p.canonicalExample && { example: p.canonicalExample.file })
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
editPreflight = {
|
|
824
|
+
mode: 'lite',
|
|
825
|
+
riskLevel,
|
|
826
|
+
confidence: computeIndexConfidence(),
|
|
827
|
+
evidenceLock: buildEvidenceLock({
|
|
828
|
+
results,
|
|
829
|
+
preferredPatterns: preferredPatternsForEvidence.slice(0, 5),
|
|
830
|
+
relatedMemories,
|
|
831
|
+
failureWarnings: [],
|
|
832
|
+
patternConflicts: [],
|
|
833
|
+
searchQualityStatus: searchQuality.status
|
|
834
|
+
})
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
catch {
|
|
838
|
+
// editPreflight is best-effort - never fail search over it
|
|
839
|
+
}
|
|
840
|
+
}
|
|
773
841
|
// Compose preflight card for edit/refactor/migrate intents
|
|
774
842
|
let preflight = undefined;
|
|
775
843
|
const preflightIntents = ['edit', 'refactor', 'migrate'];
|
|
@@ -810,18 +878,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
810
878
|
}
|
|
811
879
|
}
|
|
812
880
|
// --- Impact candidates (files importing the result files) ---
|
|
813
|
-
const impactCandidates = [];
|
|
814
881
|
const resultPaths = results.map((r) => r.filePath);
|
|
815
|
-
|
|
816
|
-
const allImports = intelligence.internalFileGraph.imports;
|
|
817
|
-
for (const [file, deps] of Object.entries(allImports)) {
|
|
818
|
-
if (deps.some((dep) => resultPaths.some((rp) => dep.endsWith(rp) || rp.endsWith(dep)))) {
|
|
819
|
-
if (!resultPaths.some((rp) => file.endsWith(rp) || rp.endsWith(file))) {
|
|
820
|
-
impactCandidates.push(file);
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
}
|
|
882
|
+
const impactCandidates = computeImpactCandidates(resultPaths);
|
|
825
883
|
// --- Risk level (based on circular deps + impact breadth) ---
|
|
826
884
|
let riskLevel = 'low';
|
|
827
885
|
let cycleCount = 0;
|
|
@@ -855,17 +913,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
855
913
|
score: g.score
|
|
856
914
|
}));
|
|
857
915
|
// --- Confidence (index freshness) ---
|
|
858
|
-
|
|
859
|
-
if (intelligence.generatedAt) {
|
|
860
|
-
const indexAge = Date.now() - new Date(intelligence.generatedAt).getTime();
|
|
861
|
-
const hoursOld = indexAge / (1000 * 60 * 60);
|
|
862
|
-
if (hoursOld < 24) {
|
|
863
|
-
confidence = 'fresh';
|
|
864
|
-
}
|
|
865
|
-
else if (hoursOld < 168) {
|
|
866
|
-
confidence = 'aging';
|
|
867
|
-
}
|
|
868
|
-
}
|
|
916
|
+
const confidence = computeIndexConfidence();
|
|
869
917
|
// --- Failure memories (1.5x relevance boost) ---
|
|
870
918
|
const failureWarnings = relatedMemories
|
|
871
919
|
.filter((m) => m.type === 'failure' && !m.stale)
|
|
@@ -910,7 +958,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
910
958
|
preferredPatterns: preferredPatternsForOutput,
|
|
911
959
|
relatedMemories,
|
|
912
960
|
failureWarnings,
|
|
913
|
-
patternConflicts
|
|
961
|
+
patternConflicts,
|
|
962
|
+
searchQualityStatus: searchQuality.status
|
|
914
963
|
});
|
|
915
964
|
// Bump risk if there are active failure memories for this area
|
|
916
965
|
if (failureWarnings.length > 0 && riskLevel === 'low') {
|
|
@@ -947,33 +996,70 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
947
996
|
// Preflight construction failed — skip preflight, don't fail the search
|
|
948
997
|
}
|
|
949
998
|
}
|
|
999
|
+
// For edit/refactor/migrate: return full preflight card (risk, patterns, impact, etc.).
|
|
1000
|
+
// For explore or lite-only: return flattened { ready, reason }.
|
|
1001
|
+
let preflightPayload;
|
|
1002
|
+
if (preflight) {
|
|
1003
|
+
const el = preflight.evidenceLock;
|
|
1004
|
+
// Full card per tool schema; add top-level ready/reason for backward compatibility
|
|
1005
|
+
preflightPayload = {
|
|
1006
|
+
...preflight,
|
|
1007
|
+
ready: el?.readyToEdit ?? false,
|
|
1008
|
+
...(el && !el.readyToEdit && el.nextAction && { reason: el.nextAction })
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
else if (editPreflight) {
|
|
1012
|
+
const el = editPreflight.evidenceLock;
|
|
1013
|
+
preflightPayload = {
|
|
1014
|
+
ready: el?.readyToEdit ?? false,
|
|
1015
|
+
...(el && !el.readyToEdit && el.nextAction && { reason: el.nextAction })
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
950
1018
|
return {
|
|
951
1019
|
content: [
|
|
952
1020
|
{
|
|
953
1021
|
type: 'text',
|
|
954
1022
|
text: JSON.stringify({
|
|
955
1023
|
status: 'success',
|
|
956
|
-
|
|
957
|
-
|
|
1024
|
+
searchQuality: {
|
|
1025
|
+
status: searchQuality.status,
|
|
1026
|
+
confidence: searchQuality.confidence,
|
|
1027
|
+
...(searchQuality.status === 'low_confidence' &&
|
|
1028
|
+
searchQuality.nextSteps?.[0] && {
|
|
1029
|
+
hint: searchQuality.nextSteps[0]
|
|
1030
|
+
})
|
|
1031
|
+
},
|
|
1032
|
+
...(preflightPayload && { preflight: preflightPayload }),
|
|
958
1033
|
results: results.map((r) => {
|
|
959
1034
|
const relationships = enrichResult(r);
|
|
1035
|
+
// Condensed relationships: importedBy count + hasTests flag
|
|
1036
|
+
const condensedRel = relationships
|
|
1037
|
+
? {
|
|
1038
|
+
...(relationships.importedBy &&
|
|
1039
|
+
relationships.importedBy.length > 0 && {
|
|
1040
|
+
importedByCount: relationships.importedBy.length
|
|
1041
|
+
}),
|
|
1042
|
+
...(relationships.testedIn &&
|
|
1043
|
+
relationships.testedIn.length > 0 && { hasTests: true })
|
|
1044
|
+
}
|
|
1045
|
+
: undefined;
|
|
1046
|
+
const hasCondensedRel = condensedRel && Object.keys(condensedRel).length > 0;
|
|
960
1047
|
return {
|
|
1048
|
+
file: `${r.filePath}:${r.startLine}-${r.endLine}`,
|
|
961
1049
|
summary: r.summary,
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
framework: r.framework,
|
|
969
|
-
trend: r.trend,
|
|
970
|
-
patternWarning: r.patternWarning,
|
|
971
|
-
...(relationships && { relationships })
|
|
1050
|
+
score: Math.round(r.score * 100) / 100,
|
|
1051
|
+
...(r.componentType && r.layer && { type: `${r.componentType}:${r.layer}` }),
|
|
1052
|
+
...(r.trend && r.trend !== 'Stable' && { trend: r.trend }),
|
|
1053
|
+
...(r.patternWarning && { patternWarning: r.patternWarning }),
|
|
1054
|
+
...(hasCondensedRel && { relationships: condensedRel }),
|
|
1055
|
+
...(includeSnippets && r.snippet && { snippet: r.snippet })
|
|
972
1056
|
};
|
|
973
1057
|
}),
|
|
974
1058
|
totalResults: results.length,
|
|
975
1059
|
...(relatedMemories.length > 0 && {
|
|
976
|
-
relatedMemories: relatedMemories
|
|
1060
|
+
relatedMemories: relatedMemories
|
|
1061
|
+
.slice(0, 3)
|
|
1062
|
+
.map((m) => `${m.memory} (${m.effectiveConfidence})`)
|
|
977
1063
|
})
|
|
978
1064
|
}, null, 2)
|
|
979
1065
|
}
|
|
@@ -1078,6 +1164,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1078
1164
|
}
|
|
1079
1165
|
case 'get_style_guide': {
|
|
1080
1166
|
const { query, category } = args;
|
|
1167
|
+
const queryStr = typeof query === 'string' ? query.trim() : '';
|
|
1168
|
+
const queryLower = queryStr.toLowerCase();
|
|
1169
|
+
const queryTerms = queryLower.split(/\s+/).filter(Boolean);
|
|
1170
|
+
const categoryLower = typeof category === 'string' ? category.trim().toLowerCase() : '';
|
|
1171
|
+
const limitedMode = queryTerms.length === 0;
|
|
1172
|
+
const LIMITED_MAX_FILES = 3;
|
|
1173
|
+
const LIMITED_MAX_SECTIONS_PER_FILE = 2;
|
|
1081
1174
|
const styleGuidePatterns = [
|
|
1082
1175
|
'STYLE_GUIDE.md',
|
|
1083
1176
|
'CODING_STYLE.md',
|
|
@@ -1088,8 +1181,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1088
1181
|
'docs/ARCHITECTURE.md'
|
|
1089
1182
|
];
|
|
1090
1183
|
const foundGuides = [];
|
|
1091
|
-
const queryLower = query.toLowerCase();
|
|
1092
|
-
const queryTerms = queryLower.split(/\s+/);
|
|
1093
1184
|
for (const pattern of styleGuidePatterns) {
|
|
1094
1185
|
try {
|
|
1095
1186
|
const files = await glob(pattern, {
|
|
@@ -1105,21 +1196,44 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1105
1196
|
// Find relevant sections based on query
|
|
1106
1197
|
const sections = content.split(/^##\s+/m);
|
|
1107
1198
|
const relevantSections = [];
|
|
1108
|
-
|
|
1109
|
-
const
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1199
|
+
if (limitedMode) {
|
|
1200
|
+
const headings = (content.match(/^##\s+.+$/gm) || [])
|
|
1201
|
+
.map((h) => h.trim())
|
|
1202
|
+
.filter(Boolean)
|
|
1203
|
+
.slice(0, LIMITED_MAX_SECTIONS_PER_FILE);
|
|
1204
|
+
if (headings.length > 0) {
|
|
1205
|
+
relevantSections.push(...headings);
|
|
1206
|
+
}
|
|
1207
|
+
else {
|
|
1208
|
+
const words = content.split(/\s+/).filter(Boolean);
|
|
1209
|
+
if (words.length > 0) {
|
|
1210
|
+
relevantSections.push(`Overview: ${words.slice(0, 80).join(' ')}...`);
|
|
1211
|
+
}
|
|
1116
1212
|
}
|
|
1117
1213
|
}
|
|
1214
|
+
else {
|
|
1215
|
+
for (const section of sections) {
|
|
1216
|
+
const sectionLower = section.toLowerCase();
|
|
1217
|
+
const isRelevant = queryTerms.some((term) => sectionLower.includes(term));
|
|
1218
|
+
if (isRelevant) {
|
|
1219
|
+
// Limit section size to ~500 words
|
|
1220
|
+
const words = section.split(/\s+/);
|
|
1221
|
+
const truncated = words.slice(0, 500).join(' ');
|
|
1222
|
+
relevantSections.push('## ' + (words.length > 500 ? truncated + '...' : section.trim()));
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
const categoryMatch = !categoryLower ||
|
|
1227
|
+
relativePath.toLowerCase().includes(categoryLower) ||
|
|
1228
|
+
relevantSections.some((section) => section.toLowerCase().includes(categoryLower));
|
|
1229
|
+
if (!categoryMatch) {
|
|
1230
|
+
continue;
|
|
1231
|
+
}
|
|
1118
1232
|
if (relevantSections.length > 0) {
|
|
1119
1233
|
foundGuides.push({
|
|
1120
1234
|
file: relativePath,
|
|
1121
1235
|
content: content.slice(0, 200) + '...',
|
|
1122
|
-
relevantSections: relevantSections.slice(0,
|
|
1236
|
+
relevantSections: relevantSections.slice(0, limitedMode ? LIMITED_MAX_SECTIONS_PER_FILE : 3)
|
|
1123
1237
|
});
|
|
1124
1238
|
}
|
|
1125
1239
|
}
|
|
@@ -1132,16 +1246,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1132
1246
|
// Pattern didn't match, continue
|
|
1133
1247
|
}
|
|
1134
1248
|
}
|
|
1135
|
-
|
|
1249
|
+
const results = limitedMode ? foundGuides.slice(0, LIMITED_MAX_FILES) : foundGuides;
|
|
1250
|
+
if (results.length === 0) {
|
|
1136
1251
|
return {
|
|
1137
1252
|
content: [
|
|
1138
1253
|
{
|
|
1139
1254
|
type: 'text',
|
|
1140
1255
|
text: JSON.stringify({
|
|
1141
1256
|
status: 'no_results',
|
|
1142
|
-
message:
|
|
1257
|
+
message: limitedMode
|
|
1258
|
+
? 'No style guide files found in the default locations.'
|
|
1259
|
+
: `No style guide content found matching: ${queryStr}`,
|
|
1143
1260
|
searchedPatterns: styleGuidePatterns,
|
|
1144
|
-
hint:
|
|
1261
|
+
hint: limitedMode
|
|
1262
|
+
? "Run get_style_guide with a query or category (e.g. category: 'testing') for targeted results."
|
|
1263
|
+
: "Try broader terms like 'naming', 'patterns', 'testing', 'components'"
|
|
1145
1264
|
}, null, 2)
|
|
1146
1265
|
}
|
|
1147
1266
|
]
|
|
@@ -1153,10 +1272,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1153
1272
|
type: 'text',
|
|
1154
1273
|
text: JSON.stringify({
|
|
1155
1274
|
status: 'success',
|
|
1156
|
-
query,
|
|
1275
|
+
query: queryStr || undefined,
|
|
1157
1276
|
category,
|
|
1158
|
-
|
|
1159
|
-
|
|
1277
|
+
limited: limitedMode,
|
|
1278
|
+
notice: limitedMode
|
|
1279
|
+
? 'No query provided. Results are capped. Provide query and/or category for targeted guidance.'
|
|
1280
|
+
: undefined,
|
|
1281
|
+
resultLimits: limitedMode
|
|
1282
|
+
? {
|
|
1283
|
+
maxFiles: LIMITED_MAX_FILES,
|
|
1284
|
+
maxSectionsPerFile: LIMITED_MAX_SECTIONS_PER_FILE
|
|
1285
|
+
}
|
|
1286
|
+
: undefined,
|
|
1287
|
+
results,
|
|
1288
|
+
totalFiles: results.length,
|
|
1289
|
+
totalMatches: foundGuides.length
|
|
1160
1290
|
}, null, 2)
|
|
1161
1291
|
}
|
|
1162
1292
|
]
|
|
@@ -1637,9 +1767,18 @@ export { server, performIndexing, resolveRootPath, shouldReindex, TOOLS };
|
|
|
1637
1767
|
const isDirectRun = process.argv[1]?.replace(/\\/g, '/').endsWith('index.js') ||
|
|
1638
1768
|
process.argv[1]?.replace(/\\/g, '/').endsWith('index.ts');
|
|
1639
1769
|
if (isDirectRun) {
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
process.
|
|
1643
|
-
|
|
1770
|
+
// CLI subcommand: memory list/add/remove
|
|
1771
|
+
if (process.argv[2] === 'memory') {
|
|
1772
|
+
handleMemoryCli(process.argv.slice(3)).catch((error) => {
|
|
1773
|
+
console.error('Error:', error instanceof Error ? error.message : String(error));
|
|
1774
|
+
process.exit(1);
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1777
|
+
else {
|
|
1778
|
+
main().catch((error) => {
|
|
1779
|
+
console.error('Fatal:', error);
|
|
1780
|
+
process.exit(1);
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1644
1783
|
}
|
|
1645
1784
|
//# sourceMappingURL=index.js.map
|