@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,460 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component: MessageUtils
|
|
3
|
+
* Block-UUID: 01147ddf-320f-498a-a744-198d42a9d2ee
|
|
4
|
+
* Parent-UUID: fb0b40b3-ce0b-47e2-bb3e-b39092e9b829
|
|
5
|
+
* Version: 1.2.0
|
|
6
|
+
* Description: Utility functions for processing message files, including context message detection.
|
|
7
|
+
* Language: JavaScript
|
|
8
|
+
* Created-at: 2025-07-11T19:56:54.949Z
|
|
9
|
+
* Authors: Claude 3.7 Sonnet (v1.0.0), Gemini 2.5 Pro (v1.1.0), Gemini 2.5 Flash Thinking (v1.2.0)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const ANALYZE_MESSAGE_REGEXP = /^# Analyze - `([^`]+)`\n/;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Extracts the role from the metadata section of a message file
|
|
19
|
+
* @param {string} metadata - The metadata section of the message file
|
|
20
|
+
* @returns {string} The extracted role
|
|
21
|
+
*/
|
|
22
|
+
function extractRole(metadata) {
|
|
23
|
+
const lines = metadata.split('\n');
|
|
24
|
+
for (const line of lines) {
|
|
25
|
+
if (line.trim().startsWith('; role:')) {
|
|
26
|
+
return line.trim().substring('; role:'.length).trim();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return 'user'; // Default to user if role is not found
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Unescapes code blocks by removing forward slashes before backticks
|
|
34
|
+
* @param {string} content - The message content
|
|
35
|
+
* @returns {string} The content with unescaped code blocks
|
|
36
|
+
*/
|
|
37
|
+
function unescapeCodeBlocks(content) {
|
|
38
|
+
return content.replace(/\\(`+)/g, '$1');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Gets template messages from a specific message type directory
|
|
43
|
+
* @param {string} dirname- The directory containting the template messages directory
|
|
44
|
+
* @param {string} messageType - The type of messages to retrieve (e.g., 'notes', 'draft')
|
|
45
|
+
* @returns {Array} An array of message objects with role and content properties
|
|
46
|
+
*/
|
|
47
|
+
function getChatTemplateMessages(dirname, messageType) {
|
|
48
|
+
const messagesDir = path.join(dirname, messageType);
|
|
49
|
+
const messages = [];
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
// Read all files in the directory
|
|
53
|
+
const files = fs.readdirSync(messagesDir);
|
|
54
|
+
|
|
55
|
+
// Sort files numerically (1.md, 2.md, etc.)
|
|
56
|
+
const sortedFiles = files.sort((a, b) => {
|
|
57
|
+
const numA = parseInt(a.split('.')[0]);
|
|
58
|
+
const numB = parseInt(b.split('.')[0]);
|
|
59
|
+
return numA - numB;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Process each file
|
|
63
|
+
for (const file of sortedFiles) {
|
|
64
|
+
if (path.extname(file) === '.md') {
|
|
65
|
+
const filePath = path.join(messagesDir, file);
|
|
66
|
+
const fileContent = fs.readFileSync(filePath, 'utf8');
|
|
67
|
+
|
|
68
|
+
// Split by triple newline to separate metadata from content
|
|
69
|
+
const parts = fileContent.split('\n\n\n');
|
|
70
|
+
|
|
71
|
+
if (parts.length >= 2) {
|
|
72
|
+
const metadata = parts[0];
|
|
73
|
+
// Join all remaining parts in case there are other triple newlines in the content
|
|
74
|
+
const content = parts.slice(1).join('\n\n\n').trim();
|
|
75
|
+
|
|
76
|
+
const role = extractRole(metadata);
|
|
77
|
+
const processedContent = unescapeCodeBlocks(content);
|
|
78
|
+
|
|
79
|
+
messages.push({
|
|
80
|
+
role,
|
|
81
|
+
content: processedContent
|
|
82
|
+
});
|
|
83
|
+
} else {
|
|
84
|
+
// Handle case where there's no triple newline
|
|
85
|
+
const role = extractRole(fileContent);
|
|
86
|
+
messages.push({
|
|
87
|
+
role,
|
|
88
|
+
content: ''
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return messages;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error(`Error reading messages from ${messageType}:`, error);
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Gets messages before a specific ID
|
|
103
|
+
* @param {string} model - The model to filter messages by
|
|
104
|
+
* @param {Object} message - The message object to start from
|
|
105
|
+
* @param {string} stopId - Optional ID to stop at
|
|
106
|
+
* @param {Array} messages - Optional array to accumulate messages
|
|
107
|
+
* @returns {Array} Array of messages
|
|
108
|
+
*/
|
|
109
|
+
function getMessagesBeforeId(model, message, stopId, messages = [], debug = false) {
|
|
110
|
+
if (!model)
|
|
111
|
+
throw new Error('MessageUtils: No model defined');
|
|
112
|
+
|
|
113
|
+
if (!message)
|
|
114
|
+
throw new Error('MessageUtils: No message defined');
|
|
115
|
+
|
|
116
|
+
const { role, id, kids, model: m } = message;
|
|
117
|
+
|
|
118
|
+
if (id === stopId) {
|
|
119
|
+
return messages;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!m || m === model) {
|
|
123
|
+
messages.push(message);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!kids || !kids.length) {
|
|
127
|
+
return messages;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return getMessagesBeforeId(model, kids[0], stopId, messages);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Gets a specific message by its ID from a chat tree.
|
|
135
|
+
* @param {Object} chatOrMessage - The root chat object or a message node to start searching from.
|
|
136
|
+
* @param {number|string} id - The ID of the message to find.
|
|
137
|
+
* @returns {Object|null} The found message object or null if not found.
|
|
138
|
+
*/
|
|
139
|
+
function getMessageById(chatOrMessage, id) {
|
|
140
|
+
// Use a stack for iterative depth-first search to avoid deep recursion issues
|
|
141
|
+
const stack = [];
|
|
142
|
+
|
|
143
|
+
// Determine starting point: root chat object or a specific message node
|
|
144
|
+
if (chatOrMessage && chatOrMessage.messages && Array.isArray(chatOrMessage.messages)) {
|
|
145
|
+
// If it's the root chat object, start with its top-level messages
|
|
146
|
+
stack.push(...chatOrMessage.messages);
|
|
147
|
+
} else if (chatOrMessage && chatOrMessage.id) {
|
|
148
|
+
// If it's a message node itself
|
|
149
|
+
stack.push(chatOrMessage);
|
|
150
|
+
} else {
|
|
151
|
+
// Invalid input
|
|
152
|
+
console.error("getMessageById: Invalid input provided.", chatOrMessage);
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
while (stack.length > 0) {
|
|
157
|
+
const currentMessage = stack.pop();
|
|
158
|
+
|
|
159
|
+
if (!currentMessage) continue; // Skip if undefined/null somehow got pushed
|
|
160
|
+
|
|
161
|
+
// Check if the current message is the one we're looking for
|
|
162
|
+
// Ensure type consistency for comparison if needed (e.g., both numbers or both strings)
|
|
163
|
+
if (currentMessage.id == id) { // Use == for potential type coercion if IDs might be numbers or strings
|
|
164
|
+
return currentMessage;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Add children to the stack to search deeper
|
|
168
|
+
if (currentMessage.kids && Array.isArray(currentMessage.kids)) {
|
|
169
|
+
// Push children in reverse order to maintain depth-first order
|
|
170
|
+
for (let i = currentMessage.kids.length - 1; i >= 0; i--) {
|
|
171
|
+
stack.push(currentMessage.kids[i]);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Message not found
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Gets the last message in a specific conversation thread (follows the first child path).
|
|
182
|
+
* @param {Object} message - The message to start from.
|
|
183
|
+
* @returns {Object} The last message in the thread.
|
|
184
|
+
*/
|
|
185
|
+
function getLastMessage(message) {
|
|
186
|
+
let current = message;
|
|
187
|
+
while (current && current.kids && current.kids.length > 0) {
|
|
188
|
+
// Assuming the main thread follows the first child
|
|
189
|
+
current = current.kids[0];
|
|
190
|
+
}
|
|
191
|
+
return current;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Recursively finds all messages in a chat tree that match a given filter function.
|
|
196
|
+
* Uses an iterative approach with a stack to prevent deep recursion errors.
|
|
197
|
+
*
|
|
198
|
+
* @param {Object} rootNode - The starting node (can be the root chat object with a `messages` array, or any message node).
|
|
199
|
+
* @param {function(Object): boolean} filterFn - A function that takes a message object and returns true if it matches the criteria.
|
|
200
|
+
* @returns {Array<Object>} An array of message objects that matched the filter.
|
|
201
|
+
*/
|
|
202
|
+
function findMessages(rootNode, filterFn) {
|
|
203
|
+
const matchingMessages = [];
|
|
204
|
+
const stack = [];
|
|
205
|
+
|
|
206
|
+
// Initialize stack based on rootNode type
|
|
207
|
+
if (rootNode && rootNode.messages && Array.isArray(rootNode.messages)) {
|
|
208
|
+
// If it's the root chat object, start with its top-level messages (in reverse for stack processing)
|
|
209
|
+
for (let i = rootNode.messages.length - 1; i >= 0; i--) {
|
|
210
|
+
stack.push(rootNode.messages[i]);
|
|
211
|
+
}
|
|
212
|
+
} else if (rootNode && rootNode.id) {
|
|
213
|
+
// If it's a message node itself
|
|
214
|
+
stack.push(rootNode);
|
|
215
|
+
} else {
|
|
216
|
+
console.warn("findMessages: Invalid rootNode provided.", rootNode);
|
|
217
|
+
return []; // Return empty array for invalid input
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
while (stack.length > 0) {
|
|
221
|
+
const currentMessage = stack.pop();
|
|
222
|
+
|
|
223
|
+
if (!currentMessage) continue; // Skip if undefined/null
|
|
224
|
+
|
|
225
|
+
// Apply the filter function to the current message
|
|
226
|
+
try {
|
|
227
|
+
if (filterFn(currentMessage)) {
|
|
228
|
+
matchingMessages.push(currentMessage);
|
|
229
|
+
}
|
|
230
|
+
} catch (error) {
|
|
231
|
+
console.error("Error executing filter function on message:", currentMessage.id, error);
|
|
232
|
+
// Decide whether to skip this message or re-throw, depending on desired robustness
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
// Add children to the stack to search deeper (push in reverse order to maintain logical traversal order)
|
|
237
|
+
if (currentMessage.kids && Array.isArray(currentMessage.kids)) {
|
|
238
|
+
for (let i = currentMessage.kids.length - 1; i >= 0; i--) {
|
|
239
|
+
stack.push(currentMessage.kids[i]);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return matchingMessages;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Deletes messages by their IDs from a nested chat structure.
|
|
249
|
+
* Modifies the structure in place or returns a new root depending on input.
|
|
250
|
+
*
|
|
251
|
+
* @param {Object} rootNode - The root node of the chat structure. This can be the main chat object
|
|
252
|
+
* (containing a `messages` array) or a specific message node.
|
|
253
|
+
* @param {Array<number|string>} idsToDeleteArray - An array of message IDs to delete.
|
|
254
|
+
* @returns {Object} The modified rootNode. If the provided rootNode itself was deleted
|
|
255
|
+
* (and it was a message node), this function's behavior might need adjustment
|
|
256
|
+
* based on caller expectations (e.g., return null or throw error).
|
|
257
|
+
* Currently assumes the rootNode itself is not in idsToDeleteArray if it's a message node.
|
|
258
|
+
* @throws {Error} If idsToDeleteArray is not an array.
|
|
259
|
+
*/
|
|
260
|
+
function deleteMessagesByIds(rootNode, idsToDeleteArray) {
|
|
261
|
+
if (!Array.isArray(idsToDeleteArray)) {
|
|
262
|
+
throw new Error("idsToDeleteArray must be an array.");
|
|
263
|
+
}
|
|
264
|
+
if (!rootNode) {
|
|
265
|
+
console.warn("deleteMessagesByIds: rootNode is null or undefined.");
|
|
266
|
+
return rootNode; // Return unchanged if root is invalid
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const idsToDeleteSet = new Set(idsToDeleteArray);
|
|
270
|
+
|
|
271
|
+
// Handle based on whether rootNode is the main chat object or a message node
|
|
272
|
+
if (rootNode.messages && Array.isArray(rootNode.messages)) {
|
|
273
|
+
// rootNode is the main chat object containing the top-level messages array
|
|
274
|
+
rootNode.messages = _processNodeListForDeletion(rootNode.messages, null, idsToDeleteSet);
|
|
275
|
+
} else if (rootNode.id) {
|
|
276
|
+
// rootNode is a message node itself.
|
|
277
|
+
// Check if the root message node itself needs deletion (this case is complex)
|
|
278
|
+
if (idsToDeleteSet.has(rootNode.id)) {
|
|
279
|
+
// This scenario is ambiguous. Deleting the root node passed in means
|
|
280
|
+
// the reference becomes invalid. The caller needs to handle this.
|
|
281
|
+
// For now, we'll log a warning and potentially return null or an empty structure.
|
|
282
|
+
// A safer approach is for the caller to ensure the root isn't deleted,
|
|
283
|
+
// or to always pass the main chat object.
|
|
284
|
+
console.warn(`deleteMessagesByIds: The provided rootNode (ID: ${rootNode.id}) itself is marked for deletion. Returning null.`);
|
|
285
|
+
// To properly handle this, we'd need to return the *children* of the deleted root,
|
|
286
|
+
// but the function signature returns the 'modified rootNode'.
|
|
287
|
+
// Returning null might be the clearest signal.
|
|
288
|
+
const children = rootNode.kids || [];
|
|
289
|
+
const parentIdForOrphanedChildren = 0; // Assuming root's parent is 0
|
|
290
|
+
const processedChildren = _processNodeListForDeletion(children, null, idsToDeleteSet);
|
|
291
|
+
for (const child of processedChildren) {
|
|
292
|
+
child.parent_id = parentIdForOrphanedChildren;
|
|
293
|
+
}
|
|
294
|
+
// This isn't ideal as it returns an array, not the expected rootNode object.
|
|
295
|
+
// Returning null might be the clearest signal.
|
|
296
|
+
return null; // Indicate the root was deleted.
|
|
297
|
+
}
|
|
298
|
+
// Process the children of the root message node
|
|
299
|
+
rootNode.kids = _processNodeListForDeletion(rootNode.kids || [], rootNode, idsToDeleteSet);
|
|
300
|
+
} else {
|
|
301
|
+
console.warn("deleteMessagesByIds: Invalid rootNode structure.", rootNode);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return rootNode;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Recursively processes a list of message nodes, deleting specified messages
|
|
309
|
+
* and re-parenting their children.
|
|
310
|
+
*
|
|
311
|
+
* @param {Array<Object>} nodeList - The list of message nodes to process.
|
|
312
|
+
* @param {Object|null} parentNode - The parent node of the current list (null for root messages).
|
|
313
|
+
* @param {Set<number|string>} idsToDeleteSet - A Set containing the IDs of messages to delete.
|
|
314
|
+
* @returns {Array<Object>} A new list of nodes with specified messages removed and children re-parented.
|
|
315
|
+
* @private
|
|
316
|
+
*/
|
|
317
|
+
function _processNodeListForDeletion(nodeList, parentNode, idsToDeleteSet) {
|
|
318
|
+
if (!nodeList || nodeList.length === 0) {
|
|
319
|
+
return [];
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const newNodelist = [];
|
|
323
|
+
// Determine the correct parent ID for children of deleted nodes
|
|
324
|
+
// Assuming 0 or null is used for top-level messages if parentNode is the root chat object
|
|
325
|
+
const parentIdForOrphanedChildren = parentNode ? parentNode.id : 0;
|
|
326
|
+
|
|
327
|
+
for (const node of nodeList) {
|
|
328
|
+
if (idsToDeleteSet.has(node.id)) {
|
|
329
|
+
// Node needs to be deleted
|
|
330
|
+
const children = node.kids || [];
|
|
331
|
+
// Recursively process the children of the deleted node *before* adding them
|
|
332
|
+
// Pass the *original parent* of the deleted node to maintain the level
|
|
333
|
+
const processedChildren = _processNodeListForDeletion(children, parentNode, idsToDeleteSet);
|
|
334
|
+
|
|
335
|
+
// Re-parent the processed children
|
|
336
|
+
for (const child of processedChildren) {
|
|
337
|
+
child.parent_id = parentIdForOrphanedChildren;
|
|
338
|
+
// Optional: Adjust child.level if necessary, though level might become less meaningful
|
|
339
|
+
// child.level = parentNode ? parentNode.level + 1 : 0;
|
|
340
|
+
}
|
|
341
|
+
// Add the re-parented children directly to the new list, effectively skipping the deleted node
|
|
342
|
+
newNodelist.push(...processedChildren);
|
|
343
|
+
|
|
344
|
+
} else {
|
|
345
|
+
// Node is kept
|
|
346
|
+
// Recursively process the children of the *kept* node
|
|
347
|
+
node.kids = _processNodeListForDeletion(node.kids || [], node, idsToDeleteSet);
|
|
348
|
+
// Add the processed node (with potentially modified kids) to the new list
|
|
349
|
+
newNodelist.push(node);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return newNodelist;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Determines the type of message content based on specific prefixes.
|
|
358
|
+
* @param {string} messageContent - The content of the message.
|
|
359
|
+
* @returns {'overview-context'|'file-content-context'|'analyze-message'|'regular'} The type of the message content.
|
|
360
|
+
*/
|
|
361
|
+
function getMessageContentType(messageContent) {
|
|
362
|
+
if (typeof messageContent !== 'string') {
|
|
363
|
+
return 'regular'; // Handle non-string input gracefully
|
|
364
|
+
}
|
|
365
|
+
const trimmedContent = messageContent.trimStart(); // Check from the beginning, ignoring leading whitespace
|
|
366
|
+
if (trimmedContent.startsWith('## FILE CONTENT')) {
|
|
367
|
+
return 'file-content-context';
|
|
368
|
+
} else if (trimmedContent.startsWith('## OVERVIEW')) {
|
|
369
|
+
return 'overview-context';
|
|
370
|
+
} else if (trimmedContent.startsWith('## Context Items Overview\n\nThis section provides')) {
|
|
371
|
+
return 'context-items-overview';
|
|
372
|
+
} else if (trimmedContent.match(ANALYZE_MESSAGE_REGEXP)) {
|
|
373
|
+
return 'analyze-message';
|
|
374
|
+
} else {
|
|
375
|
+
return 'regular';
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Checks if a message is a context message (overview or file content).
|
|
381
|
+
* @param {string} messageContent - The content of the message.
|
|
382
|
+
* @returns {boolean} True if the message is a context message, false otherwise.
|
|
383
|
+
*/
|
|
384
|
+
function isContextMessage(messageContent) {
|
|
385
|
+
const type = getMessageContentType(messageContent);
|
|
386
|
+
return type === 'overview-context' || type === 'file-content-context';
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function isContextItemsOverviewMessage(messageContent) {
|
|
390
|
+
const type = getMessageContentType(messageContent);
|
|
391
|
+
return type === 'context-items-overview';
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function isAnalyzeMessage(messageContent) {
|
|
395
|
+
const type = getMessageContentType(messageContent);
|
|
396
|
+
return type === 'analyze-message';
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Checks if a message is the final "New Analyzer Instructions" message.
|
|
401
|
+
* @param {string} messageContent - The content of the message.
|
|
402
|
+
* @param {boolean} strict - Enable or disable strict parsing
|
|
403
|
+
* @returns {boolean} True if the message is the new analyzer instructions message, false otherwise.
|
|
404
|
+
*/
|
|
405
|
+
function isNewAnalyzerInstructionsMessage(messageContent, strict = true) {
|
|
406
|
+
if (typeof messageContent !== 'string')
|
|
407
|
+
return false;
|
|
408
|
+
|
|
409
|
+
const trimmedMessage = messageContent.trimStart()
|
|
410
|
+
const header = '# New Analyzer Instructions';
|
|
411
|
+
|
|
412
|
+
if (strict) {
|
|
413
|
+
// In strict mode, we need the first list to contain the header
|
|
414
|
+
return trimmedMessage.startsWith(header);
|
|
415
|
+
} else {
|
|
416
|
+
// LLMs are instructed to always start with the header but they don't always
|
|
417
|
+
// comply so we will need to find the header and check the next line to make
|
|
418
|
+
// sure it has a unique identiifer
|
|
419
|
+
const lines = trimmedMessage.split('\n');
|
|
420
|
+
const newAnalyzerHeaderIndex = lines.indexOf(header);
|
|
421
|
+
|
|
422
|
+
if (newAnalyzerHeaderIndex === -1)
|
|
423
|
+
return false;
|
|
424
|
+
|
|
425
|
+
const analyzerId = lines[newAnalyzerHeaderIndex+1];
|
|
426
|
+
|
|
427
|
+
if (analyzerId.split('::').length === 3) {
|
|
428
|
+
return true;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function parseAnalyzeMessage(messageContent) {
|
|
436
|
+
const match = messageContent.match(ANALYZE_MESSAGE_REGEXP);
|
|
437
|
+
|
|
438
|
+
if (!match || match.length !== 2)
|
|
439
|
+
return {};
|
|
440
|
+
|
|
441
|
+
const uniqueAnalyzerId = match[1];
|
|
442
|
+
const [ analyzer, content, instructions ] = uniqueAnalyzerId.split('::');
|
|
443
|
+
|
|
444
|
+
return { id: uniqueAnalyzerId, analyzer, content, instructions };
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
module.exports = {
|
|
448
|
+
deleteMessagesByIds,
|
|
449
|
+
getChatTemplateMessages,
|
|
450
|
+
getMessagesBeforeId,
|
|
451
|
+
getMessageById,
|
|
452
|
+
getLastMessage,
|
|
453
|
+
getMessageContentType,
|
|
454
|
+
isNewAnalyzerInstructionsMessage,
|
|
455
|
+
findMessages,
|
|
456
|
+
isAnalyzeMessage,
|
|
457
|
+
isContextMessage,
|
|
458
|
+
isContextItemsOverviewMessage,
|
|
459
|
+
parseAnalyzeMessage
|
|
460
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component: PatchUtils Constants
|
|
3
|
+
* Block-UUID: c6747054-2c8b-461f-910d-0fd725d9a350
|
|
4
|
+
* Parent-UUID: 32adc00e-7509-4219-8e40-6c1319371db9
|
|
5
|
+
* Version: 2.0.0
|
|
6
|
+
* Description: Contains shared constants and regular expressions used by the enhanced patch utilities.
|
|
7
|
+
* Language: JavaScript
|
|
8
|
+
* Created-at: 2025-05-14T16:55:00.000Z
|
|
9
|
+
* Authors: Gemini 2.5 Flash Thinking (v1.0.0), Claude 3.7 Sonnet (v2.0.0)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
// Regex to parse context/deletion/addition lines with line numbers
|
|
14
|
+
// Captures: 1: diff prefix (' ', '-', or '+'), 2: line number, 3: content after 'NNN: '
|
|
15
|
+
const CONTENT_LINE_REGEX = /^([ +-])\s*[-+]*(\d+):\s?(.*)$/;
|
|
16
|
+
|
|
17
|
+
// Regex to parse hunk headers
|
|
18
|
+
// Captures: 1: old_start, 2: old_count (optional), 3: new_start, 4: new_count (optional)
|
|
19
|
+
const HUNK_HEADER_REGEX = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
|
|
20
|
+
|
|
21
|
+
// Regex to detect line number prefixes (e.g., " 12: ")
|
|
22
|
+
const LINE_NUMBER_PREFIX_REGEX = /^\s*\d+:\s/;
|
|
23
|
+
|
|
24
|
+
// Minimum confidence threshold for fuzzy matching
|
|
25
|
+
const FUZZY_MATCH_THRESHOLD = 0.7;
|
|
26
|
+
|
|
27
|
+
// Maximum number of alternative matches to consider
|
|
28
|
+
const MAX_ALTERNATIVE_MATCHES = 3;
|
|
29
|
+
|
|
30
|
+
// Sliding window size for fuzzy matching
|
|
31
|
+
const DEFAULT_SLIDING_WINDOW_SIZE = 3;
|
|
32
|
+
|
|
33
|
+
// Maximum context lines to use for fuzzy matching
|
|
34
|
+
// If context is longer than this, we'll use a sliding window approach
|
|
35
|
+
const MAX_CONTEXT_LINES_FOR_DIRECT_MATCH = 10;
|
|
36
|
+
|
|
37
|
+
// Patch markers
|
|
38
|
+
const PATCH_START_MARKER = '# --- PATCH START MARKER ---';
|
|
39
|
+
const PATCH_END_MARKER = '# --- PATCH END MARKER ---';
|
|
40
|
+
|
|
41
|
+
// Patch metadata header
|
|
42
|
+
const PATCH_METADATA_HEADER = '# Patch Metadata';
|
|
43
|
+
|
|
44
|
+
// Required metadata fields
|
|
45
|
+
const REQUIRED_METADATA_FIELDS = [
|
|
46
|
+
'Source-Block-UUID',
|
|
47
|
+
'Target-Block-UUID',
|
|
48
|
+
'Source-Version',
|
|
49
|
+
'Target-Version',
|
|
50
|
+
'Description',
|
|
51
|
+
'Authors'
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
// Diff file headers
|
|
55
|
+
const ORIGINAL_FILE_HEADER = '--- Original';
|
|
56
|
+
const MODIFIED_FILE_HEADER = '+++ Modified';
|
|
57
|
+
|
|
58
|
+
module.exports = {
|
|
59
|
+
CONTENT_LINE_REGEX,
|
|
60
|
+
HUNK_HEADER_REGEX,
|
|
61
|
+
LINE_NUMBER_PREFIX_REGEX,
|
|
62
|
+
FUZZY_MATCH_THRESHOLD,
|
|
63
|
+
MAX_ALTERNATIVE_MATCHES,
|
|
64
|
+
DEFAULT_SLIDING_WINDOW_SIZE,
|
|
65
|
+
MAX_CONTEXT_LINES_FOR_DIRECT_MATCH,
|
|
66
|
+
PATCH_START_MARKER,
|
|
67
|
+
PATCH_END_MARKER,
|
|
68
|
+
PATCH_METADATA_HEADER,
|
|
69
|
+
REQUIRED_METADATA_FIELDS,
|
|
70
|
+
ORIGINAL_FILE_HEADER,
|
|
71
|
+
MODIFIED_FILE_HEADER
|
|
72
|
+
};
|