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);