@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,372 @@
1
+ /**
2
+ * Component: CodeBlockUtils Update Code Block
3
+ * Block-UUID: 6a7b8c9d-0e1f-42a3-b4c5-d6e7f8a9b0c1
4
+ * Parent-UUID: 5f9c1e3a-7b2d-4e8f-a1d0-9c4b2e1f8a0b
5
+ * Version: 1.4.0
6
+ * Description: Provides functions to update the content of a specific code block within a string, identified by index, UUID, or direct reference.
7
+ * Language: JavaScript
8
+ * Created-at: 2025-04-16T00:56:57.352Z
9
+ * Authors: Gemini 2.5 Pro (v1.0.0), Gemini 2.5 Pro (v1.1.0), Gemini 2.5 Pro (v1.2.0), Gemini 2.5 Pro (v1.3.0), Gemini 2.5 Flash Thinking (v1.4.0)
10
+ */
11
+
12
+
13
+ // Dependencies from other modules within CodeBlockUtils
14
+ const { findAllCodeFences, matchFencesAndExtractBlocks, findCodeBlockByUUID } = require('./blockExtractor'); // Added findCodeBlockByUUID
15
+ const { processCodeBlocks } = require('./blockProcessor'); // To get parsed block info
16
+
17
+ /**
18
+ * Replaces the content of a code block specified by its index within a message string.
19
+ *
20
+ * @param {string} messageContent - The original message string containing code blocks.
21
+ * @param {number} blockIndex - The 0-based index of the code block to replace.
22
+ * @param {string} newCodeContent - The new raw content (code, potentially including header) to insert into the block.
23
+ * @param {string} [language] - Optional: The language identifier for the updated code block fence. If omitted, the original language is preserved.
24
+ * @returns {string} The message content with the specified code block updated.
25
+ * @throws {Error} If the blockIndex is out of bounds or if block boundaries cannot be determined.
26
+ */
27
+ function updateCodeBlockByIndex(messageContent, blockIndex, newCodeContent, language = undefined) {
28
+ if (typeof messageContent !== 'string') {
29
+ throw new Error("messageContent must be a string.");
30
+ }
31
+ if (typeof blockIndex !== 'number' || blockIndex < 0) {
32
+ throw new Error("blockIndex must be a non-negative number.");
33
+ }
34
+ if (typeof newCodeContent !== 'string') {
35
+ // Allow empty string replacement, but not other types
36
+ throw new Error("newCodeContent must be a string.");
37
+ }
38
+
39
+ // Step 1: Find all fence positions to get accurate start/end of the full block
40
+ const { openingPositions, closingPositions } = findAllCodeFences(messageContent);
41
+ const { completeBlocks, incompleteBlocks, warnings: extractorWarnings } = matchFencesAndExtractBlocks(
42
+ messageContent, openingPositions, closingPositions
43
+ );
44
+
45
+ // Combine complete and incomplete for indexing, assuming we might want to update an incomplete one too
46
+ // Note: Replacing content in an incomplete block might be unusual, but technically possible.
47
+ const allBlocks = [...completeBlocks, ...incompleteBlocks];
48
+
49
+ // Step 2: Validate index
50
+ if (blockIndex >= allBlocks.length) {
51
+ throw new Error(`blockIndex ${blockIndex} is out of bounds. Found ${allBlocks.length} blocks.`);
52
+ }
53
+
54
+ // Step 3: Get the target block's boundary information
55
+ const targetBlockBoundaries = allBlocks[blockIndex];
56
+ const openingFence = targetBlockBoundaries.opening;
57
+ const closingFence = targetBlockBoundaries.closing; // Will be undefined for incomplete blocks
58
+
59
+ if (!openingFence) {
60
+ // Should not happen if index is valid, but good practice to check
61
+ throw new Error(`Could not find opening fence for block at index ${blockIndex}.`);
62
+ }
63
+
64
+ // Determine the exact start and end of the full markdown block
65
+ const blockStartPos = openingFence.position;
66
+ // If the block is incomplete, the end is the end of the messageContent
67
+ const blockEndPos = closingFence
68
+ ? closingFence.position + closingFence.length
69
+ : messageContent.length;
70
+
71
+ // Step 4: Construct the replacement block string
72
+ const targetLanguage = language !== undefined ? language : (openingFence.language || ''); // Use provided language or original/empty
73
+ const newBlockString = `\`\`\`${targetLanguage}\n${newCodeContent}\n\`\`\``;
74
+
75
+ // Step 5: Perform the replacement
76
+ const updatedMessageContent =
77
+ messageContent.substring(0, blockStartPos) +
78
+ newBlockString +
79
+ messageContent.substring(blockEndPos);
80
+
81
+ return updatedMessageContent;
82
+ }
83
+
84
+ /**
85
+ * Replaces the content of a code block specified by its Block-UUID within a message string.
86
+ *
87
+ * @param {string} messageContent - The original message string containing code blocks.
88
+ * @param {string} blockUUID - The Block-UUID of the code block to replace.
89
+ * @param {string} newCodeContent - The new raw content (code, potentially including header) to insert into the block.
90
+ * @param {string} [language] - Optional: The language identifier for the updated code block fence. If omitted, the original language is preserved.
91
+ * @returns {string} The message content with the specified code block updated.
92
+ * @throws {Error} If no block with the specified UUID is found, or if multiple blocks have the same UUID.
93
+ */
94
+ function updateCodeBlockByUUID(messageContent, blockUUID, newCodeContent, language = undefined) {
95
+ if (typeof messageContent !== 'string') {
96
+ throw new Error("messageContent must be a string.");
97
+ }
98
+ if (typeof blockUUID !== 'string' || !blockUUID) {
99
+ throw new Error("blockUUID must be a non-empty string.");
100
+ }
101
+ if (typeof newCodeContent !== 'string') {
102
+ throw new Error("newCodeContent must be a string.");
103
+ }
104
+
105
+ // Step 1: Process blocks to get headers and find the target index
106
+ const { blocks: processedBlocks, warnings } = processCodeBlocks(messageContent, { silent: true });
107
+
108
+ let targetIndex = -1;
109
+ let foundCount = 0;
110
+
111
+ for (let i = 0; i < processedBlocks.length; i++) {
112
+ const block = processedBlocks[i];
113
+ let match = false;
114
+
115
+ // Check standard code blocks by Block-UUID
116
+ if ((block.type === 'code') && block.header && block.header['Block-UUID'] === blockUUID) {
117
+ match = true;
118
+ }
119
+ // Check patch blocks by Target-Block-UUID
120
+ else if (block.type === 'patch' && block.metadata && block.metadata['Target-Block-UUID'] === blockUUID) {
121
+ match = true;
122
+ }
123
+
124
+ if (match) {
125
+ // If we've already found one, log a warning but still update the first one found.
126
+ if (targetIndex !== -1) {
127
+ console.warn(`Multiple blocks found matching UUID: ${blockUUID} (could be Block-UUID or Target-Block-UUID). Updating the first occurrence.`);
128
+ } else {
129
+ targetIndex = i;
130
+ }
131
+ foundCount++; // Increment count regardless to detect duplicates
132
+ }
133
+ }
134
+
135
+ // Step 2: Handle findings
136
+ if (targetIndex === -1) { // Use targetIndex to check if *any* valid match was set
137
+ throw new Error(`No code or patch block found matching UUID: ${blockUUID} (checked Block-UUID and Target-Block-UUID).`);
138
+ }
139
+ // Warning for multiple matches is handled inside the loop now.
140
+
141
+ // Step 3: Call the index-based function
142
+ // We need the index relative to *all* blocks (complete and incomplete), not just processed ones.
143
+ // Re-extract boundaries to get the correct index mapping.
144
+ const { openingPositions, closingPositions } = findAllCodeFences(messageContent);
145
+ const { completeBlocks, incompleteBlocks } = matchFencesAndExtractBlocks(
146
+ messageContent, openingPositions, closingPositions
147
+ );
148
+ const allBlocks = [...completeBlocks, ...incompleteBlocks];
149
+
150
+ // Find the index in the combined list that corresponds to the processed block's position
151
+ const targetProcessedBlock = processedBlocks[targetIndex];
152
+ const actualIndex = allBlocks.findIndex(b => b.opening.position === targetProcessedBlock.position);
153
+
154
+ if (actualIndex === -1) {
155
+ // This indicates a discrepancy between blockProcessor and blockExtractor results, which shouldn't happen.
156
+ throw new Error(`Internal error: Could not map processed block at index ${targetIndex} (matching UUID: ${blockUUID}) back to extracted block boundaries.`);
157
+ }
158
+
159
+ // Pass the optional language parameter down
160
+ return updateCodeBlockByIndex(messageContent, actualIndex, newCodeContent, language);
161
+ }
162
+
163
+ /**
164
+ * Updates a code block in message text identified by UUID.
165
+ * (Moved from original PatchUtils)
166
+ * @param {string} messageText - The original message text
167
+ * @param {string} blockUUID - The Block-UUID to update
168
+ * @param {string} newCode - The new code content (raw content, including header if applicable)
169
+ * @param {string} language - The code language for the fence
170
+ * @returns {string} Updated message text
171
+ * @throws {Error} If block not found or parameters missing.
172
+ */
173
+ function updateCodeBlockInMessage(messageText, blockUUID, newCode, language) {
174
+ if (!messageText || !blockUUID || newCode === null || newCode === undefined) { // Allow empty string for newCode
175
+ throw new Error("Missing required parameters for updating code block (messageText, blockUUID, newCode)");
176
+ }
177
+
178
+ // Use findCodeBlockByUUID from blockExtractor
179
+ const block = findCodeBlockByUUID(messageText, blockUUID);
180
+
181
+ if (!block) {
182
+ throw new Error(`Code block with UUID ${blockUUID} not found`);
183
+ }
184
+
185
+ // Construct the new full block string
186
+ const newBlockString = '```' + (language || block.language) + '\n' + newCode + '\n```';
187
+
188
+ // Replace the old block with the new one using precise indices
189
+ return messageText.substring(0, block.startIndex) +
190
+ newBlockString +
191
+ messageText.substring(block.endIndex);
192
+ }
193
+
194
+
195
+ /**
196
+ * Generic router function to update a code block by index or UUID.
197
+ *
198
+ * @param {string} messageContent - The original message string.
199
+ * @param {number|string} identifier - The block index (number) or Block-UUID (string).
200
+ * @param {string} newCodeContent - The new raw content for the block.
201
+ * @param {string} [language] - Optional: The language identifier for the updated code block fence.
202
+ * @returns {string} The updated message content.
203
+ * @throws {Error} If the identifier type is invalid or if the underlying update function fails.
204
+ */
205
+ function updateCodeBlock(messageContent, identifier, newCodeContent, language = undefined) {
206
+ if (typeof identifier === 'number') {
207
+ // Pass language to index-based function
208
+ return updateCodeBlockByIndex(messageContent, identifier, newCodeContent, language);
209
+ } else if (typeof identifier === 'string') {
210
+ // Pass language to UUID-based function
211
+ return updateCodeBlockByUUID(messageContent, identifier, newCodeContent, language);
212
+ } else {
213
+ throw new Error("Invalid identifier type. Must be a number (index) or a string (UUID).");
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Deletes a code block specified by its index within a message string.
219
+ *
220
+ * @param {string} messageContent - The original message string containing code blocks.
221
+ * @param {number} blockIndex - The 0-based index of the code block to delete.
222
+ * @returns {string} The message content with the specified code block deleted.
223
+ * @throws {Error} If the blockIndex is out of bounds or if block boundaries cannot be determined.
224
+ */
225
+ function deleteCodeBlockByIndex(messageContent, blockIndex) {
226
+ if (typeof messageContent !== 'string') {
227
+ throw new Error("messageContent must be a string.");
228
+ }
229
+ if (typeof blockIndex !== 'number' || blockIndex < 0) {
230
+ throw new Error("blockIndex must be a non-negative number.");
231
+ }
232
+
233
+ // Step 1: Find all fence positions to get accurate start/end of the full block
234
+ const { openingPositions, closingPositions } = findAllCodeFences(messageContent);
235
+ const { completeBlocks, incompleteBlocks } = matchFencesAndExtractBlocks(
236
+ messageContent, openingPositions, closingPositions
237
+ );
238
+
239
+ // Combine complete and incomplete for indexing
240
+ const allBlocks = [...completeBlocks, ...incompleteBlocks];
241
+
242
+ // Step 2: Validate index
243
+ if (blockIndex >= allBlocks.length) {
244
+ throw new Error(`blockIndex ${blockIndex} is out of bounds. Found ${allBlocks.length} blocks.`);
245
+ }
246
+
247
+ // Step 3: Get the target block's boundary information
248
+ const targetBlockBoundaries = allBlocks[blockIndex];
249
+ const openingFence = targetBlockBoundaries.opening;
250
+ const closingFence = targetBlockBoundaries.closing; // Will be undefined for incomplete blocks
251
+
252
+ if (!openingFence) {
253
+ // Should not happen if index is valid, but good practice to check
254
+ throw new Error(`Could not find opening fence for block at index ${blockIndex}.`);
255
+ }
256
+
257
+ // Determine the exact start and end of the full markdown block
258
+ const blockStartPos = openingFence.position;
259
+ // If the block is incomplete, the end is the end of the messageContent
260
+ const blockEndPos = closingFence
261
+ ? closingFence.position + closingFence.length
262
+ : messageContent.length;
263
+
264
+ // Step 4: Perform the deletion
265
+ const updatedMessageContent =
266
+ messageContent.substring(0, blockStartPos) +
267
+ messageContent.substring(blockEndPos);
268
+
269
+ return updatedMessageContent;
270
+ }
271
+
272
+ /**
273
+ * Deletes a code block specified by its Block-UUID within a message string.
274
+ *
275
+ * @param {string} messageContent - The original message string containing code blocks.
276
+ * @param {string} blockUUID - The Block-UUID of the code block to delete.
277
+ * @returns {string} The message content with the specified code block deleted.
278
+ * @throws {Error} If no block with the specified UUID is found, or if multiple blocks have the same UUID.
279
+ */
280
+ function deleteCodeBlockByUUID(messageContent, blockUUID) {
281
+ if (typeof messageContent !== 'string') {
282
+ throw new Error("messageContent must be a string.");
283
+ }
284
+ if (typeof blockUUID !== 'string' || !blockUUID) {
285
+ throw new Error("blockUUID must be a non-empty string.");
286
+ }
287
+
288
+ // Step 1: Process blocks to get headers and find the target index
289
+ const { blocks: processedBlocks, warnings } = processCodeBlocks(messageContent, { silent: true });
290
+
291
+ let targetIndex = -1;
292
+ let foundCount = 0;
293
+
294
+ for (let i = 0; i < processedBlocks.length; i++) {
295
+ const block = processedBlocks[i];
296
+ let match = false;
297
+
298
+ // Check standard code blocks by Block-UUID
299
+ if (block.type === 'code' && block.header && block.header['Block-UUID'] === blockUUID) {
300
+ match = true;
301
+ }
302
+ // Check patch blocks by Target-Block-UUID
303
+ else if (block.type === 'patch' && block.metadata && block.metadata['Target-Block-UUID'] === blockUUID) {
304
+ match = true;
305
+ }
306
+
307
+ if (match) {
308
+ // If we've already found one, log a warning but still target the first one found.
309
+ if (targetIndex !== -1) {
310
+ console.warn(`Multiple blocks found matching UUID: ${blockUUID} (could be Block-UUID or Target-Block-UUID). Deleting the first occurrence.`);
311
+ } else {
312
+ targetIndex = i;
313
+ }
314
+ foundCount++; // Increment count regardless to detect duplicates
315
+ }
316
+ }
317
+
318
+ // Step 2: Handle findings
319
+ if (targetIndex === -1) { // Use targetIndex to check if *any* valid match was set
320
+ throw new Error(`No code or patch block found matching UUID: ${blockUUID} (checked Block-UUID and Target-Block-UUID).`);
321
+ }
322
+ // Warning for multiple matches is handled inside the loop now.
323
+
324
+ // Step 3: Call the index-based function
325
+ // We need the index relative to *all* blocks (complete and incomplete), not just processed ones.
326
+ // Re-extract boundaries to get the correct index mapping.
327
+ const { openingPositions, closingPositions } = findAllCodeFences(messageContent);
328
+ const { completeBlocks, incompleteBlocks } = matchFencesAndExtractBlocks(
329
+ messageContent, openingPositions, closingPositions
330
+ );
331
+ const allBlocks = [...completeBlocks, ...incompleteBlocks];
332
+
333
+ // Find the index in the combined list that corresponds to the processed block's position
334
+ const targetProcessedBlock = processedBlocks[targetIndex];
335
+ const actualIndex = allBlocks.findIndex(b => b.opening.position === targetProcessedBlock.position);
336
+
337
+ if (actualIndex === -1) {
338
+ // This indicates a discrepancy between blockProcessor and blockExtractor results, which shouldn't happen.
339
+ throw new Error(`Internal error: Could not map processed block at index ${targetIndex} (matching UUID: ${blockUUID}) back to extracted block boundaries.`);
340
+ }
341
+
342
+ return deleteCodeBlockByIndex(messageContent, actualIndex);
343
+ }
344
+
345
+ /**
346
+ * Generic router function to delete a code block by index or UUID.
347
+ *
348
+ * @param {string} messageContent - The original message string.
349
+ * @param {number|string} identifier - The block index (number) or Block-UUID (string).
350
+ * @returns {string} The updated message content.
351
+ * @throws {Error} If the identifier type is invalid or if the underlying delete function fails.
352
+ */
353
+ function deleteCodeBlock(messageContent, identifier) {
354
+ if (typeof identifier === 'number') {
355
+ return deleteCodeBlockByIndex(messageContent, identifier);
356
+ } else if (typeof identifier === 'string') {
357
+ return deleteCodeBlockByUUID(messageContent, identifier);
358
+ } else {
359
+ throw new Error("Invalid identifier type. Must be a number (index) or a string (UUID).");
360
+ }
361
+ }
362
+
363
+ module.exports = {
364
+ updateCodeBlockByIndex,
365
+ updateCodeBlockByUUID,
366
+ updateCodeBlock,
367
+ updateCodeBlockInMessage,
368
+ deleteCodeBlockByIndex,
369
+ deleteCodeBlockByUUID,
370
+ deleteCodeBlock,
371
+ };
372
+
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Component: CodeBlockUtils UUID Utilities
3
+ * Block-UUID: 37525056-8d61-4501-bd9d-c098c19542a5
4
+ * Parent-UUID: 7e9d3f8a-1b2c-4d5e-8f9a-0123456789ab
5
+ * Version: 1.0.0
6
+ * Description: Provides utility functions for generating and validating RFC 4122 UUID v4 strings.
7
+ * Language: JavaScript
8
+ * Created-at: 2025-04-15T15:52:06.507Z
9
+ * Authors: Gemini 2.5 Pro (v1.0.0)
10
+ */
11
+
12
+
13
+ /**
14
+ * Generates a valid RFC 4122 UUID v4
15
+ * @returns {string} A valid UUID v4
16
+ */
17
+ function generateUUID() {
18
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
19
+ const r = Math.random() * 16 | 0;
20
+ const v = c === 'x' ? r : (r & 0x3 | 0x8);
21
+ return v.toString(16);
22
+ });
23
+ }
24
+
25
+ /**
26
+ * Validates UUID and returns object with validation results
27
+ * @param {string} uuid - The UUID string to validate
28
+ * @returns {Object} Object containing validation results and replacement UUID if needed
29
+ */
30
+ function validateUUID(uuid) {
31
+ const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
32
+
33
+ if (!uuidPattern.test(uuid)) {
34
+ return {
35
+ "Block-UUID": "INVALID UUID",
36
+ "Correct Block-UUID": generateUUID()
37
+ };
38
+ }
39
+
40
+ return {
41
+ "Block-UUID": uuid
42
+ };
43
+ }
44
+
45
+ module.exports = {
46
+ generateUUID,
47
+ validateUUID
48
+ };
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Component: ContextUtils
3
+ * Block-UUID: c199efe3-003c-4226-af3c-d460392a6569
4
+ * Parent-UUID: N/A
5
+ * Version: 1.0.0
6
+ * Description: Provides utility functions for parsing context message sections to extract file details and code blocks.
7
+ * Language: JavaScript
8
+ * Created-at: 2025-05-09T01:36:20.107Z
9
+ * Authors: Gemini 2.5 Flash Thinking (v1.0.0)
10
+ */
11
+
12
+
13
+ const CodeBlockUtils = require('./CodeBlockUtils');
14
+ const MessageUtils = require('./MessageUtils');
15
+
16
+ /**
17
+ * Parses context details from a context message section.
18
+ * @param {string} sectionText - The text content of a single context section (starting from the file header).
19
+ * @returns {Object|null} An object with file details (name, path, meta, content) or null if parsing fails.
20
+ */
21
+ function parseContextSection(sectionText) {
22
+ const contextSection = {
23
+ name: 'Unknown File',
24
+ content: null,
25
+ sectionText
26
+ };
27
+
28
+ // Extract filename from #### `filename` line
29
+ const nameMatch = sectionText.match(/^####\s*`([^`]+)`/);
30
+ if (nameMatch && nameMatch[1]) {
31
+ contextSection.name = nameMatch[1];
32
+ } else {
33
+ // Attempt fallback if format is slightly different, e.g., #### filename without backticks
34
+ const fallbackNameMatch = sectionText.match(/^####\s*([^\n]+)/);
35
+ if (fallbackNameMatch && fallbackNameMatch[1]) {
36
+ contextSection.name = fallbackNameMatch[1].trim();
37
+ } else {
38
+ console.warn("Could not parse filename from section head:", sectionText.split('\n')[0]);
39
+ // Keep default 'Unknown File' or decide to return null
40
+ }
41
+ }
42
+
43
+ // Extract metadata lines (simple approach: grab lines starting with '-')
44
+ const properties = ['repo', 'path', 'chat id', 'parent id', 'size', 'tokens', 'type'];
45
+ const lines = sectionText.split('\n').filter(line => line.trim().startsWith('-'));
46
+ const metaLines = [];
47
+ let stop = false;
48
+ lines.forEach(line => {
49
+ if (stop) return;
50
+
51
+ const trimmedLine = line.substring(1).trim(); // Remove '-' and trim
52
+ if (trimmedLine === '' || trimmedLine.startsWith('```')) {
53
+ stop = true;
54
+ return;
55
+ }
56
+
57
+ const property = trimmedLine.toLowerCase().split(':')[0];
58
+
59
+ if (properties.includes(property)) {
60
+ let value = trimmedLine.split(':').slice(1).join(':').trim();
61
+
62
+ if (property.match(/id/))
63
+ value = parseInt(value);
64
+
65
+ contextSection[property] = value;
66
+ metaLines.push(line);
67
+ }
68
+ });
69
+
70
+ const { blocks, warnings } = CodeBlockUtils.extractCodeBlocks(sectionText, { silent: true });
71
+ const codeBlocks = blocks.filter(block => block.type === 'code');
72
+
73
+ if (codeBlocks.length === 0) {
74
+ console.warn(`No code block found exists for ${contextSection.name}`);
75
+ throw("");
76
+ } else if (codeBlocks.length > 1) {
77
+ console.warn(`More than one code block found for ${contextSection.name}`);
78
+ } else {
79
+ const block = codeBlocks[0];
80
+ const { headerText, content, language, highlight } = block;
81
+ contextSection.content = (headerText ? headerText+'\n\n\n' : '')+content;
82
+ contextSection.language = language;
83
+ contextSection.highlight = highlight;
84
+ contextSection.block = block;
85
+ contextSection.header = metaLines.join('\n');
86
+ }
87
+
88
+ return contextSection;
89
+ }
90
+
91
+ /**
92
+ * Extracts and parses all context sections from a context message string.
93
+ * @param {string} messageContent - The full content of the context message.
94
+ * @returns {Array<Object>} An array of parsed context section objects.
95
+ * @throws {Error} If the message content is not a valid context message.
96
+ */
97
+ function extractContextSections(messageContent) {
98
+ // Use the utility function to validate the message type
99
+ if (!MessageUtils.isContextMessage(messageContent)) {
100
+ throw new Error("Invalid message type: Content is not a context message.");
101
+ }
102
+
103
+ const contextSections = [];
104
+
105
+ // Split the message content into sections based the markers
106
+ const [summary, items] = messageContent.split(/\n---Start of Context---\n\n/);
107
+ const sections = items.split(/\n---End of Item---\n/);
108
+
109
+ // Process sections starting from the first potential path delimiter.
110
+ for (let i = 0; i < sections.length; i++) {
111
+ const contextSection = parseContextSection(sections[i]);
112
+
113
+ if (contextSection) {
114
+ contextSections.push(contextSection);
115
+ }
116
+ }
117
+
118
+ if (contextSections.length === 0) {
119
+ console.warn("Context message detected, but no context sections could be parsed by extractContextSections.");
120
+ // Depending on desired behavior, could throw an error or return empty array
121
+ }
122
+
123
+ return contextSections;
124
+ }
125
+
126
+ function extractContextItemsOverviewTableRows(messageContent) {
127
+ const lines = messageContent.trim().split('\n');
128
+ const startIndex = lines.findIndex(line => line.startsWith('---Start of Overview Items---')) + 4;
129
+
130
+ // A starting index of 2 means we never found it
131
+ if (startIndex === 3) {
132
+ return null;
133
+ }
134
+
135
+ const col2Field = {
136
+ 1: 'chatId',
137
+ 2: 'type', // file, tree, etc.
138
+ 3: 'repoFullName', // owner/name => facebook/react
139
+ 4: 'gitRef', // branch name, commit ahs, etc.
140
+ 5: 'gitPath', // relative repo path and not the chat path
141
+ 6: 'purpose', // single sentence
142
+ 7: 'keywords', // comma separated
143
+ };
144
+
145
+ const rows = [];
146
+
147
+ for (let i = startIndex; i < lines.length; i++ ) {
148
+ const line = lines[i];
149
+
150
+ // Since we are parsing a Markdown table, we expect the first line to be the vertical bar
151
+ if (!line.startsWith('|'))
152
+ break;
153
+
154
+ const values = line.split('|');
155
+ const row = {};
156
+
157
+ values.forEach((value, col) => {
158
+ if (col === 0)
159
+ return;
160
+
161
+ const field = col2Field[col];
162
+
163
+ if (!field)
164
+ return;
165
+
166
+ value = value.trim();
167
+ row[field] = field === 'chatId' ? parseInt(value) : value;
168
+ });
169
+
170
+ rows.push(row);
171
+ }
172
+
173
+ return rows;
174
+ }
175
+
176
+ module.exports = {
177
+ parseContextSection,
178
+ extractContextSections,
179
+ extractContextItemsOverviewTableRows
180
+ };