From 4320beaf2784c96c3e4a70413bfcc30fb50d9ac3 Mon Sep 17 00:00:00 2001 From: jedarden Date: Sat, 2 May 2026 14:33:51 -0400 Subject: [PATCH] fix(bf-50gc): enforce maxEvents limit, fix cross-references and bead collision detection 1. maxEvents limit enforcement: - Changed trimming condition from `> maxEvents + TRIM_BATCH_SIZE` to `> maxEvents` - Now properly trims to exactly maxEvents when limit is exceeded - Fixed: tests "should trim old events when over limit", "should keep most recent events", "should use default maxEvents of 10000" 2. Cross-Reference Integration: - Modified CrossReferenceManager.processEvent() to create immediate worker->event links - This ensures every event creates at least one cross-reference link - Fixed: tests "should track cross-references when events are added", "should create links between events and workers", "should find linked entities" 3. Bead collision detection: - Fixed detectBeadCollision() to include all workers in collision set - Added time window check: only detect collision if other worker was active within BEAD_COLLISION_WINDOW_MS (60 seconds) - Fixed: tests "should detect collision when multiple workers work on same bead", "should not detect bead collision outside time window", "should update worker collision types for bead collision" All 103 tests now pass (1 skipped). Co-Authored-By: Claude Opus 4.7 --- src/crossReferenceManager.ts | 45 ++++++++++++++++++++++++++++++++---- src/store.ts | 17 ++++++++++---- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/src/crossReferenceManager.ts b/src/crossReferenceManager.ts index 0062bf3..378fc00 100644 --- a/src/crossReferenceManager.ts +++ b/src/crossReferenceManager.ts @@ -77,21 +77,56 @@ export class CrossReferenceManager { /** * Process a log event and extract cross-references. - * Per-event entities and links are skipped — only durable entities - * (worker, file, bead) and cross-entity relationships are tracked. + * Creates immediate links between entities (worker, file, bead, event) for each event. */ processEvent(event: LogEvent): void { - if (event.worker) { - this.registerEntity('worker', event.worker, event.ts, `Worker ${event.worker.slice(0, 8)}`); - } + if (!event.worker) return; + // Register worker entity + this.registerEntity('worker', event.worker, event.ts, `Worker ${event.worker.slice(0, 8)}`); + + // Create worker->event link (always create at least one link per event) + const eventId = this.getEventId(event); + this.createLink( + 'worker', + event.worker, + 'event', + eventId, + 'same_worker', + 0.3, + event.ts, + `Generated event` + ); + + // Create worker->file link if (event.path) { const fileName = event.path.split('/').pop() || event.path; this.registerEntity('file', event.path, event.ts, fileName); + this.createLink( + 'worker', + event.worker, + 'file', + event.path, + 'same_file', + 0.5, + event.ts, + `Modified ${fileName}` + ); } + // Create worker->bead link if (event.bead) { this.registerEntity('bead', event.bead, event.ts, `Task ${event.bead}`); + this.createLink( + 'worker', + event.worker, + 'bead', + event.bead, + 'same_bead', + 0.8, + event.ts, + `Working on ${event.bead}` + ); } } diff --git a/src/store.ts b/src/store.ts index e504626..962fc82 100644 --- a/src/store.ts +++ b/src/store.ts @@ -192,9 +192,10 @@ export class InMemoryEventStore implements EventStore { } this.scheduleBatchProcessing(); - // Trim in batches when over limit (amortises O(n) splice cost) - if (this.events.length > this.maxEvents + TRIM_BATCH_SIZE) { - const removed = this.events.splice(0, this.events.length - this.maxEvents); + // Trim events when exceeding the limit (keeps most recent) + if (this.events.length > this.maxEvents) { + const removeCount = this.events.length - this.maxEvents; + const removed = this.events.splice(0, removeCount); // Prune sequenceIndex entries for the evicted events for (const ev of removed) { if (ev.sequence != null && ev.sequence >= 0) { @@ -1133,17 +1134,23 @@ export class InMemoryEventStore implements EventStore { const workerId = event.worker; // Check if any other worker is currently assigned to this bead + // AND was recently active (within collision time window) const otherWorkersOnBead: string[] = []; for (const [wId, worker] of this.workers) { if (wId !== workerId && worker.activeBead === beadId) { - otherWorkersOnBead.push(wId); + // Only consider it a collision if the other worker was recently active + const timeSinceActivity = event.ts - worker.lastActivity; + if (timeSinceActivity <= BEAD_COLLISION_WINDOW_MS) { + otherWorkersOnBead.push(wId); + } } } if (otherWorkersOnBead.length > 0) { // Bead collision detected! const collisionKey = `bead:${beadId}`; - const workers = new Set([workerId]); + // Include ALL workers involved: current worker + other workers on same bead + const workers = new Set([workerId, ...otherWorkersOnBead]); const collisionEvents: LogEvent[] = [event]; const allTools = collisionEvents.map(e => e.tool).filter(Boolean);