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 <noreply@anthropic.com>
This commit is contained in:
parent
f824c2e9f7
commit
4320beaf27
2 changed files with 52 additions and 10 deletions
|
|
@ -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}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
17
src/store.ts
17
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<string>([workerId]);
|
||||
// Include ALL workers involved: current worker + other workers on same bead
|
||||
const workers = new Set<string>([workerId, ...otherWorkersOnBead]);
|
||||
const collisionEvents: LogEvent[] = [event];
|
||||
|
||||
const allTools = collisionEvents.map(e => e.tool).filter(Boolean);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue