@gitsense/gsc-utils 0.2.5 → 0.2.7

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.
@@ -18,8 +18,8 @@ function getDefaultExportFromCjs (x) {
18
18
  * Authors: Claude 3.7 Sonnet (v1.0.0), Gemini 2.5 Pro (v1.1.0), Gemini 2.5 Flash Thinking (v1.2.0)
19
19
  */
20
20
 
21
- const fs$6 = require$$0;
22
- const path$4 = require$$1;
21
+ const fs$7 = require$$0;
22
+ const path$5 = require$$1;
23
23
  const ANALYZE_MESSAGE_REGEXP = /^# Analyze - `([^`]+)`\n/;
24
24
 
25
25
  /**
@@ -53,12 +53,12 @@ function unescapeCodeBlocks(content) {
53
53
  * @returns {Array} An array of message objects with role and content properties
54
54
  */
55
55
  function getChatTemplateMessages$1(dirname, messageType) {
56
- const messagesDir = path$4.join(dirname, messageType);
56
+ const messagesDir = path$5.join(dirname, messageType);
57
57
  const messages = [];
58
58
 
59
59
  try {
60
60
  // Read all files in the directory
61
- const files = fs$6.readdirSync(messagesDir);
61
+ const files = fs$7.readdirSync(messagesDir);
62
62
 
63
63
  // Sort files numerically (1.md, 2.md, etc.)
64
64
  const sortedFiles = files.sort((a, b) => {
@@ -69,9 +69,9 @@ function getChatTemplateMessages$1(dirname, messageType) {
69
69
 
70
70
  // Process each file
71
71
  for (const file of sortedFiles) {
72
- if (path$4.extname(file) === '.md') {
73
- const filePath = path$4.join(messagesDir, file);
74
- const fileContent = fs$6.readFileSync(filePath, 'utf8');
72
+ if (path$5.extname(file) === '.md') {
73
+ const filePath = path$5.join(messagesDir, file);
74
+ const fileContent = fs$7.readFileSync(filePath, 'utf8');
75
75
 
76
76
  // Split by triple newline to separate metadata from content
77
77
  const parts = fileContent.split('\n\n\n');
@@ -10925,8 +10925,8 @@ var dataValidator = {
10925
10925
  * Authors: Gemini 2.5 Flash (v1.0.0)
10926
10926
  */
10927
10927
 
10928
- const fs$5 = require$$0.promises;
10929
- const path$3 = require$$1;
10928
+ const fs$6 = require$$0.promises;
10929
+ const path$4 = require$$1;
10930
10930
 
10931
10931
  /**
10932
10932
  * Reads and parses the config.json file in a directory.
@@ -10935,9 +10935,9 @@ const path$3 = require$$1;
10935
10935
  * or null if the file doesn't exist or is invalid.
10936
10936
  */
10937
10937
  async function readConfig$1(dirPath) {
10938
- const configPath = path$3.join(dirPath, 'config.json');
10938
+ const configPath = path$4.join(dirPath, 'config.json');
10939
10939
  try {
10940
- const fileContent = await fs$5.readFile(configPath, 'utf8');
10940
+ const fileContent = await fs$6.readFile(configPath, 'utf8');
10941
10941
  return JSON.parse(fileContent);
10942
10942
  } catch (error) {
10943
10943
  if (error.code !== 'ENOENT') {
@@ -10974,41 +10974,41 @@ async function getAnalyzers$2(analyzeMessagesBasePath) {
10974
10974
  const analyzers = [];
10975
10975
 
10976
10976
  try {
10977
- const analyzerEntries = await fs$5.readdir(analyzeMessagesBasePath, { withFileTypes: true });
10977
+ const analyzerEntries = await fs$6.readdir(analyzeMessagesBasePath, { withFileTypes: true });
10978
10978
 
10979
10979
  for (const analyzerEntry of analyzerEntries) {
10980
10980
  if (analyzerEntry.isDirectory() && isValidDirName(analyzerEntry.name)) {
10981
10981
  const analyzerName = analyzerEntry.name;
10982
- const analyzerPath = path$3.join(analyzeMessagesBasePath, analyzerName);
10982
+ const analyzerPath = path$4.join(analyzeMessagesBasePath, analyzerName);
10983
10983
  const analyzerConfig = await readConfig$1(analyzerPath);
10984
10984
  const analyzerLabel = analyzerConfig?.label || analyzerName;
10985
10985
 
10986
- const contentEntries = await fs$5.readdir(analyzerPath, { withFileTypes: true });
10986
+ const contentEntries = await fs$6.readdir(analyzerPath, { withFileTypes: true });
10987
10987
 
10988
10988
  for (const contentEntry of contentEntries) {
10989
10989
  if (contentEntry.isDirectory() && isValidDirName(contentEntry.name)) {
10990
10990
  const contentType = contentEntry.name;
10991
- const contentPath = path$3.join(analyzerPath, contentType);
10991
+ const contentPath = path$4.join(analyzerPath, contentType);
10992
10992
  const contentConfig = await readConfig$1(contentPath);
10993
10993
  const contentLabel = contentConfig?.label || contentType;
10994
10994
 
10995
- const instructionsEntries = await fs$5.readdir(contentPath, { withFileTypes: true });
10995
+ const instructionsEntries = await fs$6.readdir(contentPath, { withFileTypes: true });
10996
10996
 
10997
10997
  for (const instructionsEntry of instructionsEntries) {
10998
10998
  if (instructionsEntry.isDirectory() && isValidDirName(instructionsEntry.name)) {
10999
10999
  const instructionsType = instructionsEntry.name;
11000
- const instructionsPath = path$3.join(contentPath, instructionsType);
11000
+ const instructionsPath = path$4.join(contentPath, instructionsType);
11001
11001
  const instructionsConfig = await readConfig$1(instructionsPath);
11002
11002
  const instructionsLabel = instructionsConfig?.label || instructionsType;
11003
11003
 
11004
11004
  // Check for the existence of 1.md to confirm a valid analyzer configuration
11005
- const instructionsFilePath = path$3.join(instructionsPath, '1.md');
11005
+ const instructionsFilePath = path$4.join(instructionsPath, '1.md');
11006
11006
  try {
11007
- await fs$5.access(instructionsFilePath); // Check if file exists and is accessible
11007
+ await fs$6.access(instructionsFilePath); // Check if file exists and is accessible
11008
11008
 
11009
11009
  // If analyzerName starts with 'tutorial-', check its last modified time.
11010
11010
  if (analyzerName.startsWith('tutorial-')) {
11011
- const stats = await fs$5.stat(instructionsFilePath);
11011
+ const stats = await fs$6.stat(instructionsFilePath);
11012
11012
  const lastModified = stats.mtime.getTime(); // Get timestamp in milliseconds
11013
11013
  const sixtyMinutesAgo = Date.now() - (60 * 60 * 1000); // Current time - 60 minutes in ms
11014
11014
 
@@ -11051,6 +11051,139 @@ var discovery = {
11051
11051
  getAnalyzers: getAnalyzers$2,
11052
11052
  readConfig: readConfig$1};
11053
11053
 
11054
+ /**
11055
+ * Component: Analyzer Saver Utility
11056
+ * Block-UUID: a373f4ba-89ce-465f-8624-24258c923e61
11057
+ * Parent-UUID: N/A
11058
+ * Version: 1.1.0
11059
+ * Description: Utility function to save or update an analyzer configuration based on its ID and content.
11060
+ * Language: JavaScript
11061
+ * Created-at: 2025-07-12T04:12:33.454Z
11062
+ * Authors: Gemini 2.5 Flash Thinking (v1.0.0), Gemini 2.5 Flash Thinking (v1.1.0)
11063
+ */
11064
+
11065
+ const fs$5 = require$$0.promises;
11066
+ const path$3 = require$$1;
11067
+
11068
+ /**
11069
+ * Saves or updates an analyzer configuration.
11070
+ *
11071
+ * This function takes the analyzer ID and its full instructions content,
11072
+ * parses the ID to determine the directory structure, creates directories
11073
+ * if necessary, saves the instructions to '1.md'. Optionally, it can
11074
+ * ensure config.json files exist with labels derived from directory names.
11075
+ *
11076
+ * @param {string} analyzeMessagesBasePath - The absolute or relative path to the 'messages/analyze' directory.
11077
+ * @param {string} analyzerId - The unique ID of the analyzer (format: 'analyzer_name::content_type::instructions_type').
11078
+ * @param {string} instructionsContent - The full content of the analyzer instructions message to be saved in '1.md'.
11079
+ * @param {object} [options={}] - Optional configuration options.
11080
+ * @param {boolean} [options.ensureConfigs=false] - If true, ensures config.json files exist in the analyzer, content, and instructions directories. Defaults to false.
11081
+ * @returns {Promise<{success: boolean, message?: string}>} A promise that resolves with a result object.
11082
+ */
11083
+ async function saveConfiguration$1(analyzeMessagesBasePath, analyzerId, instructionsContent, options = {}) {
11084
+ const { ensureConfigs = false } = options;
11085
+
11086
+ // 1. Validate inputs
11087
+ if (typeof analyzeMessagesBasePath !== 'string' || analyzeMessagesBasePath.trim() === '') {
11088
+ return { success: false, message: 'analyzeMessagesBasePath is required.' };
11089
+ }
11090
+ if (typeof analyzerId !== 'string' || analyzerId.trim() === '') {
11091
+ return { success: false, message: 'analyzerId is required.' };
11092
+ }
11093
+ if (typeof instructionsContent !== 'string' || instructionsContent.trim() === '') {
11094
+ return { success: false, message: 'instructionsContent is required.' };
11095
+ }
11096
+
11097
+ // 2. Parse analyzerId
11098
+ const parts = analyzerId.split('::');
11099
+ if (parts.length !== 3) {
11100
+ return { success: false, message: `Invalid analyzerId format. Expected 'analyzer_name::content_type::instructions_type', but got '${analyzerId}'.` };
11101
+ }
11102
+ const [analyzerName, contentType, instructionsType] = parts;
11103
+
11104
+ // Helper to validate directory names based on README.md rules
11105
+ const isValidDirName = (name) => {
11106
+ // Cannot start with underscore, cannot contain dots, must be alphanumeric, dash, or underscore
11107
+ return /^[a-zA-Z0-9_-]+$/.test(name) && !name.startsWith('_') && !name.includes('.');
11108
+ };
11109
+
11110
+ if (!isValidDirName(analyzerName)) {
11111
+ return { success: false, message: `Invalid analyzer name '${analyzerName}'. Names must be alphanumeric, dash, or underscore, cannot start with underscore, and cannot contain dots.` };
11112
+ }
11113
+ if (!isValidDirName(contentType)) {
11114
+ return { success: false, message: `Invalid content type name '${contentType}'. Names must be alphanumeric, dash, or underscore, cannot start with underscore, and cannot contain dots.` };
11115
+ }
11116
+ if (!isValidDirName(instructionsType)) {
11117
+ return { success: false, message: `Invalid instructions type name '${instructionsType}'. Names must be alphanumeric, dash, or underscore, cannot start with underscore, and cannot contain dots.` };
11118
+ }
11119
+
11120
+ // 3. Construct directory paths
11121
+ const analyzerDir = path$3.join(analyzeMessagesBasePath, analyzerName);
11122
+ const contentDir = path$3.join(analyzerDir, contentType);
11123
+ const instructionsDir = path$3.join(contentDir, instructionsType);
11124
+ const instructionsFilePath = path$3.join(instructionsDir, '1.md');
11125
+
11126
+ try {
11127
+ // 4. Create directories recursively
11128
+ await fs$5.mkdir(instructionsDir, { recursive: true });
11129
+
11130
+ // 5. Save instructions content to 1.md
11131
+ const finalContent = `; role: assistant\n\n\n${instructionsContent}`;
11132
+ await fs$5.writeFile(instructionsFilePath, finalContent, 'utf8');
11133
+
11134
+ // 6. Optionally create/Update config.json files
11135
+ if (ensureConfigs) {
11136
+ await ensureConfigJson(analyzerDir, analyzerName);
11137
+ await ensureConfigJson(contentDir, contentType);
11138
+ await ensureConfigJson(instructionsDir, instructionsType);
11139
+ }
11140
+
11141
+ return { success: true, message: `Analyzer configuration '${analyzerId}' saved successfully.` };
11142
+
11143
+ } catch (error) {
11144
+ console.error(`Error saving analyzer configuration '${analyzerId}':`, error);
11145
+ return { success: false, message: `Failed to save analyzer configuration: ${error.message}` };
11146
+ }
11147
+ }
11148
+
11149
+ /**
11150
+ * Ensures a config.json file exists in the given directory with a label.
11151
+ * If the file exists, it reads it and adds the label if missing.
11152
+ * If the file doesn't exist, it creates it with the label.
11153
+ *
11154
+ * @param {string} dirPath - The path to the directory.
11155
+ * @param {string} label - The label to ensure is in the config.json.
11156
+ */
11157
+ async function ensureConfigJson(dirPath, label) {
11158
+ const configPath = path$3.join(dirPath, 'config.json');
11159
+ let config = {};
11160
+
11161
+ try {
11162
+ const fileContent = await fs$5.readFile(configPath, 'utf8');
11163
+ config = JSON.parse(fileContent);
11164
+ } catch (error) {
11165
+ // If file doesn't exist or parsing fails, start with an empty config
11166
+ if (error.code !== 'ENOENT') {
11167
+ console.warn(`Failed to read or parse existing config.json in ${dirPath}: ${error.message}`);
11168
+ }
11169
+ config = {}; // Ensure config is an object even on error
11170
+ }
11171
+
11172
+ // Add or update the label if it's missing or empty
11173
+ if (!config.label || typeof config.label !== 'string' || config.label.trim() === '') {
11174
+ // Capitalize the first letter for the label
11175
+ config.label = label.charAt(0).toUpperCase() + label.slice(1);
11176
+ }
11177
+
11178
+ // Write the updated config back to the file
11179
+ await fs$5.writeFile(configPath, JSON.stringify(config, null, 4), 'utf8');
11180
+ }
11181
+
11182
+
11183
+ var saver = {
11184
+ saveConfiguration: saveConfiguration$1,
11185
+ };
11186
+
11054
11187
  /*
11055
11188
  * Component: AnalyzerUtils Schema Loader
11056
11189
  * Block-UUID: 0c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f
@@ -11414,6 +11547,7 @@ const { buildChatIdToPathMap: buildChatIdToPathMap$1 } = contextMapper;
11414
11547
  const { processLLMAnalysisResponse: processLLMAnalysisResponse$1 } = responseProcessor;
11415
11548
  const { validateLLMAnalysisData: validateLLMAnalysisData$1 } = dataValidator;
11416
11549
  const { getAnalyzers: getAnalyzers$1 } = discovery;
11550
+ const { saveConfiguration } = saver;
11417
11551
  const { getAnalyzerSchema: getAnalyzerSchema$1 } = schemaLoader;
11418
11552
  const { deleteAnalyzer: deleteAnalyzer$1 } = management;
11419
11553
  const { getAnalyzerInstructionsContent: getAnalyzerInstructionsContent$1 } = instructionLoader;
@@ -11426,6 +11560,7 @@ var AnalyzerUtils$1 = {
11426
11560
  getAnalyzerSchema: getAnalyzerSchema$1,
11427
11561
  deleteAnalyzer: deleteAnalyzer$1,
11428
11562
  getAnalyzerInstructionsContent: getAnalyzerInstructionsContent$1,
11563
+ saveConfiguration,
11429
11564
  };
11430
11565
 
11431
11566
  /**
@@ -11648,7 +11783,7 @@ const CodeBlockUtils = CodeBlockUtils$4;
11648
11783
  const ContextUtils = ContextUtils$2;
11649
11784
  const MessageUtils = MessageUtils$3;
11650
11785
  const AnalysisBlockUtils = AnalysisBlockUtils$3;
11651
- const AnalyzerUtils = AnalyzerUtils$1; // Updated import
11786
+ const AnalyzerUtils = AnalyzerUtils$1;
11652
11787
  const PatchUtils = PatchUtils$2;
11653
11788
  const GSToolBlockUtils = GSToolBlockUtils$3;
11654
11789
  const LLMUtils = LLMUtils$1;
@@ -11688,7 +11823,7 @@ const {
11688
11823
  validateOverviewMetadata,
11689
11824
  } = AnalysisBlockUtils;
11690
11825
 
11691
- const { // Updated AnalyzerUtils destructuring
11826
+ const {
11692
11827
  buildChatIdToPathMap,
11693
11828
  processLLMAnalysisResponse,
11694
11829
  validateLLMAnalysisData,
@@ -11696,6 +11831,7 @@ const { // Updated AnalyzerUtils destructuring
11696
11831
  getAnalyzerSchema,
11697
11832
  deleteAnalyzer,
11698
11833
  getAnalyzerInstructionsContent,
11834
+ saveAnalyzerConfiguration,
11699
11835
  } = AnalyzerUtils;
11700
11836
 
11701
11837
  const {
@@ -16,8 +16,8 @@ function getDefaultExportFromCjs (x) {
16
16
  * Authors: Claude 3.7 Sonnet (v1.0.0), Gemini 2.5 Pro (v1.1.0), Gemini 2.5 Flash Thinking (v1.2.0)
17
17
  */
18
18
 
19
- const fs$6 = require$$0;
20
- const path$4 = require$$1;
19
+ const fs$7 = require$$0;
20
+ const path$5 = require$$1;
21
21
  const ANALYZE_MESSAGE_REGEXP = /^# Analyze - `([^`]+)`\n/;
22
22
 
23
23
  /**
@@ -51,12 +51,12 @@ function unescapeCodeBlocks(content) {
51
51
  * @returns {Array} An array of message objects with role and content properties
52
52
  */
53
53
  function getChatTemplateMessages$1(dirname, messageType) {
54
- const messagesDir = path$4.join(dirname, messageType);
54
+ const messagesDir = path$5.join(dirname, messageType);
55
55
  const messages = [];
56
56
 
57
57
  try {
58
58
  // Read all files in the directory
59
- const files = fs$6.readdirSync(messagesDir);
59
+ const files = fs$7.readdirSync(messagesDir);
60
60
 
61
61
  // Sort files numerically (1.md, 2.md, etc.)
62
62
  const sortedFiles = files.sort((a, b) => {
@@ -67,9 +67,9 @@ function getChatTemplateMessages$1(dirname, messageType) {
67
67
 
68
68
  // Process each file
69
69
  for (const file of sortedFiles) {
70
- if (path$4.extname(file) === '.md') {
71
- const filePath = path$4.join(messagesDir, file);
72
- const fileContent = fs$6.readFileSync(filePath, 'utf8');
70
+ if (path$5.extname(file) === '.md') {
71
+ const filePath = path$5.join(messagesDir, file);
72
+ const fileContent = fs$7.readFileSync(filePath, 'utf8');
73
73
 
74
74
  // Split by triple newline to separate metadata from content
75
75
  const parts = fileContent.split('\n\n\n');
@@ -10923,8 +10923,8 @@ var dataValidator = {
10923
10923
  * Authors: Gemini 2.5 Flash (v1.0.0)
10924
10924
  */
10925
10925
 
10926
- const fs$5 = require$$0.promises;
10927
- const path$3 = require$$1;
10926
+ const fs$6 = require$$0.promises;
10927
+ const path$4 = require$$1;
10928
10928
 
10929
10929
  /**
10930
10930
  * Reads and parses the config.json file in a directory.
@@ -10933,9 +10933,9 @@ const path$3 = require$$1;
10933
10933
  * or null if the file doesn't exist or is invalid.
10934
10934
  */
10935
10935
  async function readConfig$1(dirPath) {
10936
- const configPath = path$3.join(dirPath, 'config.json');
10936
+ const configPath = path$4.join(dirPath, 'config.json');
10937
10937
  try {
10938
- const fileContent = await fs$5.readFile(configPath, 'utf8');
10938
+ const fileContent = await fs$6.readFile(configPath, 'utf8');
10939
10939
  return JSON.parse(fileContent);
10940
10940
  } catch (error) {
10941
10941
  if (error.code !== 'ENOENT') {
@@ -10972,41 +10972,41 @@ async function getAnalyzers$2(analyzeMessagesBasePath) {
10972
10972
  const analyzers = [];
10973
10973
 
10974
10974
  try {
10975
- const analyzerEntries = await fs$5.readdir(analyzeMessagesBasePath, { withFileTypes: true });
10975
+ const analyzerEntries = await fs$6.readdir(analyzeMessagesBasePath, { withFileTypes: true });
10976
10976
 
10977
10977
  for (const analyzerEntry of analyzerEntries) {
10978
10978
  if (analyzerEntry.isDirectory() && isValidDirName(analyzerEntry.name)) {
10979
10979
  const analyzerName = analyzerEntry.name;
10980
- const analyzerPath = path$3.join(analyzeMessagesBasePath, analyzerName);
10980
+ const analyzerPath = path$4.join(analyzeMessagesBasePath, analyzerName);
10981
10981
  const analyzerConfig = await readConfig$1(analyzerPath);
10982
10982
  const analyzerLabel = analyzerConfig?.label || analyzerName;
10983
10983
 
10984
- const contentEntries = await fs$5.readdir(analyzerPath, { withFileTypes: true });
10984
+ const contentEntries = await fs$6.readdir(analyzerPath, { withFileTypes: true });
10985
10985
 
10986
10986
  for (const contentEntry of contentEntries) {
10987
10987
  if (contentEntry.isDirectory() && isValidDirName(contentEntry.name)) {
10988
10988
  const contentType = contentEntry.name;
10989
- const contentPath = path$3.join(analyzerPath, contentType);
10989
+ const contentPath = path$4.join(analyzerPath, contentType);
10990
10990
  const contentConfig = await readConfig$1(contentPath);
10991
10991
  const contentLabel = contentConfig?.label || contentType;
10992
10992
 
10993
- const instructionsEntries = await fs$5.readdir(contentPath, { withFileTypes: true });
10993
+ const instructionsEntries = await fs$6.readdir(contentPath, { withFileTypes: true });
10994
10994
 
10995
10995
  for (const instructionsEntry of instructionsEntries) {
10996
10996
  if (instructionsEntry.isDirectory() && isValidDirName(instructionsEntry.name)) {
10997
10997
  const instructionsType = instructionsEntry.name;
10998
- const instructionsPath = path$3.join(contentPath, instructionsType);
10998
+ const instructionsPath = path$4.join(contentPath, instructionsType);
10999
10999
  const instructionsConfig = await readConfig$1(instructionsPath);
11000
11000
  const instructionsLabel = instructionsConfig?.label || instructionsType;
11001
11001
 
11002
11002
  // Check for the existence of 1.md to confirm a valid analyzer configuration
11003
- const instructionsFilePath = path$3.join(instructionsPath, '1.md');
11003
+ const instructionsFilePath = path$4.join(instructionsPath, '1.md');
11004
11004
  try {
11005
- await fs$5.access(instructionsFilePath); // Check if file exists and is accessible
11005
+ await fs$6.access(instructionsFilePath); // Check if file exists and is accessible
11006
11006
 
11007
11007
  // If analyzerName starts with 'tutorial-', check its last modified time.
11008
11008
  if (analyzerName.startsWith('tutorial-')) {
11009
- const stats = await fs$5.stat(instructionsFilePath);
11009
+ const stats = await fs$6.stat(instructionsFilePath);
11010
11010
  const lastModified = stats.mtime.getTime(); // Get timestamp in milliseconds
11011
11011
  const sixtyMinutesAgo = Date.now() - (60 * 60 * 1000); // Current time - 60 minutes in ms
11012
11012
 
@@ -11049,6 +11049,139 @@ var discovery = {
11049
11049
  getAnalyzers: getAnalyzers$2,
11050
11050
  readConfig: readConfig$1};
11051
11051
 
11052
+ /**
11053
+ * Component: Analyzer Saver Utility
11054
+ * Block-UUID: a373f4ba-89ce-465f-8624-24258c923e61
11055
+ * Parent-UUID: N/A
11056
+ * Version: 1.1.0
11057
+ * Description: Utility function to save or update an analyzer configuration based on its ID and content.
11058
+ * Language: JavaScript
11059
+ * Created-at: 2025-07-12T04:12:33.454Z
11060
+ * Authors: Gemini 2.5 Flash Thinking (v1.0.0), Gemini 2.5 Flash Thinking (v1.1.0)
11061
+ */
11062
+
11063
+ const fs$5 = require$$0.promises;
11064
+ const path$3 = require$$1;
11065
+
11066
+ /**
11067
+ * Saves or updates an analyzer configuration.
11068
+ *
11069
+ * This function takes the analyzer ID and its full instructions content,
11070
+ * parses the ID to determine the directory structure, creates directories
11071
+ * if necessary, saves the instructions to '1.md'. Optionally, it can
11072
+ * ensure config.json files exist with labels derived from directory names.
11073
+ *
11074
+ * @param {string} analyzeMessagesBasePath - The absolute or relative path to the 'messages/analyze' directory.
11075
+ * @param {string} analyzerId - The unique ID of the analyzer (format: 'analyzer_name::content_type::instructions_type').
11076
+ * @param {string} instructionsContent - The full content of the analyzer instructions message to be saved in '1.md'.
11077
+ * @param {object} [options={}] - Optional configuration options.
11078
+ * @param {boolean} [options.ensureConfigs=false] - If true, ensures config.json files exist in the analyzer, content, and instructions directories. Defaults to false.
11079
+ * @returns {Promise<{success: boolean, message?: string}>} A promise that resolves with a result object.
11080
+ */
11081
+ async function saveConfiguration$1(analyzeMessagesBasePath, analyzerId, instructionsContent, options = {}) {
11082
+ const { ensureConfigs = false } = options;
11083
+
11084
+ // 1. Validate inputs
11085
+ if (typeof analyzeMessagesBasePath !== 'string' || analyzeMessagesBasePath.trim() === '') {
11086
+ return { success: false, message: 'analyzeMessagesBasePath is required.' };
11087
+ }
11088
+ if (typeof analyzerId !== 'string' || analyzerId.trim() === '') {
11089
+ return { success: false, message: 'analyzerId is required.' };
11090
+ }
11091
+ if (typeof instructionsContent !== 'string' || instructionsContent.trim() === '') {
11092
+ return { success: false, message: 'instructionsContent is required.' };
11093
+ }
11094
+
11095
+ // 2. Parse analyzerId
11096
+ const parts = analyzerId.split('::');
11097
+ if (parts.length !== 3) {
11098
+ return { success: false, message: `Invalid analyzerId format. Expected 'analyzer_name::content_type::instructions_type', but got '${analyzerId}'.` };
11099
+ }
11100
+ const [analyzerName, contentType, instructionsType] = parts;
11101
+
11102
+ // Helper to validate directory names based on README.md rules
11103
+ const isValidDirName = (name) => {
11104
+ // Cannot start with underscore, cannot contain dots, must be alphanumeric, dash, or underscore
11105
+ return /^[a-zA-Z0-9_-]+$/.test(name) && !name.startsWith('_') && !name.includes('.');
11106
+ };
11107
+
11108
+ if (!isValidDirName(analyzerName)) {
11109
+ return { success: false, message: `Invalid analyzer name '${analyzerName}'. Names must be alphanumeric, dash, or underscore, cannot start with underscore, and cannot contain dots.` };
11110
+ }
11111
+ if (!isValidDirName(contentType)) {
11112
+ return { success: false, message: `Invalid content type name '${contentType}'. Names must be alphanumeric, dash, or underscore, cannot start with underscore, and cannot contain dots.` };
11113
+ }
11114
+ if (!isValidDirName(instructionsType)) {
11115
+ return { success: false, message: `Invalid instructions type name '${instructionsType}'. Names must be alphanumeric, dash, or underscore, cannot start with underscore, and cannot contain dots.` };
11116
+ }
11117
+
11118
+ // 3. Construct directory paths
11119
+ const analyzerDir = path$3.join(analyzeMessagesBasePath, analyzerName);
11120
+ const contentDir = path$3.join(analyzerDir, contentType);
11121
+ const instructionsDir = path$3.join(contentDir, instructionsType);
11122
+ const instructionsFilePath = path$3.join(instructionsDir, '1.md');
11123
+
11124
+ try {
11125
+ // 4. Create directories recursively
11126
+ await fs$5.mkdir(instructionsDir, { recursive: true });
11127
+
11128
+ // 5. Save instructions content to 1.md
11129
+ const finalContent = `; role: assistant\n\n\n${instructionsContent}`;
11130
+ await fs$5.writeFile(instructionsFilePath, finalContent, 'utf8');
11131
+
11132
+ // 6. Optionally create/Update config.json files
11133
+ if (ensureConfigs) {
11134
+ await ensureConfigJson(analyzerDir, analyzerName);
11135
+ await ensureConfigJson(contentDir, contentType);
11136
+ await ensureConfigJson(instructionsDir, instructionsType);
11137
+ }
11138
+
11139
+ return { success: true, message: `Analyzer configuration '${analyzerId}' saved successfully.` };
11140
+
11141
+ } catch (error) {
11142
+ console.error(`Error saving analyzer configuration '${analyzerId}':`, error);
11143
+ return { success: false, message: `Failed to save analyzer configuration: ${error.message}` };
11144
+ }
11145
+ }
11146
+
11147
+ /**
11148
+ * Ensures a config.json file exists in the given directory with a label.
11149
+ * If the file exists, it reads it and adds the label if missing.
11150
+ * If the file doesn't exist, it creates it with the label.
11151
+ *
11152
+ * @param {string} dirPath - The path to the directory.
11153
+ * @param {string} label - The label to ensure is in the config.json.
11154
+ */
11155
+ async function ensureConfigJson(dirPath, label) {
11156
+ const configPath = path$3.join(dirPath, 'config.json');
11157
+ let config = {};
11158
+
11159
+ try {
11160
+ const fileContent = await fs$5.readFile(configPath, 'utf8');
11161
+ config = JSON.parse(fileContent);
11162
+ } catch (error) {
11163
+ // If file doesn't exist or parsing fails, start with an empty config
11164
+ if (error.code !== 'ENOENT') {
11165
+ console.warn(`Failed to read or parse existing config.json in ${dirPath}: ${error.message}`);
11166
+ }
11167
+ config = {}; // Ensure config is an object even on error
11168
+ }
11169
+
11170
+ // Add or update the label if it's missing or empty
11171
+ if (!config.label || typeof config.label !== 'string' || config.label.trim() === '') {
11172
+ // Capitalize the first letter for the label
11173
+ config.label = label.charAt(0).toUpperCase() + label.slice(1);
11174
+ }
11175
+
11176
+ // Write the updated config back to the file
11177
+ await fs$5.writeFile(configPath, JSON.stringify(config, null, 4), 'utf8');
11178
+ }
11179
+
11180
+
11181
+ var saver = {
11182
+ saveConfiguration: saveConfiguration$1,
11183
+ };
11184
+
11052
11185
  /*
11053
11186
  * Component: AnalyzerUtils Schema Loader
11054
11187
  * Block-UUID: 0c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f
@@ -11412,6 +11545,7 @@ const { buildChatIdToPathMap: buildChatIdToPathMap$1 } = contextMapper;
11412
11545
  const { processLLMAnalysisResponse: processLLMAnalysisResponse$1 } = responseProcessor;
11413
11546
  const { validateLLMAnalysisData: validateLLMAnalysisData$1 } = dataValidator;
11414
11547
  const { getAnalyzers: getAnalyzers$1 } = discovery;
11548
+ const { saveConfiguration } = saver;
11415
11549
  const { getAnalyzerSchema: getAnalyzerSchema$1 } = schemaLoader;
11416
11550
  const { deleteAnalyzer: deleteAnalyzer$1 } = management;
11417
11551
  const { getAnalyzerInstructionsContent: getAnalyzerInstructionsContent$1 } = instructionLoader;
@@ -11424,6 +11558,7 @@ var AnalyzerUtils$1 = {
11424
11558
  getAnalyzerSchema: getAnalyzerSchema$1,
11425
11559
  deleteAnalyzer: deleteAnalyzer$1,
11426
11560
  getAnalyzerInstructionsContent: getAnalyzerInstructionsContent$1,
11561
+ saveConfiguration,
11427
11562
  };
11428
11563
 
11429
11564
  /**
@@ -11646,7 +11781,7 @@ const CodeBlockUtils = CodeBlockUtils$4;
11646
11781
  const ContextUtils = ContextUtils$2;
11647
11782
  const MessageUtils = MessageUtils$3;
11648
11783
  const AnalysisBlockUtils = AnalysisBlockUtils$3;
11649
- const AnalyzerUtils = AnalyzerUtils$1; // Updated import
11784
+ const AnalyzerUtils = AnalyzerUtils$1;
11650
11785
  const PatchUtils = PatchUtils$2;
11651
11786
  const GSToolBlockUtils = GSToolBlockUtils$3;
11652
11787
  const LLMUtils = LLMUtils$1;
@@ -11686,7 +11821,7 @@ const {
11686
11821
  validateOverviewMetadata,
11687
11822
  } = AnalysisBlockUtils;
11688
11823
 
11689
- const { // Updated AnalyzerUtils destructuring
11824
+ const {
11690
11825
  buildChatIdToPathMap,
11691
11826
  processLLMAnalysisResponse,
11692
11827
  validateLLMAnalysisData,
@@ -11694,6 +11829,7 @@ const { // Updated AnalyzerUtils destructuring
11694
11829
  getAnalyzerSchema,
11695
11830
  deleteAnalyzer,
11696
11831
  getAnalyzerInstructionsContent,
11832
+ saveAnalyzerConfiguration,
11697
11833
  } = AnalyzerUtils;
11698
11834
 
11699
11835
  const {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitsense/gsc-utils",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "Utilities for GitSense Chat (GSC)",
5
5
  "main": "dist/gsc-utils.cjs.js",
6
6
  "module": "dist/gsc-utils.esm.js",
@@ -14,6 +14,7 @@ const { buildChatIdToPathMap } = require('./contextMapper');
14
14
  const { processLLMAnalysisResponse } = require('./responseProcessor');
15
15
  const { validateLLMAnalysisData } = require('./dataValidator');
16
16
  const { getAnalyzers } = require('./discovery');
17
+ const { saveConfiguration } = require('./saver');
17
18
  const { getAnalyzerSchema } = require('./schemaLoader');
18
19
  const { deleteAnalyzer } = require('./management');
19
20
  const { getAnalyzerInstructionsContent } = require('./instructionLoader');
@@ -26,4 +27,5 @@ module.exports = {
26
27
  getAnalyzerSchema,
27
28
  deleteAnalyzer,
28
29
  getAnalyzerInstructionsContent,
30
+ saveConfiguration,
29
31
  };
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Component: Analyzer Saver Utility
3
+ * Block-UUID: a373f4ba-89ce-465f-8624-24258c923e61
4
+ * Parent-UUID: N/A
5
+ * Version: 1.1.0
6
+ * Description: Utility function to save or update an analyzer configuration based on its ID and content.
7
+ * Language: JavaScript
8
+ * Created-at: 2025-07-12T04:12:33.454Z
9
+ * Authors: Gemini 2.5 Flash Thinking (v1.0.0), Gemini 2.5 Flash Thinking (v1.1.0)
10
+ */
11
+
12
+
13
+ const fs = require('fs').promises;
14
+ const path = require('path');
15
+
16
+ /**
17
+ * Saves or updates an analyzer configuration.
18
+ *
19
+ * This function takes the analyzer ID and its full instructions content,
20
+ * parses the ID to determine the directory structure, creates directories
21
+ * if necessary, saves the instructions to '1.md'. Optionally, it can
22
+ * ensure config.json files exist with labels derived from directory names.
23
+ *
24
+ * @param {string} analyzeMessagesBasePath - The absolute or relative path to the 'messages/analyze' directory.
25
+ * @param {string} analyzerId - The unique ID of the analyzer (format: 'analyzer_name::content_type::instructions_type').
26
+ * @param {string} instructionsContent - The full content of the analyzer instructions message to be saved in '1.md'.
27
+ * @param {object} [options={}] - Optional configuration options.
28
+ * @param {boolean} [options.ensureConfigs=false] - If true, ensures config.json files exist in the analyzer, content, and instructions directories. Defaults to false.
29
+ * @returns {Promise<{success: boolean, message?: string}>} A promise that resolves with a result object.
30
+ */
31
+ async function saveConfiguration(analyzeMessagesBasePath, analyzerId, instructionsContent, options = {}) {
32
+ const { ensureConfigs = false } = options;
33
+
34
+ // 1. Validate inputs
35
+ if (typeof analyzeMessagesBasePath !== 'string' || analyzeMessagesBasePath.trim() === '') {
36
+ return { success: false, message: 'analyzeMessagesBasePath is required.' };
37
+ }
38
+ if (typeof analyzerId !== 'string' || analyzerId.trim() === '') {
39
+ return { success: false, message: 'analyzerId is required.' };
40
+ }
41
+ if (typeof instructionsContent !== 'string' || instructionsContent.trim() === '') {
42
+ return { success: false, message: 'instructionsContent is required.' };
43
+ }
44
+
45
+ // 2. Parse 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
+ // Helper to validate directory names based on README.md rules
53
+ const isValidDirName = (name) => {
54
+ // Cannot start with underscore, cannot contain dots, must be alphanumeric, dash, or underscore
55
+ return /^[a-zA-Z0-9_-]+$/.test(name) && !name.startsWith('_') && !name.includes('.');
56
+ };
57
+
58
+ if (!isValidDirName(analyzerName)) {
59
+ return { success: false, message: `Invalid analyzer name '${analyzerName}'. Names must be alphanumeric, dash, or underscore, cannot start with underscore, and cannot contain dots.` };
60
+ }
61
+ if (!isValidDirName(contentType)) {
62
+ return { success: false, message: `Invalid content type name '${contentType}'. Names must be alphanumeric, dash, or underscore, cannot start with underscore, and cannot contain dots.` };
63
+ }
64
+ if (!isValidDirName(instructionsType)) {
65
+ return { success: false, message: `Invalid instructions type name '${instructionsType}'. Names must be alphanumeric, dash, or underscore, cannot start with underscore, and cannot contain dots.` };
66
+ }
67
+
68
+ // 3. Construct directory paths
69
+ const analyzerDir = path.join(analyzeMessagesBasePath, analyzerName);
70
+ const contentDir = path.join(analyzerDir, contentType);
71
+ const instructionsDir = path.join(contentDir, instructionsType);
72
+ const instructionsFilePath = path.join(instructionsDir, '1.md');
73
+
74
+ try {
75
+ // 4. Create directories recursively
76
+ await fs.mkdir(instructionsDir, { recursive: true });
77
+
78
+ // 5. Save instructions content to 1.md
79
+ const finalContent = `; role: assistant\n\n\n${instructionsContent}`;
80
+ await fs.writeFile(instructionsFilePath, finalContent, 'utf8');
81
+
82
+ // 6. Optionally create/Update config.json files
83
+ if (ensureConfigs) {
84
+ await ensureConfigJson(analyzerDir, analyzerName);
85
+ await ensureConfigJson(contentDir, contentType);
86
+ await ensureConfigJson(instructionsDir, instructionsType);
87
+ }
88
+
89
+ return { success: true, message: `Analyzer configuration '${analyzerId}' saved successfully.` };
90
+
91
+ } catch (error) {
92
+ console.error(`Error saving analyzer configuration '${analyzerId}':`, error);
93
+ return { success: false, message: `Failed to save analyzer configuration: ${error.message}` };
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Ensures a config.json file exists in the given directory with a label.
99
+ * If the file exists, it reads it and adds the label if missing.
100
+ * If the file doesn't exist, it creates it with the label.
101
+ *
102
+ * @param {string} dirPath - The path to the directory.
103
+ * @param {string} label - The label to ensure is in the config.json.
104
+ */
105
+ async function ensureConfigJson(dirPath, label) {
106
+ const configPath = path.join(dirPath, 'config.json');
107
+ let config = {};
108
+
109
+ try {
110
+ const fileContent = await fs.readFile(configPath, 'utf8');
111
+ config = JSON.parse(fileContent);
112
+ } catch (error) {
113
+ // If file doesn't exist or parsing fails, start with an empty config
114
+ if (error.code !== 'ENOENT') {
115
+ console.warn(`Failed to read or parse existing config.json in ${dirPath}: ${error.message}`);
116
+ }
117
+ config = {}; // Ensure config is an object even on error
118
+ }
119
+
120
+ // Add or update the label if it's missing or empty
121
+ if (!config.label || typeof config.label !== 'string' || config.label.trim() === '') {
122
+ // Capitalize the first letter for the label
123
+ config.label = label.charAt(0).toUpperCase() + label.slice(1);
124
+ }
125
+
126
+ // Write the updated config back to the file
127
+ await fs.writeFile(configPath, JSON.stringify(config, null, 4), 'utf8');
128
+ }
129
+
130
+
131
+ module.exports = {
132
+ saveConfiguration,
133
+ };
@@ -15,7 +15,7 @@ const CodeBlockUtils = require('./CodeBlockUtils');
15
15
  const ContextUtils = require('./ContextUtils');
16
16
  const MessageUtils = require('./MessageUtils');
17
17
  const AnalysisBlockUtils = require('./AnalysisBlockUtils');
18
- const AnalyzerUtils = require('./AnalyzerUtils'); // Updated import
18
+ const AnalyzerUtils = require('./AnalyzerUtils');
19
19
  const PatchUtils = require('./PatchUtils');
20
20
  const GSToolBlockUtils = require('./GSToolBlockUtils');
21
21
  const LLMUtils = require('./LLMUtils');
@@ -55,7 +55,7 @@ const {
55
55
  validateOverviewMetadata,
56
56
  } = AnalysisBlockUtils;
57
57
 
58
- const { // Updated AnalyzerUtils destructuring
58
+ const {
59
59
  buildChatIdToPathMap,
60
60
  processLLMAnalysisResponse,
61
61
  validateLLMAnalysisData,
@@ -63,6 +63,7 @@ const { // Updated AnalyzerUtils destructuring
63
63
  getAnalyzerSchema,
64
64
  deleteAnalyzer,
65
65
  getAnalyzerInstructionsContent,
66
+ saveAnalyzerConfiguration,
66
67
  } = AnalyzerUtils;
67
68
 
68
69
  const {