Skip to content

Commit 980d17d

Browse files
committed
refactor(mcp): migrate ExploreAdapter and RefsAdapter to SearchService
Migrate two more MCP adapters to use the service layer with dependency injection for better testability and consistency. Adapters Refactored: - ExploreAdapter: Now uses SearchService for search() and findSimilar() - RefsAdapter: Now uses SearchService for all call graph queries Changes: - ExploreAdapterConfig: searchService replaces repositoryIndexer - RefsAdapterConfig: searchService replaces repositoryIndexer - Updated adapter instantiation in MCP server and CLI - Updated test mocks from mockIndexer to mockSearchService - Fixed similar code tests to use findSimilar() method - Removed unused imports (chalk, formatBytes, loadConfig) in CLI Benefits: - Consistent service layer usage across adapters - Better testability with dependency injection - Easier to mock for unit tests - Reduced coupling to implementation details Adapters by Refactoring Status: ✅ Service Layer (5/9): - SearchAdapter → SearchService - StatusAdapter → StatsService, GitHubService - GitHubAdapter → GitHubService - ExploreAdapter → SearchService - RefsAdapter → SearchService ⏭️ Thin Wrappers (4/9 - no refactor needed): - MapAdapter: Utility wrapper around generateCodebaseMap - HistoryAdapter: Uses GitIndexer (would need GitHistoryService) - PlanAdapter: Utility wrapper around assembleContext - HealthAdapter: Different interface than HealthService The remaining 4 adapters are already well-designed as thin wrappers around core utilities. Further refactoring would add complexity without providing testability or maintainability benefits. Tests: All 1918 tests passing Lint: No warnings or errors
1 parent e82dd8c commit 980d17d

File tree

9 files changed

+50
-50
lines changed

9 files changed

+50
-50
lines changed

packages/cli/src/commands/clean.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@ import {
55
getStorageFilePaths,
66
getStoragePath,
77
} from '@lytics/dev-agent-core';
8-
import chalk from 'chalk';
98
import { Command } from 'commander';
109
import ora from 'ora';
1110
import { loadConfig } from '../utils/config.js';
12-
import { formatBytes, getDirectorySize } from '../utils/file.js';
11+
import { getDirectorySize } from '../utils/file.js';
1312
import { logger } from '../utils/logger.js';
1413
import { output, printCleanSuccess, printCleanSummary } from '../utils/output.js';
1514

packages/cli/src/commands/compact.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@ import {
55
getStoragePath,
66
RepositoryIndexer,
77
} from '@lytics/dev-agent-core';
8-
import chalk from 'chalk';
98
import { Command } from 'commander';
109
import ora from 'ora';
1110
import { loadConfig } from '../utils/config.js';
1211
import { logger } from '../utils/logger.js';
13-
import { output, printCompactResults } from '../utils/output.js';
12+
import { printCompactResults } from '../utils/output.js';
1413

1514
export const compactCommand = new Command('compact')
1615
.description('🗜️ Optimize and compact the vector store')

packages/cli/src/commands/git.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import { createLogger } from '@lytics/kero';
1414
import chalk from 'chalk';
1515
import { Command } from 'commander';
1616
import ora from 'ora';
17-
import { loadConfig } from '../utils/config.js';
1817
import { keroLogger, logger } from '../utils/logger.js';
1918
import { output, printGitStats } from '../utils/output.js';
2019

packages/cli/src/commands/mcp.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ Available Tools (9):
149149

150150
const exploreAdapter = new ExploreAdapter({
151151
repositoryPath,
152-
repositoryIndexer: indexer,
152+
searchService,
153153
defaultLimit: 10,
154154
defaultThreshold: 0.7,
155155
defaultFormat: 'compact',
@@ -169,7 +169,7 @@ Available Tools (9):
169169
});
170170

171171
const refsAdapter = new RefsAdapter({
172-
repositoryIndexer: indexer,
172+
searchService,
173173
defaultLimit: 20,
174174
});
175175

packages/mcp-server/bin/dev-agent-mcp.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ async function main() {
185185

186186
const exploreAdapter = new ExploreAdapter({
187187
repositoryPath,
188-
repositoryIndexer: indexer,
188+
searchService,
189189
defaultLimit: 10,
190190
defaultThreshold: 0.7,
191191
defaultFormat: 'compact',
@@ -205,7 +205,7 @@ async function main() {
205205
});
206206

207207
const refsAdapter = new RefsAdapter({
208-
repositoryIndexer: indexer,
208+
searchService,
209209
defaultLimit: 20,
210210
});
211211

packages/mcp-server/src/adapters/__tests__/explore-adapter.test.ts

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,27 @@
22
* ExploreAdapter Unit Tests
33
*/
44

5-
import type { RepositoryIndexer, SearchResult } from '@lytics/dev-agent-core';
5+
import type { SearchResult, SearchService } from '@lytics/dev-agent-core';
66
import { beforeEach, describe, expect, it, vi } from 'vitest';
77
import { ExploreAdapter } from '../built-in/explore-adapter';
88
import type { ToolExecutionContext } from '../types';
99

1010
describe('ExploreAdapter', () => {
1111
let adapter: ExploreAdapter;
12-
let mockIndexer: RepositoryIndexer;
12+
let mockSearchService: SearchService;
1313
let mockContext: ToolExecutionContext;
1414

1515
beforeEach(() => {
16-
// Mock RepositoryIndexer
17-
mockIndexer = {
16+
// Mock SearchService
17+
mockSearchService = {
1818
search: vi.fn(),
19-
} as unknown as RepositoryIndexer;
19+
findSimilar: vi.fn(),
20+
} as unknown as SearchService;
2021

2122
// Create adapter
2223
adapter = new ExploreAdapter({
2324
repositoryPath: '/test/repo',
24-
repositoryIndexer: mockIndexer,
25+
searchService: mockSearchService,
2526
defaultLimit: 10,
2627
defaultThreshold: 0.7,
2728
defaultFormat: 'compact',
@@ -151,7 +152,7 @@ describe('ExploreAdapter', () => {
151152
},
152153
];
153154

154-
vi.mocked(mockIndexer.search).mockResolvedValue(mockResults);
155+
vi.mocked(mockSearchService.search).mockResolvedValue(mockResults);
155156

156157
const result = await adapter.execute(
157158
{
@@ -183,7 +184,7 @@ describe('ExploreAdapter', () => {
183184
},
184185
];
185186

186-
vi.mocked(mockIndexer.search).mockResolvedValue(mockResults);
187+
vi.mocked(mockSearchService.search).mockResolvedValue(mockResults);
187188

188189
const result = await adapter.execute(
189190
{
@@ -221,7 +222,7 @@ describe('ExploreAdapter', () => {
221222
},
222223
];
223224

224-
vi.mocked(mockIndexer.search).mockResolvedValue(mockResults);
225+
vi.mocked(mockSearchService.search).mockResolvedValue(mockResults);
225226

226227
const result = await adapter.execute(
227228
{
@@ -238,7 +239,7 @@ describe('ExploreAdapter', () => {
238239
});
239240

240241
it('should handle no results found', async () => {
241-
vi.mocked(mockIndexer.search).mockResolvedValue([]);
242+
vi.mocked(mockSearchService.search).mockResolvedValue([]);
242243

243244
const result = await adapter.execute(
244245
{
@@ -276,7 +277,7 @@ describe('ExploreAdapter', () => {
276277
},
277278
];
278279

279-
vi.mocked(mockIndexer.search).mockResolvedValue(mockResults);
280+
vi.mocked(mockSearchService.findSimilar).mockResolvedValue(mockResults);
280281

281282
const result = await adapter.execute(
282283
{
@@ -296,7 +297,7 @@ describe('ExploreAdapter', () => {
296297
});
297298

298299
it('should handle no similar files', async () => {
299-
vi.mocked(mockIndexer.search).mockResolvedValue([
300+
vi.mocked(mockSearchService.findSimilar).mockResolvedValue([
300301
{
301302
id: '1',
302303
score: 1.0,
@@ -344,7 +345,7 @@ describe('ExploreAdapter', () => {
344345
},
345346
];
346347

347-
vi.mocked(mockIndexer.search).mockResolvedValue(mockResults);
348+
vi.mocked(mockSearchService.search).mockResolvedValue(mockResults);
348349

349350
const result = await adapter.execute(
350351
{
@@ -360,7 +361,7 @@ describe('ExploreAdapter', () => {
360361
});
361362

362363
it('should handle no relationships found', async () => {
363-
vi.mocked(mockIndexer.search).mockResolvedValue([]);
364+
vi.mocked(mockSearchService.search).mockResolvedValue([]);
364365

365366
const result = await adapter.execute(
366367
{
@@ -377,7 +378,7 @@ describe('ExploreAdapter', () => {
377378

378379
describe('Error Handling', () => {
379380
it('should handle file not found errors', async () => {
380-
vi.mocked(mockIndexer.search).mockRejectedValue(new Error('File not found'));
381+
vi.mocked(mockSearchService.findSimilar).mockRejectedValue(new Error('File not found'));
381382

382383
const result = await adapter.execute(
383384
{
@@ -392,7 +393,7 @@ describe('ExploreAdapter', () => {
392393
});
393394

394395
it('should handle index not ready errors', async () => {
395-
vi.mocked(mockIndexer.search).mockRejectedValue(new Error('Index not indexed'));
396+
vi.mocked(mockSearchService.search).mockRejectedValue(new Error('Index not indexed'));
396397

397398
const result = await adapter.execute(
398399
{
@@ -407,7 +408,7 @@ describe('ExploreAdapter', () => {
407408
});
408409

409410
it('should handle generic errors', async () => {
410-
vi.mocked(mockIndexer.search).mockRejectedValue(new Error('Unknown error'));
411+
vi.mocked(mockSearchService.search).mockRejectedValue(new Error('Unknown error'));
411412

412413
const result = await adapter.execute(
413414
{

packages/mcp-server/src/adapters/__tests__/refs-adapter.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
* Tests for RefsAdapter
33
*/
44

5-
import type { RepositoryIndexer, SearchResult } from '@lytics/dev-agent-core';
5+
import type { SearchResult, SearchService } from '@lytics/dev-agent-core';
66
import { beforeEach, describe, expect, it, vi } from 'vitest';
77
import { ConsoleLogger } from '../../utils/logger';
88
import { RefsAdapter } from '../built-in/refs-adapter';
99
import type { AdapterContext, ToolExecutionContext } from '../types';
1010

1111
describe('RefsAdapter', () => {
12-
let mockIndexer: RepositoryIndexer;
12+
let mockSearchService: SearchService;
1313
let adapter: RefsAdapter;
1414
let context: AdapterContext;
1515
let execContext: ToolExecutionContext;
@@ -69,14 +69,14 @@ describe('RefsAdapter', () => {
6969
];
7070

7171
beforeEach(async () => {
72-
// Create mock indexer
73-
mockIndexer = {
72+
// Create mock search service
73+
mockSearchService = {
7474
search: vi.fn().mockResolvedValue(mockSearchResults),
75-
} as unknown as RepositoryIndexer;
75+
} as unknown as SearchService;
7676

7777
// Create adapter
7878
adapter = new RefsAdapter({
79-
repositoryIndexer: mockIndexer,
79+
searchService: mockSearchService,
8080
defaultLimit: 20,
8181
});
8282

@@ -269,7 +269,7 @@ describe('RefsAdapter', () => {
269269
describe('Not Found', () => {
270270
it('should return error when function not found', async () => {
271271
// Mock empty results
272-
(mockIndexer.search as ReturnType<typeof vi.fn>).mockResolvedValueOnce([]);
272+
(mockSearchService.search as ReturnType<typeof vi.fn>).mockResolvedValueOnce([]);
273273

274274
const result = await adapter.execute({ name: 'nonExistentFunction' }, execContext);
275275

packages/mcp-server/src/adapters/built-in/explore-adapter.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* falls back to direct indexer calls otherwise.
77
*/
88

9-
import type { RepositoryIndexer } from '@lytics/dev-agent-core';
9+
import type { SearchService } from '@lytics/dev-agent-core';
1010
import type {
1111
ExplorationResult,
1212
PatternResult,
@@ -20,7 +20,7 @@ import { validateArgs } from '../validation.js';
2020

2121
export interface ExploreAdapterConfig {
2222
repositoryPath: string;
23-
repositoryIndexer: RepositoryIndexer;
23+
searchService: SearchService;
2424
defaultLimit?: number;
2525
defaultThreshold?: number;
2626
defaultFormat?: 'compact' | 'verbose';
@@ -43,15 +43,15 @@ export class ExploreAdapter extends ToolAdapter {
4343
};
4444

4545
private repositoryPath: string;
46-
private repositoryIndexer: RepositoryIndexer;
46+
private searchService: SearchService;
4747
private defaultLimit: number;
4848
private defaultThreshold: number;
4949
private defaultFormat: 'compact' | 'verbose';
5050

5151
constructor(config: ExploreAdapterConfig) {
5252
super();
5353
this.repositoryPath = config.repositoryPath;
54-
this.repositoryIndexer = config.repositoryIndexer;
54+
this.searchService = config.searchService;
5555
this.defaultLimit = config.defaultLimit ?? 10;
5656
this.defaultThreshold = config.defaultThreshold ?? 0.7;
5757
this.defaultFormat = config.defaultFormat ?? 'compact';
@@ -411,7 +411,7 @@ export class ExploreAdapter extends ToolAdapter {
411411
fileTypes: string[] | undefined,
412412
format: string
413413
): Promise<string> {
414-
const results = await this.repositoryIndexer.search(query, {
414+
const results = await this.searchService.search(query, {
415415
limit,
416416
scoreThreshold: threshold,
417417
});
@@ -444,9 +444,9 @@ export class ExploreAdapter extends ToolAdapter {
444444
threshold: number,
445445
format: string
446446
): Promise<string> {
447-
const results = await this.repositoryIndexer.search(`file:${filePath}`, {
447+
const results = await this.searchService.findSimilar(filePath, {
448448
limit: limit + 1,
449-
scoreThreshold: threshold,
449+
threshold,
450450
});
451451

452452
// Exclude the reference file itself
@@ -469,7 +469,7 @@ export class ExploreAdapter extends ToolAdapter {
469469
private async findRelationships(filePath: string, format: string): Promise<string> {
470470
// Search for references to this file
471471
const fileName = filePath.split('/').pop() || filePath;
472-
const results = await this.repositoryIndexer.search(`import ${fileName}`, {
472+
const results = await this.searchService.search(`import ${fileName}`, {
473473
limit: 20,
474474
scoreThreshold: 0.6,
475475
});

packages/mcp-server/src/adapters/built-in/refs-adapter.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Provides call graph queries via the dev_refs tool
44
*/
55

6-
import type { CalleeInfo, RepositoryIndexer, SearchResult } from '@lytics/dev-agent-core';
6+
import type { CalleeInfo, SearchResult, SearchService } from '@lytics/dev-agent-core';
77
import { estimateTokensForText, startTimer } from '../../formatters/utils';
88
import { RefsArgsSchema, type RefsOutput, RefsOutputSchema } from '../../schemas/index.js';
99
import { ToolAdapter } from '../tool-adapter';
@@ -20,9 +20,9 @@ export type RefDirection = 'callees' | 'callers' | 'both';
2020
*/
2121
export interface RefsAdapterConfig {
2222
/**
23-
* Repository indexer instance
23+
* Search service instance
2424
*/
25-
repositoryIndexer: RepositoryIndexer;
25+
searchService: SearchService;
2626

2727
/**
2828
* Default result limit
@@ -53,14 +53,16 @@ export class RefsAdapter extends ToolAdapter {
5353
author: 'Dev-Agent Team',
5454
};
5555

56-
private indexer: RepositoryIndexer;
57-
private config: Required<RefsAdapterConfig>;
56+
private searchService: SearchService;
57+
private config: Required<Omit<RefsAdapterConfig, 'searchService'>> & {
58+
searchService: SearchService;
59+
};
5860

5961
constructor(config: RefsAdapterConfig) {
6062
super();
61-
this.indexer = config.repositoryIndexer;
63+
this.searchService = config.searchService;
6264
this.config = {
63-
repositoryIndexer: config.repositoryIndexer,
65+
searchService: config.searchService,
6466
defaultLimit: config.defaultLimit ?? 20,
6567
};
6668
}
@@ -156,7 +158,7 @@ export class RefsAdapter extends ToolAdapter {
156158
context.logger.debug('Executing refs query', { name, direction, limit });
157159

158160
// First, find the target component
159-
const searchResults = await this.indexer.search(name, { limit: 10 });
161+
const searchResults = await this.searchService.search(name, { limit: 10 });
160162
const target = this.findBestMatch(searchResults, name);
161163

162164
if (!target) {
@@ -288,7 +290,7 @@ export class RefsAdapter extends ToolAdapter {
288290

289291
// Search for components that might call this target
290292
// We search broadly and then filter by callees
291-
const candidates = await this.indexer.search(targetName, { limit: 100 });
293+
const candidates = await this.searchService.search(targetName, { limit: 100 });
292294

293295
const callers: RefResult[] = [];
294296

0 commit comments

Comments
 (0)