@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.
- package/README.md +380 -62
- package/dist/gsc-utils.cjs.js +14270 -523
- package/dist/gsc-utils.esm.js +14270 -523
- package/package.json +1 -1
- package/src/AnalyzerUtils/cloner.js +149 -0
- package/src/AnalyzerUtils/constants.js +1 -1
- package/src/AnalyzerUtils/defaultPromptLoader.js +10 -10
- package/src/AnalyzerUtils/discovery.js +48 -39
- package/src/AnalyzerUtils/index.js +13 -7
- package/src/AnalyzerUtils/instructionLoader.js +6 -6
- package/src/AnalyzerUtils/jsonParser.js +35 -0
- package/src/AnalyzerUtils/management.js +6 -6
- package/src/AnalyzerUtils/saver.js +5 -5
- package/src/AnalyzerUtils/schemaLoader.js +194 -26
- package/src/AnalyzerUtils/updater.js +187 -0
- package/src/CodeBlockUtils/blockProcessor.js +14 -32
- package/src/CodeBlockUtils/constants.js +8 -4
- package/src/CodeBlockUtils/headerUtils.js +19 -3
- package/src/CodeBlockUtils/index.js +7 -6
- package/src/CodeBlockUtils/lineageTracer.js +95 -0
- package/src/CompactChatUtils/CompactedMessageUtils.js +224 -0
- package/src/CompactChatUtils/README.md +321 -0
- package/src/CompactChatUtils/ReferenceMessageUtils.js +143 -0
- package/src/CompactChatUtils/index.js +40 -0
- package/src/ContextUtils.js +41 -5
- package/src/DomUtils.js +559 -0
- package/src/GSToolBlockUtils.js +66 -1
- package/src/GitSenseChatUtils.js +108 -16
- package/src/LanguageNameUtils.js +171 -0
- package/src/MarkdownUtils.js +127 -0
- package/src/MessageUtils.js +1 -1
- package/src/MetaRawResultUtils.js +244 -0
- package/src/ObjectUtils.js +54 -0
- package/src/PatchUtils/constants.js +9 -3
- package/src/PatchUtils/patchParser.js +60 -37
- package/src/PatchUtils/patchProcessor.js +8 -5
- package/src/PatchUtils/patchVerifier/detectAndFixOverlappingHunks.js +1 -1
- package/src/SVGUtils.js +1467 -0
- package/src/SharedUtils/stringUtils.js +303 -0
- package/src/CodeBlockUtils/blockProcessor.js.rej +0 -8
|
@@ -1,18 +1,93 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
2
|
* Component: AnalyzerUtils Schema Loader
|
|
3
|
-
* Block-UUID:
|
|
4
|
-
* Parent-UUID:
|
|
5
|
-
* Version: 1.
|
|
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-
|
|
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}
|
|
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(
|
|
84
|
-
if (typeof
|
|
85
|
-
console.error('Error:
|
|
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(
|
|
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(
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
//
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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:
|
|
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") {
|
|
375
|
-
|
|
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.
|
|
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
|
-
|