@gitwand/core 2.0.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -0
- package/dist/__tests__/bench.bench.js +39 -0
- package/dist/__tests__/bench.bench.js.map +1 -1
- package/dist/__tests__/corpus.d.ts.map +1 -1
- package/dist/__tests__/corpus.js +363 -0
- package/dist/__tests__/corpus.js.map +1 -1
- package/dist/__tests__/diff/block-move.test.d.ts +5 -0
- package/dist/__tests__/diff/block-move.test.d.ts.map +1 -0
- package/dist/__tests__/diff/block-move.test.js +132 -0
- package/dist/__tests__/diff/block-move.test.js.map +1 -0
- package/dist/__tests__/diff/histogram.test.d.ts +8 -0
- package/dist/__tests__/diff/histogram.test.d.ts.map +1 -0
- package/dist/__tests__/diff/histogram.test.js +150 -0
- package/dist/__tests__/diff/histogram.test.js.map +1 -0
- package/dist/__tests__/diff/parity.test.d.ts +17 -0
- package/dist/__tests__/diff/parity.test.d.ts.map +1 -0
- package/dist/__tests__/diff/parity.test.js +149 -0
- package/dist/__tests__/diff/parity.test.js.map +1 -0
- package/dist/__tests__/diff.test.js +6 -2
- package/dist/__tests__/diff.test.js.map +1 -1
- package/dist/__tests__/format-profiles/integration.test.d.ts +7 -0
- package/dist/__tests__/format-profiles/integration.test.d.ts.map +1 -0
- package/dist/__tests__/format-profiles/integration.test.js +193 -0
- package/dist/__tests__/format-profiles/integration.test.js.map +1 -0
- package/dist/__tests__/format-profiles/json-patch.test.d.ts +12 -0
- package/dist/__tests__/format-profiles/json-patch.test.d.ts.map +1 -0
- package/dist/__tests__/format-profiles/json-patch.test.js +222 -0
- package/dist/__tests__/format-profiles/json-patch.test.js.map +1 -0
- package/dist/__tests__/format-profiles/registry.test.d.ts +5 -0
- package/dist/__tests__/format-profiles/registry.test.d.ts.map +1 -0
- package/dist/__tests__/format-profiles/registry.test.js +124 -0
- package/dist/__tests__/format-profiles/registry.test.js.map +1 -0
- package/dist/__tests__/patterns/make-score.test.d.ts +9 -0
- package/dist/__tests__/patterns/make-score.test.d.ts.map +1 -0
- package/dist/__tests__/patterns/make-score.test.js +49 -0
- package/dist/__tests__/patterns/make-score.test.js.map +1 -0
- package/dist/diff/block-move.d.ts +53 -0
- package/dist/diff/block-move.d.ts.map +1 -0
- package/dist/diff/block-move.js +192 -0
- package/dist/diff/block-move.js.map +1 -0
- package/dist/diff/histogram.d.ts +45 -0
- package/dist/diff/histogram.d.ts.map +1 -0
- package/dist/diff/histogram.js +172 -0
- package/dist/diff/histogram.js.map +1 -0
- package/dist/diff/index.d.ts +30 -0
- package/dist/diff/index.d.ts.map +1 -0
- package/dist/diff/index.js +47 -0
- package/dist/diff/index.js.map +1 -0
- package/dist/diff/lcs.d.ts +34 -0
- package/dist/diff/lcs.d.ts.map +1 -0
- package/dist/diff/lcs.js +184 -0
- package/dist/diff/lcs.js.map +1 -0
- package/dist/diff/shared.d.ts +54 -0
- package/dist/diff/shared.d.ts.map +1 -0
- package/dist/diff/shared.js +164 -0
- package/dist/diff/shared.js.map +1 -0
- package/dist/diff.d.ts +6 -65
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +6 -324
- package/dist/diff.js.map +1 -1
- package/dist/format-profiles/index.d.ts +34 -0
- package/dist/format-profiles/index.d.ts.map +1 -0
- package/dist/format-profiles/index.js +86 -0
- package/dist/format-profiles/index.js.map +1 -0
- package/dist/format-profiles/json-patch.d.ts +61 -0
- package/dist/format-profiles/json-patch.d.ts.map +1 -0
- package/dist/format-profiles/json-patch.js +269 -0
- package/dist/format-profiles/json-patch.js.map +1 -0
- package/dist/format-profiles/merge-strategies.d.ts +54 -0
- package/dist/format-profiles/merge-strategies.d.ts.map +1 -0
- package/dist/format-profiles/merge-strategies.js +156 -0
- package/dist/format-profiles/merge-strategies.js.map +1 -0
- package/dist/format-profiles/profiles/composer.d.ts +18 -0
- package/dist/format-profiles/profiles/composer.d.ts.map +1 -0
- package/dist/format-profiles/profiles/composer.js +45 -0
- package/dist/format-profiles/profiles/composer.js.map +1 -0
- package/dist/format-profiles/profiles/helm-values.d.ts +21 -0
- package/dist/format-profiles/profiles/helm-values.d.ts.map +1 -0
- package/dist/format-profiles/profiles/helm-values.js +40 -0
- package/dist/format-profiles/profiles/helm-values.js.map +1 -0
- package/dist/format-profiles/profiles/kubernetes.d.ts +22 -0
- package/dist/format-profiles/profiles/kubernetes.d.ts.map +1 -0
- package/dist/format-profiles/profiles/kubernetes.js +60 -0
- package/dist/format-profiles/profiles/kubernetes.js.map +1 -0
- package/dist/format-profiles/profiles/package-json.d.ts +18 -0
- package/dist/format-profiles/profiles/package-json.d.ts.map +1 -0
- package/dist/format-profiles/profiles/package-json.js +36 -0
- package/dist/format-profiles/profiles/package-json.js.map +1 -0
- package/dist/format-profiles/profiles/tsconfig.d.ts +21 -0
- package/dist/format-profiles/profiles/tsconfig.d.ts.map +1 -0
- package/dist/format-profiles/profiles/tsconfig.js +47 -0
- package/dist/format-profiles/profiles/tsconfig.js.map +1 -0
- package/dist/format-profiles/types.d.ts +67 -0
- package/dist/format-profiles/types.d.ts.map +1 -0
- package/dist/format-profiles/types.js +9 -0
- package/dist/format-profiles/types.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/patterns/insertion-at-boundary.d.ts.map +1 -1
- package/dist/patterns/insertion-at-boundary.js +15 -33
- package/dist/patterns/insertion-at-boundary.js.map +1 -1
- package/dist/patterns/utils.d.ts +11 -8
- package/dist/patterns/utils.d.ts.map +1 -1
- package/dist/patterns/utils.js +28 -10
- package/dist/patterns/utils.js.map +1 -1
- package/dist/resolver/format-dispatch.d.ts.map +1 -1
- package/dist/resolver/format-dispatch.js +3 -1
- package/dist/resolver/format-dispatch.js.map +1 -1
- package/dist/resolver/policy.d.ts.map +1 -1
- package/dist/resolver/policy.js +2 -0
- package/dist/resolver/policy.js.map +1 -1
- package/dist/resolvers/dispatcher.d.ts +5 -1
- package/dist/resolvers/dispatcher.d.ts.map +1 -1
- package/dist/resolvers/dispatcher.js +8 -3
- package/dist/resolvers/dispatcher.js.map +1 -1
- package/dist/resolvers/json.d.ts +11 -2
- package/dist/resolvers/json.d.ts.map +1 -1
- package/dist/resolvers/json.js +55 -7
- package/dist/resolvers/json.js.map +1 -1
- package/dist/resolvers/yaml.d.ts +8 -2
- package/dist/resolvers/yaml.d.ts.map +1 -1
- package/dist/resolvers/yaml.js +156 -2
- package/dist/resolvers/yaml.js.map +1 -1
- package/dist/types.d.ts +25 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests directs de `makeScore` — la formule du `ConfidenceScore`.
|
|
3
|
+
*
|
|
4
|
+
* v2.1 ajoute la dimension optionnelle `algorithmStability` (poids −0.10).
|
|
5
|
+
* Ces tests verrouillent la rétro-compat (paramètre par défaut 0 → score
|
|
6
|
+
* identique à v1.4) et la sémantique de la nouvelle pénalité.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect } from "vitest";
|
|
9
|
+
import { makeScore } from "../../patterns/utils.js";
|
|
10
|
+
describe("makeScore — rétro-compat v1.4", () => {
|
|
11
|
+
it("sans algorithmStability, score identique à la formule v1.4", () => {
|
|
12
|
+
// typeClassification=90, dataRisk=20, scopeImpact=15, fileFrequency=0,
|
|
13
|
+
// baseAvailability=100. Score v1.4 = 90 - 8 - 2.25 + 5 = 84.75 → 85.
|
|
14
|
+
const s = makeScore(90, 20, 15, ["B1"], ["P1"], 0, 100);
|
|
15
|
+
expect(s.score).toBe(85);
|
|
16
|
+
expect(s.dimensions.typeClassification).toBe(90);
|
|
17
|
+
expect(s.dimensions.dataRisk).toBe(20);
|
|
18
|
+
expect(s.dimensions.scopeImpact).toBe(15);
|
|
19
|
+
expect(s.dimensions.fileFrequency).toBe(0);
|
|
20
|
+
expect(s.dimensions.baseAvailability).toBe(100);
|
|
21
|
+
// algorithmStability ne doit pas apparaître dans `dimensions` quand zéro,
|
|
22
|
+
// pour ne pas casser les snapshots v1.4 qui asserent l'objet exact.
|
|
23
|
+
expect(s.dimensions.algorithmStability).toBeUndefined();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
describe("makeScore — algorithmStability (v2.1)", () => {
|
|
27
|
+
it("pénalité de −10 quand algorithmStability=100", () => {
|
|
28
|
+
// Même input que ci-dessus mais avec algorithmStability=100.
|
|
29
|
+
// Score = 85 (v1.4) − 10 = 75.
|
|
30
|
+
const s = makeScore(90, 20, 15, [], [], 0, 100, 100);
|
|
31
|
+
expect(s.score).toBe(75);
|
|
32
|
+
expect(s.dimensions.algorithmStability).toBe(100);
|
|
33
|
+
});
|
|
34
|
+
it("dégrade le label sous le seuil 'high' (68)", () => {
|
|
35
|
+
// Cas où le score v1.4 est juste au-dessus de 68 (high) et la pénalité le
|
|
36
|
+
// fait passer en 'medium'.
|
|
37
|
+
const before = makeScore(80, 20, 0, [], [], 0, 0, 0);
|
|
38
|
+
const after = makeScore(80, 20, 0, [], [], 0, 0, 100);
|
|
39
|
+
expect(before.label).toBe("high");
|
|
40
|
+
expect(after.score).toBe(before.score - 10);
|
|
41
|
+
expect(after.label).toBe("medium");
|
|
42
|
+
});
|
|
43
|
+
it("score saturé à 0 même avec algorithmStability extrême", () => {
|
|
44
|
+
const s = makeScore(0, 100, 100, [], [], 100, 0, 100);
|
|
45
|
+
expect(s.score).toBe(0);
|
|
46
|
+
expect(s.label).toBe("low");
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
//# sourceMappingURL=make-score.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"make-score.test.js","sourceRoot":"","sources":["../../../src/__tests__/patterns/make-score.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitWand — Détection de blocs déplacés (block-move)
|
|
3
|
+
*
|
|
4
|
+
* Approche Rabin-Karp : on hash chaque fenêtre de N lignes consécutives dans
|
|
5
|
+
* `base`, `ours` et `theirs`, on cherche les fenêtres présentes dans **ours et
|
|
6
|
+
* theirs mais pas dans base** (ou à des positions différentes), et on les
|
|
7
|
+
* confirme littéralement (anti-collision).
|
|
8
|
+
*
|
|
9
|
+
* Usage v2.1 : primitive seule, exposée via `src/index.ts`. Les patterns
|
|
10
|
+
* peuvent l'utiliser pour pénaliser le score de confiance de `complex` quand
|
|
11
|
+
* un refactor massif est détecté côté ours et côté theirs (signal « pas
|
|
12
|
+
* d'auto-résolution »). Cette dimension `algorithmStability` est wired dans
|
|
13
|
+
* `ConfidenceScore` au commit suivant — mais aucun pattern ne la consomme
|
|
14
|
+
* encore (la primitive sert pour la v2.6 refactoring-aware merge).
|
|
15
|
+
*/
|
|
16
|
+
/** Un bloc déplacé entre `ours` et `theirs`, absent de `base` à cette position. */
|
|
17
|
+
export interface MovedBlock {
|
|
18
|
+
/** Les lignes brutes du bloc (depuis `ours`, non normalisées). */
|
|
19
|
+
block: string[];
|
|
20
|
+
/** Position de début dans `ours` (inclus). */
|
|
21
|
+
oursPos: number;
|
|
22
|
+
/** Position de début dans `theirs` (inclus). */
|
|
23
|
+
theirsPos: number;
|
|
24
|
+
/** Position dans `base` si la fenêtre y existe, sinon `null`. */
|
|
25
|
+
basePos: number | null;
|
|
26
|
+
}
|
|
27
|
+
/** Options pour `detectBlockMove`. */
|
|
28
|
+
export interface BlockMoveOptions {
|
|
29
|
+
/** Taille de la fenêtre de hash. Défaut 5 — équilibre robustesse vs sensibilité. */
|
|
30
|
+
windowSize?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Diversité minimale de tokens uniques (split sur whitespace + ponctuation)
|
|
33
|
+
* dans la fenêtre normalisée — sous ce seuil, on skip pour éviter les faux
|
|
34
|
+
* positifs sur les boucles `for (let i = 0; ...)` répétées. Défaut 4.
|
|
35
|
+
*/
|
|
36
|
+
minTokenDiversity?: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Détecte les blocs de N lignes consécutives présents à la fois dans `ours` et
|
|
40
|
+
* `theirs` mais absents (ou à une autre position) dans `base`.
|
|
41
|
+
*
|
|
42
|
+
* Heuristique anti-faux-positif :
|
|
43
|
+
* 1. Whitespace trim sur chaque ligne pour le hash (le contenu du bloc reste
|
|
44
|
+
* original dans la sortie).
|
|
45
|
+
* 2. Filtre `minTokenDiversity` : la fenêtre doit contenir suffisamment de
|
|
46
|
+
* tokens distincts pour ne pas matcher du code triviale répété.
|
|
47
|
+
* 3. Confirmation littérale : après match de hash, comparaison ligne-à-ligne.
|
|
48
|
+
* 4. Compaction : blocs adjacents fusionnés en un seul `MovedBlock` plus grand.
|
|
49
|
+
*
|
|
50
|
+
* @returns tableau de blocs déplacés détectés (vide si rien)
|
|
51
|
+
*/
|
|
52
|
+
export declare function detectBlockMove(base: string[], ours: string[], theirs: string[], opts?: BlockMoveOptions): MovedBlock[];
|
|
53
|
+
//# sourceMappingURL=block-move.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"block-move.d.ts","sourceRoot":"","sources":["../../src/diff/block-move.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,mFAAmF;AACnF,MAAM,WAAW,UAAU;IACzB,kEAAkE;IAClE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,sCAAsC;AACtC,MAAM,WAAW,gBAAgB;IAC/B,oFAAoF;IACpF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAKD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,EAAE,MAAM,EAAE,EAChB,IAAI,CAAC,EAAE,gBAAgB,GACtB,UAAU,EAAE,CA4Dd"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitWand — Détection de blocs déplacés (block-move)
|
|
3
|
+
*
|
|
4
|
+
* Approche Rabin-Karp : on hash chaque fenêtre de N lignes consécutives dans
|
|
5
|
+
* `base`, `ours` et `theirs`, on cherche les fenêtres présentes dans **ours et
|
|
6
|
+
* theirs mais pas dans base** (ou à des positions différentes), et on les
|
|
7
|
+
* confirme littéralement (anti-collision).
|
|
8
|
+
*
|
|
9
|
+
* Usage v2.1 : primitive seule, exposée via `src/index.ts`. Les patterns
|
|
10
|
+
* peuvent l'utiliser pour pénaliser le score de confiance de `complex` quand
|
|
11
|
+
* un refactor massif est détecté côté ours et côté theirs (signal « pas
|
|
12
|
+
* d'auto-résolution »). Cette dimension `algorithmStability` est wired dans
|
|
13
|
+
* `ConfidenceScore` au commit suivant — mais aucun pattern ne la consomme
|
|
14
|
+
* encore (la primitive sert pour la v2.6 refactoring-aware merge).
|
|
15
|
+
*/
|
|
16
|
+
const DEFAULT_WINDOW_SIZE = 5;
|
|
17
|
+
const DEFAULT_MIN_TOKEN_DIVERSITY = 4;
|
|
18
|
+
/**
|
|
19
|
+
* Détecte les blocs de N lignes consécutives présents à la fois dans `ours` et
|
|
20
|
+
* `theirs` mais absents (ou à une autre position) dans `base`.
|
|
21
|
+
*
|
|
22
|
+
* Heuristique anti-faux-positif :
|
|
23
|
+
* 1. Whitespace trim sur chaque ligne pour le hash (le contenu du bloc reste
|
|
24
|
+
* original dans la sortie).
|
|
25
|
+
* 2. Filtre `minTokenDiversity` : la fenêtre doit contenir suffisamment de
|
|
26
|
+
* tokens distincts pour ne pas matcher du code triviale répété.
|
|
27
|
+
* 3. Confirmation littérale : après match de hash, comparaison ligne-à-ligne.
|
|
28
|
+
* 4. Compaction : blocs adjacents fusionnés en un seul `MovedBlock` plus grand.
|
|
29
|
+
*
|
|
30
|
+
* @returns tableau de blocs déplacés détectés (vide si rien)
|
|
31
|
+
*/
|
|
32
|
+
export function detectBlockMove(base, ours, theirs, opts) {
|
|
33
|
+
const W = opts?.windowSize ?? DEFAULT_WINDOW_SIZE;
|
|
34
|
+
const minDiv = opts?.minTokenDiversity ?? DEFAULT_MIN_TOKEN_DIVERSITY;
|
|
35
|
+
if (ours.length < W || theirs.length < W)
|
|
36
|
+
return [];
|
|
37
|
+
// ─── 1. Hash de chaque fenêtre dans les trois fichiers ─────
|
|
38
|
+
const hashOurs = hashWindows(ours, W);
|
|
39
|
+
const hashTheirs = hashWindows(theirs, W);
|
|
40
|
+
const hashBase = hashWindows(base, W);
|
|
41
|
+
// Index : pour chaque hash, la première position dans chaque fichier.
|
|
42
|
+
const oursIndex = buildPositionIndex(hashOurs);
|
|
43
|
+
const theirsIndex = buildPositionIndex(hashTheirs);
|
|
44
|
+
const baseIndex = buildPositionIndex(hashBase);
|
|
45
|
+
// ─── 2. Pour chaque hash présent dans ours ET theirs, vérifier ─
|
|
46
|
+
const candidates = [];
|
|
47
|
+
for (const [hash, oursPositions] of oursIndex) {
|
|
48
|
+
const theirsPositions = theirsIndex.get(hash);
|
|
49
|
+
if (!theirsPositions)
|
|
50
|
+
continue;
|
|
51
|
+
// Considérer la première occurrence de chaque côté (les suivantes sont
|
|
52
|
+
// gérées par compaction au step 3 si elles sont adjacentes).
|
|
53
|
+
const oursPos = oursPositions[0];
|
|
54
|
+
const theirsPos = theirsPositions[0];
|
|
55
|
+
// Confirmation littérale (anti-collision Rabin-Karp).
|
|
56
|
+
if (!windowsMatch(ours, oursPos, theirs, theirsPos, W))
|
|
57
|
+
continue;
|
|
58
|
+
// Filtre de diversité de tokens.
|
|
59
|
+
const block = ours.slice(oursPos, oursPos + W);
|
|
60
|
+
if (countDistinctTokens(block) < minDiv)
|
|
61
|
+
continue;
|
|
62
|
+
// Position dans base : si la fenêtre y existe avec exactement le même
|
|
63
|
+
// contenu, on note la position (utile au consommateur pour qualifier le
|
|
64
|
+
// déplacement). Sinon `null` — la fenêtre est purement nouvelle des deux
|
|
65
|
+
// côtés.
|
|
66
|
+
const basePositions = baseIndex.get(hash);
|
|
67
|
+
let basePos = null;
|
|
68
|
+
if (basePositions) {
|
|
69
|
+
for (const bp of basePositions) {
|
|
70
|
+
if (windowsMatch(ours, oursPos, base, bp, W)) {
|
|
71
|
+
basePos = bp;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Si la fenêtre est dans base à la même position relative dans les trois
|
|
76
|
+
// fichiers (delta de position négligeable), ce n'est pas un block-move.
|
|
77
|
+
if (basePos !== null && Math.abs(oursPos - basePos) < W && Math.abs(theirsPos - basePos) < W) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
candidates.push({ block, oursPos, theirsPos, basePos });
|
|
82
|
+
}
|
|
83
|
+
// ─── 3. Compaction : fusionner les blocs adjacents avec le même delta ─
|
|
84
|
+
return compactAdjacent(candidates, W);
|
|
85
|
+
}
|
|
86
|
+
// ─── Utils internes ───────────────────────────────────────────
|
|
87
|
+
/** Polynomial rolling hash — base 257, modulo 2^32 (overflow naturel JS bit-ops). */
|
|
88
|
+
function hashWindows(lines, W) {
|
|
89
|
+
const n = lines.length;
|
|
90
|
+
if (n < W)
|
|
91
|
+
return [];
|
|
92
|
+
const hashes = new Array(n - W + 1);
|
|
93
|
+
// Pré-calcule un hash sur la ligne normalisée (trim) pour stabilité whitespace.
|
|
94
|
+
const lineHashes = lines.map((l) => stringHash(l.trim()));
|
|
95
|
+
// Fenêtre : combine W line-hashes via une formule polynomiale.
|
|
96
|
+
for (let i = 0; i + W <= n; i++) {
|
|
97
|
+
let h = 0;
|
|
98
|
+
for (let k = 0; k < W; k++) {
|
|
99
|
+
h = (Math.imul(h, 257) + lineHashes[i + k]) | 0;
|
|
100
|
+
}
|
|
101
|
+
hashes[i] = h;
|
|
102
|
+
}
|
|
103
|
+
return hashes;
|
|
104
|
+
}
|
|
105
|
+
/** Hash 32-bit d'une chaîne (variante djb2). */
|
|
106
|
+
function stringHash(s) {
|
|
107
|
+
let h = 5381;
|
|
108
|
+
for (let i = 0; i < s.length; i++) {
|
|
109
|
+
h = (Math.imul(h, 33) ^ s.charCodeAt(i)) | 0;
|
|
110
|
+
}
|
|
111
|
+
return h;
|
|
112
|
+
}
|
|
113
|
+
/** Index hash → liste des positions où il apparaît. */
|
|
114
|
+
function buildPositionIndex(hashes) {
|
|
115
|
+
const index = new Map();
|
|
116
|
+
for (let i = 0; i < hashes.length; i++) {
|
|
117
|
+
const h = hashes[i];
|
|
118
|
+
let arr = index.get(h);
|
|
119
|
+
if (!arr) {
|
|
120
|
+
arr = [];
|
|
121
|
+
index.set(h, arr);
|
|
122
|
+
}
|
|
123
|
+
arr.push(i);
|
|
124
|
+
}
|
|
125
|
+
return index;
|
|
126
|
+
}
|
|
127
|
+
/** Compare littéralement deux fenêtres (ligne par ligne, contenu brut). */
|
|
128
|
+
function windowsMatch(a, aPos, b, bPos, W) {
|
|
129
|
+
for (let k = 0; k < W; k++) {
|
|
130
|
+
if (a[aPos + k] !== b[bPos + k])
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
/** Nombre de tokens distincts dans un bloc (split simple sur whitespace + ponctuation). */
|
|
136
|
+
function countDistinctTokens(block) {
|
|
137
|
+
const tokens = new Set();
|
|
138
|
+
for (const line of block) {
|
|
139
|
+
for (const t of line.split(/[\s{}[\](),:;"'`=<>+\-*/!?]+/)) {
|
|
140
|
+
if (t.length > 0)
|
|
141
|
+
tokens.add(t);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return tokens.size;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Fusionne les blocs adjacents avec la même paire (deltaOurs, deltaTheirs).
|
|
148
|
+
* Deux candidats sont adjacents si la fenêtre suivante commence à exactement
|
|
149
|
+
* `oursPos + 1` (et idem en theirs) — typique d'un grand bloc de longueur
|
|
150
|
+
* L > W découpé en (L - W + 1) fenêtres qui se chevauchent.
|
|
151
|
+
*
|
|
152
|
+
* On track séparément la position de la *dernière* fenêtre fusionnée (`lastOursPos`,
|
|
153
|
+
* `lastTheirsPos`) parce que `current.oursPos` reste figé sur le début du bloc
|
|
154
|
+
* compacté. Un `basePos` non-null d'un candidat plus tardif gagne sur un null
|
|
155
|
+
* antérieur (la moved-block info est précieuse).
|
|
156
|
+
*/
|
|
157
|
+
function compactAdjacent(candidates, W) {
|
|
158
|
+
if (candidates.length === 0)
|
|
159
|
+
return [];
|
|
160
|
+
const sorted = [...candidates].sort((a, b) => a.oursPos - b.oursPos);
|
|
161
|
+
const result = [];
|
|
162
|
+
let current = sorted[0];
|
|
163
|
+
let lastOursPos = current.oursPos;
|
|
164
|
+
let lastTheirsPos = current.theirsPos;
|
|
165
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
166
|
+
const next = sorted[i];
|
|
167
|
+
const isAdjacent = next.oursPos === lastOursPos + 1 &&
|
|
168
|
+
next.theirsPos === lastTheirsPos + 1;
|
|
169
|
+
if (isAdjacent) {
|
|
170
|
+
current = {
|
|
171
|
+
...current,
|
|
172
|
+
block: [...current.block, next.block[W - 1]],
|
|
173
|
+
// basePos : si le candidat suivant en a un et nous pas (ou vice versa),
|
|
174
|
+
// on garde celui qui est non-null. Si les deux en ont un avec un delta
|
|
175
|
+
// constant vs oursPos, on garde le premier (sémantique : le bloc commence
|
|
176
|
+
// à la position d'origine du déplacement).
|
|
177
|
+
basePos: current.basePos ?? next.basePos,
|
|
178
|
+
};
|
|
179
|
+
lastOursPos = next.oursPos;
|
|
180
|
+
lastTheirsPos = next.theirsPos;
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
result.push(current);
|
|
184
|
+
current = next;
|
|
185
|
+
lastOursPos = next.oursPos;
|
|
186
|
+
lastTheirsPos = next.theirsPos;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
result.push(current);
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=block-move.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"block-move.js","sourceRoot":"","sources":["../../src/diff/block-move.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitWand — Histogram diff
|
|
3
|
+
*
|
|
4
|
+
* Implémentation récursive inspirée de l'algorithme Histogram (Git, JGit) :
|
|
5
|
+
* trouver une **ancre rare** (ligne dont la somme des fréquences `freqA + freqB`
|
|
6
|
+
* est minimale, idéalement 1 + 1), splitter récursivement autour de l'ancre.
|
|
7
|
+
*
|
|
8
|
+
* Vs LCS pur :
|
|
9
|
+
* - LCS aligne sur n'importe quelle ligne commune ; sur du code source, les
|
|
10
|
+
* ancres faibles (`}`, `return;`, lignes vides) produisent des splits hostiles.
|
|
11
|
+
* - Histogram s'ancre sur les lignes uniques d'abord, ce qui produit des diffs
|
|
12
|
+
* plus stables et plus lisibles, et augmente le taux de succès de
|
|
13
|
+
* `non_overlapping` / `insertion_at_boundary` qui dépendent de l'alignement.
|
|
14
|
+
*
|
|
15
|
+
* Référence : Nugroho et al., « How different are different diff algorithms in
|
|
16
|
+
* Git? » Springer EMSE 2019. Histogram domine Myers et LCS sur le code source.
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
/** Options pour `histogramDiff`. */
|
|
21
|
+
export interface HistogramOptions {
|
|
22
|
+
/** Profondeur max de récursion (défaut 100, garde-fou stack overflow). */
|
|
23
|
+
maxDepth?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Court-circuit DP plein si `(aEnd-aStart) * (bEnd-bStart) ≤ smallInputThreshold`.
|
|
26
|
+
* L'overhead d'indexation Histogram domine sur les petits inputs ; le LCS
|
|
27
|
+
* legacy DP est plus rapide. Défaut 200 (≈ 14×14 cellules).
|
|
28
|
+
*/
|
|
29
|
+
smallInputThreshold?: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Calcule la Longest Common Subsequence entre deux tableaux de lignes via
|
|
33
|
+
* l'algorithme **Histogram**. Retourne les indices des lignes communes dans
|
|
34
|
+
* chaque tableau, en ordre strictement croissant.
|
|
35
|
+
*
|
|
36
|
+
* Contrat : `histogramDiff(a, b).length === lcsLegacy(a, b).length` sur tous
|
|
37
|
+
* les inputs (les paires retournées peuvent différer sur les tie-breaks).
|
|
38
|
+
*
|
|
39
|
+
* @param a - premier tableau de lignes
|
|
40
|
+
* @param b - second tableau de lignes
|
|
41
|
+
* @param opts - options optionnelles
|
|
42
|
+
* @returns paires `[i, j]` telles que `a[i] === b[j]`, ordre croissant
|
|
43
|
+
*/
|
|
44
|
+
export declare function histogramDiff(a: string[], b: string[], opts?: HistogramOptions): Array<[number, number]>;
|
|
45
|
+
//# sourceMappingURL=histogram.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"histogram.d.ts","sourceRoot":"","sources":["../../src/diff/histogram.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,oCAAoC;AACpC,MAAM,WAAW,gBAAgB;IAC/B,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAKD;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAC3B,CAAC,EAAE,MAAM,EAAE,EACX,CAAC,EAAE,MAAM,EAAE,EACX,IAAI,CAAC,EAAE,gBAAgB,GACtB,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAIzB"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitWand — Histogram diff
|
|
3
|
+
*
|
|
4
|
+
* Implémentation récursive inspirée de l'algorithme Histogram (Git, JGit) :
|
|
5
|
+
* trouver une **ancre rare** (ligne dont la somme des fréquences `freqA + freqB`
|
|
6
|
+
* est minimale, idéalement 1 + 1), splitter récursivement autour de l'ancre.
|
|
7
|
+
*
|
|
8
|
+
* Vs LCS pur :
|
|
9
|
+
* - LCS aligne sur n'importe quelle ligne commune ; sur du code source, les
|
|
10
|
+
* ancres faibles (`}`, `return;`, lignes vides) produisent des splits hostiles.
|
|
11
|
+
* - Histogram s'ancre sur les lignes uniques d'abord, ce qui produit des diffs
|
|
12
|
+
* plus stables et plus lisibles, et augmente le taux de succès de
|
|
13
|
+
* `non_overlapping` / `insertion_at_boundary` qui dépendent de l'alignement.
|
|
14
|
+
*
|
|
15
|
+
* Référence : Nugroho et al., « How different are different diff algorithms in
|
|
16
|
+
* Git? » Springer EMSE 2019. Histogram domine Myers et LCS sur le code source.
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
import { lcsLegacy } from "./lcs.js";
|
|
21
|
+
const DEFAULT_MAX_DEPTH = 100;
|
|
22
|
+
const DEFAULT_SMALL_INPUT_THRESHOLD = 200;
|
|
23
|
+
/**
|
|
24
|
+
* Calcule la Longest Common Subsequence entre deux tableaux de lignes via
|
|
25
|
+
* l'algorithme **Histogram**. Retourne les indices des lignes communes dans
|
|
26
|
+
* chaque tableau, en ordre strictement croissant.
|
|
27
|
+
*
|
|
28
|
+
* Contrat : `histogramDiff(a, b).length === lcsLegacy(a, b).length` sur tous
|
|
29
|
+
* les inputs (les paires retournées peuvent différer sur les tie-breaks).
|
|
30
|
+
*
|
|
31
|
+
* @param a - premier tableau de lignes
|
|
32
|
+
* @param b - second tableau de lignes
|
|
33
|
+
* @param opts - options optionnelles
|
|
34
|
+
* @returns paires `[i, j]` telles que `a[i] === b[j]`, ordre croissant
|
|
35
|
+
*/
|
|
36
|
+
export function histogramDiff(a, b, opts) {
|
|
37
|
+
const maxDepth = opts?.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
38
|
+
const smallInputThreshold = opts?.smallInputThreshold ?? DEFAULT_SMALL_INPUT_THRESHOLD;
|
|
39
|
+
return diffWindow(a, 0, a.length, b, 0, b.length, 0, maxDepth, smallInputThreshold);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Coeur récursif. Opère sur les fenêtres `a[aStart..aEnd], b[bStart..bEnd]`.
|
|
43
|
+
* Retourne les paires `[i, j]` en coordonnées **absolues** (par rapport aux
|
|
44
|
+
* tableaux d'origine), pas relatives à la fenêtre.
|
|
45
|
+
*/
|
|
46
|
+
function diffWindow(a, aStart, aEnd, b, bStart, bEnd, depth, maxDepth, smallInputThreshold) {
|
|
47
|
+
const n = aEnd - aStart;
|
|
48
|
+
const m = bEnd - bStart;
|
|
49
|
+
// ─── Cas de base ─────────────────────────────────────────────
|
|
50
|
+
if (n <= 0 || m <= 0)
|
|
51
|
+
return [];
|
|
52
|
+
if (n === 1) {
|
|
53
|
+
// Première occurrence dans b — même tie-break que `lcsHirschberg`.
|
|
54
|
+
const target = a[aStart];
|
|
55
|
+
for (let j = bStart; j < bEnd; j++) {
|
|
56
|
+
if (b[j] === target)
|
|
57
|
+
return [[aStart, j]];
|
|
58
|
+
}
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
if (m === 1) {
|
|
62
|
+
const target = b[bStart];
|
|
63
|
+
for (let i = aStart; i < aEnd; i++) {
|
|
64
|
+
if (a[i] === target)
|
|
65
|
+
return [[i, bStart]];
|
|
66
|
+
}
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
// ─── Garde-fous : profondeur + petit input ───────────────────
|
|
70
|
+
if (depth >= maxDepth || n * m <= smallInputThreshold) {
|
|
71
|
+
// Fallback DP plein sur la sous-fenêtre. On copie les sous-tableaux
|
|
72
|
+
// pour rester dans le contrat de `lcsLegacy(string[], string[])`.
|
|
73
|
+
const aSub = a.slice(aStart, aEnd);
|
|
74
|
+
const bSub = b.slice(bStart, bEnd);
|
|
75
|
+
const sub = lcsLegacy(aSub, bSub);
|
|
76
|
+
return sub.map(([i, j]) => [i + aStart, j + bStart]);
|
|
77
|
+
}
|
|
78
|
+
// ─── Histogramme : fréquences sur la fenêtre courante ────────
|
|
79
|
+
const freqA = new Map();
|
|
80
|
+
for (let i = aStart; i < aEnd; i++) {
|
|
81
|
+
freqA.set(a[i], (freqA.get(a[i]) ?? 0) + 1);
|
|
82
|
+
}
|
|
83
|
+
const freqB = new Map();
|
|
84
|
+
for (let j = bStart; j < bEnd; j++) {
|
|
85
|
+
freqB.set(b[j], (freqB.get(b[j]) ?? 0) + 1);
|
|
86
|
+
}
|
|
87
|
+
// ─── Indexation des positions de chaque ligne dans la fenêtre b ──
|
|
88
|
+
// (sert à étendre le match pour chaque candidat ancre vers la position de b
|
|
89
|
+
// qui maximise la région contiguë alignée — heuristique JGit Histogram.)
|
|
90
|
+
const posB = new Map();
|
|
91
|
+
for (let j = bStart; j < bEnd; j++) {
|
|
92
|
+
const line = b[j];
|
|
93
|
+
let arr = posB.get(line);
|
|
94
|
+
if (!arr) {
|
|
95
|
+
arr = [];
|
|
96
|
+
posB.set(line, arr);
|
|
97
|
+
}
|
|
98
|
+
arr.push(j);
|
|
99
|
+
}
|
|
100
|
+
// ─── Recherche de l'ancre — variant JGit ─────────────────────
|
|
101
|
+
// Pour chaque ligne candidate (présente dans freqB), parcourir toutes ses
|
|
102
|
+
// positions dans b et étendre le match (forward + backward) tant que les
|
|
103
|
+
// lignes correspondent. Conserver le triplet (i, j, regionLen) qui maximise
|
|
104
|
+
// `regionLen / (freqA + freqB)`. À regionLen + score égaux, conserver le
|
|
105
|
+
// plus petit i (tie-break stable).
|
|
106
|
+
let bestI = -1;
|
|
107
|
+
let bestJ = -1;
|
|
108
|
+
let bestRegionLen = 0;
|
|
109
|
+
let bestScore = Number.POSITIVE_INFINITY;
|
|
110
|
+
for (let i = aStart; i < aEnd; i++) {
|
|
111
|
+
const line = a[i];
|
|
112
|
+
const positions = posB.get(line);
|
|
113
|
+
if (!positions)
|
|
114
|
+
continue;
|
|
115
|
+
const fA = freqA.get(line);
|
|
116
|
+
const fB = positions.length;
|
|
117
|
+
const score = fA + fB;
|
|
118
|
+
// Si le score est strictement pire que le best courant ET que regionLen
|
|
119
|
+
// ne peut pas le battre, skip (optim).
|
|
120
|
+
if (score > bestScore && bestRegionLen >= 1)
|
|
121
|
+
continue;
|
|
122
|
+
for (const j of positions) {
|
|
123
|
+
// Étendre forward
|
|
124
|
+
let fwd = 1;
|
|
125
|
+
while (i + fwd < aEnd &&
|
|
126
|
+
j + fwd < bEnd &&
|
|
127
|
+
a[i + fwd] === b[j + fwd]) {
|
|
128
|
+
fwd++;
|
|
129
|
+
}
|
|
130
|
+
// Étendre backward (mais en restant dans la fenêtre)
|
|
131
|
+
let bwd = 0;
|
|
132
|
+
while (i - bwd - 1 >= aStart &&
|
|
133
|
+
j - bwd - 1 >= bStart &&
|
|
134
|
+
a[i - bwd - 1] === b[j - bwd - 1]) {
|
|
135
|
+
bwd++;
|
|
136
|
+
}
|
|
137
|
+
const regionLen = fwd + bwd;
|
|
138
|
+
// Critère de sélection : prioriser (a) la région la plus longue, (b) à
|
|
139
|
+
// longueur égale le score le plus bas (ancre la plus rare), (c) à score
|
|
140
|
+
// égal le plus petit i (déterminisme).
|
|
141
|
+
if (regionLen > bestRegionLen ||
|
|
142
|
+
(regionLen === bestRegionLen && score < bestScore) ||
|
|
143
|
+
(regionLen === bestRegionLen && score === bestScore && i < bestI)) {
|
|
144
|
+
bestI = i - bwd; // début de la région étendue côté a
|
|
145
|
+
bestJ = j - bwd; // début de la région étendue côté b
|
|
146
|
+
bestRegionLen = regionLen;
|
|
147
|
+
bestScore = score;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// ─── Pas d'ancre commune ─────────────────────────────────────
|
|
152
|
+
if (bestI === -1) {
|
|
153
|
+
// Aucune ligne de a n'apparaît dans b sur cette fenêtre → pas de LCS.
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
// ─── Récursion gauche + région étendue + récursion droite ────
|
|
157
|
+
// La région étendue couvre [bestI..bestI+bestRegionLen-1] dans a et
|
|
158
|
+
// [bestJ..bestJ+bestRegionLen-1] dans b (toutes paires matchent).
|
|
159
|
+
const left = diffWindow(a, aStart, bestI, b, bStart, bestJ, depth + 1, maxDepth, smallInputThreshold);
|
|
160
|
+
const right = diffWindow(a, bestI + bestRegionLen, aEnd, b, bestJ + bestRegionLen, bEnd, depth + 1, maxDepth, smallInputThreshold);
|
|
161
|
+
const result = new Array(left.length + bestRegionLen + right.length);
|
|
162
|
+
let k = 0;
|
|
163
|
+
for (let i = 0; i < left.length; i++)
|
|
164
|
+
result[k++] = left[i];
|
|
165
|
+
for (let r = 0; r < bestRegionLen; r++) {
|
|
166
|
+
result[k++] = [bestI + r, bestJ + r];
|
|
167
|
+
}
|
|
168
|
+
for (let i = 0; i < right.length; i++)
|
|
169
|
+
result[k++] = right[i];
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=histogram.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"histogram.js","sourceRoot":"","sources":["../../src/diff/histogram.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitWand — Sous-arborescence diff
|
|
3
|
+
*
|
|
4
|
+
* Point d'entrée unique pour les algorithmes de diff et leurs helpers.
|
|
5
|
+
*
|
|
6
|
+
* **v2.1 — Histogram par défaut** : `lcs()` route vers `histogramDiff` (ancrage
|
|
7
|
+
* sur lignes rares, splits plus stables sur le code source). Pour rebasculer
|
|
8
|
+
* sur l'algo legacy (DP plein / Hirschberg) :
|
|
9
|
+
*
|
|
10
|
+
* GITWAND_DIFF=lcs node ...
|
|
11
|
+
*
|
|
12
|
+
* Le contrat de longueur est préservé : `histogramDiff(a, b).length ===
|
|
13
|
+
* lcsLegacy(a, b).length` sur tous les inputs (les paires retournées peuvent
|
|
14
|
+
* différer sur les tie-breaks).
|
|
15
|
+
*/
|
|
16
|
+
export { lcsLegacy, _lcsHirschberg } from "./lcs.js";
|
|
17
|
+
export { histogramDiff, type HistogramOptions } from "./histogram.js";
|
|
18
|
+
export { detectBlockMove, type MovedBlock, type BlockMoveOptions, } from "./block-move.js";
|
|
19
|
+
/**
|
|
20
|
+
* Calcule la Longest Common Subsequence entre deux tableaux de lignes.
|
|
21
|
+
*
|
|
22
|
+
* Backend par défaut : **Histogram** (depuis v2.1). Bascule sur l'algo legacy
|
|
23
|
+
* via `GITWAND_DIFF=lcs`.
|
|
24
|
+
*
|
|
25
|
+
* @returns paires `[i, j]` telles que `a[i] === b[j]`, ordre strictement
|
|
26
|
+
* croissant dans les deux dimensions.
|
|
27
|
+
*/
|
|
28
|
+
export declare function lcs(a: string[], b: string[]): Array<[number, number]>;
|
|
29
|
+
export { computeDiff, extractEdits, editsOverlap, mergeNonOverlapping, type DiffOp, type Edit, } from "./shared.js";
|
|
30
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/diff/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EACL,eAAe,EACf,KAAK,UAAU,EACf,KAAK,gBAAgB,GACtB,MAAM,iBAAiB,CAAC;AAazB;;;;;;;;GAQG;AACH,wBAAgB,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAGrE;AAED,OAAO,EACL,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,KAAK,MAAM,EACX,KAAK,IAAI,GACV,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitWand — Sous-arborescence diff
|
|
3
|
+
*
|
|
4
|
+
* Point d'entrée unique pour les algorithmes de diff et leurs helpers.
|
|
5
|
+
*
|
|
6
|
+
* **v2.1 — Histogram par défaut** : `lcs()` route vers `histogramDiff` (ancrage
|
|
7
|
+
* sur lignes rares, splits plus stables sur le code source). Pour rebasculer
|
|
8
|
+
* sur l'algo legacy (DP plein / Hirschberg) :
|
|
9
|
+
*
|
|
10
|
+
* GITWAND_DIFF=lcs node ...
|
|
11
|
+
*
|
|
12
|
+
* Le contrat de longueur est préservé : `histogramDiff(a, b).length ===
|
|
13
|
+
* lcsLegacy(a, b).length` sur tous les inputs (les paires retournées peuvent
|
|
14
|
+
* différer sur les tie-breaks).
|
|
15
|
+
*/
|
|
16
|
+
import { lcsLegacy } from "./lcs.js";
|
|
17
|
+
import { histogramDiff } from "./histogram.js";
|
|
18
|
+
export { lcsLegacy, _lcsHirschberg } from "./lcs.js";
|
|
19
|
+
export { histogramDiff } from "./histogram.js";
|
|
20
|
+
export { detectBlockMove, } from "./block-move.js";
|
|
21
|
+
/**
|
|
22
|
+
* Lit le flag d'environnement de manière safe — `process` n'est pas défini
|
|
23
|
+
* dans tous les runtimes (browser pur, certains worklets). Sur Tauri webview
|
|
24
|
+
* et VS Code extension `process` existe, donc le rollback marche partout où
|
|
25
|
+
* `@gitwand/core` est utilisé en pratique.
|
|
26
|
+
*/
|
|
27
|
+
function shouldUseLegacy() {
|
|
28
|
+
if (typeof process === "undefined")
|
|
29
|
+
return false;
|
|
30
|
+
return process.env?.GITWAND_DIFF === "lcs";
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Calcule la Longest Common Subsequence entre deux tableaux de lignes.
|
|
34
|
+
*
|
|
35
|
+
* Backend par défaut : **Histogram** (depuis v2.1). Bascule sur l'algo legacy
|
|
36
|
+
* via `GITWAND_DIFF=lcs`.
|
|
37
|
+
*
|
|
38
|
+
* @returns paires `[i, j]` telles que `a[i] === b[j]`, ordre strictement
|
|
39
|
+
* croissant dans les deux dimensions.
|
|
40
|
+
*/
|
|
41
|
+
export function lcs(a, b) {
|
|
42
|
+
if (shouldUseLegacy())
|
|
43
|
+
return lcsLegacy(a, b);
|
|
44
|
+
return histogramDiff(a, b);
|
|
45
|
+
}
|
|
46
|
+
export { computeDiff, extractEdits, editsOverlap, mergeNonOverlapping, } from "./shared.js";
|
|
47
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/diff/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/C,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,EAAE,aAAa,EAAyB,MAAM,gBAAgB,CAAC;AACtE,OAAO,EACL,eAAe,GAGhB,MAAM,iBAAiB,CAAC;AAEzB;;;;;GAKG;AACH,SAAS,eAAe;IACtB,IAAI,OAAO,OAAO,KAAK,WAAW;QAAE,OAAO,KAAK,CAAC;IACjD,OAAO,OAAO,CAAC,GAAG,EAAE,YAAY,KAAK,KAAK,CAAC;AAC7C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,GAAG,CAAC,CAAW,EAAE,CAAW;IAC1C,IAAI,eAAe,EAAE;QAAE,OAAO,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9C,OAAO,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC7B,CAAC;AAED,OAAO,EACL,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,mBAAmB,GAGpB,MAAM,aAAa,CAAC"}
|