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.
Files changed (42) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +282 -292
  3. package/dist/analyzers/angular/index.d.ts +4 -0
  4. package/dist/analyzers/angular/index.d.ts.map +1 -1
  5. package/dist/analyzers/angular/index.js +44 -8
  6. package/dist/analyzers/angular/index.js.map +1 -1
  7. package/dist/cli.d.ts +6 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +113 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/core/indexer.d.ts +0 -5
  12. package/dist/core/indexer.d.ts.map +1 -1
  13. package/dist/core/indexer.js +13 -47
  14. package/dist/core/indexer.js.map +1 -1
  15. package/dist/core/reranker.js +2 -2
  16. package/dist/core/search.js +5 -5
  17. package/dist/core/search.js.map +1 -1
  18. package/dist/embeddings/transformers.js +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +257 -118
  21. package/dist/index.js.map +1 -1
  22. package/dist/memory/store.d.ts +3 -0
  23. package/dist/memory/store.d.ts.map +1 -1
  24. package/dist/memory/store.js +9 -0
  25. package/dist/memory/store.js.map +1 -1
  26. package/dist/patterns/semantics.d.ts +6 -0
  27. package/dist/patterns/semantics.d.ts.map +1 -1
  28. package/dist/patterns/semantics.js +22 -6
  29. package/dist/patterns/semantics.js.map +1 -1
  30. package/dist/preflight/evidence-lock.d.ts +2 -0
  31. package/dist/preflight/evidence-lock.d.ts.map +1 -1
  32. package/dist/preflight/evidence-lock.js +17 -3
  33. package/dist/preflight/evidence-lock.js.map +1 -1
  34. package/dist/types/index.d.ts +2 -0
  35. package/dist/types/index.d.ts.map +1 -1
  36. package/dist/utils/dependency-detection.d.ts +2 -1
  37. package/dist/utils/dependency-detection.d.ts.map +1 -1
  38. package/dist/utils/dependency-detection.js.map +1 -1
  39. package/dist/utils/usage-tracker.js +2 -2
  40. package/dist/utils/usage-tracker.js.map +1 -1
  41. package/docs/capabilities.md +92 -0
  42. 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 using natural language queries. Returns code summaries with file locations. ' +
136
- 'Supports framework-specific queries and architectural layer filtering. ' +
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: 'Search intent. Use "explore" (default) for read-only browsing. ' +
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') shows all 34 files using it.",
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: '📝 CALL IMMEDIATELY when user explicitly asks to remember/record something.\n\n' +
291
+ description: 'CALL IMMEDIATELY when user explicitly asks to remember/record something.\n\n' +
293
292
  'USER TRIGGERS:\n' +
294
- ' "Remember this: [X]"\n' +
295
- ' "Record this: [Y]"\n' +
296
- ' "Save this for next time: [Z]"\n\n' +
297
- '⚠️ DO NOT call unless user explicitly requests it.\n\n' +
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
- ' ONE convention per memory (if user lists 5 things, call this 5 times)\n' +
300
- ' memory: 5-10 words (the specific rule)\n' +
301
- ' reason: 1 sentence (why it matters)\n' +
302
- ' Skip: one-time features, code examples, essays',
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('⚠️ CRITICAL: This is what YOUR codebase actually uses, not generic recommendations.');
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}\` ${paths.join(', ')}`);
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(' Computed and effect are complementary Signals primitives and are commonly used together.');
433
- lines.push(' Treat this as balanced usage, not a hard split decision.');
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(` Your codebase is 100% consistent - ALWAYS use ${primary.name}`);
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(` Your team strongly prefers ${primary.name}`);
448
+ lines.push(` -> Your team strongly prefers ${primary.name}`);
450
449
  if (alternatives.length) {
451
450
  const alt = alternatives[0];
452
- lines.push(` Minority pattern: ${alt.name} (${alt.frequency}) - avoid for new code`);
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(` Most code uses ${primary.name}, but not unanimous`);
456
+ lines.push(` -> Most code uses ${primary.name}, but not unanimous`);
458
457
  if (alternatives.length) {
459
- lines.push(` Also detected: ${alternatives[0].name} (${alternatives[0].frequency})`);
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}: ⚠️ NO TEAM CONSENSUS`);
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(` ASK the team which approach to use for new features`);
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(query, limit || 5, filters, {
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(query, limit || 5, filters, {
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 = query.toLowerCase().split(/\s+/);
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
- if (intelligence.internalFileGraph?.imports) {
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
- let confidence = 'stale';
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
- ...(preflight && { preflight }),
957
- searchQuality,
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
- snippet: r.snippet,
963
- filePath: `${r.filePath}:${r.startLine}-${r.endLine}`,
964
- score: r.score,
965
- relevanceReason: r.relevanceReason,
966
- componentType: r.componentType,
967
- layer: r.layer,
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.slice(0, 5)
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
- for (const section of sections) {
1109
- const sectionLower = section.toLowerCase();
1110
- const isRelevant = queryTerms.some((term) => sectionLower.includes(term));
1111
- if (isRelevant) {
1112
- // Limit section size to ~500 words
1113
- const words = section.split(/\s+/);
1114
- const truncated = words.slice(0, 500).join(' ');
1115
- relevantSections.push('## ' + (words.length > 500 ? truncated + '...' : section.trim()));
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, 3) // Max 3 sections per file
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
- if (foundGuides.length === 0) {
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: `No style guide content found matching: ${query}`,
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: "Try broader terms like 'naming', 'patterns', 'testing', 'components'"
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
- results: foundGuides,
1159
- totalFiles: foundGuides.length
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
- main().catch((error) => {
1641
- console.error('Fatal:', error);
1642
- process.exit(1);
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