Skip to content

Commit fe0ab5f

Browse files
committed
feat(core): add event bus integration for automatic metrics
Phase 1.4: Event-driven metrics persistence - Updated IndexUpdatedEvent to include DetailedIndexStats & isIncremental flag - Added optional eventBus parameter to RepositoryIndexer constructor - Emit index.updated events after index() and update() complete - Fire-and-forget pattern (waitForHandlers: false) to avoid blocking - Fixed event bus test to include required stats field Event payload includes: - type: 'code' | 'github' - documentsCount, duration, path - stats: Full DetailedIndexStats snapshot - isIncremental: Whether this was an update vs full index This enables automatic snapshot recording via MetricsStore listeners. Next: CLI integration for MetricsStore
1 parent 7c4bc14 commit fe0ab5f

3 files changed

Lines changed: 56 additions & 2 deletions

File tree

packages/core/src/events/__tests__/event-bus.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44

55
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
6+
import { createDetailedIndexStats } from '../../indexer/__tests__/test-factories';
67
import { AsyncEventBus, createTypedEventBus } from '../event-bus';
78
import type { SystemEventMap } from '../types';
89

@@ -352,11 +353,19 @@ describe('createTypedEventBus', () => {
352353
const handler = vi.fn();
353354
bus.on('index.updated', handler);
354355

356+
const stats = createDetailedIndexStats({
357+
filesScanned: 100,
358+
documentsIndexed: 100,
359+
duration: 1000,
360+
repositoryPath: '/test',
361+
});
362+
355363
await bus.emit('index.updated', {
356364
type: 'code',
357365
documentsCount: 100,
358366
duration: 1000,
359367
path: '/test',
368+
stats,
360369
});
361370
await new Promise((resolve) => setTimeout(resolve, 10));
362371

@@ -365,6 +374,7 @@ describe('createTypedEventBus', () => {
365374
documentsCount: 100,
366375
duration: 1000,
367376
path: '/test',
377+
stats,
368378
});
369379
});
370380

packages/core/src/events/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
* Designed for Node.js async patterns.
66
*/
77

8+
import type { DetailedIndexStats } from '../indexer/types.js';
9+
810
/**
911
* Event handler function type
1012
* All handlers are async to support non-blocking operations
@@ -141,6 +143,10 @@ export interface IndexUpdatedEvent {
141143
documentsCount: number;
142144
duration: number;
143145
path: string;
146+
/** Full statistics snapshot */
147+
stats: DetailedIndexStats;
148+
/** Whether this was an incremental update (vs full index) */
149+
isIncremental?: boolean;
144150
}
145151

146152
export interface IndexErrorEvent {

packages/core/src/indexer/index.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import * as crypto from 'node:crypto';
66
import * as fs from 'node:fs/promises';
77
import * as path from 'node:path';
8+
import type { EventBus } from '../events/types.js';
89
import { scanRepository } from '../scanner';
910
import type { Document } from '../scanner/types';
1011
import { getCurrentSystemResources, getOptimalConcurrency } from '../utils/concurrency';
@@ -40,8 +41,9 @@ export class RepositoryIndexer {
4041
private readonly config: Required<IndexerConfig>;
4142
private vectorStorage: VectorStorage;
4243
private state: IndexerState | null = null;
44+
private eventBus?: EventBus;
4345

44-
constructor(config: IndexerConfig) {
46+
constructor(config: IndexerConfig, eventBus?: EventBus) {
4547
this.config = {
4648
statePath: path.join(config.repositoryPath, DEFAULT_STATE_PATH),
4749
embeddingModel: 'Xenova/all-MiniLM-L6-v2',
@@ -57,6 +59,8 @@ export class RepositoryIndexer {
5759
embeddingModel: this.config.embeddingModel,
5860
dimension: this.config.embeddingDimension,
5961
});
62+
63+
this.eventBus = eventBus;
6064
}
6165

6266
/**
@@ -271,6 +275,22 @@ export class RepositoryIndexer {
271275
this.state.lastUpdate = endTime;
272276
}
273277

278+
// Emit index.updated event (fire-and-forget)
279+
if (this.eventBus) {
280+
void this.eventBus.emit(
281+
'index.updated',
282+
{
283+
type: 'code',
284+
documentsCount: documentsIndexed,
285+
duration: stats.duration,
286+
path: this.config.repositoryPath,
287+
stats,
288+
isIncremental: false,
289+
},
290+
{ waitForHandlers: false }
291+
);
292+
}
293+
274294
return stats;
275295
} catch (error) {
276296
errors.push({
@@ -410,7 +430,7 @@ export class RepositoryIndexer {
410430
const warning = this.getStatsWarning(incrementalUpdatesSince);
411431

412432
// Return incremental stats (what changed) with metadata
413-
return {
433+
const stats: DetailedIndexStats = {
414434
filesScanned: filesToReindex.length,
415435
documentsExtracted,
416436
documentsIndexed,
@@ -431,6 +451,24 @@ export class RepositoryIndexer {
431451
warning,
432452
},
433453
};
454+
455+
// Emit index.updated event (fire-and-forget)
456+
if (this.eventBus) {
457+
void this.eventBus.emit(
458+
'index.updated',
459+
{
460+
type: 'code',
461+
documentsCount: documentsIndexed,
462+
duration: stats.duration,
463+
path: this.config.repositoryPath,
464+
stats,
465+
isIncremental: true,
466+
},
467+
{ waitForHandlers: false }
468+
);
469+
}
470+
471+
return stats;
434472
}
435473

436474
/**

0 commit comments

Comments
 (0)