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