- Add flow-runner-decisions.ts: decision-aware step execution - Extend flow-runner.ts: dynamic step decisions - Extend conductor types: additional flow state types - Add collision-detector.test.ts: edit collision unit tests - Add conflict-index.test.ts: conflict resolution index tests
91 lines
3.8 KiB
TypeScript
91 lines
3.8 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { findConflicts } from '../collision-detector.js';
|
|
import type { ConflictEntry, ConflictIndexData } from '../collision-detector.js';
|
|
|
|
function entry(worktreeId: string, agent: string, start?: number, end?: number): ConflictEntry {
|
|
return {
|
|
worktreeId,
|
|
agent,
|
|
lineRange: start !== undefined && end !== undefined ? { start, end } : undefined,
|
|
status: 'pending' as const,
|
|
timestamp: 1000,
|
|
};
|
|
}
|
|
|
|
function index(entries: Array<[string, ConflictEntry[]]>): ConflictIndexData {
|
|
return new Map(entries.map(([path, es]) => [path, new Set(es)] as const));
|
|
}
|
|
|
|
describe('findConflicts', () => {
|
|
it('returns empty when no files in index', () => {
|
|
const result = findConflicts(['src/a.ts'], 'wt-1', new Map(), new Map());
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it('returns empty when only own worktree has the file', () => {
|
|
const idx = index([['src/a.ts', [entry('wt-1', 'agent-a', 1, 10)]]]);
|
|
const result = findConflicts(['src/a.ts'], 'wt-1', new Map(), idx);
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it('detects same_file conflict from another worktree', () => {
|
|
const idx = index([['src/a.ts', [entry('wt-2', 'agent-b', 5, 15)]]]);
|
|
const result = findConflicts(['src/a.ts'], 'wt-1', new Map(), idx);
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0]!.filePath).toBe('src/a.ts');
|
|
expect(result[0]!.worktrees).toEqual(['wt-2']);
|
|
expect(result[0]!.agents).toEqual(['agent-b']);
|
|
});
|
|
|
|
it('reports same_line severity when ranges overlap', () => {
|
|
const idx = index([['src/a.ts', [entry('wt-2', 'agent-b', 10, 20)]]]);
|
|
const ranges = new Map([['src/a.ts', { start: 15, end: 25 }]]);
|
|
const result = findConflicts(['src/a.ts'], 'wt-1', ranges, idx);
|
|
expect(result[0]!.severity).toBe('same_line');
|
|
});
|
|
|
|
it('reports different_area severity when ranges are far apart', () => {
|
|
const idx = index([['src/a.ts', [entry('wt-2', 'agent-b', 1, 10)]]]);
|
|
const ranges = new Map([['src/a.ts', { start: 100, end: 200 }]]);
|
|
const result = findConflicts(['src/a.ts'], 'wt-1', ranges, idx);
|
|
expect(result[0]!.severity).toBe('different_area');
|
|
});
|
|
|
|
it('reports adjacent_line severity when ranges are 3 lines apart', () => {
|
|
const idx = index([['src/a.ts', [entry('wt-2', 'agent-b', 10, 15)]]]);
|
|
const ranges = new Map([['src/a.ts', { start: 19, end: 25 }]]);
|
|
const result = findConflicts(['src/a.ts'], 'wt-1', ranges, idx);
|
|
expect(result[0]!.severity).toBe('adjacent_line');
|
|
});
|
|
|
|
it('returns entry for each conflicting file', () => {
|
|
const idx = index([
|
|
['src/a.ts', [entry('wt-2', 'agent-b', 1, 10)]],
|
|
['src/b.ts', [entry('wt-3', 'agent-c', 1, 10)]],
|
|
]);
|
|
const result = findConflicts(['src/a.ts', 'src/b.ts', 'src/c.ts'], 'wt-1', new Map(), idx);
|
|
expect(result).toHaveLength(2);
|
|
expect(result.map((v) => v.filePath).sort()).toEqual(['src/a.ts', 'src/b.ts']);
|
|
});
|
|
|
|
it('excludes entries from the same worktree', () => {
|
|
const idx = index([['src/a.ts', [entry('wt-1', 'agent-a', 1, 10), entry('wt-2', 'agent-b', 5, 15)]]]);
|
|
const result = findConflicts(['src/a.ts'], 'wt-1', new Map(), idx);
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0]!.worktrees).toEqual(['wt-2']);
|
|
});
|
|
|
|
it('deduplicates worktree IDs in verdict', () => {
|
|
const idx = index([['src/a.ts', [entry('wt-2', 'agent-b', 1, 5), entry('wt-2', 'agent-b', 10, 15)]]]);
|
|
const result = findConflicts(['src/a.ts'], 'wt-1', new Map(), idx);
|
|
expect(result[0]!.worktrees).toEqual(['wt-2']);
|
|
});
|
|
|
|
it('reports same_line when no lineRange on either side (create/delete conflates)', () => {
|
|
const idx = index([['src/a.ts', [entry('wt-2', 'agent-b')]]]);
|
|
const result = findConflicts(['src/a.ts'], 'wt-1', new Map(), idx);
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0]!.severity).toBe('different_area');
|
|
});
|
|
});
|