@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,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component: CodeBlockUtils Continuation Utilities
|
|
3
|
+
* Block-UUID: 6fdf5c7d-4def-42a1-bc88-0312f6002cfc
|
|
4
|
+
* Parent-UUID: 6322afcd-2e5e-425a-9a43-4c72c887f668
|
|
5
|
+
* Version: 1.0.0
|
|
6
|
+
* Description: Provides functions to extract context from incomplete code blocks and generate prompts for continuation.
|
|
7
|
+
* Language: JavaScript
|
|
8
|
+
* Created-at: 2025-04-15T15:59:41.768Z
|
|
9
|
+
* Authors: Gemini 2.5 Pro (v1.0.0)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extracts continuation information from an incomplete block's content.
|
|
15
|
+
* @param {string} content - The raw content of the incomplete block (after the opening fence).
|
|
16
|
+
* @param {number} partNumber - The calculated part number for the continuation (e.g., 2 for the first continuation).
|
|
17
|
+
* @param {string} language - The language of the block.
|
|
18
|
+
* @param {Object} header - The parsed header object from the incomplete block (if available).
|
|
19
|
+
* @returns {Object} Continuation information { lastLines: Array<string>, blockUUID: string|null, partNumber: number, language: string, metadata: Object }
|
|
20
|
+
*/
|
|
21
|
+
function extractContinuationInfo(content, partNumber, language, header = {}) {
|
|
22
|
+
// Extract Block-UUID directly from the parsed header if available and valid
|
|
23
|
+
let blockUUID = (header && header['Block-UUID'] && header['Block-UUID'] !== 'INVALID UUID')
|
|
24
|
+
? header['Block-UUID']
|
|
25
|
+
: null;
|
|
26
|
+
|
|
27
|
+
// Fallback: Try to find UUID in content if not in header (less reliable)
|
|
28
|
+
if (!blockUUID) {
|
|
29
|
+
const uuidMatch = content.match(/Block-UUID: ([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})/);
|
|
30
|
+
if (uuidMatch && uuidMatch[1]) {
|
|
31
|
+
blockUUID = uuidMatch[1];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Clean up potential warning messages appended by some systems
|
|
36
|
+
// Note: This might need adjustment based on the specific format of such warnings.
|
|
37
|
+
let cleanedContent = content.replace(/\n---\nWarning: Incomplete response\n\n?/, '');
|
|
38
|
+
cleanedContent = cleanedContent.replace(/Authored by LLM .+ at .+$/, ''); // Remove potential authorship line
|
|
39
|
+
|
|
40
|
+
const lines = cleanedContent.split('\n');
|
|
41
|
+
|
|
42
|
+
// Determine if the very last line might be incomplete.
|
|
43
|
+
// Heuristic: If the content doesn't end with a newline, the last line was likely cut off.
|
|
44
|
+
// This isn't foolproof.
|
|
45
|
+
const lastLinePotentiallyIncomplete = !cleanedContent.endsWith('\n') && lines.length > 0;
|
|
46
|
+
|
|
47
|
+
// If the last line is potentially incomplete, we keep it.
|
|
48
|
+
// If it seems complete (ends with \n), we can consider removing the *very last* line
|
|
49
|
+
// as it might be fully formed and the LLM should start *after* it.
|
|
50
|
+
// However, safest is often to include it and let the LLM figure out the exact continuation point.
|
|
51
|
+
// Let's keep the last line for now, whether complete or not.
|
|
52
|
+
|
|
53
|
+
// Find the last 5 non-empty lines and include empty lines between them
|
|
54
|
+
const lastLinesForContext = [];
|
|
55
|
+
const nonEmptyIndices = [];
|
|
56
|
+
|
|
57
|
+
// Get indices of all non-empty lines
|
|
58
|
+
for (let i = 0; i < lines.length; i++) {
|
|
59
|
+
if (lines[i].trim() !== '') {
|
|
60
|
+
nonEmptyIndices.push(i);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Get the indices of the last 5 non-empty lines
|
|
65
|
+
const lastNonEmptyIndices = nonEmptyIndices.slice(-5);
|
|
66
|
+
|
|
67
|
+
if (lastNonEmptyIndices.length > 0) {
|
|
68
|
+
// Get all lines from the *start* of the first of these last 5 non-empty lines up to the end
|
|
69
|
+
const startIndex = lastNonEmptyIndices[0];
|
|
70
|
+
// Include all lines until the very end of the original lines array
|
|
71
|
+
lastLinesForContext.push(...lines.slice(startIndex));
|
|
72
|
+
} else if (lines.length > 0) {
|
|
73
|
+
// If there are fewer than 5 non-empty lines, take all available lines
|
|
74
|
+
lastLinesForContext.push(...lines);
|
|
75
|
+
}
|
|
76
|
+
// If the block was completely empty after the header, lastLinesForContext will be empty.
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
lastLines: lastLinesForContext, // The actual lines to show in the prompt
|
|
81
|
+
blockUUID: blockUUID,
|
|
82
|
+
partNumber: partNumber,
|
|
83
|
+
language: language,
|
|
84
|
+
lastLineIncomplete: lastLinePotentiallyIncomplete, // Hint for prompt generation
|
|
85
|
+
metadata: header // Pass the parsed header info along
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Generates a continuation prompt for an incomplete code block.
|
|
92
|
+
* @param {Object} continuationInfo - The object returned by extractContinuationInfo.
|
|
93
|
+
* @returns {string} Formatted continuation prompt.
|
|
94
|
+
*/
|
|
95
|
+
function generateContinuationPrompt(incompleteBlock, isLastPart = false) {
|
|
96
|
+
if (!incompleteBlock) {
|
|
97
|
+
console.warn("generateContinuationPrompt called with invalid incompleteBlock.");
|
|
98
|
+
return '';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const {
|
|
102
|
+
lastLines = [],
|
|
103
|
+
blockUUID = null,
|
|
104
|
+
partNumber = 2,
|
|
105
|
+
language = 'unknown',
|
|
106
|
+
lastLineIncomplete = false, // Use the flag from continuationInfo
|
|
107
|
+
metadata = {} // Use the metadata passed from continuationInfo
|
|
108
|
+
} = incompleteBlock.continuationInfo;
|
|
109
|
+
|
|
110
|
+
// Extract relevant metadata fields for the prompt example
|
|
111
|
+
const componentName = metadata['Component'] || 'UnknownComponent';
|
|
112
|
+
const parentUUID = metadata['Parent-UUID'] || 'N/A';
|
|
113
|
+
const currentVersion = metadata['Version'] || '1.0.0'; // Version from the *incomplete* block's header
|
|
114
|
+
const description = metadata['Description'] || 'No description provided.';
|
|
115
|
+
const authors = metadata['Authors'] || 'Unknown Authors';
|
|
116
|
+
// Created-at is less relevant for the *next* part's example header, use current time there.
|
|
117
|
+
|
|
118
|
+
// Calculate the expected next version (increment patch version)
|
|
119
|
+
let nextVersion = '1.0.1'; // Default if current version is invalid
|
|
120
|
+
const versionPattern = /^(\d+)\.(\d+)\.(\d+)$/;
|
|
121
|
+
const versionMatch = typeof currentVersion === 'string' ? currentVersion.match(versionPattern) : null;
|
|
122
|
+
if (versionMatch) {
|
|
123
|
+
nextVersion = `${versionMatch[1]}.${versionMatch[2]}.${parseInt(versionMatch[3]) + 1}`;
|
|
124
|
+
} else {
|
|
125
|
+
console.warn(`Invalid current version format "${currentVersion}" in continuation metadata. Defaulting next version to 1.0.1.`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Build UUID context string
|
|
129
|
+
const uuidSection = blockUUID
|
|
130
|
+
? `This is PART ${partNumber} of a multi-part code block.\nPlease continue the code block with Block-UUID: ${blockUUID}`
|
|
131
|
+
: `This is PART ${partNumber} of a multi-part code block.\nPlease continue the code block.`;
|
|
132
|
+
|
|
133
|
+
// Add specific instruction if the last line was likely cut off
|
|
134
|
+
const lastLineInstruction = lastLineIncomplete
|
|
135
|
+
? "The last line provided below might be incomplete. Start your response by completing that line if necessary, then continue the code."
|
|
136
|
+
: "The last line provided below appears complete. Start your response *after* this line.";
|
|
137
|
+
|
|
138
|
+
// Create metadata instructions
|
|
139
|
+
const metadataInstructions = `
|
|
140
|
+
METADATA PRESERVATION INSTRUCTIONS:
|
|
141
|
+
1. Keep the same Block-UUID: ${blockUUID}
|
|
142
|
+
2. Add "Continuation-Part: ${partNumber}" to the metadata
|
|
143
|
+
3. Preserve all other metadata fields (Description, Language, etc.)
|
|
144
|
+
`;
|
|
145
|
+
|
|
146
|
+
// Construct the prompt
|
|
147
|
+
return `[CONTINUATION REQUEST - PART ${partNumber}]
|
|
148
|
+
${uuidSection}
|
|
149
|
+
|
|
150
|
+
The previous part ended with the following lines:
|
|
151
|
+
\`\`\`${language}
|
|
152
|
+
${lastLines.join('\n')}
|
|
153
|
+
\`\`\`
|
|
154
|
+
|
|
155
|
+
IMPORTANT INSTRUCTIONS:
|
|
156
|
+
1. ${lastLineInstruction}
|
|
157
|
+
2. Your response MUST start with the complete metadata header block, followed by the repeated code lines from above (adjusting the last line if it was incomplete), and then the new code.
|
|
158
|
+
3. Follow the METADATA REQUIREMENTS listed below precisely.
|
|
159
|
+
4. Maintain the original coding style, indentation, and structure.
|
|
160
|
+
5. Wrap your entire response in a single markdown code block (\`\`\`${language} ... \`\`\`).
|
|
161
|
+
|
|
162
|
+
${metadataInstructions}
|
|
163
|
+
|
|
164
|
+
Example of the START of your response:
|
|
165
|
+
\`\`\`${language}
|
|
166
|
+
/**
|
|
167
|
+
* Component: ${componentName}
|
|
168
|
+
* Block-UUID: 01147ddf-320f-498a-a744-198d42a9d2ee
|
|
169
|
+
* Parent-UUID: e4c0d839-ea17-467d-a0cc-d269d1dbc404
|
|
170
|
+
* Version: ${nextVersion}
|
|
171
|
+
* Description: ${description}
|
|
172
|
+
* Language: ${language}
|
|
173
|
+
* Created-at: ${new Date().toISOString()}
|
|
174
|
+
* Authors: ${authors}, Your Name (v${nextVersion})
|
|
175
|
+
* Continuation-Part: ${partNumber}
|
|
176
|
+
*/
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
${lastLines.join('\n')} // <-- Repeat the lines from above (adjust last if needed)
|
|
180
|
+
// New code starts here...
|
|
181
|
+
const nextVariable = true;
|
|
182
|
+
\`\`\`
|
|
183
|
+
Contribute code starting from the line immediately following the repeated code block.
|
|
184
|
+
`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
module.exports = {
|
|
189
|
+
extractContinuationInfo,
|
|
190
|
+
generateContinuationPrompt
|
|
191
|
+
};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component: CodeBlockUtils Header Parser (Comment-Delimited)
|
|
3
|
+
* Block-UUID: ab5ec4f5-0e35-4052-b6ce-a7e9c3117bd0
|
|
4
|
+
* Parent-UUID: 6322afcd-2e5e-425a-9a43-4c72c887f668
|
|
5
|
+
* Version: 1.0.0
|
|
6
|
+
* Description: Parses code blocks where metadata headers are enclosed in language-specific comment delimiters (e.g., /** ... * /, """ ... """), not markdown fences. Extracts metadata and content.
|
|
7
|
+
* Language: JavaScript
|
|
8
|
+
* Created-at: 2025-04-15T16:00:32.420Z
|
|
9
|
+
* Authors: Gemini 2.5 Pro (v1.0.0)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
// Note: This parser operates differently from the markdown fence-based parser.
|
|
14
|
+
// It looks for specific comment block styles to delimit the header.
|
|
15
|
+
|
|
16
|
+
function parseCodeBlocks(input) {
|
|
17
|
+
let state = "START";
|
|
18
|
+
let position = { line: 1, column: 1, offset: 0 };
|
|
19
|
+
let metadata = {};
|
|
20
|
+
let content = "";
|
|
21
|
+
let errors = [];
|
|
22
|
+
let buffer = ""; // Temporary buffer for metadata fields
|
|
23
|
+
let headerLanguage = null; // To track the type of comment delimiter found
|
|
24
|
+
|
|
25
|
+
// Helper function to update position
|
|
26
|
+
function updatePosition(char) {
|
|
27
|
+
if (char === "\n") {
|
|
28
|
+
position.line++;
|
|
29
|
+
position.column = 1;
|
|
30
|
+
} else {
|
|
31
|
+
position.column++;
|
|
32
|
+
}
|
|
33
|
+
position.offset++;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Helper function to check for opening delimiters and determine language context
|
|
37
|
+
function checkOpeningDelimiter(input, index) {
|
|
38
|
+
if (input.slice(index, index + 3) === '"""') return { lang: "PYTHON", length: 3 };
|
|
39
|
+
if (input.slice(index, index + 3) === '/**') return { lang: "JAVASCRIPT", length: 3 }; // Common C-style block comment start
|
|
40
|
+
if (input.slice(index, index + 2) === '/*') return { lang: "C_STYLE_GENERIC", length: 2 }; // Generic C-style, might not be header
|
|
41
|
+
if (input.slice(index, index + 6) === '=begin') return { lang: "RUBY", length: 6}; // Ruby block comment
|
|
42
|
+
// Add other language-specific block comment openings if needed
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Helper function to check for closing delimiters based on the detected header language
|
|
47
|
+
function checkClosingDelimiter(input, index, detectedLanguage) {
|
|
48
|
+
if (detectedLanguage === "PYTHON" && input.slice(index, index + 3) === '"""') return 3;
|
|
49
|
+
if ((detectedLanguage === "JAVASCRIPT" || detectedLanguage === "C_STYLE_GENERIC") && input.slice(index, index + 2) === '*/') return 2;
|
|
50
|
+
if (detectedLanguage === "RUBY" && input.slice(index, index + 4) === '=end') return 4;
|
|
51
|
+
// Add other language-specific block comment closings
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Helper function to parse metadata fields from the buffer
|
|
56
|
+
function parseMetadataLine(line) {
|
|
57
|
+
// Handle different comment line prefixes within the block
|
|
58
|
+
const cleanedLine = line.replace(/^[\s\*#;!]*/, '').trim(); // Remove leading comment chars/whitespace
|
|
59
|
+
if (!cleanedLine) return; // Skip empty or comment-only lines
|
|
60
|
+
|
|
61
|
+
const colonIndex = cleanedLine.indexOf(":");
|
|
62
|
+
if (colonIndex === -1) {
|
|
63
|
+
// Allow lines without colons if they are part of a multi-line value (heuristic)
|
|
64
|
+
// This is tricky; requires more robust parsing or stricter format.
|
|
65
|
+
// For now, we'll treat lines without colons as errors unless a specific key allows multi-line.
|
|
66
|
+
if (!Object.keys(metadata).some(k => ['Description', 'Authors'].includes(k))) { // Example: Allow multi-line for Description/Authors
|
|
67
|
+
errors.push({ message: `Invalid metadata line (missing colon?): ${cleanedLine}`, position: { ...position } });
|
|
68
|
+
} else {
|
|
69
|
+
// Append to the last key if it allows multi-line (simple approach)
|
|
70
|
+
const lastKey = Object.keys(metadata).pop();
|
|
71
|
+
if (lastKey && ['Description', 'Authors'].includes(lastKey)) {
|
|
72
|
+
metadata[lastKey] += '\n' + cleanedLine;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const key = cleanedLine.slice(0, colonIndex).trim();
|
|
78
|
+
const value = cleanedLine.slice(colonIndex + 1).trim();
|
|
79
|
+
metadata[key] = value;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Main parsing loop
|
|
83
|
+
for (let i = 0; i < input.length; i++) {
|
|
84
|
+
const char = input[i];
|
|
85
|
+
|
|
86
|
+
switch (state) {
|
|
87
|
+
case "START": {
|
|
88
|
+
const delimiterInfo = checkOpeningDelimiter(input, i);
|
|
89
|
+
if (delimiterInfo) {
|
|
90
|
+
state = "HEADER";
|
|
91
|
+
headerLanguage = delimiterInfo.lang;
|
|
92
|
+
// Skip the delimiter characters
|
|
93
|
+
for(let k=0; k<delimiterInfo.length; k++) updatePosition(input[i+k]);
|
|
94
|
+
i += delimiterInfo.length - 1;
|
|
95
|
+
buffer = ""; // Reset buffer for the new header
|
|
96
|
+
} else {
|
|
97
|
+
updatePosition(char); // Consume char if not starting a header
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
case "HEADER": {
|
|
103
|
+
const closingLength = checkClosingDelimiter(input, i, headerLanguage);
|
|
104
|
+
if (closingLength > 0) {
|
|
105
|
+
// Found end of header block
|
|
106
|
+
if (buffer.trim()) {
|
|
107
|
+
parseMetadataLine(buffer); // Parse the last line in the buffer
|
|
108
|
+
}
|
|
109
|
+
state = "CONTENT";
|
|
110
|
+
// Skip the closing delimiter characters
|
|
111
|
+
for(let k=0; k<closingLength; k++) updatePosition(input[i+k]);
|
|
112
|
+
i += closingLength - 1;
|
|
113
|
+
buffer = ""; // Clear buffer
|
|
114
|
+
} else if (char === "\n") {
|
|
115
|
+
// End of a line within the header
|
|
116
|
+
if (buffer.trim() || buffer === '') { // Process line even if empty within header
|
|
117
|
+
parseMetadataLine(buffer);
|
|
118
|
+
buffer = "";
|
|
119
|
+
}
|
|
120
|
+
updatePosition(char);
|
|
121
|
+
} else {
|
|
122
|
+
buffer += char;
|
|
123
|
+
updatePosition(char);
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
case "CONTENT": {
|
|
129
|
+
// Once in content, we assume the rest is content until EOF or potentially another START?
|
|
130
|
+
// This simple parser assumes only one header block at the beginning.
|
|
131
|
+
content += char;
|
|
132
|
+
updatePosition(char);
|
|
133
|
+
// To handle multiple blocks, state would need to transition back to START
|
|
134
|
+
// after consuming whitespace or based on other rules.
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// case "END": // Not really used in this simple model
|
|
139
|
+
// break;
|
|
140
|
+
|
|
141
|
+
default: {
|
|
142
|
+
errors.push({ message: `Unexpected parser state: ${state}`, position: { ...position } });
|
|
143
|
+
// Attempt recovery? For now, just log and continue consuming as content.
|
|
144
|
+
state = "CONTENT";
|
|
145
|
+
content += char;
|
|
146
|
+
updatePosition(char);
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// If loop finishes while still in HEADER state, it's an unterminated header
|
|
153
|
+
if (state === "HEADER") {
|
|
154
|
+
errors.push({ message: "Unterminated metadata header block.", position: { ...position } });
|
|
155
|
+
// Process any remaining buffer content from the unterminated header
|
|
156
|
+
if (buffer.trim()) {
|
|
157
|
+
parseMetadataLine(buffer);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Basic validation of essential metadata fields after parsing
|
|
162
|
+
// More robust validation (UUID format, Version format, Timestamp format) should be done separately
|
|
163
|
+
if (!metadata["Component"] || !metadata["Block-UUID"] || !metadata["Version"]) {
|
|
164
|
+
errors.push({ message: "Parsing finished, but missing required metadata fields (Component, Block-UUID, Version).", position: { ...position } });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
metadata,
|
|
169
|
+
content: content.trim(), // Trim final content
|
|
170
|
+
position, // Final position reached
|
|
171
|
+
errors,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
module.exports = { parseCodeBlocks };
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component: CodeBlockUtils Header Utilities
|
|
3
|
+
* Block-UUID: 2565f708-feec-4b59-88fd-984084410fd4
|
|
4
|
+
* Parent-UUID: 01147ddf-320f-498a-a744-198d42a9d2ee
|
|
5
|
+
* Version: 1.2.0
|
|
6
|
+
* Description: Provides utility functions for parsing metadata headers from code blocks, validating timestamps, and calculating header line counts.
|
|
7
|
+
* Language: JavaScript
|
|
8
|
+
* Created-at: 2025-07-21T00:23:28.169Z
|
|
9
|
+
* Authors: Gemini 2.5 Pro (v1.0.0), Gemini 2.5 Flash Thinking (v1.1.0), Gemini 2.5 Flash Thinking (v1.2.0)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
// Dependencies from other modules within CodeBlockUtils
|
|
14
|
+
const { COMMENT_STYLES } = require('./constants');
|
|
15
|
+
const { removeLineNumbers } = require('./lineNumberFormatter'); // Assuming this utility exists
|
|
16
|
+
const { validateUUID } = require('./uuidUtils'); // Assuming uuidUtils.js is in the same directory
|
|
17
|
+
|
|
18
|
+
const MAX_HEADER_LINES = 12; // Maximum allowed non-empty lines for a header
|
|
19
|
+
/**
|
|
20
|
+
* Validates if a string is a valid ISO 8601 timestamp
|
|
21
|
+
* @param {string} timestamp - The timestamp to validate
|
|
22
|
+
* @returns {boolean} - True if valid, false otherwise
|
|
23
|
+
*/
|
|
24
|
+
function isValidISOTimestamp(timestamp) {
|
|
25
|
+
const isoPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/;
|
|
26
|
+
if (!isoPattern.test(timestamp)) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const date = new Date(timestamp);
|
|
31
|
+
return date instanceof Date && !isNaN(date);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Parses the header string into a structured object.
|
|
36
|
+
* @param {string} header - The header string to parse.
|
|
37
|
+
* @param {string} language - The programming language of the code block.
|
|
38
|
+
* @returns {Object} - The parsed header object.
|
|
39
|
+
* @throws {Error} If header parsing fails (e.g., missing fields, invalid format).
|
|
40
|
+
*/
|
|
41
|
+
function parseHeader(header, language) {
|
|
42
|
+
const headerObject = {};
|
|
43
|
+
|
|
44
|
+
// Get comment style for the language
|
|
45
|
+
const commentStyle = COMMENT_STYLES[language] || { type: 'unknown' };
|
|
46
|
+
|
|
47
|
+
// Remove comment syntax based on language style
|
|
48
|
+
let cleanHeader = header;
|
|
49
|
+
|
|
50
|
+
switch (commentStyle.type) {
|
|
51
|
+
case 'c-style':
|
|
52
|
+
cleanHeader = header.replace(/^\/\*\*|\*\/$/g, '')
|
|
53
|
+
.split('\n')
|
|
54
|
+
.map(line => line.replace(/^\s*\*\s?/, ''))
|
|
55
|
+
.join('\n');
|
|
56
|
+
break;
|
|
57
|
+
|
|
58
|
+
case 'hash':
|
|
59
|
+
cleanHeader = header.split('\n')
|
|
60
|
+
.map(line => line.replace(/^\s*#\s?/, ''))
|
|
61
|
+
.join('\n');
|
|
62
|
+
break;
|
|
63
|
+
|
|
64
|
+
case 'ruby-block':
|
|
65
|
+
cleanHeader = header.replace(/^=begin\s*\n/, '')
|
|
66
|
+
.replace(/\n=end\s*$/, '');
|
|
67
|
+
break;
|
|
68
|
+
|
|
69
|
+
case 'semicolon':
|
|
70
|
+
cleanHeader = header.split('\n')
|
|
71
|
+
.map(line => line.replace(/^\s*;\s?/, ''))
|
|
72
|
+
.join('\n');
|
|
73
|
+
break;
|
|
74
|
+
|
|
75
|
+
case 'ada':
|
|
76
|
+
cleanHeader = header.split('\n')
|
|
77
|
+
.map(line => line.replace(/^\s*--\s?/, ''))
|
|
78
|
+
.join('\n');
|
|
79
|
+
break;
|
|
80
|
+
|
|
81
|
+
case 'lua':
|
|
82
|
+
cleanHeader = header.split('\n')
|
|
83
|
+
.map(line => line.replace(/^\s*--\s?/, ''))
|
|
84
|
+
.join('\n');
|
|
85
|
+
break;
|
|
86
|
+
|
|
87
|
+
case 'percent':
|
|
88
|
+
cleanHeader = header.split('\n')
|
|
89
|
+
.map(line => line.replace(/^\s*%\s?/, ''))
|
|
90
|
+
.join('\n');
|
|
91
|
+
break;
|
|
92
|
+
|
|
93
|
+
case 'haskell':
|
|
94
|
+
cleanHeader = header.split('\n')
|
|
95
|
+
.map(line => line.replace(/^\s*--\s?/, ''))
|
|
96
|
+
.join('\n');
|
|
97
|
+
break;
|
|
98
|
+
|
|
99
|
+
case 'sql':
|
|
100
|
+
cleanHeader = header.split('\n')
|
|
101
|
+
.map(line => line.replace(/^\s*--\s?/, ''))
|
|
102
|
+
.join('\n');
|
|
103
|
+
break;
|
|
104
|
+
|
|
105
|
+
case 'exclamation':
|
|
106
|
+
cleanHeader = header.split('\n')
|
|
107
|
+
.map(line => line.replace(/^\s*!\s?/, ''))
|
|
108
|
+
.join('\n');
|
|
109
|
+
break;
|
|
110
|
+
|
|
111
|
+
case 'apostrophe':
|
|
112
|
+
cleanHeader = header.split('\n')
|
|
113
|
+
.map(line => line.replace(/^\s*'\s?/, ''))
|
|
114
|
+
.join('\n');
|
|
115
|
+
break;
|
|
116
|
+
|
|
117
|
+
case 'unknown':
|
|
118
|
+
// Attempt basic detection if language wasn't found in COMMENT_STYLES
|
|
119
|
+
if (header.startsWith('/**')) {
|
|
120
|
+
cleanHeader = header.replace(/^\/\*\*|\*\/$/g, '')
|
|
121
|
+
.split('\n')
|
|
122
|
+
.map(line => line.replace(/^\s*\*\s?/, ''))
|
|
123
|
+
.join('\n');
|
|
124
|
+
} else if (header.startsWith('#')) {
|
|
125
|
+
cleanHeader = header.split('\n')
|
|
126
|
+
.map(line => line.replace(/^\s*#\s?/, ''))
|
|
127
|
+
.join('\n');
|
|
128
|
+
}
|
|
129
|
+
// Add more basic detections if needed
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Parse the cleaned header
|
|
134
|
+
const lines = cleanHeader.split('\n').filter(line => line.trim() !== '');
|
|
135
|
+
if (lines.length > MAX_HEADER_LINES) {
|
|
136
|
+
throw new Error(`Header exceeds maximum allowed lines (${MAX_HEADER_LINES}). Found ${lines.length} non-empty lines.`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const missingFields = [];
|
|
140
|
+
|
|
141
|
+
for (const line of lines) {
|
|
142
|
+
const match = line.match(/^\s*([^:]+):\s*(.*)$/);
|
|
143
|
+
if (match) {
|
|
144
|
+
const key = match[1].trim();
|
|
145
|
+
let value = match[2].trim();
|
|
146
|
+
|
|
147
|
+
// Special handling for Block-UUID and Parent-UUID
|
|
148
|
+
if (key === "Block-UUID" || key === "Parent-UUID") {
|
|
149
|
+
if (value !== 'N/A') {
|
|
150
|
+
const uuidValidation = validateUUID(value); // Use imported function
|
|
151
|
+
if (uuidValidation["Block-UUID"] === "INVALID UUID") {
|
|
152
|
+
// Store invalid status and the suggested correction
|
|
153
|
+
headerObject[key] = "INVALID UUID";
|
|
154
|
+
headerObject[`Correct ${key}`] = uuidValidation["Correct Block-UUID"];
|
|
155
|
+
} else {
|
|
156
|
+
headerObject[key] = value;
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
headerObject[key] = value; // Keep 'N/A' as is
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
headerObject[key] = value;
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
// Do nothing since some lines will just have something like `/*` or `*/`
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Check required fields
|
|
170
|
+
const requiredFields = [
|
|
171
|
+
'Component',
|
|
172
|
+
'Block-UUID',
|
|
173
|
+
'Version', // Keep Version as string here, handle parsing elsewhere if needed
|
|
174
|
+
'Language',
|
|
175
|
+
'Created-at',
|
|
176
|
+
'Authors'
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
// Special handling for Parent-UUID - if missing, set it to 'N/A'
|
|
180
|
+
if (!('Parent-UUID' in headerObject)) {
|
|
181
|
+
headerObject['Parent-UUID'] = 'N/A';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
for (const field of requiredFields) {
|
|
185
|
+
if (!(field in headerObject)) {
|
|
186
|
+
missingFields.push(field);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (missingFields.length > 0) {
|
|
191
|
+
throw new Error(`Missing required fields: ${missingFields.join(', ')}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Validate timestamp format
|
|
195
|
+
if (!isValidISOTimestamp(headerObject['Created-at'])) { // Use local function
|
|
196
|
+
throw new Error('Invalid ISO 8601 timestamp format in Created-at field');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Validate Version format (simple check for X.Y.Z structure)
|
|
200
|
+
const versionPattern = /^\d+\.\d+\.\d+$/;
|
|
201
|
+
if (typeof headerObject['Version'] !== 'string' || !versionPattern.test(headerObject['Version'])) {
|
|
202
|
+
throw new Error(`Invalid Version format: "${headerObject['Version']}". Expected format X.Y.Z`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
return headerObject;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Calculates the total number of lines a code block's header occupies,
|
|
211
|
+
* including the comment block and the two blank lines that follow it.
|
|
212
|
+
* This is crucial for correctly adjusting line numbers in patches.
|
|
213
|
+
*
|
|
214
|
+
* @param {string} headerText - The raw string content of the header comment block
|
|
215
|
+
* @param {string} language - The programming language of the code block.
|
|
216
|
+
* @returns {number} The total number of lines the header occupies in the full code block.
|
|
217
|
+
*/
|
|
218
|
+
function getHeaderLineCount(headerText, language) {
|
|
219
|
+
if (typeof headerText !== 'string') {
|
|
220
|
+
return 2; // Default to 2 blank lines if no header text
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// The headerText already includes the comment delimiters if they are multi-line.
|
|
224
|
+
// We just need to count the lines in the headerText and add the 2 blank lines.
|
|
225
|
+
const linesInHeaderCommentBlock = headerText.split('\n').length;
|
|
226
|
+
|
|
227
|
+
// Add 2 for the mandatory blank lines between the header comment block and the code implementation.
|
|
228
|
+
return linesInHeaderCommentBlock + 2;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
module.exports = {
|
|
233
|
+
isValidISOTimestamp,
|
|
234
|
+
parseHeader,
|
|
235
|
+
getHeaderLineCount // Export the new function
|
|
236
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component: CodeBlockUtils Index
|
|
3
|
+
* Block-UUID: 7e9d3f8a-1b2c-4d5e-8f9a-0123456789ab
|
|
4
|
+
* Parent-UUID: a3b9c8d1-e0f2-4a1b-8c7d-5e6f0a9b1c2d
|
|
5
|
+
* Version: 1.4.0
|
|
6
|
+
* Description: Aggregates and exports all utilities related to code block processing from the CodeBlockUtils module. Serves as the main entry point for this module.
|
|
7
|
+
* Language: JavaScript
|
|
8
|
+
* Created-at: 2025-04-15T16:02:20.217Z
|
|
9
|
+
* Authors: Gemini 2.5 Pro (v1.0.0), Gemini 2.5 Pro (v1.1.0), Claude 3.7 Sonnet (v1.2.0), Gemini 2.5 Pro (v1.3.0), Gemini 2.5 Flash Thinking (v1.4.0)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
// Import from individual utility files
|
|
14
|
+
const { COMMENT_STYLES } = require('./constants');
|
|
15
|
+
const { generateUUID, validateUUID } = require('./uuidUtils');
|
|
16
|
+
const { isValidISOTimestamp, parseHeader, getHeaderLineCount } = require('./headerUtils');
|
|
17
|
+
const { findAllCodeFences, matchFencesAndExtractBlocks, extractCodeBlocksWithUUIDs, findCodeBlockByUUID } = require('./blockExtractor');
|
|
18
|
+
const { processCodeBlocks, extractCodeBlocks, fixTextCodeBlocks } = require('./blockProcessor');
|
|
19
|
+
const { containsPatch } = require('./patchIntegration');
|
|
20
|
+
const { detectCodeBlockRelationships, detectIncompleteCodeBlock, extractFilePaths } = require('./relationshipUtils');
|
|
21
|
+
const { extractContinuationInfo, generateContinuationPrompt } = require('./continuationUtils');
|
|
22
|
+
const { parseCodeBlocks: parseCommentDelimitedBlocks } = require('./headerParser');
|
|
23
|
+
const { removeCodeBlockMarkers } = require('./markerRemover');
|
|
24
|
+
const { updateCodeBlockByIndex, updateCodeBlockByUUID, updateCodeBlock, updateCodeBlockInMessage, deleteCodeBlockByIndex } = require('./updateCodeBlock');
|
|
25
|
+
const { formatWithLineNumbers, formatBlockWithLineNumbers, formatBlocksWithLineNumbers, removeLineNumbers } = require('./lineNumberFormatter');
|
|
26
|
+
|
|
27
|
+
// Export all imported items
|
|
28
|
+
module.exports = {
|
|
29
|
+
// Constants
|
|
30
|
+
COMMENT_STYLES,
|
|
31
|
+
|
|
32
|
+
// UUID Utilities
|
|
33
|
+
generateUUID,
|
|
34
|
+
validateUUID,
|
|
35
|
+
|
|
36
|
+
// Header/Timestamp Utilities
|
|
37
|
+
isValidISOTimestamp,
|
|
38
|
+
parseHeader, // Parses headers from markdown blocks
|
|
39
|
+
getHeaderLineCount, // New: Calculates header line count
|
|
40
|
+
|
|
41
|
+
// Block Extraction (Markdown Fences)
|
|
42
|
+
findAllCodeFences,
|
|
43
|
+
matchFencesAndExtractBlocks,
|
|
44
|
+
extractCodeBlocksWithUUIDs,
|
|
45
|
+
findCodeBlockByUUID,
|
|
46
|
+
|
|
47
|
+
// Block Processing (Markdown Fences)
|
|
48
|
+
processCodeBlocks, // Core processor, returns detailed info
|
|
49
|
+
extractCodeBlocks, // Simpler API wrapper
|
|
50
|
+
fixTextCodeBlocks, // Fixes UUIDs in markdown blocks
|
|
51
|
+
|
|
52
|
+
// Patch Integration
|
|
53
|
+
containsPatch,
|
|
54
|
+
|
|
55
|
+
// Relationship/Context Utilities
|
|
56
|
+
detectCodeBlockRelationships,
|
|
57
|
+
detectIncompleteCodeBlock,
|
|
58
|
+
extractFilePaths,
|
|
59
|
+
|
|
60
|
+
// Continuation Utilities
|
|
61
|
+
extractContinuationInfo,
|
|
62
|
+
generateContinuationPrompt,
|
|
63
|
+
|
|
64
|
+
// Comment-Delimited Header Parsing
|
|
65
|
+
parseCommentDelimitedBlocks,
|
|
66
|
+
|
|
67
|
+
// Marker Removal
|
|
68
|
+
removeCodeBlockMarkers,
|
|
69
|
+
|
|
70
|
+
// Code Block Updating Utilities
|
|
71
|
+
updateCodeBlockByIndex,
|
|
72
|
+
updateCodeBlockByUUID,
|
|
73
|
+
updateCodeBlock,
|
|
74
|
+
updateCodeBlockInMessage,
|
|
75
|
+
deleteCodeBlockByIndex,
|
|
76
|
+
|
|
77
|
+
// Line Number Formatting Utilities
|
|
78
|
+
formatWithLineNumbers,
|
|
79
|
+
formatBlockWithLineNumbers,
|
|
80
|
+
formatBlocksWithLineNumbers,
|
|
81
|
+
removeLineNumbers,
|
|
82
|
+
};
|
|
83
|
+
|