mirror of
https://github.com/Jeuners/ECC.git
synced 2026-09-09 15:02:30 +02:00
feat(layer4): line-range channel + trigger firing
- Line precision: parse git diff --unified=0 into per-file changed line ranges
(defaultWorkingSetFor), so two agents in the SAME file but DIFFERENT functions
no longer false-collide. Overlap channel now uses the overlap coefficient
(|A∩B|/min(|A|,|B|)) — high when one edit sits inside the other's region, low
for disjoint ranges; whole-file edit = 1. Docstring + design doc updated.
- Trigger firing: buildProximityTriggers() turns advisories into the concrete
messages — transmit-intent to both on a Traffic Advisory, steer-away to the
yielding agent + a hold notice on a Resolution Advisory. buildProximitySnapshot
now returns triggers; dispatchProximityTriggers(triggers, {sendMessage}) delivers
them through an injectable sink (the ECC messages table), best-effort.
- 12 new tests (line-range disjoint vs overlapping, parseDiffRanges, triggers,
dispatch). Full suite 2881/2881; lint green.
This commit is contained in:
parent
e2b8a51cea
commit
bd1be0c1ce
6 changed files with 251 additions and 43 deletions
|
|
@ -46,10 +46,14 @@ test('treeDistance: siblings are closer than cousins', () => {
|
|||
test('lineRangeOverlap: full overlap when whole-file (no ranges)', () => {
|
||||
assert.strictEqual(lineRangeOverlap([], []), 1);
|
||||
});
|
||||
test('lineRangeOverlap: partial overlapping ranges', () => {
|
||||
test('lineRangeOverlap: partial overlapping ranges (overlap coefficient)', () => {
|
||||
const r = lineRangeOverlap([[1, 10]], [[5, 14]]);
|
||||
// overlap lines 5..10 = 6, union 1..14 = 14 → 6/14
|
||||
assert.ok(Math.abs(r - 6 / 14) < 1e-9, `got ${r}`);
|
||||
// overlap lines 5..10 = 6; min size = 10 → 6/10
|
||||
assert.ok(Math.abs(r - 6 / 10) < 1e-9, `got ${r}`);
|
||||
});
|
||||
test('lineRangeOverlap: smaller edit fully inside the larger ⇒ 1', () => {
|
||||
// overlap coefficient catches "B's whole edit is inside A's region".
|
||||
assert.strictEqual(lineRangeOverlap([[1, 200]], [[40, 60]]), 1);
|
||||
});
|
||||
test('lineRangeOverlap: disjoint ranges are 0', () => {
|
||||
assert.strictEqual(lineRangeOverlap([[1, 5]], [[20, 25]]), 0);
|
||||
|
|
@ -78,12 +82,18 @@ test('graphDistance: direct edge is 1, two hops is 2, unreachable is Infinity',
|
|||
|
||||
// ── collision risk channels ──
|
||||
test('collisionRisk: two agents editing the SAME file ⇒ high risk', () => {
|
||||
const a = { agentId: 'a', files: [{ path: 'src/api/users.js', lines: [[1, 50]] }] };
|
||||
const b = { agentId: 'b', files: [{ path: 'src/api/users.js', lines: [[40, 90]] }] };
|
||||
// Whole-file edits to the same file (no line info) ⇒ full overlap.
|
||||
const a = { agentId: 'a', files: [{ path: 'src/api/users.js' }] };
|
||||
const b = { agentId: 'b', files: [{ path: 'src/api/users.js' }] };
|
||||
const { risk, channels } = collisionRisk(a, b, {});
|
||||
assert.ok(risk > 0.5, `same-file risk ${risk} should be high`);
|
||||
assert.ok(channels.overlap > 0);
|
||||
});
|
||||
test('collisionRisk: same file but heavily-overlapping lines ⇒ still high', () => {
|
||||
const a = { agentId: 'a', files: [{ path: 'src/api/users.js', lines: [[1, 50]] }] };
|
||||
const b = { agentId: 'b', files: [{ path: 'src/api/users.js', lines: [[5, 55]] }] };
|
||||
assert.ok(collisionRisk(a, b, {}).risk > 0.5);
|
||||
});
|
||||
test('collisionRisk: unrelated far-apart files ⇒ low risk', () => {
|
||||
const a = { agentId: 'a', files: [{ path: 'src/api/users.js' }] };
|
||||
const b = { agentId: 'b', files: [{ path: 'docs/guide.md' }] };
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
const assert = require('assert');
|
||||
|
||||
const { buildProximitySnapshot, sessionsToAgents } = require('../../scripts/lib/control-pane/proximity');
|
||||
const { buildProximitySnapshot, sessionsToAgents, parseDiffRanges, dispatchProximityTriggers } = require('../../scripts/lib/control-pane/proximity');
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
|
@ -87,5 +87,64 @@ test('buildProximitySnapshot: advisories carry human-readable labels', () => {
|
|||
assert.ok(collision.aLabel && collision.bLabel, 'labels present');
|
||||
});
|
||||
|
||||
test('parseDiffRanges: extracts new-side line ranges per file', () => {
|
||||
const diff = ['diff --git a/src/x.js b/src/x.js', '--- a/src/x.js', '+++ b/src/x.js', '@@ -10,0 +11,3 @@', '+a', '+b', '+c', '@@ -40,2 +44,1 @@', '+z'].join('\n');
|
||||
const ranges = parseDiffRanges(diff);
|
||||
assert.deepStrictEqual(ranges.get('src/x.js'), [
|
||||
[11, 13],
|
||||
[44, 44]
|
||||
]);
|
||||
});
|
||||
|
||||
test('line-range channel: same file but disjoint ranges ⇒ no resolution', () => {
|
||||
// Two agents in the same file, far-apart functions. workingSetFor provides ranges.
|
||||
const workingSetFor = s =>
|
||||
({
|
||||
'lead-hermes': [{ path: 'src/api/users.js', lines: [[1, 20]] }],
|
||||
'worker-kb': [{ path: 'src/api/users.js', lines: [[500, 540]] }]
|
||||
})[s.id] || [];
|
||||
const prox = buildProximitySnapshot([sessions[0], sessions[1]], { workingSetFor, graph: { adjacency: {} } });
|
||||
const collision = prox.advisories.find(a => [a.a, a.b].includes('worker-kb'));
|
||||
// Disjoint line ranges in the same file should NOT be a resolution-level collision.
|
||||
assert.ok(!collision || collision.level !== 'resolution', `disjoint ranges should not force a steer (got ${collision && collision.level})`);
|
||||
});
|
||||
|
||||
test('line-range channel: same file overlapping ranges ⇒ resolution', () => {
|
||||
// worker's edit sits inside the lead's region — a definite conflict zone.
|
||||
const workingSetFor = s =>
|
||||
({
|
||||
'lead-hermes': [{ path: 'src/api/users.js', lines: [[1, 120]] }],
|
||||
'worker-kb': [{ path: 'src/api/users.js', lines: [[30, 70]] }]
|
||||
})[s.id] || [];
|
||||
const prox = buildProximitySnapshot([sessions[0], sessions[1]], { workingSetFor, graph: { adjacency: {} } });
|
||||
const collision = prox.advisories.find(a => [a.a, a.b].includes('worker-kb'));
|
||||
assert.ok(collision && collision.level === 'resolution', 'overlapping ranges should force a steer');
|
||||
});
|
||||
|
||||
test('triggers: resolution produces a steer message to the yielding agent and a hold notice', () => {
|
||||
const prox = buildProximitySnapshot(sessions, { changedFilesFor, graph: { adjacency: {} } });
|
||||
const steer = prox.triggers.find(t => t.type === 'proximity_steer');
|
||||
const hold = prox.triggers.find(t => t.type === 'proximity_hold');
|
||||
assert.ok(steer && steer.to === 'worker-kb', 'steer message goes to the yielding worker');
|
||||
assert.ok(hold && hold.to === 'lead-hermes', 'hold notice goes to the lead');
|
||||
assert.ok(/steer away/i.test(steer.content));
|
||||
});
|
||||
|
||||
test('dispatchProximityTriggers: delivers each trigger through the injected sink', () => {
|
||||
const prox = buildProximitySnapshot(sessions, { changedFilesFor, graph: { adjacency: {} } });
|
||||
const sent = [];
|
||||
const result = dispatchProximityTriggers(prox.triggers, {
|
||||
sendMessage: m => sent.push(m)
|
||||
});
|
||||
assert.strictEqual(result.dispatched, prox.triggers.length);
|
||||
assert.ok(sent.every(m => m.fromSession && m.toSession && m.content && m.msgType));
|
||||
});
|
||||
|
||||
test('dispatchProximityTriggers: no sink ⇒ nothing thrown, all skipped', () => {
|
||||
const r = dispatchProximityTriggers([{ to: 'a', from: 'b', type: 'x', content: 'c' }], {});
|
||||
assert.strictEqual(r.dispatched, 0);
|
||||
assert.strictEqual(r.skipped, 1);
|
||||
});
|
||||
|
||||
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
||||
if (failed > 0) process.exit(1);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue