@gitsense/gsc-utils 0.1.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/LICENSE +21 -0
- package/dist/gitsense-chat-utils.cjs.js +10977 -0
- package/dist/gitsense-chat-utils.esm.js +10975 -0
- package/dist/gsc-utils.cjs.js +11043 -0
- package/dist/gsc-utils.esm.js +11041 -0
- package/package.json +37 -0
- package/src/AnalysisBlockUtils.js +151 -0
- package/src/ChatUtils.js +126 -0
- package/src/CodeBlockUtils/blockExtractor.js +277 -0
- package/src/CodeBlockUtils/blockProcessor.js +559 -0
- package/src/CodeBlockUtils/blockProcessor.js.rej +8 -0
- package/src/CodeBlockUtils/constants.js +62 -0
- package/src/CodeBlockUtils/continuationUtils.js +191 -0
- package/src/CodeBlockUtils/headerParser.js +175 -0
- package/src/CodeBlockUtils/headerUtils.js +236 -0
- package/src/CodeBlockUtils/index.js +83 -0
- package/src/CodeBlockUtils/lineNumberFormatter.js +117 -0
- package/src/CodeBlockUtils/markerRemover.js +89 -0
- package/src/CodeBlockUtils/patchIntegration.js +38 -0
- package/src/CodeBlockUtils/relationshipUtils.js +159 -0
- package/src/CodeBlockUtils/updateCodeBlock.js +372 -0
- package/src/CodeBlockUtils/uuidUtils.js +48 -0
- package/src/ContextUtils.js +180 -0
- package/src/GSToolBlockUtils.js +108 -0
- package/src/GitSenseChatUtils.js +386 -0
- package/src/JsonUtils.js +101 -0
- package/src/LLMUtils.js +31 -0
- package/src/MessageUtils.js +460 -0
- package/src/PatchUtils/constants.js +72 -0
- package/src/PatchUtils/diagnosticReporter.js +213 -0
- package/src/PatchUtils/enhancedPatchProcessor.js +390 -0
- package/src/PatchUtils/fuzzyMatcher.js +252 -0
- package/src/PatchUtils/hunkCorrector.js +204 -0
- package/src/PatchUtils/hunkValidator.js +305 -0
- package/src/PatchUtils/index.js +135 -0
- package/src/PatchUtils/patchExtractor.js +175 -0
- package/src/PatchUtils/patchHeaderFormatter.js +143 -0
- package/src/PatchUtils/patchParser.js +289 -0
- package/src/PatchUtils/patchProcessor.js +389 -0
- package/src/PatchUtils/patchVerifier/constants.js +23 -0
- package/src/PatchUtils/patchVerifier/detectAndFixOverlappingHunks.js +281 -0
- package/src/PatchUtils/patchVerifier/detectAndFixRedundantChanges.js +404 -0
- package/src/PatchUtils/patchVerifier/formatAndAddLineNumbers.js +165 -0
- package/src/PatchUtils/patchVerifier/index.js +25 -0
- package/src/PatchUtils/patchVerifier/verifyAndCorrectHunkHeaders.js +202 -0
- package/src/PatchUtils/patchVerifier/verifyAndCorrectLineNumbers.js +254 -0
- package/src/SharedUtils/timestampUtils.js +41 -0
- package/src/SharedUtils/versionUtils.js +58 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component: PatchUtils Fuzzy Matcher
|
|
3
|
+
* Block-UUID: 12e76392-f0ac-4c9d-a5be-c98401487352
|
|
4
|
+
* Parent-UUID: 7679fc85-9213-4637-8d41-9bb5c551c62d
|
|
5
|
+
* Version: 1.1.0
|
|
6
|
+
* Description: Implements fuzzy matching algorithms to find the best location for patch hunks.
|
|
7
|
+
* Language: JavaScript
|
|
8
|
+
* Created-at: 2025-05-14T18:09:37.976Z
|
|
9
|
+
* Authors: Claude 3.7 Sonnet (v1.0.0), Gemini 2.5 Flash (v1.1.0)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Calculates the longest common subsequence length between two strings
|
|
15
|
+
* @param {string} a - First string
|
|
16
|
+
* @param {string} b - Second string
|
|
17
|
+
* @returns {number} Length of the longest common subsequence
|
|
18
|
+
* @private
|
|
19
|
+
*/
|
|
20
|
+
function _lcsLength(a, b) {
|
|
21
|
+
const m = a.length;
|
|
22
|
+
const n = b.length;
|
|
23
|
+
const dp = Array(m + 1).fill().map(() => Array(n + 1).fill(0));
|
|
24
|
+
|
|
25
|
+
for (let i = 1; i <= m; i++) {
|
|
26
|
+
for (let j = 1; j <= n; j++) {
|
|
27
|
+
if (a[i - 1] === b[j - 1]) {
|
|
28
|
+
dp[i][j] = dp[i - 1][j - 1] + 1;
|
|
29
|
+
} else {
|
|
30
|
+
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return dp[m][n];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Calculates similarity score between two strings using LCS
|
|
40
|
+
* @param {string} a - First string
|
|
41
|
+
* @param {string} b - Second string
|
|
42
|
+
* @returns {number} Similarity score between 0 and 1
|
|
43
|
+
* @private
|
|
44
|
+
*/
|
|
45
|
+
function _calculateSimilarity(a, b) {
|
|
46
|
+
if (!a.length || !b.length) return 0;
|
|
47
|
+
const lcs = _lcsLength(a, b);
|
|
48
|
+
return lcs / Math.max(a.length, b.length);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Calculates similarity score between two blocks of text
|
|
53
|
+
* @param {string[]} blockA - First block of text (array of lines)
|
|
54
|
+
* @param {string[]} blockB - Second block of text (array of lines)
|
|
55
|
+
* @returns {number} Similarity score between 0 and 1
|
|
56
|
+
* @private
|
|
57
|
+
*/
|
|
58
|
+
function _calculateBlockSimilarity(blockA, blockB) {
|
|
59
|
+
if (blockA.length !== blockB.length) {
|
|
60
|
+
// If blocks have different number of lines, calculate average similarity
|
|
61
|
+
let totalSimilarity = 0;
|
|
62
|
+
const minLength = Math.min(blockA.length, blockB.length);
|
|
63
|
+
|
|
64
|
+
// Compare available lines
|
|
65
|
+
for (let i = 0; i < minLength; i++) {
|
|
66
|
+
totalSimilarity += _calculateSimilarity(blockA[i], blockB[i]);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Penalize for length difference
|
|
70
|
+
const lengthPenalty = minLength / Math.max(blockA.length, blockB.length);
|
|
71
|
+
return (totalSimilarity / blockA.length) * lengthPenalty;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// If blocks have same number of lines, calculate average line similarity
|
|
75
|
+
let totalSimilarity = 0;
|
|
76
|
+
for (let i = 0; i < blockA.length; i++) {
|
|
77
|
+
totalSimilarity += _calculateSimilarity(blockA[i], blockB[i]);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return totalSimilarity / blockA.length;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Finds the best match for context lines within source code
|
|
85
|
+
* @param {string[]} contextLinesForMatching - Array of context lines (including removed lines)
|
|
86
|
+
* @param {string} sourceCode - Full source code to search within
|
|
87
|
+
* @param {object} options - Optional parameters
|
|
88
|
+
* @param {number} options.threshold - Minimum similarity threshold (default: 0.7)
|
|
89
|
+
* @param {number} options.maxMatches - Maximum number of matches to return (default: 3)
|
|
90
|
+
* @returns {object} Best match information with position and confidence
|
|
91
|
+
*/
|
|
92
|
+
function findBestContextMatch(contextLinesForMatching, sourceCode, options = {}) {
|
|
93
|
+
const { threshold = 0.7, maxMatches = 3 } = options;
|
|
94
|
+
|
|
95
|
+
// If no context lines, return no match
|
|
96
|
+
if (!contextLinesForMatching.length) {
|
|
97
|
+
return {
|
|
98
|
+
found: false,
|
|
99
|
+
reason: "No context lines provided for matching",
|
|
100
|
+
matches: []
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const sourceLines = sourceCode.split('\n');
|
|
105
|
+
const matches = [];
|
|
106
|
+
|
|
107
|
+
// For each possible starting position in the source
|
|
108
|
+
for (let i = 0; i <= sourceLines.length - contextLinesForMatching.length; i++) {
|
|
109
|
+
const sourceBlock = sourceLines.slice(i, i + contextLinesForMatching.length);
|
|
110
|
+
const similarity = _calculateBlockSimilarity(
|
|
111
|
+
contextLinesForMatching.map(line => line.trim()),
|
|
112
|
+
sourceBlock.map(line => line.trim())
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (similarity >= threshold) {
|
|
116
|
+
matches.push({
|
|
117
|
+
position: i + 1, // Convert to 1-based line numbers
|
|
118
|
+
confidence: similarity,
|
|
119
|
+
sourceBlock
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Sort matches by confidence (descending)
|
|
125
|
+
matches.sort((a, b) => b.confidence - a.confidence);
|
|
126
|
+
|
|
127
|
+
// Limit number of matches
|
|
128
|
+
const topMatches = matches.slice(0, maxMatches);
|
|
129
|
+
|
|
130
|
+
if (topMatches.length === 0) {
|
|
131
|
+
return {
|
|
132
|
+
found: false,
|
|
133
|
+
reason: "No matches found above threshold",
|
|
134
|
+
threshold,
|
|
135
|
+
matches: []
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
found: true,
|
|
141
|
+
bestMatch: topMatches[0],
|
|
142
|
+
matches: topMatches
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Finds exact matches for a single context line
|
|
148
|
+
* @param {string} contextLine - A single context line to find
|
|
149
|
+
* @param {string} sourceCode - Full source code to search within
|
|
150
|
+
* @returns {number[]} Array of line numbers (1-based) where the context line appears
|
|
151
|
+
*/
|
|
152
|
+
function findExactLineMatches(contextLine, sourceCode) {
|
|
153
|
+
const sourceLines = sourceCode.split('\n');
|
|
154
|
+
const matches = [];
|
|
155
|
+
|
|
156
|
+
const trimmedContext = contextLine.trim();
|
|
157
|
+
|
|
158
|
+
for (let i = 0; i < sourceLines.length; i++) {
|
|
159
|
+
if (sourceLines[i].trim() === trimmedContext) {
|
|
160
|
+
matches.push(i + 1); // Convert to 1-based line numbers
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return matches;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Finds the best match using a sliding window approach
|
|
169
|
+
* @param {string[]} contextLinesForMatching - Array of context lines (including removed lines)
|
|
170
|
+
* @param {string} sourceCode - Full source code
|
|
171
|
+
* @param {number} windowSize - Size of the sliding window
|
|
172
|
+
* @returns {object} Best match information
|
|
173
|
+
*/
|
|
174
|
+
function findBestMatchWithSlidingWindow(contextLinesForMatching, sourceCode, windowSize = 3) {
|
|
175
|
+
if (contextLinesForMatching.length < windowSize) {
|
|
176
|
+
// If we have fewer context lines than the window size, use regular matching
|
|
177
|
+
return findBestContextMatch(contextLinesForMatching, sourceCode);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const sourceLines = sourceCode.split('\n');
|
|
181
|
+
const numWindows = contextLinesForMatching.length - windowSize + 1;
|
|
182
|
+
const candidatePositions = [];
|
|
183
|
+
|
|
184
|
+
// For each window in the context lines
|
|
185
|
+
for (let windowStart = 0; windowStart < numWindows; windowStart++) {
|
|
186
|
+
const windowLines = contextLinesForMatching.slice(windowStart, windowStart + windowSize);
|
|
187
|
+
|
|
188
|
+
// Find matches for this window
|
|
189
|
+
const windowMatches = findBestContextMatch(windowLines, sourceCode, { threshold: 0.9 });
|
|
190
|
+
|
|
191
|
+
if (windowMatches.found) {
|
|
192
|
+
// For each match of this window
|
|
193
|
+
for (const match of windowMatches.matches) {
|
|
194
|
+
// Calculate the position where the full context would start
|
|
195
|
+
const fullContextStart = match.position - windowStart;
|
|
196
|
+
|
|
197
|
+
// Only consider valid positions
|
|
198
|
+
if (fullContextStart >= 1 && fullContextStart + contextLinesForMatching.length <= sourceLines.length + 1) {
|
|
199
|
+
candidatePositions.push({
|
|
200
|
+
position: fullContextStart,
|
|
201
|
+
windowConfidence: match.confidence,
|
|
202
|
+
windowStart
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// If no candidate positions found
|
|
210
|
+
if (candidatePositions.length === 0) {
|
|
211
|
+
return {
|
|
212
|
+
found: false,
|
|
213
|
+
reason: "No matching windows found",
|
|
214
|
+
matches: []
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Evaluate each candidate position with the full context
|
|
219
|
+
const evaluatedPositions = candidatePositions.map(candidate => {
|
|
220
|
+
const sourceBlock = sourceLines.slice(
|
|
221
|
+
candidate.position - 1,
|
|
222
|
+
candidate.position - 1 + contextLinesForMatching.length
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
const fullSimilarity = _calculateBlockSimilarity(
|
|
226
|
+
contextLinesForMatching.map(line => line.trim()),
|
|
227
|
+
sourceBlock.map(line => line.trim())
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
position: candidate.position,
|
|
232
|
+
confidence: fullSimilarity,
|
|
233
|
+
sourceBlock,
|
|
234
|
+
windowConfidence: candidate.windowConfidence
|
|
235
|
+
};
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Sort by confidence
|
|
239
|
+
evaluatedPositions.sort((a, b) => b.confidence - a.confidence);
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
found: evaluatedPositions.length > 0,
|
|
243
|
+
bestMatch: evaluatedPositions[0] || null,
|
|
244
|
+
matches: evaluatedPositions.slice(0, 3)
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
module.exports = {
|
|
249
|
+
findBestContextMatch,
|
|
250
|
+
findExactLineMatches,
|
|
251
|
+
findBestMatchWithSlidingWindow
|
|
252
|
+
};
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component: PatchUtils Hunk Corrector
|
|
3
|
+
* Block-UUID: f0c9d9fe-facc-4d04-a246-d24aab8de3d8
|
|
4
|
+
* Parent-UUID: N/A
|
|
5
|
+
* Version: 1.0.0
|
|
6
|
+
* Description: Generates corrected hunks based on validation results and fuzzy matching.
|
|
7
|
+
* Language: JavaScript
|
|
8
|
+
* Created-at: 2025-05-14T16:45:00.000Z
|
|
9
|
+
* Authors: Claude 3.7 Sonnet (v1.0.0)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Recalculates a hunk header based on the matched position and content
|
|
15
|
+
* @param {number} matchPosition - The 1-based line number where the hunk should start
|
|
16
|
+
* @param {string[]} contextLines - Context lines in the hunk
|
|
17
|
+
* @param {string[]} addedLines - Lines being added
|
|
18
|
+
* @param {string[]} removedLines - Lines being removed
|
|
19
|
+
* @returns {string} Corrected hunk header
|
|
20
|
+
*/
|
|
21
|
+
function recalculateHunkHeader(matchPosition, contextLines, addedLines, removedLines) {
|
|
22
|
+
// Calculate old count (context + removed)
|
|
23
|
+
const oldCount = contextLines.length + removedLines.length;
|
|
24
|
+
|
|
25
|
+
// Calculate new count (context + added)
|
|
26
|
+
const newCount = contextLines.length + addedLines.length;
|
|
27
|
+
|
|
28
|
+
// Format the header parts
|
|
29
|
+
const oldPart = oldCount === 1 ? `${matchPosition}` : `${matchPosition},${oldCount}`;
|
|
30
|
+
const newPart = newCount === 1 ? `${matchPosition}` : `${matchPosition},${newCount}`;
|
|
31
|
+
|
|
32
|
+
return `@@ -${oldPart} +${newPart} @@`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Reconstructs a hunk with the corrected header
|
|
37
|
+
* @param {string} header - The corrected hunk header
|
|
38
|
+
* @param {string[]} contextLines - Context lines in the hunk
|
|
39
|
+
* @param {string[]} addedLines - Lines being added
|
|
40
|
+
* @param {string[]} removedLines - Lines being removed
|
|
41
|
+
* @returns {string} Reconstructed hunk
|
|
42
|
+
*/
|
|
43
|
+
function reconstructHunk(header, contextLines, addedLines, removedLines) {
|
|
44
|
+
const lines = [header];
|
|
45
|
+
|
|
46
|
+
// A simple approach would be to add all context, then removals, then additions
|
|
47
|
+
// But that's not how patches typically look. Instead, we need to interleave them.
|
|
48
|
+
|
|
49
|
+
// This is a simplified approach - a real implementation would need to preserve
|
|
50
|
+
// the original structure of context/added/removed lines
|
|
51
|
+
|
|
52
|
+
// Add context and removed lines first (interleaved)
|
|
53
|
+
let contextIndex = 0;
|
|
54
|
+
let removedIndex = 0;
|
|
55
|
+
|
|
56
|
+
while (contextIndex < contextLines.length || removedIndex < removedLines.length) {
|
|
57
|
+
// Add a context line
|
|
58
|
+
if (contextIndex < contextLines.length) {
|
|
59
|
+
lines.push(` ${contextLines[contextIndex]}`);
|
|
60
|
+
contextIndex++;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Add a removed line
|
|
64
|
+
if (removedIndex < removedLines.length) {
|
|
65
|
+
lines.push(`-${removedLines[removedIndex]}`);
|
|
66
|
+
removedIndex++;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Add all addition lines at the end
|
|
71
|
+
for (const line of addedLines) {
|
|
72
|
+
lines.push(`+${line}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return lines.join('\n');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Generates a corrected hunk based on fuzzy matching results
|
|
80
|
+
* @param {object} originalHunk - The original parsed hunk
|
|
81
|
+
* @param {object} matchResult - Result from fuzzy matching
|
|
82
|
+
* @returns {object} Corrected hunk information
|
|
83
|
+
*/
|
|
84
|
+
function generateCorrectedHunk(originalHunk, matchResult) {
|
|
85
|
+
if (!matchResult.found || !matchResult.bestMatch) {
|
|
86
|
+
return {
|
|
87
|
+
success: false,
|
|
88
|
+
reason: matchResult.reason || "No suitable match found",
|
|
89
|
+
originalHunk
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const { contextLines, addedLines, removedLines } = originalHunk;
|
|
94
|
+
const matchPosition = matchResult.bestMatch.position;
|
|
95
|
+
|
|
96
|
+
// Recalculate the header
|
|
97
|
+
const correctedHeader = recalculateHunkHeader(
|
|
98
|
+
matchPosition,
|
|
99
|
+
contextLines,
|
|
100
|
+
addedLines,
|
|
101
|
+
removedLines
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// Reconstruct the hunk
|
|
105
|
+
const correctedHunkText = reconstructHunk(
|
|
106
|
+
correctedHeader,
|
|
107
|
+
contextLines,
|
|
108
|
+
addedLines,
|
|
109
|
+
removedLines
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
success: true,
|
|
114
|
+
correctedHunkText,
|
|
115
|
+
correctedHeader,
|
|
116
|
+
matchPosition,
|
|
117
|
+
confidence: matchResult.bestMatch.confidence,
|
|
118
|
+
originalHunk
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Preserves the original structure of a hunk while updating its header
|
|
124
|
+
* @param {string} originalHunkText - The original hunk text
|
|
125
|
+
* @param {string} correctedHeader - The corrected header
|
|
126
|
+
* @returns {string} Hunk with preserved structure but updated header
|
|
127
|
+
*/
|
|
128
|
+
function preserveHunkStructure(originalHunkText, correctedHeader) {
|
|
129
|
+
const lines = originalHunkText.split('\n');
|
|
130
|
+
|
|
131
|
+
// Replace just the header line
|
|
132
|
+
if (lines.length > 0 && lines[0].startsWith('@@ ')) {
|
|
133
|
+
lines[0] = correctedHeader;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return lines.join('\n');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Generates a corrected version of a problematic hunk
|
|
141
|
+
* @param {object} hunkValidation - Validation result for the hunk
|
|
142
|
+
* @param {object} matchResult - Result from fuzzy matching
|
|
143
|
+
* @returns {object} Corrected hunk with explanation
|
|
144
|
+
*/
|
|
145
|
+
function generateHunkCorrection(hunkValidation, matchResult) {
|
|
146
|
+
if (!hunkValidation || !matchResult) {
|
|
147
|
+
return {
|
|
148
|
+
success: false,
|
|
149
|
+
reason: "Missing validation or match result"
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (hunkValidation.valid) {
|
|
154
|
+
return {
|
|
155
|
+
success: true,
|
|
156
|
+
message: "Hunk is already valid",
|
|
157
|
+
hunkText: hunkValidation.hunk.originalLines.join('\n'),
|
|
158
|
+
needsCorrection: false
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!matchResult.found) {
|
|
163
|
+
return {
|
|
164
|
+
success: false,
|
|
165
|
+
reason: matchResult.reason || "No suitable match found",
|
|
166
|
+
diagnostics: {
|
|
167
|
+
originalHeader: hunkValidation.hunk.header,
|
|
168
|
+
contextLines: hunkValidation.hunk.contextLines,
|
|
169
|
+
matchAttempts: matchResult.matches || []
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Generate corrected hunk
|
|
175
|
+
const correctedHeader = recalculateHunkHeader(
|
|
176
|
+
matchResult.bestMatch.position,
|
|
177
|
+
hunkValidation.hunk.contextLines,
|
|
178
|
+
hunkValidation.hunk.addedLines,
|
|
179
|
+
hunkValidation.hunk.removedLines
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
// Preserve the original structure but update the header
|
|
183
|
+
const originalHunkText = [hunkValidation.hunk.header, ...hunkValidation.hunk.originalLines].join('\n');
|
|
184
|
+
const correctedHunkText = preserveHunkStructure(originalHunkText, correctedHeader);
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
success: true,
|
|
188
|
+
correctedHunkText,
|
|
189
|
+
correctedHeader,
|
|
190
|
+
originalHeader: hunkValidation.hunk.header,
|
|
191
|
+
matchPosition: matchResult.bestMatch.position,
|
|
192
|
+
confidence: matchResult.bestMatch.confidence,
|
|
193
|
+
needsCorrection: true,
|
|
194
|
+
explanation: `Hunk header corrected from "${hunkValidation.hunk.header}" to "${correctedHeader}" based on fuzzy matching with ${(matchResult.bestMatch.confidence * 100).toFixed(1)}% confidence.`
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
module.exports = {
|
|
199
|
+
recalculateHunkHeader,
|
|
200
|
+
reconstructHunk,
|
|
201
|
+
generateCorrectedHunk,
|
|
202
|
+
preserveHunkStructure,
|
|
203
|
+
generateHunkCorrection
|
|
204
|
+
};
|