@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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/dist/gitsense-chat-utils.cjs.js +10977 -0
  3. package/dist/gitsense-chat-utils.esm.js +10975 -0
  4. package/dist/gsc-utils.cjs.js +11043 -0
  5. package/dist/gsc-utils.esm.js +11041 -0
  6. package/package.json +37 -0
  7. package/src/AnalysisBlockUtils.js +151 -0
  8. package/src/ChatUtils.js +126 -0
  9. package/src/CodeBlockUtils/blockExtractor.js +277 -0
  10. package/src/CodeBlockUtils/blockProcessor.js +559 -0
  11. package/src/CodeBlockUtils/blockProcessor.js.rej +8 -0
  12. package/src/CodeBlockUtils/constants.js +62 -0
  13. package/src/CodeBlockUtils/continuationUtils.js +191 -0
  14. package/src/CodeBlockUtils/headerParser.js +175 -0
  15. package/src/CodeBlockUtils/headerUtils.js +236 -0
  16. package/src/CodeBlockUtils/index.js +83 -0
  17. package/src/CodeBlockUtils/lineNumberFormatter.js +117 -0
  18. package/src/CodeBlockUtils/markerRemover.js +89 -0
  19. package/src/CodeBlockUtils/patchIntegration.js +38 -0
  20. package/src/CodeBlockUtils/relationshipUtils.js +159 -0
  21. package/src/CodeBlockUtils/updateCodeBlock.js +372 -0
  22. package/src/CodeBlockUtils/uuidUtils.js +48 -0
  23. package/src/ContextUtils.js +180 -0
  24. package/src/GSToolBlockUtils.js +108 -0
  25. package/src/GitSenseChatUtils.js +386 -0
  26. package/src/JsonUtils.js +101 -0
  27. package/src/LLMUtils.js +31 -0
  28. package/src/MessageUtils.js +460 -0
  29. package/src/PatchUtils/constants.js +72 -0
  30. package/src/PatchUtils/diagnosticReporter.js +213 -0
  31. package/src/PatchUtils/enhancedPatchProcessor.js +390 -0
  32. package/src/PatchUtils/fuzzyMatcher.js +252 -0
  33. package/src/PatchUtils/hunkCorrector.js +204 -0
  34. package/src/PatchUtils/hunkValidator.js +305 -0
  35. package/src/PatchUtils/index.js +135 -0
  36. package/src/PatchUtils/patchExtractor.js +175 -0
  37. package/src/PatchUtils/patchHeaderFormatter.js +143 -0
  38. package/src/PatchUtils/patchParser.js +289 -0
  39. package/src/PatchUtils/patchProcessor.js +389 -0
  40. package/src/PatchUtils/patchVerifier/constants.js +23 -0
  41. package/src/PatchUtils/patchVerifier/detectAndFixOverlappingHunks.js +281 -0
  42. package/src/PatchUtils/patchVerifier/detectAndFixRedundantChanges.js +404 -0
  43. package/src/PatchUtils/patchVerifier/formatAndAddLineNumbers.js +165 -0
  44. package/src/PatchUtils/patchVerifier/index.js +25 -0
  45. package/src/PatchUtils/patchVerifier/verifyAndCorrectHunkHeaders.js +202 -0
  46. package/src/PatchUtils/patchVerifier/verifyAndCorrectLineNumbers.js +254 -0
  47. package/src/SharedUtils/timestampUtils.js +41 -0
  48. package/src/SharedUtils/versionUtils.js +58 -0
@@ -0,0 +1,404 @@
1
+ /**
2
+ * Component: PatchUtils Detect and Fix Redundant Changes
3
+ * Block-UUID: ffd6e8fc-399d-4254-827c-ef17e2f7d0d5
4
+ * Parent-UUID: 2308ed72-91ff-48ba-bc80-310c23c01ff1
5
+ * Version: 1.0.0
6
+ * Description: Detects and optionally fixes redundant changes in a patch where content is deleted and re-added identically.
7
+ * Language: JavaScript
8
+ * Created-at: 2025-05-14T03:48:02.248Z
9
+ * Authors: Gemini 2.5 Flash Thinking (v1.0.0)
10
+ */
11
+
12
+
13
+ const { CONTENT_LINE_REGEX, HUNK_HEADER_REGEX } = require('./constants');
14
+
15
+
16
+ /**
17
+ * Represents an error found during patch verification.
18
+ * @typedef {object} VerificationError
19
+ * @property {number} [originalLineNumber] - The line number reported in the patch file (for line errors).
20
+ * @property {number} [hunkIndex] - The index (0-based) of the hunk where the error occurred (for header errors).
21
+ * @property {string} lineContent - The full content of the problematic line or header in the patch.
22
+ * @property {string} message - Description of the error.
23
+ * @property {number} [searchStart] - The starting line number (1-based) of the search window in the source.
24
+ * @property {number} [searchEnd] - The ending line number (1-based) of the search window in the source.
25
+ * @property {string} [errorType] - Type of error (e.g., 'redundant-change', 'line-mismatch', 'header-mismatch').
26
+ * @property {string[]} [suggestedFix] - Suggested lines to replace the problematic section.
27
+ */
28
+
29
+
30
+ /**
31
+ * Detects and optionally fixes redundant changes in a patch, where content is unnecessarily
32
+ * deleted and then re-added with identical content. This is a common issue in LLM-generated
33
+ * patches that can cause "malformed patch" errors when applying the patch.
34
+ *
35
+ * @param {string} patchText - The full text of the patch (including metadata and markers).
36
+ * @param {boolean} [autoFix=false] - Whether to automatically fix detected redundant changes.
37
+ * @returns {{
38
+ * correctedPatchText: string,
39
+ * correctionsMade: boolean,
40
+ * errors: VerificationError[]
41
+ * }} - An object containing the potentially corrected patch text, a flag indicating if corrections were made, and an array of errors encountered.
42
+ * @throws {Error} If patchText is not a string.
43
+ */
44
+ function detectAndFixRedundantChanges(patchText, autoFix = false) {
45
+ if (typeof patchText !== 'string') {
46
+ throw new Error("Invalid input: patchText must be a string.");
47
+ }
48
+
49
+ const lines = patchText.split('\n');
50
+ const outputLines = [];
51
+ const errors = [];
52
+ let correctionsMade = false;
53
+ let currentHunkIndex = -1;
54
+ let processingHunk = false;
55
+ let currentHunkStart = -1;
56
+
57
+ for (let i = 0; i < lines.length; i++) {
58
+ const line = lines[i];
59
+
60
+ if (line.startsWith('@@ ')) {
61
+ // Start of a new hunk
62
+ if (processingHunk) {
63
+ // Process the previous hunk before starting a new one
64
+ const result = processRedundantChanges(
65
+ lines.slice(currentHunkStart, i),
66
+ currentHunkIndex,
67
+ autoFix
68
+ );
69
+
70
+ outputLines.push(...result.correctedLines);
71
+ errors.push(...result.errors);
72
+ if (result.corrected) correctionsMade = true;
73
+ }
74
+
75
+ processingHunk = true;
76
+ currentHunkIndex++;
77
+ currentHunkStart = i;
78
+ outputLines.push(line); // Add the hunk header
79
+ } else if (line === '# --- PATCH END MARKER ---') {
80
+ // End of patch content
81
+ if (processingHunk) {
82
+ // Process the last hunk
83
+ const result = processRedundantChanges(
84
+ lines.slice(currentHunkStart, i),
85
+ currentHunkIndex,
86
+ autoFix
87
+ );
88
+
89
+ // Remove the hunk header we already added
90
+ outputLines.pop();
91
+ outputLines.push(...result.correctedLines);
92
+ errors.push(...result.errors);
93
+ if (result.corrected) correctionsMade = true;
94
+ processingHunk = false;
95
+ }
96
+ outputLines.push(line); // Add the end marker
97
+ } else if (!processingHunk) {
98
+ // Outside of a hunk (metadata, markers, etc.)
99
+ outputLines.push(line);
100
+ }
101
+ // We don't add hunk content lines here - they're handled in processRedundantChanges
102
+ }
103
+
104
+ // Process the last hunk if we're still in one and there was no end marker
105
+ if (processingHunk) {
106
+ const result = processRedundantChanges(
107
+ lines.slice(currentHunkStart),
108
+ currentHunkIndex,
109
+ autoFix
110
+ );
111
+
112
+ // Remove the hunk header we already added
113
+ outputLines.pop();
114
+ outputLines.push(...result.correctedLines);
115
+ errors.push(...result.errors);
116
+ if (result.corrected) correctionsMade = true;
117
+ }
118
+
119
+ return {
120
+ correctedPatchText: outputLines.join('\n'),
121
+ correctionsMade,
122
+ errors
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Helper function to process a single hunk for redundant changes.
128
+ * @param {string[]} hunkLines - Array of lines in the hunk, including the header.
129
+ * @param {number} hunkIndex - The 0-based index of this hunk in the patch.
130
+ * @param {boolean} autoFix - Whether to automatically fix detected issues.
131
+ * @returns {{correctedLines: string[], corrected: boolean, errors: VerificationError[]}}
132
+ * @private
133
+ */
134
+ function processRedundantChanges(hunkLines, hunkIndex, autoFix) {
135
+ if (hunkLines.length < 2) {
136
+ return { correctedLines: hunkLines, corrected: false, errors: [] };
137
+ }
138
+
139
+ const errors = [];
140
+ let corrected = false;
141
+
142
+ // First line should be the hunk header
143
+ const headerLine = hunkLines[0];
144
+ const headerMatch = headerLine.match(HUNK_HEADER_REGEX);
145
+ if (!headerMatch) {
146
+ return {
147
+ correctedLines: hunkLines,
148
+ corrected: false,
149
+ errors: [{
150
+ hunkIndex,
151
+ lineContent: headerLine,
152
+ message: "Failed to parse hunk header.",
153
+ errorType: 'header-parse-error'
154
+ }]
155
+ };
156
+ }
157
+
158
+ // Extract content lines (skip the header)
159
+ const contentLines = hunkLines.slice(1);
160
+
161
+ // Group consecutive deletion and addition lines
162
+ const groups = [];
163
+ let currentGroup = { deletions: [], additions: [], startIndex: -1 };
164
+
165
+ for (let i = 0; i < contentLines.length; i++) {
166
+ const line = contentLines[i];
167
+
168
+ if (line.startsWith('-')) {
169
+ if (currentGroup.additions.length > 0) {
170
+ // We've been collecting additions and now hit a deletion
171
+ // This is a new group
172
+ groups.push({ ...currentGroup });
173
+ currentGroup = { deletions: [line], additions: [], startIndex: i };
174
+ } else {
175
+ // Continue collecting deletions
176
+ if (currentGroup.startIndex === -1) currentGroup.startIndex = i;
177
+ currentGroup.deletions.push(line);
178
+ }
179
+ } else if (line.startsWith('+')) {
180
+ // Continue collecting additions
181
+ if (currentGroup.startIndex === -1) currentGroup.startIndex = i;
182
+ currentGroup.additions.push(line);
183
+ } else {
184
+ // Context line - end the current group if it has any content
185
+ if (currentGroup.deletions.length > 0 || currentGroup.additions.length > 0) {
186
+ groups.push({ ...currentGroup });
187
+ currentGroup = { deletions: [], additions: [], startIndex: -1 };
188
+ }
189
+ }
190
+ }
191
+
192
+ // Add the last group if it has content
193
+ if (currentGroup.deletions.length > 0 || currentGroup.additions.length > 0) {
194
+ groups.push(currentGroup);
195
+ }
196
+
197
+ // Check each group for redundant changes
198
+ const redundantGroups = [];
199
+
200
+ for (const group of groups) {
201
+ if (group.deletions.length > 0 && group.additions.length > 0) {
202
+ // Extract content without diff markers and line numbers
203
+ const deletionContents = group.deletions.map(line => {
204
+ const match = line.match(CONTENT_LINE_REGEX);
205
+ return match ? match[3].trim() : line.substring(1).trim();
206
+ });
207
+
208
+ const additionContents = group.additions.map(line => {
209
+ const match = line.match(CONTENT_LINE_REGEX);
210
+ return match ? match[3].trim() : line.substring(1).trim();
211
+ });
212
+
213
+ // Check if the contents are identical
214
+ if (deletionContents.length === additionContents.length) {
215
+ let identical = true;
216
+ for (let i = 0; i < deletionContents.length; i++) {
217
+ if (deletionContents[i] !== additionContents[i]) {
218
+ identical = false;
219
+ break;
220
+ }
221
+ }
222
+
223
+ if (identical) {
224
+ redundantGroups.push({
225
+ ...group,
226
+ fixedLines: group.deletions.map((line, i) => {
227
+ // Convert to context lines by replacing '-' with ' '
228
+ const match = line.match(CONTENT_LINE_REGEX);
229
+ if (match) {
230
+ return ` ${match[2]}: ${match[3]}`;
231
+ } else {
232
+ // Fallback if regex fails, try to preserve original line number if possible
233
+ const fallbackMatch = line.match(/^-\s*(\d+):\s?(.*)$/);
234
+ if (fallbackMatch) {
235
+ return ` ${fallbackMatch[1]}: ${fallbackMatch[2]}`;
236
+ }
237
+ return ` ${line.substring(1)}`; // Just remove the '-'
238
+ }
239
+ })
240
+ });
241
+ }
242
+ }
243
+ }
244
+ }
245
+
246
+ // If we found redundant groups, report them and optionally fix
247
+ if (redundantGroups.length > 0) {
248
+ for (const group of redundantGroups) {
249
+ // Calculate line numbers relative to the start of the hunk content lines (after the header)
250
+ const startLineInHunkContent = group.startIndex;
251
+ const endLineInHunkContent = startLineInHunkContent + group.deletions.length + group.additions.length - 1;
252
+
253
+ // Find the actual line number in the patch file for the start of the redundant block
254
+ // This requires iterating through the original hunkLines to find the line index
255
+ let actualStartLineInPatch = -1;
256
+ let currentContentLineIndex = 0;
257
+ for(let k = 1; k < hunkLines.length; k++) { // Start from 1 to skip header
258
+ if (currentContentLineIndex === startLineInHunkContent) {
259
+ // The line index in the original patch file is the index in hunkLines + the start line of the hunk in the full patch
260
+ // We don't have the start line of the hunk in the full patch here, so we'll report relative to the hunk start.
261
+ // A better approach might be to pass the global line index to processHunk.
262
+ // For now, report relative to the start of the hunk content.
263
+ actualStartLineInPatch = k; // Index within hunkLines (0-based)
264
+ break;
265
+ }
266
+ // Only increment content line index for lines that are part of the content (not header)
267
+ if (!hunkLines[k].startsWith('@@ ')) {
268
+ currentContentLineIndex++;
269
+ }
270
+ }
271
+
272
+
273
+ errors.push({
274
+ hunkIndex,
275
+ // Report the line in the original hunkLines array where the redundant block starts
276
+ lineContent: hunkLines[actualStartLineInPatch !== -1 ? actualStartLineInPatch : group.startIndex + 1], // Fallback to index relative to content start + 1 for header
277
+ message: `Redundant change detected: content is deleted and then re-added with identical content (starts at line ${startLineInHunkContent + 1} within hunk content).`,
278
+ errorType: 'redundant-change',
279
+ suggestedFix: autoFix ? group.fixedLines : undefined // Only suggest fix if autoFix is true
280
+ });
281
+ }
282
+
283
+ // If autoFix is enabled, replace the redundant changes with context lines
284
+ if (autoFix) {
285
+ let correctedContentLines = [...contentLines];
286
+
287
+ // Process groups in reverse order to avoid index shifting
288
+ redundantGroups.sort((a, b) => b.startIndex - a.startIndex);
289
+
290
+ for (const group of redundantGroups) {
291
+ const startIdx = group.startIndex;
292
+ const endIdx = startIdx + group.deletions.length + group.additions.length;
293
+
294
+ // Replace the redundant deletion+addition with context lines
295
+ correctedContentLines.splice(
296
+ startIdx,
297
+ endIdx - startIdx,
298
+ ...group.fixedLines
299
+ );
300
+ }
301
+
302
+ // Count the different types of lines in the corrected content
303
+ let contextLineCount = 0;
304
+ let deletionLineCount = 0;
305
+ let additionLineCount = 0;
306
+
307
+ for (const line of correctedContentLines) {
308
+ if (line.startsWith(' ')) contextLineCount++;
309
+ else if (line.startsWith('-')) deletionLineCount++;
310
+ else if (line.startsWith('+')) additionLineCount++;
311
+ }
312
+
313
+ // Parse the original header
314
+ const oldStart = parseInt(headerMatch[1], 10);
315
+ const newStart = parseInt(headerMatch[3], 10);
316
+
317
+ // Calculate the new counts
318
+ const oldCount = contextLineCount + deletionLineCount;
319
+ const newCount = contextLineCount + additionLineCount;
320
+
321
+ // If both counts are 0, this means the entire hunk was redundant changes
322
+ // In this case, we should preserve at least one context line to maintain a valid hunk
323
+ if (oldCount === 0 && newCount === 0) {
324
+ // Find the first line number from the original content
325
+ let lineNumber = null;
326
+ for (const line of contentLines) {
327
+ const match = line.match(CONTENT_LINE_REGEX);
328
+ if (match) {
329
+ lineNumber = match[2];
330
+ break;
331
+ }
332
+ }
333
+
334
+ // If we found a line number, add a context line
335
+ if (lineNumber) {
336
+ // Find the corresponding line in the original content
337
+ // Need to find the original line content to reconstruct the context line
338
+ let originalLineContent = '';
339
+ for (const line of contentLines) {
340
+ const match = line.match(CONTENT_LINE_REGEX);
341
+ if (match && match[2] === lineNumber) {
342
+ originalLineContent = match[3];
343
+ break;
344
+ }
345
+ }
346
+
347
+ if (originalLineContent) {
348
+ // Reconstruct the context line with the found line number and content
349
+ const padding = String(lineNumber).length; // Use the line number's length for padding
350
+ correctedContentLines = [` ${String(lineNumber).padStart(padding, ' ')}: ${originalLineContent}`];
351
+ contextLineCount = 1;
352
+ } else {
353
+ // If we couldn't find the original content, just add a generic context line
354
+ correctedContentLines = [` ${String(oldStart).padStart(String(oldStart).length, ' ')}: ...`]; // Use oldStart as a fallback line number
355
+ contextLineCount = 1;
356
+ }
357
+ } else {
358
+ // If no line numbers were found at all, add a minimal valid hunk
359
+ correctedContentLines = [` ${String(oldStart).padStart(String(oldStart).length, ' ')}: ...`];
360
+ contextLineCount = 1;
361
+ }
362
+ }
363
+
364
+ // Recalculate in case we added a context line
365
+ const finalOldCount = contextLineCount + deletionLineCount;
366
+ const finalNewCount = contextLineCount + additionLineCount;
367
+
368
+ // Format the new header
369
+ const oldPart = finalOldCount === 1 ? `${oldStart}` : `${oldStart},${finalOldCount}`;
370
+ const newPart = finalNewCount === 1 ? `${newStart}` : `${newStart},${finalNewCount}`;
371
+ const newHeader = `@@ -${oldPart} +${newPart} @@`;
372
+
373
+ // If we have no content lines left, this hunk should be removed entirely
374
+ if (correctedContentLines.length === 0) {
375
+ return {
376
+ correctedLines: [], // Return empty array to signal this hunk should be removed
377
+ corrected: true,
378
+ errors: [...errors, {
379
+ hunkIndex,
380
+ lineContent: headerLine,
381
+ message: "Entire hunk consists of redundant changes and has been removed.",
382
+ errorType: 'hunk-removed'
383
+ }]
384
+ };
385
+ }
386
+
387
+ return {
388
+ correctedLines: [newHeader, ...correctedContentLines],
389
+ corrected: true,
390
+ errors
391
+ };
392
+ }
393
+ }
394
+
395
+ return {
396
+ correctedLines: hunkLines,
397
+ corrected: false,
398
+ errors
399
+ };
400
+ }
401
+
402
+ module.exports = {
403
+ detectAndFixRedundantChanges,
404
+ };
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Component: PatchUtils Format and Add Line Numbers
3
+ * Block-UUID: 37c3f528-dede-4d2f-9e4f-df87d1dce78f
4
+ * Parent-UUID: 2308ed72-91ff-48ba-bc80-310c23c01ff1
5
+ * Version: 1.0.0
6
+ * Description: Ensures patch content lines have NNN: line number prefixes with consistent padding, adding them if missing.
7
+ * Language: JavaScript
8
+ * Created-at: 2025-05-14T03:46:14.708Z
9
+ * Authors: Gemini 2.5 Flash Thinking (v1.0.0)
10
+ */
11
+
12
+
13
+ const { removeLineNumbers } = require('../../CodeBlockUtils/lineNumberFormatter'); // Assuming this utility exists
14
+ const { CONTENT_LINE_REGEX, HUNK_HEADER_REGEX } = require('./constants');
15
+
16
+
17
+ /**
18
+ * Ensures patch content lines have NNN: line number prefixes with consistent padding,
19
+ * adding them if missing. This function focuses solely on the line number prefix
20
+ * format and presence, not the correctness of the numbers themselves (which is
21
+ * handled by verifyAndCorrectLineNumbers).
22
+ *
23
+ * @param {string} patchText - The full text of the patch (including metadata, markers, etc.).
24
+ * @param {string} sourceText - The original source code text (MUST be clean, without NNN: prefixes).
25
+ * @returns {{
26
+ * formattedPatchText: string,
27
+ * correctionsMade: boolean
28
+ * }} - An object containing the potentially corrected patch text and a flag indicating if corrections were made.
29
+ * @throws {Error} If inputs are invalid (e.g., non-string text).
30
+ */
31
+ function formatAndAddLineNumbers(patchText, sourceText) {
32
+ if (typeof patchText !== 'string') {
33
+ throw new Error("Invalid input: patchText must be a string.");
34
+ }
35
+ if (typeof sourceText !== 'string') {
36
+ throw new Error("Invalid input: sourceText must be a string.");
37
+ }
38
+
39
+ const patchLines = patchText.split('\n');
40
+ const sourceLineCount = sourceText.split('\n').length;
41
+ // Calculate padding width based on the number of lines in the source file
42
+ const padding = String(sourceLineCount).length;
43
+ // Ensure minimum padding is 1 even for empty source
44
+ const requiredPadding = Math.max(1, padding);
45
+
46
+ const outputLines = [];
47
+ let correctionsMade = false;
48
+ let currentHunkOldStart = null;
49
+ let currentHunkNewStart = null;
50
+ let oldLineCounter = 0; // Counter for lines in the original file perspective within the hunk
51
+ let inHunk = false;
52
+
53
+ for (let i = 0; i < patchLines.length; i++) {
54
+ const currentPatchLine = patchLines[i];
55
+
56
+ const headerMatch = currentPatchLine.match(HUNK_HEADER_REGEX);
57
+ if (headerMatch) {
58
+ // Start of a new hunk
59
+ inHunk = true;
60
+ currentHunkOldStart = parseInt(headerMatch[1], 10);
61
+ currentHunkNewStart = parseInt(headerMatch[3], 10);
62
+ // Reset counter for the new hunk
63
+ oldLineCounter = 0;
64
+ outputLines.push(currentPatchLine);
65
+ continue; // Move to the next line
66
+ }
67
+
68
+ // Check for patch markers to potentially reset hunk state
69
+ if (currentPatchLine === '# --- PATCH START MARKER ---' || currentPatchLine === '# --- PATCH END MARKER ---') {
70
+ // Assume markers are outside hunks and reset hunk processing
71
+ inHunk = false;
72
+ outputLines.push(currentPatchLine);
73
+ continue;
74
+ }
75
+
76
+ if (inHunk) {
77
+ const contentMatch = currentPatchLine.match(CONTENT_LINE_REGEX);
78
+ const prefix = currentPatchLine.charAt(0); // Get the diff prefix (+, -, or space)
79
+
80
+ if (prefix === '+' || prefix === '-' || prefix === ' ') {
81
+ // This is a content line within a hunk
82
+ // Get content after the potential prefix and line number
83
+ const content = contentMatch ? contentMatch[3] : currentPatchLine.substring(1);
84
+
85
+ let newLineCounter = oldLineCounter; // Track position in the new file
86
+
87
+ let lineNumberToUse;
88
+ if (contentMatch) {
89
+ // Line already has a number prefix, use the parsed number
90
+ lineNumberToUse = parseInt(contentMatch[2], 10);
91
+ } else {
92
+ // Line is missing a number prefix, estimate based on hunk start and counter
93
+ // The number prefix NNN: always refers to the original file's line number
94
+ if (prefix === '+') {
95
+ // For addition lines, use the new file's line number
96
+ lineNumberToUse = currentHunkNewStart + newLineCounter;
97
+ } else {
98
+ // For context and deletion lines, use the original file's line number
99
+ lineNumberToUse = currentHunkOldStart + oldLineCounter;
100
+ }
101
+ }
102
+
103
+ const formattedLine = `${prefix} ${String(lineNumberToUse).padStart(requiredPadding, ' ')}: ${content}`;
104
+
105
+ if (formattedLine !== currentPatchLine) {
106
+ correctionsMade = true;
107
+ }
108
+
109
+ outputLines.push(formattedLine);
110
+
111
+ // Increment the oldLineCounter for context and deletion lines
112
+ // Addition lines (+), while part of the hunk, don't exist in the original file
113
+ // sequence for the purpose of this counter.
114
+ if (prefix === ' ' || prefix === '-') {
115
+ oldLineCounter++;
116
+ }
117
+ // For context and addition lines, increment the new file counter
118
+ if (prefix === ' ' || prefix === '+') {
119
+ newLineCounter++;
120
+ }
121
+ } else {
122
+ // Line is within a hunk but not a standard content line (e.g., comments within hunk)
123
+ // Add as is, but check if it looks like a malformed content line without prefix
124
+ if (currentPatchLine.length > 0 && (currentPatchLine.startsWith('+') || currentPatchLine.startsWith('-') || currentPatchLine.startsWith(' '))) {
125
+ // It starts with a diff prefix but didn't match the regex, likely missing NNN:
126
+ // Treat it as missing the prefix and add one
127
+ const content = currentPatchLine.substring(1);
128
+
129
+ let estimatedLineNumber;
130
+ if (prefix === '+') {
131
+ estimatedLineNumber = currentHunkNewStart + oldLineCounter; // Use new file position for additions
132
+ } else {
133
+ estimatedLineNumber = currentHunkOldStart + oldLineCounter; // Use old file position for context/deletions
134
+ }
135
+
136
+
137
+ const formattedLine = `${prefix} ${String(estimatedLineNumber).padStart(requiredPadding, ' ')}: ${content}`;
138
+ outputLines.push(formattedLine);
139
+ correctionsMade = true;
140
+
141
+ // Increment counter if it's a line type that affects the original file sequence
142
+ if (prefix === ' ' || prefix === '-') {
143
+ oldLineCounter++;
144
+ }
145
+
146
+ } else {
147
+ // Truly not a content line, add as is
148
+ outputLines.push(currentPatchLine);
149
+ }
150
+ }
151
+ } else {
152
+ // Line is outside a hunk (metadata, etc.)
153
+ outputLines.push(currentPatchLine);
154
+ }
155
+ }
156
+
157
+ return {
158
+ formattedPatchText: outputLines.join('\n'),
159
+ correctionsMade: correctionsMade
160
+ };
161
+ }
162
+
163
+ module.exports = {
164
+ formatAndAddLineNumbers,
165
+ };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Component: PatchUtils Verifier Index
3
+ * Block-UUID: 0bca5027-9656-44f8-89e1-e32991b386bd
4
+ * Parent-UUID: 2308ed72-91ff-48ba-bc80-310c23c01ff1
5
+ * Version: 1.0.0
6
+ * Description: Provides a backwards-compatible entry point for the patch verification utilities.
7
+ * Language: JavaScript
8
+ * Created-at: 2025-05-14T03:49:18.423Z
9
+ * Authors: Gemini 2.5 Flash Thinking (v1.0.0)
10
+ */
11
+
12
+
13
+ const { formatAndAddLineNumbers } = require('./formatAndAddLineNumbers');
14
+ const { verifyAndCorrectLineNumbers } = require('./verifyAndCorrectLineNumbers');
15
+ const { verifyAndCorrectHunkHeaders } = require('./verifyAndCorrectHunkHeaders');
16
+ const { detectAndFixRedundantChanges } = require('./detectAndFixRedundantChanges');
17
+ const { detectAndFixOverlappingHunks } = require('./detectAndFixOverlappingHunks');
18
+
19
+ module.exports = {
20
+ formatAndAddLineNumbers,
21
+ verifyAndCorrectLineNumbers,
22
+ verifyAndCorrectHunkHeaders,
23
+ detectAndFixRedundantChanges,
24
+ detectAndFixOverlappingHunks,
25
+ };