Skip to content

Commit ec6448f

Browse files
committed
feat(cli): integrate MetricsStore with automatic persistence
Phase 1 Complete! Foundation + Event Bus CLI Integration: - Wired up MetricsStore in dev index and dev update commands - Created event bus for each command invocation - Subscribed MetricsStore to index.updated events - Automatic snapshot recording on every index/update - Proper error logging (non-blocking, metrics are non-critical) - Proper cleanup (close() on completion) Metrics Database: - Stored in ~/.dev-agent/indexes/<repo>/metrics.db - SQLite with WAL mode for concurrency - Automatic persistence via event-driven architecture Phase 1 Deliverables (ALL COMPLETE): ✅ better-sqlite3 dependency added ✅ MetricsStore class with CRUD operations ✅ SQLite schema with indexes and WAL mode ✅ Comprehensive tests (25 tests, all passing) ✅ Event bus integration in RepositoryIndexer ✅ CLI commands automatically record metrics ✅ Fire-and-forget pattern for non-blocking persistence ✅ Proper error handling with logging Next: Phase 2 - code_metadata table and hotspot detection
1 parent fe0ab5f commit ec6448f

File tree

2 files changed

+68
-14
lines changed

2 files changed

+68
-14
lines changed

packages/cli/src/commands/index.ts

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ import { execSync } from 'node:child_process';
22
import { existsSync } from 'node:fs';
33
import { join } from 'node:path';
44
import {
5+
AsyncEventBus,
56
ensureStorageDirectory,
67
GitIndexer,
78
getStorageFilePaths,
89
getStoragePath,
10+
type IndexUpdatedEvent,
911
LocalGitExtractor,
12+
MetricsStore,
1013
RepositoryIndexer,
1114
updateIndexedStats,
1215
VectorStorage,
@@ -116,16 +119,39 @@ export const indexCommand = new Command('index')
116119
const filePaths = getStorageFilePaths(storagePath);
117120

118121
spinner.text = 'Initializing indexer...';
119-
const indexer = new RepositoryIndexer({
120-
repositoryPath: resolvedRepoPath,
121-
vectorStorePath: filePaths.vectors,
122-
statePath: filePaths.indexerState,
123-
excludePatterns: config.repository?.excludePatterns || config.excludePatterns,
124-
languages: config.repository?.languages || config.languages,
125-
embeddingModel: config.embeddingModel,
126-
embeddingDimension: config.dimension,
122+
123+
// Create event bus for metrics (no logger in CLI to keep it simple)
124+
const eventBus = new AsyncEventBus();
125+
126+
// Initialize metrics store (no logger in CLI to avoid noise)
127+
const metricsDbPath = join(storagePath, 'metrics.db');
128+
const metricsStore = new MetricsStore(metricsDbPath);
129+
130+
// Subscribe to index.updated events for automatic metrics persistence
131+
eventBus.on<IndexUpdatedEvent>('index.updated', async (event) => {
132+
try {
133+
metricsStore.recordSnapshot(event.stats, event.isIncremental ? 'update' : 'index');
134+
} catch (error) {
135+
// Log error but don't fail indexing - metrics are non-critical
136+
logger.error(
137+
`Failed to record metrics snapshot: ${error instanceof Error ? error.message : String(error)}`
138+
);
139+
}
127140
});
128141

142+
const indexer = new RepositoryIndexer(
143+
{
144+
repositoryPath: resolvedRepoPath,
145+
vectorStorePath: filePaths.vectors,
146+
statePath: filePaths.indexerState,
147+
excludePatterns: config.repository?.excludePatterns || config.excludePatterns,
148+
languages: config.repository?.languages || config.languages,
149+
embeddingModel: config.embeddingModel,
150+
embeddingDimension: config.dimension,
151+
},
152+
eventBus
153+
);
154+
129155
await indexer.initialize();
130156

131157
spinner.text = 'Scanning repository...';
@@ -165,6 +191,7 @@ export const indexCommand = new Command('index')
165191
});
166192

167193
await indexer.close();
194+
metricsStore.close();
168195

169196
const codeDuration = (Date.now() - startTime) / 1000;
170197

packages/cli/src/commands/update.ts

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import * as path from 'node:path';
22
import {
3+
AsyncEventBus,
34
ensureStorageDirectory,
45
getStorageFilePaths,
56
getStoragePath,
7+
type IndexUpdatedEvent,
8+
MetricsStore,
69
RepositoryIndexer,
710
} from '@lytics/dev-agent-core';
811
import chalk from 'chalk';
@@ -38,14 +41,37 @@ export const updateCommand = new Command('update')
3841
const filePaths = getStorageFilePaths(storagePath);
3942

4043
spinner.text = 'Initializing indexer...';
41-
const indexer = new RepositoryIndexer({
42-
repositoryPath: resolvedRepoPath,
43-
vectorStorePath: filePaths.vectors,
44-
statePath: filePaths.indexerState,
45-
excludePatterns: config.repository?.excludePatterns || config.excludePatterns,
46-
languages: config.repository?.languages || config.languages,
44+
45+
// Create event bus for metrics (no logger in CLI to keep it simple)
46+
const eventBus = new AsyncEventBus();
47+
48+
// Initialize metrics store (no logger in CLI to avoid noise)
49+
const metricsDbPath = path.join(storagePath, 'metrics.db');
50+
const metricsStore = new MetricsStore(metricsDbPath);
51+
52+
// Subscribe to index.updated events for automatic metrics persistence
53+
eventBus.on<IndexUpdatedEvent>('index.updated', async (event) => {
54+
try {
55+
metricsStore.recordSnapshot(event.stats, event.isIncremental ? 'update' : 'index');
56+
} catch (error) {
57+
// Log error but don't fail update - metrics are non-critical
58+
logger.error(
59+
`Failed to record metrics snapshot: ${error instanceof Error ? error.message : String(error)}`
60+
);
61+
}
4762
});
4863

64+
const indexer = new RepositoryIndexer(
65+
{
66+
repositoryPath: resolvedRepoPath,
67+
vectorStorePath: filePaths.vectors,
68+
statePath: filePaths.indexerState,
69+
excludePatterns: config.repository?.excludePatterns || config.excludePatterns,
70+
languages: config.repository?.languages || config.languages,
71+
},
72+
eventBus
73+
);
74+
4975
await indexer.initialize();
5076

5177
spinner.text = 'Detecting changed files...';
@@ -66,6 +92,7 @@ export const updateCommand = new Command('update')
6692
});
6793

6894
await indexer.close();
95+
metricsStore.close();
6996

7097
const duration = (Date.now() - startTime) / 1000;
7198

0 commit comments

Comments
 (0)