-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdiff-parser.ts
More file actions
104 lines (88 loc) · 2.78 KB
/
Copy pathdiff-parser.ts
File metadata and controls
104 lines (88 loc) · 2.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
/**
* Lightweight unified diff parser. Extracts changed lines per file
* for convergence comparison across agents.
*/
export interface DiffFile {
path: string;
addedLines: string[];
removedLines: string[];
}
/**
* Parse a unified diff string into structured file changes.
*/
export function parseDiff(diff: string): DiffFile[] {
const files: DiffFile[] = [];
let current: DiffFile | null = null;
for (const line of diff.split("\n")) {
// New file header: diff --git a/path b/path
if (line.startsWith("diff --git")) {
// Handle both quoted and unquoted paths (spaces in filenames)
const match = line.match(/diff --git "?a\/(.+?)"? "?b\/(.+?)"?$/);
if (match?.[2]) {
current = { path: match[2], addedLines: [], removedLines: [] };
files.push(current);
}
continue;
}
if (!current) continue;
// Skip metadata lines
if (line.startsWith("index ") || line.startsWith("---") || line.startsWith("+++")) {
continue;
}
if (line.startsWith("@@")) continue;
// Collect added/removed lines (strip the +/- prefix)
if (line.startsWith("+")) {
current.addedLines.push(line.slice(1));
} else if (line.startsWith("-")) {
current.removedLines.push(line.slice(1));
}
}
return files;
}
/**
* Compute similarity between two diffs using Jaccard similarity
* on the set of added lines. Returns 0-1 where 1 = identical changes.
*/
export function diffSimilarity(diffA: string, diffB: string): number {
const filesA = parseDiff(diffA);
const filesB = parseDiff(diffB);
// Collect all added lines as a set (file:line for uniqueness)
const setA = new Set<string>();
const setB = new Set<string>();
for (const f of filesA) {
for (const line of f.addedLines) {
setA.add(`${f.path}:${line.trim()}`);
}
}
for (const f of filesB) {
for (const line of f.addedLines) {
setB.add(`${f.path}:${line.trim()}`);
}
}
if (setA.size === 0 && setB.size === 0) return 1; // Both empty = same
// Jaccard similarity: |A ∩ B| / |A ∪ B|
let intersection = 0;
for (const item of setA) {
if (setB.has(item)) intersection++;
}
const union = setA.size + setB.size - intersection;
return union === 0 ? 1 : intersection / union;
}
/**
* Compute pairwise similarity matrix for a list of diffs.
* Returns a Map of "agentA-agentB" -> similarity score.
*/
export function pairwiseSimilarity(
agents: Array<{ id: number; diff: string }>,
): Map<string, number> {
const matrix = new Map<string, number>();
for (let i = 0; i < agents.length; i++) {
for (let j = i + 1; j < agents.length; j++) {
const a = agents[i]!;
const b = agents[j]!;
const sim = diffSimilarity(a.diff, b.diff);
matrix.set(`${a.id}-${b.id}`, sim);
}
}
return matrix;
}