@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,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component: PatchUtils Verify and Correct Hunk Headers
|
|
3
|
+
* Block-UUID: 61c74798-bddb-47e1-be48-4a102659933c
|
|
4
|
+
* Parent-UUID: 2308ed72-91ff-48ba-bc80-310c23c01ff1
|
|
5
|
+
* Version: 1.0.0
|
|
6
|
+
* Description: Verifies and corrects the hunk headers within a patch based on the line number prefixes in the content lines.
|
|
7
|
+
* Language: JavaScript
|
|
8
|
+
* Created-at: 2025-05-14T03:47:25.525Z
|
|
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
|
+
* Verifies and corrects the hunk headers (`@@ -old,count +new,count @@`) within a patch
|
|
32
|
+
* based on the line number prefixes (`NNN:`) found in the hunk's content lines.
|
|
33
|
+
* Assumes the NNN: prefixes on content lines are already correct (e.g., after running verifyAndCorrectLineNumbers).
|
|
34
|
+
*
|
|
35
|
+
* @param {string} patchText - The full text of the patch (ideally, already processed by verifyAndCorrectLineNumbers).
|
|
36
|
+
* @returns {{
|
|
37
|
+
* correctedPatchText: string,
|
|
38
|
+
* correctionsMade: boolean,
|
|
39
|
+
* errors: VerificationError[]
|
|
40
|
+
* }} - An object containing the potentially corrected patch text, a flag indicating if corrections were made, and an array of errors encountered.
|
|
41
|
+
* @throws {Error} If patchText is not a string.
|
|
42
|
+
*/
|
|
43
|
+
function verifyAndCorrectHunkHeaders(patchText) {
|
|
44
|
+
if (typeof patchText !== 'string') {
|
|
45
|
+
throw new Error("Invalid input: patchText must be a string.");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const lines = patchText.split('\n');
|
|
49
|
+
const outputLines = [];
|
|
50
|
+
const errors = [];
|
|
51
|
+
let correctionsMade = false;
|
|
52
|
+
let currentHunkIndex = -1; // Track which hunk we are in
|
|
53
|
+
let currentHunkLines = [];
|
|
54
|
+
let processingHunk = false;
|
|
55
|
+
|
|
56
|
+
for (let i = 0; i < lines.length; i++) {
|
|
57
|
+
const line = lines[i];
|
|
58
|
+
|
|
59
|
+
if (line.startsWith('@@ ')) {
|
|
60
|
+
// End of previous hunk (if any), process it
|
|
61
|
+
if (processingHunk) {
|
|
62
|
+
const result = processHunk(outputLines[outputLines.length - 1], currentHunkLines, currentHunkIndex);
|
|
63
|
+
outputLines[outputLines.length - 1] = result.correctedHeader; // Replace header in output
|
|
64
|
+
outputLines.push(...currentHunkLines); // Add content lines
|
|
65
|
+
errors.push(...result.hunkErrors);
|
|
66
|
+
if (result.corrected) correctionsMade = true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Start of new hunk
|
|
70
|
+
processingHunk = true;
|
|
71
|
+
currentHunkIndex++;
|
|
72
|
+
currentHunkLines = [];
|
|
73
|
+
outputLines.push(line); // Add header temporarily, will be replaced after processing
|
|
74
|
+
|
|
75
|
+
} else if (processingHunk) {
|
|
76
|
+
// Line is part of the current hunk's content
|
|
77
|
+
// Stop collecting if we hit the end marker
|
|
78
|
+
if (line === '# --- PATCH END MARKER ---') {
|
|
79
|
+
processingHunk = false; // Stop processing before the marker
|
|
80
|
+
// Process the last hunk before adding the marker
|
|
81
|
+
const result = processHunk(outputLines[outputLines.length - 1], currentHunkLines, currentHunkIndex);
|
|
82
|
+
outputLines[outputLines.length - 1] = result.correctedHeader;
|
|
83
|
+
outputLines.push(...currentHunkLines);
|
|
84
|
+
errors.push(...result.hunkErrors);
|
|
85
|
+
if (result.corrected) correctionsMade = true;
|
|
86
|
+
outputLines.push(line); // Now add the end marker
|
|
87
|
+
} else {
|
|
88
|
+
currentHunkLines.push(line);
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
// Line is outside a hunk (metadata, markers, etc.)
|
|
92
|
+
outputLines.push(line);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Process the very last hunk if the patch didn't end with a marker
|
|
97
|
+
if (processingHunk) {
|
|
98
|
+
const result = processHunk(outputLines[outputLines.length - 1], currentHunkLines, currentHunkIndex);
|
|
99
|
+
outputLines[outputLines.length - 1] = result.correctedHeader;
|
|
100
|
+
outputLines.push(...currentHunkLines);
|
|
101
|
+
errors.push(...result.hunkErrors);
|
|
102
|
+
if (result.corrected) correctionsMade = true;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
correctedPatchText: outputLines.join('\n'),
|
|
107
|
+
correctionsMade: correctionsMade,
|
|
108
|
+
errors: errors
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Helper function to process a single hunk's header and content.
|
|
114
|
+
* @param {string} headerLine - The original hunk header line (@@ ... @@).
|
|
115
|
+
* @param {string[]} contentLines - Array of lines within the hunk (' ', '+', '-').
|
|
116
|
+
* @param {number} hunkIndex - The 0-based index of this hunk in the patch.
|
|
117
|
+
* @returns {{correctedHeader: string, corrected: boolean, hunkErrors: VerificationError[]}}
|
|
118
|
+
* @private
|
|
119
|
+
*/
|
|
120
|
+
function processHunk(headerLine, contentLines, hunkIndex) {
|
|
121
|
+
const hunkErrors = [];
|
|
122
|
+
let corrected = false;
|
|
123
|
+
|
|
124
|
+
// Parse the original header
|
|
125
|
+
const headerMatch = headerLine.match(HUNK_HEADER_REGEX);
|
|
126
|
+
if (!headerMatch) {
|
|
127
|
+
hunkErrors.push({ hunkIndex, lineContent: headerLine, message: "Failed to parse original hunk header." });
|
|
128
|
+
return { correctedHeader: headerLine, corrected, hunkErrors }; // Return original if unparseable
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// If there are no content lines, keep the original header
|
|
132
|
+
if (contentLines.length === 0) {
|
|
133
|
+
return { correctedHeader: headerLine, corrected: false, hunkErrors };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Track line numbers and counts
|
|
137
|
+
let firstOldLineNumber = null;
|
|
138
|
+
let lastOldLineNumber = null;
|
|
139
|
+
let firstNewLineNumber = null;
|
|
140
|
+
let lastNewLineNumber = null;
|
|
141
|
+
let additionCount = 0;
|
|
142
|
+
let deletionCount = 0;
|
|
143
|
+
|
|
144
|
+
// First pass: find the line number ranges
|
|
145
|
+
for (const line of contentLines) {
|
|
146
|
+
const match = line.match(CONTENT_LINE_REGEX);
|
|
147
|
+
|
|
148
|
+
if (!match) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const lineNum = parseInt(match[2], 10);
|
|
153
|
+
const prefix = match[1]; // ' ', '-', or '+'
|
|
154
|
+
|
|
155
|
+
if (prefix === ' ' || prefix === '-') {
|
|
156
|
+
// Update old file line numbers
|
|
157
|
+
if (firstOldLineNumber === null) firstOldLineNumber = lineNum;
|
|
158
|
+
lastOldLineNumber = lineNum;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (prefix === ' ' || prefix === '+') {
|
|
162
|
+
// Update new file line numbers
|
|
163
|
+
if (firstNewLineNumber === null) firstNewLineNumber = lineNum;
|
|
164
|
+
lastNewLineNumber = lineNum;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (prefix === '+') additionCount++;
|
|
168
|
+
if (prefix === '-') deletionCount++;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
// If we couldn't determine line numbers from content, use the original header values
|
|
173
|
+
if (firstOldLineNumber === null) {
|
|
174
|
+
firstOldLineNumber = parseInt(headerMatch[1], 10);
|
|
175
|
+
firstNewLineNumber = parseInt(headerMatch[3], 10);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Calculate the spans including all lines in the range
|
|
179
|
+
const oldSpan = lastOldLineNumber - firstOldLineNumber + 1;
|
|
180
|
+
const newSpan = lastNewLineNumber - firstNewLineNumber + 1;
|
|
181
|
+
|
|
182
|
+
// Adjust new span for additions and deletions
|
|
183
|
+
const finalOldSpan = oldSpan;
|
|
184
|
+
const finalNewSpan = oldSpan - deletionCount + additionCount;
|
|
185
|
+
|
|
186
|
+
// Format the new header
|
|
187
|
+
// Note: We always include the count even if it's 1, for consistency
|
|
188
|
+
const oldPart = `${firstOldLineNumber},${finalOldSpan}`;
|
|
189
|
+
const newPart = `${firstNewLineNumber},${finalNewSpan}`;
|
|
190
|
+
const correctedHeader = `@@ -${oldPart} +${newPart} @@`;
|
|
191
|
+
|
|
192
|
+
// Check if the header actually changed
|
|
193
|
+
if (correctedHeader !== headerLine) {
|
|
194
|
+
corrected = true;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return { correctedHeader, corrected, hunkErrors };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
module.exports = {
|
|
201
|
+
verifyAndCorrectHunkHeaders,
|
|
202
|
+
};
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component: PatchUtils Verify and Correct Line Numbers
|
|
3
|
+
* Block-UUID: 8a9c6d2f-e3b7-4f15-9d0a-b2c1e5f7a8d9
|
|
4
|
+
* Parent-UUID: 647c1aef-0552-4072-810c-59bf72e3fdad
|
|
5
|
+
* Version: 1.2.0
|
|
6
|
+
* Description: Verifies and corrects the NNN: line number prefixes on context and deletion lines within a patch.
|
|
7
|
+
* Language: JavaScript
|
|
8
|
+
* Created-at: 2025-05-14T06:27:12.345Z
|
|
9
|
+
* Authors: Gemini 2.5 Flash Thinking (v1.0.0), Claude 3.7 Sonnet (v1.1.0, v1.2.0)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const { removeLineNumbers } = require('../../CodeBlockUtils/lineNumberFormatter'); // Assuming this utility exists
|
|
14
|
+
const { CONTENT_LINE_REGEX } = require('./constants');
|
|
15
|
+
const { formatAndAddLineNumbers } = require('./formatAndAddLineNumbers'); // Import the helper
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Represents an error found during patch verification.
|
|
20
|
+
* @typedef {object} VerificationError
|
|
21
|
+
* @property {number} [originalLineNumber] - The line number reported in the patch file (for line errors).
|
|
22
|
+
* @property {number} [hunkIndex] - The index (0-based) of the hunk where the error occurred (for header errors).
|
|
23
|
+
* @property {string} lineContent - The full content of the problematic line or header in the patch.
|
|
24
|
+
* @property {string} message - Description of the error.
|
|
25
|
+
* @property {number} [searchStart] - The starting line number (1-based) of the search window in the source.
|
|
26
|
+
* @property {number} [searchEnd] - The ending line number (1-based) of the search window in the source.
|
|
27
|
+
* @property {string} [errorType] - Type of error (e.g., 'redundant-change', 'line-mismatch', 'header-mismatch').
|
|
28
|
+
* @property {string[]} [suggestedFix] - Suggested lines to replace the problematic section.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Verifies and corrects the `NNN:` line number prefixes on context (' ') and deletion ('-') lines
|
|
34
|
+
* within a patch by comparing their content against the original source code within a sliding window.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} patchText - The full text of the patch (including metadata, markers, and NNN: prefixes).
|
|
37
|
+
* @param {string} sourceText - The original source code text (MUST be clean, without NNN: prefixes).
|
|
38
|
+
* @param {number} [windowSize=251] - The total size of the search window (e.g., 5 means +/- 2 lines around the reported number). Must be an odd positive integer.
|
|
39
|
+
* @returns {{
|
|
40
|
+
* correctedPatchText: string,
|
|
41
|
+
* correctionsMade: boolean,
|
|
42
|
+
* errors: VerificationError[]
|
|
43
|
+
* }} - An object containing the potentially corrected patch text, a flag indicating if corrections were made, and an array of errors encountered.
|
|
44
|
+
* @throws {Error} If inputs are invalid (e.g., non-string text, invalid window size).
|
|
45
|
+
*/
|
|
46
|
+
function verifyAndCorrectLineNumbers(patchText, sourceText, windowSize = 251) {
|
|
47
|
+
if (typeof patchText !== 'string') {
|
|
48
|
+
throw new Error("Invalid input: patchText must be a string.");
|
|
49
|
+
}
|
|
50
|
+
if (typeof sourceText !== 'string') {
|
|
51
|
+
throw new Error("Invalid input: sourceText must be a string.");
|
|
52
|
+
}
|
|
53
|
+
if (!Number.isInteger(windowSize) || windowSize <= 0 || windowSize % 2 === 0) {
|
|
54
|
+
throw new Error("Invalid input: windowSize must be a positive odd integer (e.g., 3, 5, 7).");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Ensure line numbers are present and correctly formatted before verification
|
|
58
|
+
const formatResult = formatAndAddLineNumbers(patchText, sourceText);
|
|
59
|
+
const patchLines = formatResult.formattedPatchText.split('\n');
|
|
60
|
+
// Ensure source is clean (defensive check, though caller should provide clean source)
|
|
61
|
+
const sourceLines = removeLineNumbers(sourceText).split('\n');
|
|
62
|
+
const outputLines = [];
|
|
63
|
+
const errors = [];
|
|
64
|
+
let correctionsMade = formatResult.correctionsMade; // Inherit corrections made by formatter
|
|
65
|
+
const windowRadius = Math.floor(windowSize / 2);
|
|
66
|
+
const padding = String(sourceText.split('\n').length).length;
|
|
67
|
+
|
|
68
|
+
// Track current position in source and target files
|
|
69
|
+
let currentSourceLineNumber = 0;
|
|
70
|
+
let currentTargetLineNumber = 0;
|
|
71
|
+
|
|
72
|
+
// Track hunk boundaries
|
|
73
|
+
let inHunk = false;
|
|
74
|
+
let hunkSourceStart = 0;
|
|
75
|
+
let hunkTargetStart = 0;
|
|
76
|
+
|
|
77
|
+
// Process each line in the patch
|
|
78
|
+
for (let i = 0; i < patchLines.length; i++) {
|
|
79
|
+
const currentPatchLine = patchLines[i];
|
|
80
|
+
|
|
81
|
+
// Check for hunk headers to reset line tracking
|
|
82
|
+
if (currentPatchLine.startsWith('@@')) {
|
|
83
|
+
inHunk = true;
|
|
84
|
+
outputLines.push(currentPatchLine);
|
|
85
|
+
|
|
86
|
+
// Extract hunk header information to reset line counters
|
|
87
|
+
const hunkMatch = currentPatchLine.match(/@@ -(\d+),\d+ \+(\d+),\d+ @@/);
|
|
88
|
+
if (hunkMatch) {
|
|
89
|
+
hunkSourceStart = parseInt(hunkMatch[1], 10);
|
|
90
|
+
hunkTargetStart = parseInt(hunkMatch[2], 10);
|
|
91
|
+
currentSourceLineNumber = hunkSourceStart;
|
|
92
|
+
currentTargetLineNumber = hunkTargetStart;
|
|
93
|
+
}
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Skip non-hunk lines (metadata, markers, etc.)
|
|
98
|
+
if (
|
|
99
|
+
!inHunk ||
|
|
100
|
+
currentPatchLine.startsWith('#') || currentPatchLine.startsWith('---') || currentPatchLine.startsWith('+++')
|
|
101
|
+
) {
|
|
102
|
+
outputLines.push(currentPatchLine);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const match = currentPatchLine.match(CONTENT_LINE_REGEX);
|
|
107
|
+
|
|
108
|
+
// Process context lines (space-prefixed)
|
|
109
|
+
if (currentPatchLine.startsWith(' ')) {
|
|
110
|
+
if (match) {
|
|
111
|
+
const reportedLineNum = parseInt(match[2], 10);
|
|
112
|
+
const patchLineContent = match[3];
|
|
113
|
+
|
|
114
|
+
// Verify content against source
|
|
115
|
+
let foundMatch = false;
|
|
116
|
+
let bestMatchIndex = -1;
|
|
117
|
+
|
|
118
|
+
// Define search window
|
|
119
|
+
const searchStartIndex = Math.max(0, currentSourceLineNumber - 1 - windowRadius);
|
|
120
|
+
const searchEndIndex = Math.min(sourceLines.length - 1, currentSourceLineNumber - 1 + windowRadius);
|
|
121
|
+
|
|
122
|
+
for (let j = searchStartIndex; j <= searchEndIndex; j++) {
|
|
123
|
+
if (sourceLines[j].trim() === patchLineContent.trim()) {
|
|
124
|
+
bestMatchIndex = j;
|
|
125
|
+
foundMatch = true;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (foundMatch) {
|
|
131
|
+
// Use the actual line number from the source
|
|
132
|
+
const actualLineNum = bestMatchIndex + 1;
|
|
133
|
+
const correctedLine = ` ${String(actualLineNum).padStart(padding, ' ')}: ${patchLineContent}`;
|
|
134
|
+
outputLines.push(correctedLine);
|
|
135
|
+
|
|
136
|
+
// Update tracking variables
|
|
137
|
+
if (actualLineNum !== reportedLineNum) {
|
|
138
|
+
correctionsMade = true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
currentSourceLineNumber = actualLineNum + 1;
|
|
142
|
+
currentTargetLineNumber = actualLineNum + 1;
|
|
143
|
+
} else {
|
|
144
|
+
// Fallback: use current tracking position
|
|
145
|
+
const correctedLine = ` ${String(currentSourceLineNumber).padStart(padding, ' ')}: ${patchLineContent}`;
|
|
146
|
+
outputLines.push(correctedLine);
|
|
147
|
+
|
|
148
|
+
if (currentSourceLineNumber !== reportedLineNum) {
|
|
149
|
+
correctionsMade = true;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
errors.push({
|
|
153
|
+
originalLineNumber: i + 1,
|
|
154
|
+
lineContent: currentPatchLine,
|
|
155
|
+
message: `Content not found in source window [${searchStartIndex + 1}-${searchEndIndex + 1}]. Using current position ${currentSourceLineNumber}.`,
|
|
156
|
+
searchStart: searchStartIndex + 1,
|
|
157
|
+
searchEnd: searchEndIndex + 1
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
currentSourceLineNumber++;
|
|
161
|
+
currentTargetLineNumber++;
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
// No line number match, keep original
|
|
165
|
+
outputLines.push(currentPatchLine);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Process deletion lines (minus-prefixed)
|
|
169
|
+
else if (currentPatchLine.startsWith('-')) {
|
|
170
|
+
if (match) {
|
|
171
|
+
const reportedLineNum = parseInt(match[2], 10);
|
|
172
|
+
const patchLineContent = match[3];
|
|
173
|
+
|
|
174
|
+
// Verify content against source
|
|
175
|
+
let foundMatch = false;
|
|
176
|
+
let bestMatchIndex = -1;
|
|
177
|
+
|
|
178
|
+
// Define search window
|
|
179
|
+
const searchStartIndex = Math.max(0, currentSourceLineNumber - 1 - windowRadius);
|
|
180
|
+
const searchEndIndex = Math.min(sourceLines.length - 1, currentSourceLineNumber - 1 + windowRadius);
|
|
181
|
+
|
|
182
|
+
for (let j = searchStartIndex; j <= searchEndIndex; j++) {
|
|
183
|
+
if (sourceLines[j].trim() === patchLineContent.trim()) {
|
|
184
|
+
bestMatchIndex = j;
|
|
185
|
+
foundMatch = true;
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (foundMatch) {
|
|
191
|
+
// Use the actual line number from the source
|
|
192
|
+
const actualLineNum = bestMatchIndex + 1;
|
|
193
|
+
const correctedLine = `- ${String(actualLineNum).padStart(padding, ' ')}: ${patchLineContent}`;
|
|
194
|
+
outputLines.push(correctedLine);
|
|
195
|
+
|
|
196
|
+
// Update tracking variables
|
|
197
|
+
if (actualLineNum !== reportedLineNum) {
|
|
198
|
+
correctionsMade = true;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
currentSourceLineNumber = actualLineNum + 1;
|
|
202
|
+
} else {
|
|
203
|
+
// Fallback: use current tracking position
|
|
204
|
+
const correctedLine = `- ${String(currentSourceLineNumber).padStart(padding, ' ')}: ${patchLineContent}`;
|
|
205
|
+
outputLines.push(correctedLine);
|
|
206
|
+
|
|
207
|
+
if (currentSourceLineNumber !== reportedLineNum) {
|
|
208
|
+
correctionsMade = true;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
errors.push({
|
|
212
|
+
originalLineNumber: i + 1,
|
|
213
|
+
lineContent: currentPatchLine,
|
|
214
|
+
message: `Content not found in source window [${searchStartIndex + 1}-${searchEndIndex + 1}]. Using current position ${currentSourceLineNumber}.`,
|
|
215
|
+
searchStart: searchStartIndex + 1,
|
|
216
|
+
searchEnd: searchEndIndex + 1
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
currentSourceLineNumber++;
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
// No line number match, keep original
|
|
223
|
+
outputLines.push(currentPatchLine);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// Process addition lines (plus-prefixed)
|
|
227
|
+
else if (currentPatchLine.startsWith('+')) {
|
|
228
|
+
if (match) {
|
|
229
|
+
const patchLineContent = match[3];
|
|
230
|
+
// For addition lines, use the current target line number
|
|
231
|
+
const correctedLine = `+ ${String(currentTargetLineNumber).padStart(padding, ' ')}: ${patchLineContent}`;
|
|
232
|
+
outputLines.push(correctedLine);
|
|
233
|
+
currentTargetLineNumber++;
|
|
234
|
+
} else {
|
|
235
|
+
// No line number match, keep original
|
|
236
|
+
outputLines.push(currentPatchLine);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Any other lines (should be rare in a valid patch)
|
|
240
|
+
else {
|
|
241
|
+
outputLines.push(currentPatchLine);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
correctedPatchText: outputLines.join('\n'),
|
|
247
|
+
correctionsMade: correctionsMade,
|
|
248
|
+
errors: errors
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
module.exports = {
|
|
253
|
+
verifyAndCorrectLineNumbers,
|
|
254
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
i/**
|
|
2
|
+
* Component: SharedUtils Timestamp Utilities
|
|
3
|
+
* Block-UUID: 495fbd8d-a818-4db0-89c4-5311bdfb8537
|
|
4
|
+
* Parent-UUID: N/A
|
|
5
|
+
* Version: 1.0.0
|
|
6
|
+
* Description: Provides utility functions for formatting timestamps. Originally part of PatchUtils.
|
|
7
|
+
* Language: JavaScript
|
|
8
|
+
* Created-at: 2025-04-18T02:59:04.322Z
|
|
9
|
+
* Authors: Gemini 2.5 Pro (v1.0.0)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Formats a timestamp in a human-readable format
|
|
15
|
+
* @param {string|Date} timestamp - ISO timestamp or Date object
|
|
16
|
+
* @returns {string} Formatted date string
|
|
17
|
+
*/
|
|
18
|
+
function formatTimestamp(timestamp) {
|
|
19
|
+
if (!timestamp) {
|
|
20
|
+
return 'Unknown date';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const date = timestamp instanceof Date ? timestamp : new Date(timestamp);
|
|
24
|
+
|
|
25
|
+
if (isNaN(date.getTime())) {
|
|
26
|
+
return 'Invalid date';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return date.toLocaleString(undefined, {
|
|
30
|
+
year: 'numeric',
|
|
31
|
+
month: 'short',
|
|
32
|
+
day: 'numeric',
|
|
33
|
+
hour: '2-digit',
|
|
34
|
+
minute: '2-digit',
|
|
35
|
+
second: '2-digit'
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
formatTimestamp
|
|
41
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component: SharedUtils Version Utilities
|
|
3
|
+
* Block-UUID: c18e08cb-1fc1-4d10-963a-eab9e70f6e6e
|
|
4
|
+
* Parent-UUID: N/A
|
|
5
|
+
* Version: 1.0.0
|
|
6
|
+
* Description: Provides utility functions for validating and manipulating semantic version strings. Originally part of PatchUtils.
|
|
7
|
+
* Language: JavaScript
|
|
8
|
+
* Created-at: 2025-04-18T02:59:04.322Z
|
|
9
|
+
* Authors: Gemini 2.5 Pro (v1.0.0)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Validates version string format (X.Y.Z)
|
|
15
|
+
* @param {string} version - Version string
|
|
16
|
+
* @returns {boolean} Whether version is valid
|
|
17
|
+
*/
|
|
18
|
+
function isValidVersion(version) {
|
|
19
|
+
// Note: This was originally _isValidVersion in PatchUtils
|
|
20
|
+
return /^\d+\.\d+\.\d+$/.test(version);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Increments a semantic version string
|
|
25
|
+
* @param {string} version - Version in format X.Y.Z
|
|
26
|
+
* @param {string} type - Type of increment: 'major', 'minor', or 'patch'
|
|
27
|
+
* @returns {string} Incremented version
|
|
28
|
+
*/
|
|
29
|
+
function incrementVersion(version, type = 'patch') {
|
|
30
|
+
if (!version || !/^\d+\.\d+\.\d+$/.test(version)) {
|
|
31
|
+
throw new Error(`Invalid version format: ${version}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const parts = version.split('.').map(Number);
|
|
35
|
+
|
|
36
|
+
switch (type.toLowerCase()) {
|
|
37
|
+
case 'major':
|
|
38
|
+
parts[0] += 1;
|
|
39
|
+
parts[1] = 0;
|
|
40
|
+
parts[2] = 0;
|
|
41
|
+
break;
|
|
42
|
+
case 'minor':
|
|
43
|
+
parts[1] += 1;
|
|
44
|
+
parts[2] = 0;
|
|
45
|
+
break;
|
|
46
|
+
case 'patch':
|
|
47
|
+
default:
|
|
48
|
+
parts[2] += 1;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return parts.join('.');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = {
|
|
56
|
+
isValidVersion,
|
|
57
|
+
incrementVersion
|
|
58
|
+
};
|