@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,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
+