import * as plugins from '../plugins.js'; import type { IContextConfig, ITrimConfig, ITaskConfig, TaskType, ContextMode } from './types.js'; /** * Manages configuration for context building */ export class ConfigManager { private static instance: ConfigManager; private config: IContextConfig; private projectDir: string = ''; /** * Get the singleton instance of ConfigManager */ public static getInstance(): ConfigManager { if (!ConfigManager.instance) { ConfigManager.instance = new ConfigManager(); } return ConfigManager.instance; } /** * Private constructor for singleton pattern */ private constructor() { this.config = this.getDefaultConfig(); } /** * Initialize the config manager with a project directory * @param projectDir The project directory */ public async initialize(projectDir: string): Promise { this.projectDir = projectDir; await this.loadConfig(); } /** * Get the default configuration */ private getDefaultConfig(): IContextConfig { return { maxTokens: 190000, // Default for o4-mini with some buffer defaultMode: 'trimmed', taskSpecificSettings: { readme: { mode: 'trimmed', includePaths: ['ts/', 'src/'], excludePaths: ['test/', 'node_modules/'] }, commit: { mode: 'trimmed', focusOnChangedFiles: true }, description: { mode: 'trimmed', includePackageInfo: true } }, trimming: { removeImplementations: true, preserveInterfaces: true, preserveTypeDefs: true, preserveJSDoc: true, maxFunctionLines: 5, removeComments: true, removeBlankLines: true } }; } /** * Load configuration from npmextra.json */ private async loadConfig(): Promise { try { if (!this.projectDir) { return; } // Create KeyValueStore for this project // We'll just use smartfile directly instead of KeyValueStore // Read the npmextra.json file const npmextraJsonFile = await plugins.smartfile.SmartFile.fromFilePath( plugins.path.join(this.projectDir, 'npmextra.json') ); const npmextraContent = JSON.parse(npmextraJsonFile.contents.toString()); // Check for tsdoc context configuration if (npmextraContent?.tsdoc?.context) { // Merge with default config this.config = this.mergeConfigs(this.config, npmextraContent.tsdoc.context); } } catch (error) { console.error('Error loading context configuration:', error); } } /** * Merge configurations, with userConfig taking precedence * @param defaultConfig The default configuration * @param userConfig The user configuration */ private mergeConfigs(defaultConfig: IContextConfig, userConfig: Partial): IContextConfig { const result: IContextConfig = { ...defaultConfig }; // Merge top-level properties if (userConfig.maxTokens !== undefined) result.maxTokens = userConfig.maxTokens; if (userConfig.defaultMode !== undefined) result.defaultMode = userConfig.defaultMode; // Merge task-specific settings if (userConfig.taskSpecificSettings) { result.taskSpecificSettings = result.taskSpecificSettings || {}; // For each task type, merge settings (['readme', 'commit', 'description'] as TaskType[]).forEach(taskType => { if (userConfig.taskSpecificSettings?.[taskType]) { result.taskSpecificSettings![taskType] = { ...result.taskSpecificSettings![taskType], ...userConfig.taskSpecificSettings[taskType] }; } }); } // Merge trimming configuration if (userConfig.trimming) { result.trimming = { ...result.trimming, ...userConfig.trimming }; } return result; } /** * Get the complete configuration */ public getConfig(): IContextConfig { return this.config; } /** * Get the trimming configuration */ public getTrimConfig(): ITrimConfig { return this.config.trimming || {}; } /** * Get configuration for a specific task * @param taskType The type of task */ public getTaskConfig(taskType: TaskType): ITaskConfig { // Get task-specific config or empty object const taskConfig = this.config.taskSpecificSettings?.[taskType] || {}; // If mode is not specified, use default mode if (!taskConfig.mode) { taskConfig.mode = this.config.defaultMode; } return taskConfig; } /** * Get the maximum tokens allowed for context */ public getMaxTokens(): number { return this.config.maxTokens || 190000; } /** * Update the configuration * @param config The new configuration */ public async updateConfig(config: Partial): Promise { // Merge with existing config this.config = this.mergeConfigs(this.config, config); try { if (!this.projectDir) { return; } // Read the existing npmextra.json file const npmextraJsonPath = plugins.path.join(this.projectDir, 'npmextra.json'); let npmextraContent = {}; if (await plugins.smartfile.fs.fileExists(npmextraJsonPath)) { const npmextraJsonFile = await plugins.smartfile.SmartFile.fromFilePath(npmextraJsonPath); npmextraContent = JSON.parse(npmextraJsonFile.contents.toString()) || {}; } // Update the tsdoc context configuration const typedContent = npmextraContent as any; if (!typedContent.tsdoc) typedContent.tsdoc = {}; typedContent.tsdoc.context = this.config; // Write back to npmextra.json const updatedContent = JSON.stringify(npmextraContent, null, 2); await plugins.smartfile.memory.toFs(updatedContent, npmextraJsonPath); } catch (error) { console.error('Error updating context configuration:', error); } } }