@gitsense/gsc-utils 0.2.24 → 0.2.27

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 (40) hide show
  1. package/README.md +380 -62
  2. package/dist/gsc-utils.cjs.js +14270 -523
  3. package/dist/gsc-utils.esm.js +14270 -523
  4. package/package.json +1 -1
  5. package/src/AnalyzerUtils/cloner.js +149 -0
  6. package/src/AnalyzerUtils/constants.js +1 -1
  7. package/src/AnalyzerUtils/defaultPromptLoader.js +10 -10
  8. package/src/AnalyzerUtils/discovery.js +48 -39
  9. package/src/AnalyzerUtils/index.js +13 -7
  10. package/src/AnalyzerUtils/instructionLoader.js +6 -6
  11. package/src/AnalyzerUtils/jsonParser.js +35 -0
  12. package/src/AnalyzerUtils/management.js +6 -6
  13. package/src/AnalyzerUtils/saver.js +5 -5
  14. package/src/AnalyzerUtils/schemaLoader.js +194 -26
  15. package/src/AnalyzerUtils/updater.js +187 -0
  16. package/src/CodeBlockUtils/blockProcessor.js +14 -32
  17. package/src/CodeBlockUtils/constants.js +8 -4
  18. package/src/CodeBlockUtils/headerUtils.js +19 -3
  19. package/src/CodeBlockUtils/index.js +7 -6
  20. package/src/CodeBlockUtils/lineageTracer.js +95 -0
  21. package/src/CompactChatUtils/CompactedMessageUtils.js +224 -0
  22. package/src/CompactChatUtils/README.md +321 -0
  23. package/src/CompactChatUtils/ReferenceMessageUtils.js +143 -0
  24. package/src/CompactChatUtils/index.js +40 -0
  25. package/src/ContextUtils.js +41 -5
  26. package/src/DomUtils.js +559 -0
  27. package/src/GSToolBlockUtils.js +66 -1
  28. package/src/GitSenseChatUtils.js +108 -16
  29. package/src/LanguageNameUtils.js +171 -0
  30. package/src/MarkdownUtils.js +127 -0
  31. package/src/MessageUtils.js +1 -1
  32. package/src/MetaRawResultUtils.js +244 -0
  33. package/src/ObjectUtils.js +54 -0
  34. package/src/PatchUtils/constants.js +9 -3
  35. package/src/PatchUtils/patchParser.js +60 -37
  36. package/src/PatchUtils/patchProcessor.js +8 -5
  37. package/src/PatchUtils/patchVerifier/detectAndFixOverlappingHunks.js +1 -1
  38. package/src/SVGUtils.js +1467 -0
  39. package/src/SharedUtils/stringUtils.js +303 -0
  40. package/src/CodeBlockUtils/blockProcessor.js.rej +0 -8
@@ -1,18 +1,93 @@
1
- /*
1
+ /**
2
2
  * Component: AnalyzerUtils Schema Loader
3
- * Block-UUID: 0c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f
4
- * Parent-UUID: N/A
5
- * Version: 1.0.0
3
+ * Block-UUID: bf25c501-f9e1-4e5b-ad22-9ed24ba50787
4
+ * Parent-UUID: 66208fdc-2c80-4264-8500-06d5f4db103a
5
+ * Version: 1.4.1
6
6
  * Description: Provides utility functions for retrieving and deducing JSON schemas for analyzers.
7
7
  * Language: JavaScript
8
- * Created-at: 2025-08-28T23:48:00.000Z
9
- * Authors: Gemini 2.5 Flash (v1.0.0)
8
+ * Created-at: 2025-11-23T05:40:43.016Z
9
+ * Authors: Gemini 2.5 Flash (v1.0.0), Gemini 2.5 Pro (v1.1.0), Claude Haiku 4.5 (v1.2.0), Claude Haiku 4.5 (v1.3.0), Qwen 3 Coder 480B - Cerebras (v1.4.0), GLM-4.6 (v1.4.1)
10
+ */
11
+
12
+
13
+ /**
14
+ * Parses the 'Custom Metadata Definitions' block from the markdown content.
15
+ * Handles both formats:
16
+ * - With backticks: * `fieldName` (type): description
17
+ * - Without backticks: * fieldName (type): description
18
+ *
19
+ * @param {string} markdownContent - The full content of the 1.md file.
20
+ * @returns {Map<string, {type: string, description: string}>} A map from field name to its type and description.
10
21
  */
22
+ function parseMetadataDefinitions(markdownContent) {
23
+ const definitions = new Map();
24
+
25
+ // Match the "### Custom Metadata Definitions" section until the next code block or section
26
+ const sectionRegex = /### Custom Metadata Definitions\s*([\s\S]*?)(?=```|\n##|\n---)/;
27
+ const match = markdownContent.match(sectionRegex);
28
+
29
+ if (!match || !match[1]) {
30
+ return definitions;
31
+ }
32
+
33
+ const definitionBlock = match[1];
34
+
35
+ // Regex to match both formats:
36
+ // Format 1: * `fieldName` (type): description
37
+ // Format 2: * fieldName (type): description
38
+ // Capture groups:
39
+ // Group 1: backtick-wrapped field name (if present)
40
+ // Group 2: non-backtick field name (if present)
41
+ // Group 3: field type
42
+ // Group 4: description
43
+ const lineRegex = /^\s*\*\s+(?:`([^`]+)`|([a-zA-Z0-9_-]+))\s+\(([^)]+)\):\s*(.+)/gm;
44
+
45
+ let lineMatch;
46
+ while ((lineMatch = lineRegex.exec(definitionBlock)) !== null) {
47
+ // Extract field name from either group 1 (backtick-wrapped) or group 2 (plain)
48
+ const fieldName = (lineMatch[1] || lineMatch[2]).trim();
49
+ const fieldType = lineMatch[3].trim();
50
+ const description = lineMatch[4].trim();
11
51
 
52
+ if (fieldName && fieldType && description) {
53
+ definitions.set(fieldName, { type: fieldType, description });
54
+ }
55
+ }
56
+
57
+ return definitions;
58
+ }
12
59
 
13
60
  const fs = require('fs').promises;
14
61
  const path = require('path');
15
62
  const CodeBlockUtils = require('../CodeBlockUtils');
63
+ const { preprocessJsonForValidation } = require('./jsonParser');
64
+
65
+ /**
66
+ * Maps a simple type string (from the definitions block) to a JSON schema type object.
67
+ * @param {string} simpleType - The type string (e.g., "number", "array of strings").
68
+ * @returns {object} A JSON schema type definition.
69
+ */
70
+ function mapSimpleTypeToSchema(simpleType) {
71
+ const lowerType = simpleType.toLowerCase().trim();
72
+ switch (lowerType) {
73
+ case 'string':
74
+ return { type: 'string' };
75
+ case 'number':
76
+ case 'integer':
77
+ return { type: 'number' };
78
+ case 'boolean':
79
+ return { type: 'boolean' };
80
+ case 'array of strings':
81
+ return { type: 'array', items: { type: 'string' } };
82
+ case 'date':
83
+ return { type: 'string', format: 'date' };
84
+ case 'datetime':
85
+ case 'iso 8601 timestamp':
86
+ return { type: 'string', format: 'date-time' };
87
+ default:
88
+ return { type: 'string' }; // Default to string for unknown types
89
+ }
90
+ }
16
91
 
17
92
  /**
18
93
  * Deduces the JSON schema type and format/items from a string value pattern.
@@ -25,6 +100,15 @@ const CodeBlockUtils = require('../CodeBlockUtils');
25
100
  */
26
101
  function deduceSchemaType(value, fieldName) {
27
102
  const defaultSchema = { type: 'string' }; // Default fallback
103
+
104
+ // Handle new placeholder format {{SYSTEM: ...}} and {{ANALYZER: ...}}
105
+ // These are valid placeholders and should not generate warnings
106
+ if (typeof value === 'string') {
107
+ const trimmedValue = value.trim();
108
+ if (trimmedValue.startsWith('{{SYSTEM:') || trimmedValue.startsWith('{{ANALYZER:')) {
109
+ return defaultSchema; // Return default string schema for system/analyzer placeholders
110
+ }
111
+ }
28
112
 
29
113
  if (typeof value !== 'string') {
30
114
  const jsType = typeof value;
@@ -36,35 +120,35 @@ function deduceSchemaType(value, fieldName) {
36
120
  }
37
121
 
38
122
  const trimmedValue = value.trim();
39
-
123
+
40
124
  if (/^\[string:.*\]$/.test(trimmedValue) || (/^\[[^:]+\]$/.test(trimmedValue) && !/^\[(number|datetime|date|<string>):.*\]$/.test(trimmedValue))) {
41
125
  return { type: 'string' };
42
126
  }
43
-
127
+
44
128
  if (/^\[number:.*\]$/.test(trimmedValue)) {
45
129
  return { type: 'number' };
46
130
  }
47
-
131
+
48
132
  if (/^\[boolean:.*\]$/.test(trimmedValue)) {
49
133
  return { type: 'boolean' };
50
134
  }
51
-
135
+
52
136
  if (/^\[date-*time:.*\]$/.test(trimmedValue)) {
53
137
  return { type: 'string', format: 'date-time' };
54
138
  }
55
-
139
+
56
140
  if (/^\[date:.*\]$/.test(trimmedValue)) {
57
141
  return { type: 'string', format: 'date' };
58
142
  }
59
-
143
+
60
144
  if (/^\[<string>:.*\]$/.test(trimmedValue) || trimmedValue.toLowerCase().includes('array of strings')) {
61
145
  return { type: 'array', items: { type: 'string' } };
62
146
  }
63
-
147
+
64
148
  if (trimmedValue.toLowerCase().includes("output 'true' or 'false'") || trimmedValue.toLowerCase().includes("determine if") && (trimmedValue.toLowerCase().includes("true") || trimmedValue.toLowerCase().includes("false"))) {
65
149
  return { type: 'boolean' };
66
150
  }
67
-
151
+
68
152
  console.warn(`Warning: Unknown metadata value pattern for field "${fieldName}". Defaulting to type 'string'. Value: "${value}"`);
69
153
  return defaultSchema;
70
154
  }
@@ -75,14 +159,14 @@ function deduceSchemaType(value, fieldName) {
75
159
  * Reads the corresponding '1.md' file, extracts the JSON block,
76
160
  * and deduces schema types from the string values.
77
161
  *
78
- * @param {string} analyzeMessagesBasePath - The absolute or relative path to the base directory containing the analyzer message files (e.g., 'messages/analyze').
162
+ * @param {string} analyzersBasePath - The absolute or relative path to the base directory containing the analyzer message files (e.g., 'messages/analyze').
79
163
  * @param {string} analyzerId - The unique ID of the analyzer (format: 'analyzer_name::content_type::instructions_type').
80
164
  * @returns {Promise<object|null>} A promise that resolves with the JSON schema object or null if the analyzer ID is invalid or the schema cannot be retrieved/parsed.
81
165
  * @throws {Error} If the 1.md file is found but does not contain exactly one JSON code block.
82
166
  */
83
- async function getAnalyzerSchema(analyzeMessagesBasePath, analyzerId) {
84
- if (typeof analyzeMessagesBasePath !== 'string' || analyzeMessagesBasePath.trim() === '') {
85
- console.error('Error: analyzeMessagesBasePath is required.');
167
+ async function getAnalyzerSchema(analyzersBasePath, analyzerId) {
168
+ if (typeof analyzersBasePath !== 'string' || analyzersBasePath.trim() === '') {
169
+ console.error('Error: analyzersBasePath is required.');
86
170
  return null;
87
171
  }
88
172
  if (typeof analyzerId !== 'string' || analyzerId.trim() === '') {
@@ -97,10 +181,13 @@ async function getAnalyzerSchema(analyzeMessagesBasePath, analyzerId) {
97
181
  }
98
182
  const [analyzerName, contentType, instructionsType] = parts;
99
183
 
100
- const instructionsFilePath = path.join(analyzeMessagesBasePath, analyzerName, contentType, instructionsType, '1.md');
184
+ const instructionsFilePath = path.join(analyzersBasePath, analyzerName, contentType, instructionsType, '1.md');
101
185
 
102
186
  try {
103
187
  const fileContent = await fs.readFile(instructionsFilePath, 'utf8');
188
+ // New: Parse the structured definitions block first
189
+ const definedFields = parseMetadataDefinitions(fileContent);
190
+
104
191
  const { blocks } = CodeBlockUtils.extractCodeBlocks(fileContent, { silent: true });
105
192
  const jsonBlocks = blocks.filter(block => block.type === 'code' && block.language === 'json');
106
193
 
@@ -109,19 +196,24 @@ async function getAnalyzerSchema(analyzeMessagesBasePath, analyzerId) {
109
196
  }
110
197
 
111
198
  const jsonBlockContent = jsonBlocks[0].content;
199
+ const preprocessedContent = preprocessJsonForValidation(jsonBlockContent);
112
200
  let rawJson = null;
113
201
  try {
114
- rawJson = JSON.parse(jsonBlockContent);
202
+ rawJson = JSON.parse(preprocessedContent);
115
203
  } catch (parseError) {
116
204
  console.error(`Error parsing JSON content from ${instructionsFilePath}: ${parseError.message}`);
117
205
  return null;
118
206
  }
119
207
 
208
+ const startOfInstructions = fileContent.indexOf('# Analyze ');
209
+
120
210
  const schema = {
121
211
  type: 'object',
122
212
  description: rawJson.description,
213
+ version: rawJson.version,
123
214
  properties: {},
124
- required: []
215
+ required: [],
216
+ instructions: fileContent.slice(startOfInstructions),
125
217
  };
126
218
 
127
219
  const metadataProperties = rawJson?.extracted_metadata;
@@ -129,9 +221,20 @@ async function getAnalyzerSchema(analyzeMessagesBasePath, analyzerId) {
129
221
  if (metadataProperties && typeof metadataProperties === 'object') {
130
222
  for (const fieldName in metadataProperties) {
131
223
  if (Object.hasOwnProperty.call(metadataProperties, fieldName)) {
132
- const rawValue = metadataProperties[fieldName];
133
- const fieldSchema = deduceSchemaType(rawValue, fieldName);
134
- const description = rawValue.match(/^\[\w+: ([^\]]+)\]/)?.[1] || '';
224
+ let fieldSchema;
225
+ let description;
226
+
227
+ if (definedFields.has(fieldName)) {
228
+ // Priority 1: Use the explicit definition
229
+ const definition = definedFields.get(fieldName);
230
+ fieldSchema = mapSimpleTypeToSchema(definition.type);
231
+ description = definition.description;
232
+ } else {
233
+ // Priority 2: Fallback for system fields or backward compatibility
234
+ const rawValue = metadataProperties[fieldName];
235
+ fieldSchema = deduceSchemaType(rawValue, fieldName);
236
+ description = rawValue.match(/^\[\w+: ([^\]]+)\]/)?.[1] || '';
237
+ }
135
238
 
136
239
  schema.properties[fieldName] = {
137
240
  ...fieldSchema,
@@ -145,7 +248,6 @@ async function getAnalyzerSchema(analyzeMessagesBasePath, analyzerId) {
145
248
  }
146
249
 
147
250
  return schema;
148
-
149
251
  } catch (error) {
150
252
  if (error.code === 'ENOENT') {
151
253
  console.warn(`Analyzer instructions file not found: ${instructionsFilePath}`);
@@ -157,7 +259,73 @@ async function getAnalyzerSchema(analyzeMessagesBasePath, analyzerId) {
157
259
  }
158
260
  }
159
261
 
262
+ /**
263
+ * Retrieves the configuration metadata for a specific analyzer.
264
+ * Reads the corresponding '1.md' file and extracts the top-level properties
265
+ * from the JSON block (description, version, tags).
266
+ *
267
+ * @param {string} analyzersBasePath - The absolute path to the base directory containing the analyzers (e.g., 'analyzers/analyzer-1').
268
+ * @param {string} analyzerId - The unique ID of the analyzer (format: 'analyzer_name::content_type::instructions_type').
269
+ * @returns {Promise<object|null>} A promise that resolves with an object containing the analyzer's configuration metadata (description, version, tags) or null if not found/invalid.
270
+ */
271
+ async function getAnalyzerConfigMetadata(analyzersBasePath, analyzerId) {
272
+ if (typeof analyzersBasePath !== 'string' || analyzersBasePath.trim() === '') {
273
+ console.error('Error: analyzersBasePath is required.');
274
+ return null;
275
+ }
276
+ if (typeof analyzerId !== 'string' || analyzerId.trim() === '') {
277
+ console.error('Error: analyzerId is required.');
278
+ return null;
279
+ }
280
+
281
+ const parts = analyzerId.split('::');
282
+ if (parts.length !== 3) {
283
+ console.error(`Error: Invalid analyzerId format. Expected 'analyzer_name::content_type::instructions_type', but got '${analyzerId}'.`);
284
+ return null;
285
+ }
286
+ const [analyzerName, contentType, instructionsType] = parts;
287
+
288
+ const instructionsFilePath = path.join(analyzersBasePath, analyzerName, contentType, instructionsType, '1.md');
289
+
290
+ try {
291
+ const fileContent = await fs.readFile(instructionsFilePath, 'utf8');
292
+ const { blocks } = CodeBlockUtils.extractCodeBlocks(fileContent, { silent: true });
293
+ const jsonBlocks = blocks.filter(block => block.type === 'code' && block.language === 'json');
294
+
295
+ if (jsonBlocks.length !== 1) {
296
+ throw new Error(`Expected exactly one JSON code block in ${instructionsFilePath}, but found ${jsonBlocks.length}.`);
297
+ }
298
+
299
+ const jsonBlockContent = jsonBlocks[0].content;
300
+ const preprocessedContent = preprocessJsonForValidation(jsonBlockContent);
301
+ let rawJson = null;
302
+ try {
303
+ rawJson = JSON.parse(preprocessedContent);
304
+ } catch (parseError) {
305
+ console.error(`Error parsing JSON content from ${instructionsFilePath}: ${parseError.message}`);
306
+ return null;
307
+ }
308
+
309
+ // Extract configuration metadata
310
+ return {
311
+ description: rawJson.description || null,
312
+ version: rawJson.version || null,
313
+ tags: rawJson.tags ? rawJson.tags.split(/\s*,\s*/) : []
314
+ };
315
+ } catch (error) {
316
+ if (error.code === 'ENOENT') {
317
+ console.warn(`Analyzer instructions file not found: ${instructionsFilePath}`);
318
+ } else {
319
+ console.error(`Error retrieving configuration metadata for analyzer ${analyzerId}: ${error.message}`);
320
+ }
321
+ return null;
322
+ }
323
+ }
324
+
160
325
  module.exports = {
161
326
  getAnalyzerSchema,
162
- deduceSchemaType
327
+ getAnalyzerConfigMetadata,
328
+ deduceSchemaType,
329
+ parseMetadataDefinitions,
330
+ mapSimpleTypeToSchema
163
331
  };
@@ -0,0 +1,187 @@
1
+ /*
2
+ * Component: AnalyzerUtils Updater
3
+ * Block-UUID: 8a7b6c5d-4e3f-2a1b-9c8d-7e6f5a4b3c2d
4
+ * Parent-UUID: N/A
5
+ * Version: 1.0.0
6
+ * Description: Provides utility functions for updating analyzer configurations, including renaming and metadata updates.
7
+ * Language: JavaScript
8
+ * Created-at: 2025-12-25T19:55:00.000Z
9
+ * Authors: GLM-4.6 (v1.0.0)
10
+ */
11
+
12
+
13
+ const fs = require('fs').promises;
14
+ const path = require('path');
15
+ const CodeBlockUtils = require('../CodeBlockUtils');
16
+ const { preprocessJsonForValidation } = require('./jsonParser');
17
+ const { isValidDirName } = require('./discovery');
18
+ const { readConfig } = require('./discovery');
19
+
20
+ /**
21
+ * Updates an analyzer configuration, including renaming the analyzer if needed.
22
+ *
23
+ * @param {string} analyzersBasePath - The absolute path to the base directory containing the analyzers.
24
+ * @param {string} analyzerId - The current unique ID of the analyzer (format: 'analyzer_name::content_type::instructions_type').
25
+ * @param {object} updates - The updates to apply to the analyzer.
26
+ * @param {string} [updates.name] - The new analyzer name (for renaming the directory).
27
+ * @param {string} [updates.label] - The new label for the analyzer.
28
+ * @param {string} [updates.description] - The new description for the analyzer.
29
+ * @param {string} [updates.version] - The new version for the analyzer.
30
+ * @param {Array<string>} [updates.tags] - The new tags for the analyzer.
31
+ * @returns {Promise<{success: boolean, message: string, newAnalyzerId?: string}>} A promise that resolves with a result object.
32
+ */
33
+ async function updateAnalyzer(analyzersBasePath, analyzerId, updates) {
34
+ // 1. Validate inputs
35
+ if (typeof analyzersBasePath !== 'string' || analyzersBasePath.trim() === '') {
36
+ return { success: false, message: 'analyzersBasePath is required.' };
37
+ }
38
+ if (typeof analyzerId !== 'string' || analyzerId.trim() === '') {
39
+ return { success: false, message: 'analyzerId is required.' };
40
+ }
41
+ if (!updates || typeof updates !== 'object') {
42
+ return { success: false, message: 'updates object is required.' };
43
+ }
44
+
45
+ // 2.0 Parse current analyzerId
46
+ const parts = analyzerId.split('::');
47
+ if (parts.length !== 3) {
48
+ return { success: false, message: `Invalid analyzerId format. Expected 'analyzer_name::content_type::instructions_type', but got '${analyzerId}'.` };
49
+ }
50
+ const [analyzerName, contentType, instructionsType] = parts;
51
+
52
+ // 2.1 Unset name if it has been provided but is the same as the analyzerName
53
+ if (updates.name && analyzerName === updates.name) {
54
+ delete updates.name;
55
+ }
56
+
57
+ // 3. Validate new name if provided
58
+ if (updates.name && !isValidDirName(updates.name)) {
59
+ return { success: false, message: `Invalid analyzer name '${updates.name}'. Names must be alphanumeric, dash, or underscore, cannot start with underscore, and cannot contain dots.` };
60
+ }
61
+
62
+ // 4. Check if analyzer is protected
63
+ const analyzerDir = path.join(analyzersBasePath, analyzerName);
64
+ const analyzerConfig = await readConfig(analyzerDir);
65
+ if (analyzerConfig?.protected) {
66
+ return { success: false, message: `Analyzer '${analyzerName}' is protected and cannot be updated.` };
67
+ }
68
+
69
+ // 5. Get current instructions content
70
+ const instructionsFilePath = path.join(analyzerDir, contentType, instructionsType, '1.md');
71
+ let instructionsContent;
72
+ try {
73
+ instructionsContent = await fs.readFile(instructionsFilePath, 'utf8');
74
+ } catch (error) {
75
+ return { success: false, message: `Failed to read analyzer instructions: ${error.message}` };
76
+ }
77
+
78
+ // 6. Extract and update JSON block
79
+ const { blocks } = CodeBlockUtils.extractCodeBlocks(instructionsContent, { silent: true });
80
+ const jsonBlock = blocks.find(block => block.language === 'json');
81
+
82
+ if (!jsonBlock) {
83
+ return { success: false, message: 'No JSON block found in analyzer instructions.' };
84
+ }
85
+
86
+ let jsonData;
87
+ try {
88
+ const preprocessedContent = preprocessJsonForValidation(jsonBlock.content);
89
+ jsonData = JSON.parse(preprocessedContent);
90
+ } catch (error) {
91
+ return { success: false, message: `Failed to parse JSON block: ${error.message}` };
92
+ }
93
+
94
+ // 7. Update JSON data with provided values
95
+ if (updates.label !== undefined) jsonData.label = updates.label;
96
+ if (updates.description !== undefined) jsonData.description = updates.description;
97
+ if (updates.version !== undefined) jsonData.version = updates.version;
98
+ if (updates.tags !== undefined) jsonData.tags = updates.tags;
99
+
100
+ // 8. Rebuild the instructions content with updated JSON
101
+ const updatedJsonContent = JSON.stringify(jsonData, null, 2);
102
+ const updatedInstructionsContent = instructionsContent.replace(
103
+ jsonBlock.content,
104
+ updatedJsonContent
105
+ );
106
+
107
+ // 9. Handle directory renaming if needed
108
+ let newAnalyzerId = analyzerId;
109
+ if (updates.name && updates.name !== analyzerName) {
110
+ // Create new directory structure
111
+ const newAnalyzerDir = path.join(analyzersBasePath, updates.name);
112
+ const newContentDir = path.join(newAnalyzerDir, contentType);
113
+ const newInstructionsDir = path.join(newContentDir, instructionsType);
114
+ const newInstructionsFilePath = path.join(newInstructionsDir, '1.md');
115
+
116
+ try {
117
+ // Create new directories
118
+ await fs.mkdir(newInstructionsDir, { recursive: true });
119
+
120
+ // Copy config.json if it exists
121
+ const configPath = path.join(analyzerDir, 'config.json');
122
+ const newConfigPath = path.join(newAnalyzerDir, 'config.json');
123
+ try {
124
+ const configContent = await fs.readFile(configPath, 'utf8');
125
+ await fs.writeFile(newConfigPath, configContent, 'utf8');
126
+ } catch (error) {
127
+ // Config file might not exist, that's okay
128
+ }
129
+
130
+ // Copy content config.json if it exists
131
+ const contentConfigPath = path.join(analyzerDir, contentType, 'config.json');
132
+ const newContentConfigPath = path.join(newContentDir, 'config.json');
133
+ try {
134
+ const contentConfigContent = await fs.readFile(contentConfigPath, 'utf8');
135
+ await fs.writeFile(newContentConfigPath, contentConfigContent, 'utf8');
136
+ } catch (error) {
137
+ // Config file might not exist, that's okay
138
+ }
139
+
140
+ // Copy instructions config.json if it exists
141
+ const instructionsConfigPath = path.join(analyzerDir, contentType, instructionsType, 'config.json');
142
+ const newInstructionsConfigPath = path.join(newInstructionsDir, 'config.json');
143
+ try {
144
+ const instructionsConfigContent = await fs.readFile(instructionsConfigPath, 'utf8');
145
+ await fs.writeFile(newInstructionsConfigPath, instructionsConfigContent, 'utf8');
146
+ } catch (error) {
147
+ // Config file might not exist, that's okay
148
+ }
149
+
150
+ // Write updated instructions to new location
151
+ const finalContent = `; role: assistant\n\n\n${updatedInstructionsContent}`;
152
+ await fs.writeFile(newInstructionsFilePath, finalContent, 'utf8');
153
+
154
+ // Update the analyzer ID
155
+ newAnalyzerId = `${updates.name}::${contentType}::${instructionsType}`;
156
+
157
+ // Delete old analyzer directory (will be done after successful save)
158
+ } catch (error) {
159
+ return { success: false, message: `Failed to create new analyzer directory: ${error.message}` };
160
+ }
161
+ } else {
162
+ // Just update the existing file
163
+ const finalContent = `; role: assistant\n\n\n${updatedInstructionsContent}`;
164
+ await fs.writeFile(instructionsFilePath, finalContent, 'utf8');
165
+ }
166
+
167
+ // 10. Clean up old directory if renamed
168
+ if (updates.name && updates.name !== analyzerName) {
169
+ try {
170
+ // Use the deleteAnalyzer function to clean up the old directory
171
+ const { deleteAnalyzer } = require('./management');
172
+ await deleteAnalyzer(analyzersBasePath, analyzerId);
173
+ } catch (error) {
174
+ console.warn(`Warning: Could not clean up old analyzer directory: ${error.message}`);
175
+ }
176
+ }
177
+
178
+ return {
179
+ success: true,
180
+ message: `Analyzer '${analyzerId}' updated successfully.`,
181
+ newAnalyzerId
182
+ };
183
+ }
184
+
185
+ module.exports = {
186
+ updateAnalyzer
187
+ };
@@ -2,17 +2,17 @@
2
2
  * Component: CodeBlockUtils Block Processor
3
3
  * Block-UUID: 1d8a559b-4f7a-4b0b-9e0a-13786f94a74e
4
4
  * Parent-UUID: 082abf8b-079f-4c95-8464-0e66c0de45eb
5
- * Version: 1.4.0
5
+ * Version: 1.5.0
6
6
  * Description: Processes the content of identified code blocks, parsing headers or patch details, and provides utilities like fixing UUIDs within blocks.
7
7
  * Language: JavaScript
8
8
  * Created-at: 2025-07-21T00:33:22.312Z
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 Flash Thinking (v1.3.0), Gemini 2.5 Flash Thinking (v1.4.0)
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 Flash Thinking (v1.3.0), Gemini 2.5 Flash Thinking (v1.4.0), Qwen 3 Coder 480B - Cerebras (v1.5.0)
10
10
  */
11
11
 
12
12
 
13
13
  const { findAllCodeFences, matchFencesAndExtractBlocks } = require('./blockExtractor');
14
14
  const { parseHeader } = require('./headerUtils');
15
- const { validateUUID, generateUUID } = require('./uuidUtils');
15
+ const { validateUUID } = require('./uuidUtils');
16
16
  const AnalysisBlockUtils = require('../AnalysisBlockUtils');
17
17
  const PatchUtils = require('../PatchUtils');
18
18
  const GSToolBlockUtils = require('../GSToolBlockUtils');
@@ -131,7 +131,7 @@ function processBlockContent(content, language, position, incomplete, options =
131
131
  if (PatchUtils.isPatchBlock(content)) {
132
132
  const patchFormat = PatchUtils.determinePatchFormat(content);
133
133
  const metadata = PatchUtils.extractPatchMetadata(content); // Extract metadata regardless of validation
134
- const patchContent = PatchUtils.extractPatchContent(content); // Extract raw patch content
134
+ const patchContent = PatchUtils.extractPatchContent(content, patchFormat); // Extract raw patch content
135
135
 
136
136
  let patchValidationResult = { valid: true, errors: [], patches: null }; // Default valid state
137
137
 
@@ -148,36 +148,16 @@ function processBlockContent(content, language, position, incomplete, options =
148
148
  // For now, just warn, but return the extracted (potentially invalid) metadata.
149
149
  }
150
150
 
151
- // Optionally validate context patch structure if requested
152
- if (patchFormat === 'context' && validatePatches) {
153
- try {
154
- const patches = PatchUtils.extractContextPatches(content);
155
- if (!patches || patches.length === 0) {
156
- patchValidationResult = { valid: false, errors: ['No valid context patch content found.'], patches: null };
157
- warnings.push({
158
- position: position,
159
- type: 'patch_content_error',
160
- message: 'No valid context patch content found.',
161
- content: content,
162
- });
163
- } else {
164
- // Basic validation passed (structure was parsable)
165
- patchValidationResult = { valid: true, errors: [], patches: patches };
166
- }
167
- } catch (error) {
168
- patchValidationResult = { valid: false, errors: [error.message], patches: null };
169
- warnings.push({
170
- position: position,
171
- type: 'patch_parse_error',
172
- message: `Error parsing context patch: ${error.message}`,
173
- content: content,
174
- });
175
- }
151
+ // Determine the correct language for the block
152
+ let blockLanguage = language; // Default to the fence language
153
+ if (patchFormat === 'traditional') {
154
+ blockLanguage = 'diff'; // Enforce 'diff' for traditional patches
176
155
  }
156
+ // For abbreviated patches, keep the original language (e.g., 'javascript', 'python')
177
157
 
178
158
  return {
179
159
  type: 'patch',
180
- language: language === 'diff' ? 'diff' : language, // Use 'diff' if specified, else keep original
160
+ language: blockLanguage, // Use determined language
181
161
  content: content, // Full original content within fences
182
162
  position: position,
183
163
  incomplete: incomplete,
@@ -371,8 +351,10 @@ function processBlockContents(text, completeBlocks, incompleteBlocks, options) {
371
351
  * @returns {Object} { blocks: Array, warnings: Array, hasIncompleteBlocks: boolean, lastIncompleteBlock: Object|null }
372
352
  */
373
353
  function processCodeBlocks(text, options = { silent: false, validatePatches: false }) {
374
- if (typeof text !== "string") { // Allow empty strings
375
- console.warn("Warning: Input must be a string.");
354
+ if (typeof text !== "string") {
355
+ if (text != null )
356
+ console.warn("Warning: Input must be a string.");
357
+
376
358
  return {
377
359
  blocks: [],
378
360
  warnings: [{ type: 'invalid_input', message: 'Input must be a string.' }],
@@ -2,11 +2,11 @@
2
2
  * Component: CodeBlockUtils Constants
3
3
  * Block-UUID: 01b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d
4
4
  * Parent-UUID: 144d19a5-139a-4e80-8abe-84965583e35e
5
- * Version: 1.0.1
5
+ * Version: 1.0.2
6
6
  * Description: Defines constants used across the CodeBlockUtils modules, such as comment styles for various languages.
7
7
  * Language: JavaScript
8
8
  * Created-at: 2025-04-15T15:51:16.973Z
9
- * Authors: Gemini 2.5 Pro (v1.0.0), Gemini 2.5 Flash Thinking (v1.0.1)
9
+ * Authors: Gemini 2.5 Pro (v1.0.0), Gemini 2.5 Flash Thinking (v1.0.1), Qwen 3 Coder 480B - Cerebras (v1.0.2)
10
10
  */
11
11
 
12
12
 
@@ -40,6 +40,12 @@ const COMMENT_STYLES = {
40
40
  'r': { type: 'hash' },
41
41
  'julia': { type: 'hash' },
42
42
 
43
+ // HTML/XML/SVG/Markdown comment style
44
+ 'html': { type: 'html' },
45
+ 'xml': { type: 'html' },
46
+ 'svg': { type: 'html' },
47
+ 'markdown': { type: 'html' }, // For metadata in Markdown files
48
+
43
49
  // Other language styles
44
50
  'lisp': { type: 'semicolon' },
45
51
  'clojure': { type: 'semicolon' },
@@ -58,5 +64,3 @@ const COMMENT_STYLES = {
58
64
  module.exports = {
59
65
  COMMENT_STYLES
60
66
  };
61
-
62
-