@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitsense/gsc-utils",
3
- "version": "0.2.24",
3
+ "version": "0.2.27",
4
4
  "description": "Utilities for GitSense Chat (GSC)",
5
5
  "main": "dist/gsc-utils.cjs.js",
6
6
  "module": "dist/gsc-utils.esm.js",
@@ -0,0 +1,149 @@
1
+ /*
2
+ * Component: AnalyzerUtils Cloner
3
+ * Block-UUID: 56b8c9f4-97ce-406c-a598-22a838ebefda
4
+ * Parent-UUID: N/A
5
+ * Version: 1.0.0
6
+ * Description: Provides utility functions for cloning analyzer configurations.
7
+ * Language: JavaScript
8
+ * Created-at: 2025-12-26T16:59:39.782Z
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 { saveConfiguration } = require('./saver');
19
+ const { getAnalyzers } = require('./discovery');
20
+
21
+ /**
22
+ * Clones an existing analyzer with a new name.
23
+ *
24
+ * @param {string} analyzersBasePath - The absolute path to the base directory containing the analyzers.
25
+ * @param {string} originalAnalyzerId - The ID of the analyzer to clone (format: 'analyzer_name::content_type::instructions_type').
26
+ * @param {string} newAnalyzerName - The new name for the cloned analyzer.
27
+ * @param {object} [options={}] - Optional configuration options.
28
+ * @param {string} [options.label] - The new label for the cloned analyzer (optional, defaults to newAnalyzerName).
29
+ * @param {string} [options.description] - The new description for the cloned analyzer (optional, keeps original if not provided).
30
+ * @returns {Promise<{success: boolean, message: string, newAnalyzerId?: string}>} A promise that resolves with a result object.
31
+ */
32
+ async function cloneAnalyzer(analyzersBasePath, originalAnalyzerId, newAnalyzerName, options = {}) {
33
+ // 1. Validate inputs
34
+ if (typeof analyzersBasePath !== 'string' || analyzersBasePath.trim() === '') {
35
+ return { success: false, message: 'analyzersBasePath is required.' };
36
+ }
37
+ if (typeof originalAnalyzerId !== 'string' || originalAnalyzerId.trim() === '') {
38
+ return { success: false, message: 'originalAnalyzerId is required.' };
39
+ }
40
+ if (typeof newAnalyzerName !== 'string' || newAnalyzerName.trim() === '') {
41
+ return { success: false, message: 'newAnalyzerName is required.' };
42
+ }
43
+
44
+ // 2. Parse original analyzerId
45
+ const parts = originalAnalyzerId.split('::');
46
+ if (parts.length !== 3) {
47
+ return { success: false, message: `Invalid originalAnalyzerId format. Expected 'analyzer_name::content_type::instructions_type', but got '${originalAnalyzerId}'.` };
48
+ }
49
+ const [originalAnalyzerName, contentType, instructionsType] = parts;
50
+
51
+ // 3. Validate new analyzer name
52
+ if (!isValidDirName(newAnalyzerName)) {
53
+ return { success: false, message: `Invalid analyzer name '${newAnalyzerName}'. Names must be alphanumeric, dash, or underscore, cannot start with underscore, and cannot contain dots.` };
54
+ }
55
+
56
+ // 4. Check if new analyzer name already exists
57
+ try {
58
+ const existingAnalyzers = await getAnalyzers(analyzersBasePath);
59
+ const nameExists = existingAnalyzers.some(analyzer => analyzer.name === newAnalyzerName);
60
+ if (nameExists) {
61
+ return { success: false, message: `An analyzer with name '${newAnalyzerName}' already exists. Please choose a different name.` };
62
+ }
63
+ } catch (error) {
64
+ return { success: false, message: `Failed to check for existing analyzers: ${error.message}` };
65
+ }
66
+
67
+ // 5. Get original analyzer's instructions content
68
+ const { getAnalyzerInstructionsContent } = require('./instructionLoader');
69
+ let instructionsContent;
70
+ try {
71
+ instructionsContent = await getAnalyzerInstructionsContent(analyzersBasePath, originalAnalyzerId);
72
+ if (!instructionsContent) {
73
+ return { success: false, message: `Could not find original analyzer '${originalAnalyzerId}'.` };
74
+ }
75
+ } catch (error) {
76
+ return { success: false, message: `Failed to read original analyzer instructions: ${error.message}` };
77
+ }
78
+
79
+ // 6. Extract and update JSON block
80
+ const { blocks } = CodeBlockUtils.extractCodeBlocks(instructionsContent, { silent: true });
81
+ const jsonBlocks = blocks.filter((block, index) => {
82
+ if (block.language === 'json') {
83
+ block.index = index;
84
+ return block;
85
+ }
86
+ });
87
+ const jsonBlock = jsonBlocks[jsonBlocks.length - 1];
88
+
89
+ if (!jsonBlock) {
90
+ return { success: false, message: 'No JSON block found in original analyzer instructions.' };
91
+ }
92
+
93
+ let jsonData;
94
+ try {
95
+ const preprocessedContent = preprocessJsonForValidation(jsonBlock.content);
96
+ jsonData = JSON.parse(preprocessedContent);
97
+ } catch (error) {
98
+ return { success: false, message: `Failed to parse JSON block: ${error.message}` };
99
+ }
100
+
101
+ // 7. Update JSON data with new values
102
+ if (options.label !== undefined) {
103
+ jsonData.label = options.label;
104
+ }
105
+ if (options.description !== undefined) {
106
+ jsonData.description = options.description;
107
+ }
108
+ // Reset version for cloned analyzer
109
+ jsonData.version = '1.0.0';
110
+
111
+ // 8. Rebuild the instructions content with updated JSON
112
+ let updatedInstructionsContent = CodeBlockUtils.updateCodeBlockByIndex(
113
+ instructionsContent,
114
+ jsonBlock.index,
115
+ JSON.stringify(jsonData, null, 2),
116
+ 'json'
117
+ );
118
+
119
+ // 9. Create the new analyzer ID
120
+ const newAnalyzerId = `${newAnalyzerName}::${contentType}::${instructionsType}`;
121
+
122
+ // 10. Update the analyzer id in the instructions
123
+ updatedInstructionsContent = updatedInstructionsContent.replaceAll(originalAnalyzerId, newAnalyzerId);
124
+
125
+ // 11. Save the new analyzer
126
+ try {
127
+ const saveResult = await saveConfiguration(
128
+ analyzersBasePath,
129
+ newAnalyzerId,
130
+ updatedInstructionsContent
131
+ );
132
+
133
+ if (saveResult.success) {
134
+ return {
135
+ success: true,
136
+ message: `Analyzer '${originalAnalyzerName}' cloned successfully as '${newAnalyzerName}'.`,
137
+ newAnalyzerId
138
+ };
139
+ } else {
140
+ return { success: false, error: saveResult.message };
141
+ }
142
+ } catch (error) {
143
+ return { success: false, message: `Failed to save cloned analyzer: ${error.message}` };
144
+ }
145
+ }
146
+
147
+ module.exports = {
148
+ cloneAnalyzer
149
+ };
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * Component: AnalyzerUtils Constants
3
- * Block-UUID: 01b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d
3
+ * Block-UUID: fec5dcc4-a1d0-4ef7-8828-3e44a75892c4
4
4
  * Parent-UUID: N/A
5
5
  * Version: 1.0.0
6
6
  * Description: Defines constants specific to the AnalyzerUtils module.
@@ -16,16 +16,16 @@ const path = require('path');
16
16
  /**
17
17
  * Retrieves the raw Markdown content of the shared system message ('_shared/system/1.md').
18
18
  *
19
- * @param {string} analyzeMessagesBasePath - The absolute path to the base directory containing analyzer message files (e.g., 'messages/analyze').
19
+ * @param {string} analyzersBasePath - The absolute path to the base directory containing the analyzers (e.g., 'analyzers/analyzer-1').
20
20
  * @returns {Promise<string|null>} A promise that resolves with the full Markdown content, or null if not found/invalid.
21
21
  */
22
- async function getSystemMessageContent(analyzeMessagesBasePath) {
23
- if (typeof analyzeMessagesBasePath !== 'string' || analyzeMessagesBasePath.trim() === '') {
24
- console.error('Error: analyzeMessagesBasePath is required for getSystemMessageContent.');
22
+ async function getSystemMessageContent(analyzersBasePath) {
23
+ if (typeof analyzersBasePath !== 'string' || analyzersBasePath.trim() === '') {
24
+ console.error('Error: analyzersBasePath is required for getSystemMessageContent.');
25
25
  return null;
26
26
  }
27
27
 
28
- const systemMessageFilePath = path.join(analyzeMessagesBasePath, '_shared', 'system', '1.md');
28
+ const systemMessageFilePath = path.join(analyzersBasePath, '_shared', 'system', '1.md');
29
29
 
30
30
  try {
31
31
  const fileContent = await fs.readFile(systemMessageFilePath, 'utf8');
@@ -46,16 +46,16 @@ async function getSystemMessageContent(analyzeMessagesBasePath) {
46
46
  /**
47
47
  * Retrieves the raw Markdown content of the shared start message ('_shared/start/1.md').
48
48
  *
49
- * @param {string} analyzeMessagesBasePath - The absolute path to the base directory containing analyzer message files (e.g., 'messages/analyze').
49
+ * @param {string} analyzersBasePath - The absolute path to the base directory containing the analyzers (e.g., 'analyzers/analyzer-1').
50
50
  * @returns {Promise<string|null>} A promise that resolves with the full Markdown content, or null if not found/invalid.
51
51
  */
52
- async function getStartMessageContent(analyzeMessagesBasePath) {
53
- if (typeof analyzeMessagesBasePath !== 'string' || analyzeMessagesBasePath.trim() === '') {
54
- console.error('Error: analyzeMessagesBasePath is required for getStartMessageContent.');
52
+ async function getStartMessageContent(analyzersBasePath) {
53
+ if (typeof analyzersBasePath !== 'string' || analyzersBasePath.trim() === '') {
54
+ console.error('Error: analyzersBasePath is required for getStartMessageContent.');
55
55
  return null;
56
56
  }
57
57
 
58
- const startMessageFilePath = path.join(analyzeMessagesBasePath, '_shared', 'start', '1.md');
58
+ const startMessageFilePath = path.join(analyzersBasePath, '_shared', 'start', '1.md');
59
59
 
60
60
  try {
61
61
  const fileContent = await fs.readFile(startMessageFilePath, 'utf8');
@@ -1,18 +1,19 @@
1
- /*
1
+ /**
2
2
  * Component: AnalyzerUtils Discovery
3
- * Block-UUID: a87a86c4-69fc-4cbb-80a8-857f56122395
4
- * Parent-UUID: 0b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5f
5
- * Version: 1.1.0
6
- * Description: Provides utility functions for discovering available analyzers.
3
+ * Block-UUID: 26a7df9b-ef29-4dcd-aab2-9cdc3a11133c
4
+ * Parent-UUID: aa999515-84fb-43f6-91a7-cf79248d7286
5
+ * Version: 1.3.1
6
+ * Description: Provides utility functions for discovering available analyzers. Updated to include version and tags in analyzer objects.
7
7
  * Language: JavaScript
8
- * Created-at: 2025-08-28T23:48:00.000Z
9
- * Authors: Gemini 2.5 Flash (v1.0.0), Gemini 2.5 Flash (v1.1.0)
8
+ * Created-at: 2025-11-27T14:45:30.978Z
9
+ * Authors: Gemini 2.5 Flash (v1.0.0), Gemini 2.5 Flash (v1.1.0), Qwen 3 Coder 480B - Cerebras (v1.2.0), GLM-4.6 (v1.2.1), GLM-4.6 (v1.3.0), GLM-4.6 (v1.3.1)
10
10
  */
11
11
 
12
12
 
13
13
  const fs = require('fs').promises;
14
14
  const path = require('path');
15
15
  const { getAnalyzerInstructionsContent } = require('./instructionLoader');
16
+ const { preprocessJsonForValidation } = require('./jsonParser');
16
17
  const CodeBlockUtils = require('../CodeBlockUtils');
17
18
 
18
19
  /**
@@ -36,7 +37,7 @@ async function readConfig(dirPath) {
36
37
  }
37
38
 
38
39
  /**
39
- * Checks if a directory name is valid based on the rules in messages/analyze/README.md.
40
+ * Checks if a directory name is valid
40
41
  * Allowed: a-z, A-Z, 0-9, dash (-), underscore (_). Cannot start with underscore or contain dots.
41
42
  * @param {string} name - The directory name to check.
42
43
  * @returns {boolean} True if the name is valid, false otherwise.
@@ -54,22 +55,21 @@ function isValidDirName(name) {
54
55
  * Discovers and lists all available analyzers by traversing the directory structure.
55
56
  * An analyzer is considered valid if a '1.md' file exists in the instructions directory.
56
57
  *
57
- * @param {string} analyzeMessagesBasePath - The absolute or relative path to the base directory containing the analyzer message files (e.g., 'messages/analyze').
58
+ * @param {string} analyzersBasePath - The absolute path to the base directory containing the analyzers (e.g., 'analyzers/analyzer-1').
58
59
  * @param {object} [options={}] - Optional configuration.
59
- * @param {boolean} [options.includeDescription=false] - Whether to include the description of the analyzer.
60
- * @returns {Promise<Array<{id: string, label: string, name: string, protected: boolean, description?: string}>>} A promise that resolves to an array of analyzer objects.
60
+ * @returns {Promise<Array<{id: string, label: string, name: string, protected: boolean, description?: string, version?: string, tags?: Array<string>}>>} A promise that resolves to an array of analyzer objects.
61
61
  */
62
- async function getAnalyzers(analyzeMessagesBasePath, options = {}) {
63
- const { includeDescription = false } = options;
62
+ async function getAnalyzers(analyzersBasePath, options = {}) {
64
63
  const analyzers = [];
64
+ const demoAnalyzerPattern = /^demo-.+-\w{6}$/;
65
65
 
66
66
  try {
67
- const analyzerEntries = await fs.readdir(analyzeMessagesBasePath, { withFileTypes: true });
67
+ const analyzerEntries = await fs.readdir(analyzersBasePath, { withFileTypes: true });
68
68
 
69
69
  for (const analyzerEntry of analyzerEntries) {
70
70
  if (analyzerEntry.isDirectory() && isValidDirName(analyzerEntry.name)) {
71
71
  const analyzerName = analyzerEntry.name;
72
- const analyzerPath = path.join(analyzeMessagesBasePath, analyzerName);
72
+ const analyzerPath = path.join(analyzersBasePath, analyzerName);
73
73
  const analyzerConfig = await readConfig(analyzerPath);
74
74
  const analyzerLabel = analyzerConfig?.label || analyzerName;
75
75
 
@@ -91,13 +91,16 @@ async function getAnalyzers(analyzeMessagesBasePath, options = {}) {
91
91
  const instructionsConfig = await readConfig(instructionsPath);
92
92
  const instructionsLabel = instructionsConfig?.label || instructionsType;
93
93
 
94
+ // Construct the analyzer ID and label
95
+ const analyzerId = `${analyzerName}::${contentType}::${instructionsType}`;
96
+
94
97
  // Check for the existence of 1.md to confirm a valid analyzer configuration
95
98
  const instructionsFilePath = path.join(instructionsPath, '1.md');
96
99
  try {
97
100
  await fs.access(instructionsFilePath); // Check if file exists and is accessible
98
101
 
99
102
  // If analyzerName starts with 'tutorial-', check its last modified time.
100
- if (analyzerName.startsWith('tutorial-')) {
103
+ if (analyzerName.startsWith('tutorial-') || demoAnalyzerPattern.test(analyzerName)) {
101
104
  const stats = await fs.stat(instructionsFilePath);
102
105
  const lastModified = stats.mtime.getTime(); // Get timestamp in milliseconds
103
106
  const sixtyMinutesAgo = Date.now() - (60 * 60 * 1000); // Current time - 60 minutes in ms
@@ -107,37 +110,43 @@ async function getAnalyzers(analyzeMessagesBasePath, options = {}) {
107
110
  continue;
108
111
  }
109
112
  }
110
- // Construct the analyzer ID and label
111
- const analyzerId = `${analyzerName}::${contentType}::${instructionsType}`;
112
- const analyzerFullLabel = `${analyzerLabel} (${contentLabel} - ${instructionsLabel})`;
113
+ // TODO: Decide if we should show the contentLabel and instructionsLabel. For now we will ignore them.
114
+ const analyzerFullLabel = `${analyzerLabel}`; // (${contentLabel} - ${instructionsLabel})`;
113
115
 
116
+ // Extract description, version, and tags from the JSON block in 1.md
114
117
  let description = null;
115
- if (includeDescription) {
116
- try {
117
- const content = await getAnalyzerInstructionsContent(analyzeMessagesBasePath, analyzerId);
118
- const { blocks, warnings } = CodeBlockUtils.extractCodeBlocks(content, { silent: true });
119
- const jsonBlock = blocks.find(block => block.language === 'json');
120
-
121
- if (jsonBlock && jsonBlock.content) {
122
- try {
123
- const json = JSON.parse(jsonBlock.content);
124
- description = json.description;
125
- } catch(error) {
126
- console.warn(`${analyzerId} contains an invalid JSON`);
127
- }
128
- }
129
- } catch (descError) {
130
- console.warn(`Warning: Could not load description for ${analyzerId}: ${descError.message}`);
131
- descriptionContent = null;
118
+ let version = null;
119
+ let tags = [];
120
+ let label = null;
121
+ let requires_reference_files = null;
122
+
123
+ try {
124
+ const content = await getAnalyzerInstructionsContent(analyzersBasePath, analyzerId);
125
+ const { blocks, warnings } = CodeBlockUtils.extractCodeBlocks(content, { silent: true });
126
+ const jsonBlock = blocks.find(block => block.language === 'json');
127
+
128
+ if (jsonBlock && jsonBlock.content) {
129
+ const preprocessedContent = preprocessJsonForValidation(jsonBlock.content);
130
+ const json = JSON.parse(preprocessedContent);
131
+ description = json.description || null;
132
+ label = json.label || null;
133
+ requires_reference_files = json.requires_reference_files || false;
134
+ version = json.version || null;
135
+ tags = Array.isArray(json.tags) ? json.tags : [];
132
136
  }
137
+ } catch (descError) {
138
+ console.warn(`Warning: Could not load metadata for ${analyzerId}: ${descError.message}`);
133
139
  }
134
140
 
135
141
  analyzers.push({
136
142
  id: analyzerId,
137
- label: analyzerFullLabel,
138
143
  name: analyzerName,
144
+ description,
145
+ label: label || analyzerFullLabel,
146
+ requires_reference_files,
139
147
  protected: analyzerConfig?.protected || false,
140
- description
148
+ version,
149
+ tags
141
150
  });
142
151
  } catch (error) {
143
152
  // If 1.md doesn't exist, this is not a complete analyzer configuration, skip.
@@ -152,7 +161,7 @@ async function getAnalyzers(analyzeMessagesBasePath, options = {}) {
152
161
  }
153
162
  }
154
163
  } catch (error) {
155
- console.error(`Error traversing analyze messages directory ${analyzeMessagesBasePath}: ${error.message}`);
164
+ console.error(`Error traversing analyzers directory ${analyzersBasePath}: ${error.message}`);
156
165
  // Depending on requirements, you might want to throw the error or return an empty array
157
166
  throw error; // Re-throw to indicate failure
158
167
  }
@@ -1,12 +1,12 @@
1
1
  /*
2
2
  * Component: AnalyzerUtils Index
3
- * Block-UUID: b403b6a1-230b-4247-8cd6-2a3d068f4bbf
4
- * Parent-UUID: N/A
5
- * Version: 1.2.0
6
- * Description: Aggregates and exports all utility functions from the AnalyzerUtils module.
3
+ * Block-UUID: 780e17b0-0c1e-4d77-bf6e-302951b341bf
4
+ * Parent-UUID: b403b6a1-230b-4247-8cd6-2a3d068f4bbf
5
+ * Version: 1.5.0
6
+ * Description: Aggregates and exports all utility functions from the AnalyzerUtils module. Added cloneAnalyzer method.
7
7
  * Language: JavaScript
8
8
  * Created-at: 2025-08-28T15:56:40.319Z
9
- * Authors: Gemini 2.5 Flash (v1.0.0), Gemini 2.5 Flash (v1.1.0), Gemini 2.5 Flash (v1.2.0)
9
+ * Authors: Gemini 2.5 Flash (v1.0.0), Gemini 2.5 Flash (v1.1.0), Gemini 2.5 Flash (v1.2.0), GLM-4.6 (v1.3.0), GLM-4.6 (v1.4.0), GLM-4.6 (v1.5.0)
10
10
  */
11
11
 
12
12
 
@@ -18,7 +18,10 @@ const { saveConfiguration } = require('./saver');
18
18
  const { getAnalyzerSchema } = require('./schemaLoader');
19
19
  const { deleteAnalyzer } = require('./management');
20
20
  const { getAnalyzerInstructionsContent } = require('./instructionLoader');
21
- const { getSystemMessageContent, getStartMessageContent } = require('./defaultPromptLoader'); // NEW: Import default prompt loaders
21
+ const { getSystemMessageContent, getStartMessageContent } = require('./defaultPromptLoader');
22
+ const { preprocessJsonForValidation } = require('./jsonParser');
23
+ const { updateAnalyzer } = require('./updater');
24
+ const { cloneAnalyzer } = require('./cloner');
22
25
 
23
26
  module.exports = {
24
27
  buildChatIdToPathMap,
@@ -30,5 +33,8 @@ module.exports = {
30
33
  getAnalyzerInstructionsContent,
31
34
  saveConfiguration,
32
35
  getSystemMessageContent,
33
- getStartMessageContent
36
+ getStartMessageContent,
37
+ preprocessJsonForValidation,
38
+ updateAnalyzer,
39
+ cloneAnalyzer
34
40
  };
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * Component: AnalyzerUtils Instruction Loader
3
- * Block-UUID: 0a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5e
3
+ * Block-UUID: 3ecd422e-1dd8-482d-ae3f-90a5eae3ae44
4
4
  * Parent-UUID: N/A
5
5
  * Version: 1.0.0
6
6
  * Description: Provides utility functions for loading raw analyzer instruction content.
@@ -16,13 +16,13 @@ const path = require('path');
16
16
  /**
17
17
  * Retrieves the raw Markdown content of the analyzer's '1.md' instruction file.
18
18
  *
19
- * @param {string} analyzeMessagesBasePath - The absolute path to the base directory containing the analyzer message files (e.g., 'messages/analyze').
19
+ * @param {string} analyzersBasePath - The absolute path to the base directory containing the analyzer message files (e.g., 'messages/analyze').
20
20
  * @param {string} analyzerId - The unique ID of the analyzer (format: 'analyzer_name::content_type::instructions_type').
21
21
  * @returns {Promise<string|null>} A promise that resolves with the full Markdown content of the '1.md' file, or null if not found/invalid.
22
22
  */
23
- async function getAnalyzerInstructionsContent(analyzeMessagesBasePath, analyzerId) {
24
- if (typeof analyzeMessagesBasePath !== 'string' || analyzeMessagesBasePath.trim() === '') {
25
- console.error('Error: analyzeMessagesBasePath is required.');
23
+ async function getAnalyzerInstructionsContent(analyzersBasePath, analyzerId) {
24
+ if (typeof analyzersBasePath !== 'string' || analyzersBasePath.trim() === '') {
25
+ console.error('Error: analyzersBasePath is required.');
26
26
  return null;
27
27
  }
28
28
  if (typeof analyzerId !== 'string' || analyzerId.trim() === '') {
@@ -37,7 +37,7 @@ async function getAnalyzerInstructionsContent(analyzeMessagesBasePath, analyzerI
37
37
  }
38
38
  const [analyzerName, contentType, instructionsType] = parts;
39
39
 
40
- const instructionsFilePath = path.join(analyzeMessagesBasePath, analyzerName, contentType, instructionsType, '1.md');
40
+ const instructionsFilePath = path.join(analyzersBasePath, analyzerName, contentType, instructionsType, '1.md');
41
41
 
42
42
  try {
43
43
  const fileContent = await fs.readFile(instructionsFilePath, 'utf8');
@@ -0,0 +1,35 @@
1
+ /*
2
+ * Component: AnalyzerUtils JSON Parser
3
+ * Block-UUID: 4dd21efd-3ad3-43e1-adf0-d52d7c560970
4
+ * Version: 1.0.0
5
+ * Description: Provides utility functions for pre-processing JSON content from analyzer instructions.
6
+ * Language: JavaScript
7
+ * Created-at: 2025-11-23T05:38:15.725Z
8
+ * Authors: GLM-4.6 (v1.0.0)
9
+ */
10
+
11
+
12
+ /**
13
+ * Pre-processes JSON content to quote unquoted template strings before parsing.
14
+ * This handles cases where {{SYSTEM: ...}} and {{ANALYZER: ...}} placeholders
15
+ * are not properly quoted in the JSON.
16
+ *
17
+ * @param {string} jsonString - The raw JSON string content.
18
+ * @returns {string} The processed JSON string with template strings quoted.
19
+ */
20
+ function preprocessJsonForValidation(jsonString) {
21
+ // Find all unquoted template strings and quote them with proper escaping
22
+ // This regex looks for template strings that aren't already quoted
23
+ return jsonString.replace(
24
+ /(?<!")(\{\{(SYSTEM|ANALYZER):[^}]+\}\})(?!")/g,
25
+ (match, template) => {
26
+ // Escape any double quotes within the template string
27
+ const escapedTemplate = template.replace(/"/g, '\\"');
28
+ return `"${escapedTemplate}"`;
29
+ }
30
+ );
31
+ }
32
+
33
+ module.exports = {
34
+ preprocessJsonForValidation
35
+ };
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * Component: AnalyzerUtils Management
3
- * Block-UUID: 0d1e2f3a-4b5c-6d7e-8f9a-0b1c2d3e4f5a
3
+ * Block-UUID: 6241f381-512b-48a7-b70e-6b45683831fe
4
4
  * Parent-UUID: N/A
5
5
  * Version: 1.0.0
6
6
  * Description: Provides utility functions for managing (deleting) analyzer configurations.
@@ -34,13 +34,13 @@ async function isDirectoryEmpty(dirPath) {
34
34
  /**
35
35
  * Deletes a specific analyzer configuration and intelligently cleans up empty directories.
36
36
  *
37
- * @param {string} analyzeMessagesBasePath - The absolute or relative path to the base directory containing the analyzer message files (e.g., 'messages/analyze').
37
+ * @param {string} analyzersBasePath - The absolute path to the base directory containing the analyzers (e.g., 'analyzers/analyzer-1').
38
38
  * @param {string} analyzerId - The unique ID of the analyzer to delete (format: 'analyzer_name::content_type::instructions_type').
39
39
  * @returns {Promise<{success: boolean, message: string}>} A promise that resolves with a result object indicating success or failure.
40
40
  */
41
- async function deleteAnalyzer(analyzeMessagesBasePath, analyzerId) {
42
- if (typeof analyzeMessagesBasePath !== 'string' || analyzeMessagesBasePath.trim() === '') {
43
- return { success: false, message: 'analyzeMessagesBasePath is required.' };
41
+ async function deleteAnalyzer(analyzersBasePath, analyzerId) {
42
+ if (typeof analyzersBasePath !== 'string' || analyzersBasePath.trim() === '') {
43
+ return { success: false, message: 'analyzersBasePath is required.' };
44
44
  }
45
45
  if (typeof analyzerId !== 'string' || analyzerId.trim() === '') {
46
46
  return { success: false, message: 'analyzerId is required.' };
@@ -52,7 +52,7 @@ async function deleteAnalyzer(analyzeMessagesBasePath, analyzerId) {
52
52
  }
53
53
  const [analyzerName, contentType, instructionsType] = parts;
54
54
 
55
- const analyzerDir = path.join(analyzeMessagesBasePath, analyzerName);
55
+ const analyzerDir = path.join(analyzersBasePath, analyzerName);
56
56
  const contentDir = path.join(analyzerDir, contentType);
57
57
  const instructionsDir = path.join(contentDir, instructionsType);
58
58
  const instructionsFilePath = path.join(instructionsDir, '1.md');
@@ -21,19 +21,19 @@ const path = require('path');
21
21
  * if necessary, saves the instructions to '1.md'. Optionally, it can
22
22
  * ensure config.json files exist with labels derived from directory names.
23
23
  *
24
- * @param {string} analyzeMessagesBasePath - The absolute or relative path to the 'messages/analyze' directory.
24
+ * @param {string} analyzersBasePath - The absolute path to the base directory containing the analyzers (e.g., 'analyzers/analyzer-1').
25
25
  * @param {string} analyzerId - The unique ID of the analyzer (format: 'analyzer_name::content_type::instructions_type').
26
26
  * @param {string} instructionsContent - The full content of the analyzer instructions message to be saved in '1.md'.
27
27
  * @param {object} [options={}] - Optional configuration options.
28
28
  * @param {boolean} [options.ensureConfigs=false] - If true, ensures config.json files exist in the analyzer, content, and instructions directories. Defaults to false.
29
29
  * @returns {Promise<{success: boolean, message?: string}>} A promise that resolves with a result object.
30
30
  */
31
- async function saveConfiguration(analyzeMessagesBasePath, analyzerId, instructionsContent, options = {}) {
31
+ async function saveConfiguration(analyzersBasePath, analyzerId, instructionsContent, options = {}) {
32
32
  const { ensureConfigs = false } = options;
33
33
 
34
34
  // 1. Validate inputs
35
- if (typeof analyzeMessagesBasePath !== 'string' || analyzeMessagesBasePath.trim() === '') {
36
- return { success: false, message: 'analyzeMessagesBasePath is required.' };
35
+ if (typeof analyzersBasePath !== 'string' || analyzersBasePath.trim() === '') {
36
+ return { success: false, message: 'analyzersBasePath is required.' };
37
37
  }
38
38
  if (typeof analyzerId !== 'string' || analyzerId.trim() === '') {
39
39
  return { success: false, message: 'analyzerId is required.' };
@@ -66,7 +66,7 @@ async function saveConfiguration(analyzeMessagesBasePath, analyzerId, instructio
66
66
  }
67
67
 
68
68
  // 3. Construct directory paths
69
- const analyzerDir = path.join(analyzeMessagesBasePath, analyzerName);
69
+ const analyzerDir = path.join(analyzersBasePath, analyzerName);
70
70
  const contentDir = path.join(analyzerDir, contentType);
71
71
  const instructionsDir = path.join(contentDir, instructionsType);
72
72
  const instructionsFilePath = path.join(instructionsDir, '1.md');