@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,305 @@
1
+ /**
2
+ * Component: PatchUtils Hunk Validator
3
+ * Block-UUID: 7f9e4d2c-1b6a-4f85-9c3d-8e2a5b7f9d0e
4
+ * Parent-UUID: 6d830314-b75d-48df-b23a-8467672c9503
5
+ * Version: 1.3.0
6
+ * Description: Validates individual hunks with detailed diagnostics and fuzzy matching capabilities.
7
+ * Language: JavaScript
8
+ * Created-at: 2025-05-14T18:07:21.058Z
9
+ * Authors: Claude 3.7 Sonnet (v1.0.0), Claude 3.5 Sonnet (v1.1.0, v1.2.0), Claude 3.5 Sonnet (v1.3.0), Gemini 2.5 Flash (v1.3.0)
10
+ */
11
+
12
+
13
+ const jsdiff = require('diff');
14
+ const { findBestContextMatch, findBestMatchWithSlidingWindow } = require('./fuzzyMatcher');
15
+ const { removeLineNumbers } = require('../CodeBlockUtils/lineNumberFormatter');
16
+
17
+ /**
18
+ * Parses a hunk into its components
19
+ * @param {string} hunkText - The full text of a single hunk
20
+ * @returns {object} Parsed hunk with header, context lines, added lines, and removed lines
21
+ */
22
+ function parseHunk(hunkText) {
23
+ const lines = hunkText.split('\n');
24
+ const header = lines[0];
25
+ const contentLines = lines.slice(1);
26
+
27
+ // Arrays to store the different line types
28
+ const contextLines = [];
29
+ const contextLinesForMatching = [];
30
+ const addedLines = [];
31
+ const removedLines = [];
32
+
33
+ // Track current position in the hunk for building matching context
34
+ let currentPosition = 0;
35
+
36
+ for (const line of contentLines) {
37
+ // Clean line numbers from the content
38
+ const cleanLine = removeLineNumbers(line.substring(1));
39
+
40
+ if (line.startsWith(' ')) {
41
+ // Regular context line - add to both context arrays
42
+ contextLines.push(cleanLine);
43
+ contextLinesForMatching.push(cleanLine);
44
+ currentPosition++;
45
+ } else if (line.startsWith('+')) {
46
+ // Added line - only track in addedLines
47
+ addedLines.push(cleanLine);
48
+ currentPosition++;
49
+ } else if (line.startsWith('-')) {
50
+ // Removed line - add to removedLines and matching context
51
+ removedLines.push(cleanLine);
52
+ contextLinesForMatching.push(cleanLine);
53
+ currentPosition++;
54
+ }
55
+ }
56
+
57
+ return {
58
+ header,
59
+ // Original context lines (space-prefixed only)
60
+ contextLines,
61
+ // Context lines for matching against source (includes removed lines)
62
+ contextLinesForMatching,
63
+ // Lines being added and removed
64
+ addedLines,
65
+ removedLines,
66
+ // Original content with prefixes intact
67
+ originalLines: contentLines
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Parses a hunk header into its components
73
+ * @param {string} header - The hunk header (e.g., "@@ -1,3 +1,4 @@")
74
+ * @returns {object} Parsed header with oldStart, oldCount, newStart, newCount
75
+ */
76
+ function parseHunkHeader(header) {
77
+ const match = header.match(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);
78
+
79
+ if (!match) {
80
+ return {
81
+ valid: false,
82
+ error: "Invalid hunk header format"
83
+ };
84
+ }
85
+
86
+ const oldStart = parseInt(match[1], 10);
87
+ const oldCount = match[2] ? parseInt(match[2], 10) : 1;
88
+ const newStart = parseInt(match[3], 10);
89
+ const newCount = match[4] ? parseInt(match[4], 10) : 1;
90
+
91
+ return {
92
+ valid: true,
93
+ oldStart,
94
+ oldCount,
95
+ newStart,
96
+ newCount
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Attempts to apply a hunk directly at the specified position
102
+ * @param {string} sourceCode - Original source code
103
+ * @param {number} startLine - Starting line number (1-based)
104
+ * @param {string[]} contextLines - Context lines from the hunk
105
+ * @param {string[]} contextLinesForMatching - Context lines including removed lines for matching
106
+ * @param {string[]} addedLines - Lines to add
107
+ * @param {string[]} removedLines - Lines to remove
108
+ * @returns {object} Result of the direct application attempt
109
+ */
110
+ function tryApplyHunkDirect(sourceCode, startLine, contextLines, contextLinesForMatching, addedLines, removedLines) {
111
+ const sourceLines = sourceCode.split('\n');
112
+ const startIndex = startLine - 1;
113
+
114
+ // If the starting position seems wrong, try to find the correct position
115
+ if (startIndex < 0 || startIndex >= sourceLines.length) {
116
+ const matchResult = findBestContextMatch(contextLinesForMatching, sourceCode);
117
+
118
+ if (matchResult.found) {
119
+ // Recursively try applying at the found position
120
+ return tryApplyHunkDirect(
121
+ sourceCode,
122
+ matchResult.position + 1,
123
+ contextLines,
124
+ contextLinesForMatching,
125
+ addedLines,
126
+ removedLines
127
+ );
128
+ }
129
+
130
+ return {
131
+ valid: false,
132
+ reason: "Could not find matching context in source code"
133
+ };
134
+ }
135
+
136
+ // Verify the complete context (including removed lines) matches at the current position
137
+ let contextMatches = true;
138
+ let sourceIndex = startIndex;
139
+
140
+ // Verify the complete context matches
141
+ for (const expectedLine of contextLinesForMatching) {
142
+ if (sourceIndex >= sourceLines.length) {
143
+ contextMatches = false;
144
+ break;
145
+ }
146
+
147
+ const sourceLine = sourceLines[sourceIndex].trim();
148
+ const expectedTrimmed = expectedLine.trim();
149
+
150
+ if (sourceLine !== expectedTrimmed) {
151
+ contextMatches = false;
152
+ break;
153
+ }
154
+
155
+ sourceIndex++;
156
+ }
157
+
158
+ if (!contextMatches) {
159
+ return {
160
+ valid: false,
161
+ reason: "Context lines (including removed lines) don't match at specified position"
162
+ };
163
+ }
164
+
165
+ // Apply the changes
166
+ const resultLines = [...sourceLines];
167
+
168
+ // Remove the old lines
169
+ resultLines.splice(startIndex, contextLinesForMatching.length);
170
+
171
+ // Add context and new lines
172
+ const newContent = [];
173
+ for (let i = 0; i < contextLines.length; i++) {
174
+ newContent.push(contextLines[i]);
175
+ if (i < addedLines.length) {
176
+ newContent.push(addedLines[i]);
177
+ }
178
+ }
179
+
180
+ // Add any remaining new lines
181
+ for (let i = contextLines.length; i < addedLines.length; i++) {
182
+ newContent.push(addedLines[i]);
183
+ }
184
+
185
+ // Insert the new content
186
+ resultLines.splice(startIndex, 0, ...newContent);
187
+
188
+ return {
189
+ valid: true,
190
+ result: resultLines.join('\n')
191
+ };
192
+ }
193
+
194
+ /**
195
+ * Validates a single hunk against source code with detailed diagnostics
196
+ * @param {string} sourceCode - Original source code
197
+ * @param {string} hunkText - The full text of a single hunk
198
+ * @returns {object} Detailed validation results
199
+ */
200
+ function validateHunk(sourceCode, hunkText) {
201
+ // Parse the hunk
202
+ const hunk = parseHunk(hunkText);
203
+ const headerInfo = parseHunkHeader(hunk.header);
204
+
205
+ if (!headerInfo.valid) {
206
+ return {
207
+ valid: false,
208
+ hunk,
209
+ diagnostics: {
210
+ headerValid: false,
211
+ headerError: headerInfo.error,
212
+ suggestedFix: null
213
+ }
214
+ };
215
+ }
216
+
217
+ // Try direct application first
218
+ const directResult = tryApplyHunkDirect(
219
+ sourceCode,
220
+ headerInfo.oldStart,
221
+ hunk.contextLines,
222
+ hunk.contextLinesForMatching,
223
+ hunk.addedLines,
224
+ hunk.removedLines
225
+ );
226
+
227
+ if (directResult.valid) {
228
+ return {
229
+ valid: true,
230
+ appliedText: directResult.result,
231
+ hunk,
232
+ headerInfo,
233
+ diagnostics: {
234
+ method: "direct",
235
+ confidence: 1.0,
236
+ message: "Hunk applied successfully at specified position"
237
+ }
238
+ };
239
+ }
240
+
241
+ // If direct application failed, try finding the correct position
242
+ const matchResult = findBestContextMatch(hunk.contextLinesForMatching, sourceCode); // Use the new array for matching
243
+
244
+ if (matchResult.found) {
245
+ const adjustedResult = tryApplyHunkDirect(
246
+ sourceCode,
247
+ matchResult.position + 1,
248
+ hunk.contextLines,
249
+ hunk.contextLinesForMatching, // Use the new array for matching
250
+ hunk.addedLines,
251
+ hunk.removedLines
252
+ );
253
+
254
+ if (adjustedResult.valid) {
255
+ return {
256
+ valid: true,
257
+ appliedText: adjustedResult.result,
258
+ hunk,
259
+ headerInfo,
260
+ diagnostics: {
261
+ method: "adjusted",
262
+ confidence: matchResult.confidence,
263
+ message: `Hunk applied successfully at adjusted position (line ${matchResult.position + 1})`
264
+ }
265
+ };
266
+ }
267
+ }
268
+
269
+ // If we get here, both direct and adjusted attempts failed
270
+ return {
271
+ valid: false,
272
+ hunk,
273
+ headerInfo,
274
+ diagnostics: {
275
+ method: "direct_failed",
276
+ error: directResult.reason,
277
+ message: `Failed to apply hunk: ${directResult.reason}`,
278
+ needsFuzzyMatching: true
279
+ }
280
+ };
281
+ }
282
+
283
+ /**
284
+ * Validates all hunks in a patch
285
+ * @param {string} sourceCode - Original source code
286
+ * @param {string[]} hunks - Array of hunk texts
287
+ * @returns {object[]} Array of validation results for each hunk
288
+ */
289
+ function validateHunks(sourceCode, hunks) {
290
+ return hunks.map((hunkText, index) => {
291
+ const result = validateHunk(sourceCode, hunkText);
292
+ return {
293
+ ...result,
294
+ hunkIndex: index
295
+ };
296
+ });
297
+ }
298
+
299
+ module.exports = {
300
+ parseHunk,
301
+ parseHunkHeader,
302
+ validateHunk,
303
+ validateHunks,
304
+ tryApplyHunkDirect
305
+ };
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Component: PatchUtils Index
3
+ * Block-UUID: a7827938-bbfd-4c4b-83a7-fcf39ede8c6e
4
+ * Parent-UUID: f4a8495e-5612-40b5-8ec3-12c8e06886d5
5
+ * Version: 2.0.0
6
+ * Description: Aggregates and exports public functions from the enhanced PatchUtils modules.
7
+ * Language: JavaScript
8
+ * Created-at: 2025-05-14T16:55:00.000Z
9
+ * Authors: Gemini 2.5 Pro (v1.0.0), Claude 3.7 Sonnet (v2.0.0)
10
+ */
11
+
12
+
13
+ // Import original modules (for backward compatibility)
14
+ const parser = require('./patchParser');
15
+ const processor = require('./patchProcessor');
16
+ const headerFormatter = require('./patchHeaderFormatter');
17
+ const verifier = require('./patchVerifier');
18
+
19
+ // Import new enhanced modules
20
+ const hunkValidator = require('./hunkValidator');
21
+ const fuzzyMatcher = require('./fuzzyMatcher');
22
+ const hunkCorrector = require('./hunkCorrector');
23
+ const diagnosticReporter = require('./diagnosticReporter');
24
+ const patchExtractor = require('./patchExtractor');
25
+ const enhancedProcessor = require('./enhancedPatchProcessor');
26
+
27
+ /**
28
+ * @typedef {object} PatchValidationResult
29
+ * @property {boolean} valid - Whether the patch is valid
30
+ * @property {boolean} fixable - Whether invalid hunks can be fixed
31
+ * @property {object[]} hunkResults - Detailed results for each hunk
32
+ * @property {string} humanReadableDiagnostics - Human-readable diagnostic report
33
+ * @property {object} llmFeedback - Structured feedback for LLMs
34
+ */
35
+
36
+ /**
37
+ * @typedef {object} PatchApplicationResult
38
+ * @property {boolean} success - Whether the patch was applied successfully
39
+ * @property {string} patchedText - The resulting text after applying the patch
40
+ * @property {boolean} patchWasCorrected - Whether the patch needed correction
41
+ * @property {string} correctedPatch - The corrected patch if applicable
42
+ * @property {object[]} hunkResults - Detailed results for each hunk
43
+ * @property {string} humanReadableDiagnostics - Human-readable diagnostic report
44
+ * @property {object} llmFeedback - Structured feedback for LLMs
45
+ */
46
+
47
+ /**
48
+ * @typedef {object} HunkValidationResult
49
+ * @property {boolean} valid - Whether the hunk is valid
50
+ * @property {object} hunk - Parsed hunk information
51
+ * @property {object} headerInfo - Parsed header information
52
+ * @property {object} diagnostics - Detailed diagnostic information
53
+ */
54
+
55
+ // Export all modules
56
+ module.exports = {
57
+ // Original exports (for backward compatibility)
58
+ validateAndParseContextPatch: parser.validateAndParseContextPatch,
59
+ determinePatchFormat: parser.determinePatchFormat,
60
+ extractPatchMetadata: parser.extractPatchMetadata,
61
+ validatePatchMetadata: parser.validatePatchMetadata,
62
+ extractPatchContent: parser.extractPatchContent,
63
+ extractContextPatches: parser.extractContextPatches,
64
+ isPatchBlock: parser.isPatchBlock,
65
+ detectPatch: parser.detectPatch,
66
+ findAllPatches: parser.findAllPatches,
67
+ applyPatch: processor.applyPatch,
68
+ createPatch: processor.createPatch,
69
+ convertContextPatchToDiff: processor.convertContextPatchToDiff,
70
+ convertDiffToContextPatch: processor.convertDiffToContextPatch,
71
+ /**
72
+ * Creates a patch between two full code block strings (including their metadata headers).
73
+ * @param {string} sourceCodeBlockText - The complete string of the original code block.
74
+ * @param {string} targetCodeBlockText - The complete string of the modified code block.
75
+ * @param {object} patchMetadata - Metadata for the patch header.
76
+ * @returns {string} Patch in unified diff format.
77
+ */
78
+ createPatchFromCodeBlocks: processor.createPatchFromCodeBlocks,
79
+ formatCodeBlockHeader: headerFormatter.formatCodeBlockHeader,
80
+ verifyAndCorrectLineNumbers: verifier.verifyAndCorrectLineNumbers,
81
+
82
+ // New enhanced exports
83
+ // Hunk validation
84
+ parseHunk: hunkValidator.parseHunk,
85
+ parseHunkHeader: hunkValidator.parseHunkHeader,
86
+ validateHunk: hunkValidator.validateHunk,
87
+ validateHunks: hunkValidator.validateHunks,
88
+
89
+ // Fuzzy matching
90
+ findBestContextMatch: fuzzyMatcher.findBestContextMatch,
91
+ findExactLineMatches: fuzzyMatcher.findExactLineMatches,
92
+ findBestMatchWithSlidingWindow: fuzzyMatcher.findBestMatchWithSlidingWindow,
93
+
94
+ // Hunk correction
95
+ recalculateHunkHeader: hunkCorrector.recalculateHunkHeader,
96
+ reconstructHunk: hunkCorrector.reconstructHunk,
97
+ generateCorrectedHunk: hunkCorrector.generateCorrectedHunk,
98
+ generateHunkCorrection: hunkCorrector.generateHunkCorrection,
99
+
100
+ // Diagnostic reporting
101
+ generateHumanReadableDiagnostics: diagnosticReporter.generateHumanReadableDiagnostics,
102
+ generateLLMFeedback: diagnosticReporter.generateLLMFeedback,
103
+ formatDiagnosticSummary: diagnosticReporter.formatDiagnosticSummary,
104
+
105
+ // Patch extraction
106
+ extractHunks: patchExtractor.extractHunks,
107
+ cleanHunkLineNumbers: patchExtractor.cleanHunkLineNumbers,
108
+ extractAndCleanHunks: patchExtractor.extractAndCleanHunks,
109
+ reconstructPatch: patchExtractor.reconstructPatch,
110
+
111
+ // Enhanced patch processing
112
+ /**
113
+ * Applies a patch with detailed diagnostics
114
+ * @param {string} sourceText - Original source code
115
+ * @param {string} patchText - Patch text
116
+ * @returns {PatchApplicationResult} Comprehensive result with diagnostics
117
+ */
118
+ applyPatchWithDiagnostics: enhancedProcessor.applyPatchWithDiagnostics,
119
+
120
+ /**
121
+ * Validates a patch without applying it
122
+ * @param {string} sourceText - Original source code
123
+ * @param {string} patchText - Patch text
124
+ * @returns {PatchValidationResult} Validation result with diagnostics
125
+ */
126
+ validatePatch: enhancedProcessor.validatePatch,
127
+
128
+ /**
129
+ * Applies a single hunk to source code
130
+ * @param {string} sourceText - Original source code
131
+ * @param {string} hunkText - Text of a single hunk
132
+ * @returns {object} Result of applying the hunk
133
+ */
134
+ applyHunk: enhancedProcessor.applyHunk
135
+ };
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Component: PatchUtils Patch Extractor
3
+ * Block-UUID: ad8cda91-244b-4f47-9847-d6c0d93424fc
4
+ * Parent-UUID: N/A
5
+ * Version: 1.0.0
6
+ * Description: Extracts individual hunks from a patch and cleans line numbers from content.
7
+ * Language: JavaScript
8
+ * Created-at: 2025-05-14T16:50:00.000Z
9
+ * Authors: Claude 3.7 Sonnet (v1.0.0)
10
+ */
11
+
12
+
13
+ const { removeLineNumbers } = require('../CodeBlockUtils/lineNumberFormatter');
14
+
15
+ /**
16
+ * Extracts individual hunks from a patch
17
+ * @param {string} patchText - Full patch text including metadata and markers
18
+ * @returns {object} Object containing metadata and array of hunk texts
19
+ */
20
+ function extractHunks(patchText) {
21
+ if (!patchText) {
22
+ return { metadata: {}, hunks: [] };
23
+ }
24
+
25
+ const lines = patchText.split('\n');
26
+ const metadata = {};
27
+ const hunks = [];
28
+
29
+ let inMetadata = true;
30
+ let inHunk = false;
31
+ let currentHunk = [];
32
+ let inPatchContent = false;
33
+
34
+ for (let i = 0; i < lines.length; i++) {
35
+ const line = lines[i];
36
+
37
+ // Check for patch start marker
38
+ if (line === '# --- PATCH START MARKER ---') {
39
+ inMetadata = false;
40
+ inPatchContent = true;
41
+ continue;
42
+ }
43
+
44
+ // Check for patch end marker
45
+ if (line === '# --- PATCH END MARKER ---') {
46
+ // Save the last hunk if we were in one
47
+ if (inHunk && currentHunk.length > 0) {
48
+ hunks.push(currentHunk.join('\n'));
49
+ currentHunk = [];
50
+ }
51
+ inPatchContent = false;
52
+ break;
53
+ }
54
+
55
+ // Process metadata
56
+ if (inMetadata) {
57
+ if (line.startsWith('# ') && line !== '# Patch Metadata') {
58
+ const colonIndex = line.indexOf(':');
59
+ if (colonIndex > 0) {
60
+ const key = line.substring(2, colonIndex).trim();
61
+ const value = line.substring(colonIndex + 1).trim();
62
+ metadata[key] = value;
63
+ }
64
+ }
65
+ continue;
66
+ }
67
+
68
+ // Process patch content
69
+ if (inPatchContent) {
70
+ // Check for hunk header
71
+ if (line.startsWith('@@ ')) {
72
+ // If we were already in a hunk, save it before starting a new one
73
+ if (inHunk && currentHunk.length > 0) {
74
+ hunks.push(currentHunk.join('\n'));
75
+ currentHunk = [];
76
+ }
77
+
78
+ inHunk = true;
79
+ currentHunk.push(line);
80
+ } else if (inHunk) {
81
+ // Add line to current hunk
82
+ currentHunk.push(line);
83
+ }
84
+ // Skip lines between hunks (like file headers)
85
+ }
86
+ }
87
+
88
+ // Add the last hunk if there is one
89
+ if (inHunk && currentHunk.length > 0) {
90
+ hunks.push(currentHunk.join('\n'));
91
+ }
92
+
93
+ return { metadata, hunks };
94
+ }
95
+
96
+ /**
97
+ * Cleans line numbers from hunk content
98
+ * @param {string} hunkText - Text of a single hunk
99
+ * @returns {string} Hunk with line numbers removed from content lines
100
+ */
101
+ function cleanHunkLineNumbers(hunkText) {
102
+ if (!hunkText) {
103
+ return '';
104
+ }
105
+
106
+ const lines = hunkText.split('\n');
107
+ const cleanedLines = [];
108
+
109
+ // First line is always the hunk header
110
+ if (lines.length > 0) {
111
+ cleanedLines.push(lines[0]);
112
+ }
113
+
114
+ // Process content lines
115
+ for (let i = 1; i < lines.length; i++) {
116
+ const line = lines[i];
117
+
118
+ if (line.startsWith(' ') || line.startsWith('+') || line.startsWith('-')) {
119
+ const prefix = line[0];
120
+ const content = removeLineNumbers(line.substring(1));
121
+ cleanedLines.push(`${prefix}${content}`);
122
+ } else {
123
+ // Non-content line, keep as is
124
+ cleanedLines.push(line);
125
+ }
126
+ }
127
+
128
+ return cleanedLines.join('\n');
129
+ }
130
+
131
+ /**
132
+ * Extracts and cleans all hunks from a patch
133
+ * @param {string} patchText - Full patch text
134
+ * @returns {object} Object with metadata and cleaned hunks
135
+ */
136
+ function extractAndCleanHunks(patchText) {
137
+ const { metadata, hunks } = extractHunks(patchText);
138
+ const cleanedHunks = hunks.map(cleanHunkLineNumbers);
139
+
140
+ return { metadata, hunks: cleanedHunks };
141
+ }
142
+
143
+ /**
144
+ * Reconstructs a patch with cleaned hunks
145
+ * @param {object} metadata - Patch metadata
146
+ * @param {string[]} hunks - Array of hunk texts
147
+ * @returns {string} Reconstructed patch
148
+ */
149
+ function reconstructPatch(metadata, hunks) {
150
+ // Format metadata
151
+ let result = '# Patch Metadata\n';
152
+
153
+ for (const [key, value] of Object.entries(metadata)) {
154
+ result += `# ${key}: ${value}\n`;
155
+ }
156
+
157
+ // Add patch markers and hunks
158
+ result += '\n# --- PATCH START MARKER ---\n';
159
+ result += '--- Original\n';
160
+ result += '+++ Modified\n';
161
+
162
+ // Add hunks with a blank line between them
163
+ result += hunks.join('\n\n');
164
+
165
+ result += '\n# --- PATCH END MARKER ---';
166
+
167
+ return result;
168
+ }
169
+
170
+ module.exports = {
171
+ extractHunks,
172
+ cleanHunkLineNumbers,
173
+ extractAndCleanHunks,
174
+ reconstructPatch
175
+ };