341 lines
9.0 KiB
TypeScript
341 lines
9.0 KiB
TypeScript
import * as plugins from '../plugins.js';
|
|
import * as fs from 'fs';
|
|
import type {
|
|
IContextConfig,
|
|
ITrimConfig,
|
|
ITaskConfig,
|
|
TaskType,
|
|
ContextMode,
|
|
ICacheConfig,
|
|
IAnalyzerConfig,
|
|
IPrioritizationWeights,
|
|
ITierConfig
|
|
} from './types.js';
|
|
|
|
/**
|
|
* Manages configuration for context building
|
|
*/
|
|
export class ConfigManager {
|
|
private static instance: ConfigManager;
|
|
private config: IContextConfig;
|
|
private projectDir: string = '';
|
|
private configCache: { mtime: number; config: IContextConfig } | null = null;
|
|
|
|
/**
|
|
* 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<void> {
|
|
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
|
|
},
|
|
cache: {
|
|
enabled: true,
|
|
ttl: 3600, // 1 hour
|
|
maxSize: 100, // 100MB
|
|
directory: undefined // Will be set to .nogit/context-cache by ContextCache
|
|
},
|
|
analyzer: {
|
|
enabled: true,
|
|
useAIRefinement: false, // Disabled by default for now
|
|
aiModel: 'haiku'
|
|
},
|
|
prioritization: {
|
|
dependencyWeight: 0.3,
|
|
relevanceWeight: 0.4,
|
|
efficiencyWeight: 0.2,
|
|
recencyWeight: 0.1
|
|
},
|
|
tiers: {
|
|
essential: { minScore: 0.8, trimLevel: 'none' },
|
|
important: { minScore: 0.5, trimLevel: 'light' },
|
|
optional: { minScore: 0.2, trimLevel: 'aggressive' }
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Load configuration from npmextra.json
|
|
*/
|
|
private async loadConfig(): Promise<void> {
|
|
try {
|
|
if (!this.projectDir) {
|
|
return;
|
|
}
|
|
|
|
const npmextraJsonPath = plugins.path.join(this.projectDir, 'npmextra.json');
|
|
|
|
// Check if file exists
|
|
const fileExists = await plugins.smartfile.fs.fileExists(npmextraJsonPath);
|
|
if (!fileExists) {
|
|
return;
|
|
}
|
|
|
|
// Check cache
|
|
const stats = await fs.promises.stat(npmextraJsonPath);
|
|
const currentMtime = Math.floor(stats.mtimeMs);
|
|
|
|
if (this.configCache && this.configCache.mtime === currentMtime) {
|
|
// Use cached config
|
|
this.config = this.configCache.config;
|
|
return;
|
|
}
|
|
|
|
// Read the npmextra.json file
|
|
const npmextraJsonFile = await plugins.smartfile.SmartFile.fromFilePath(npmextraJsonPath);
|
|
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);
|
|
}
|
|
|
|
// Cache the config
|
|
this.configCache = {
|
|
mtime: currentMtime,
|
|
config: { ...this.config }
|
|
};
|
|
} 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>): 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
|
|
};
|
|
}
|
|
|
|
// Merge cache configuration
|
|
if (userConfig.cache) {
|
|
result.cache = {
|
|
...result.cache,
|
|
...userConfig.cache
|
|
};
|
|
}
|
|
|
|
// Merge analyzer configuration
|
|
if (userConfig.analyzer) {
|
|
result.analyzer = {
|
|
...result.analyzer,
|
|
...userConfig.analyzer
|
|
};
|
|
}
|
|
|
|
// Merge prioritization weights
|
|
if (userConfig.prioritization) {
|
|
result.prioritization = {
|
|
...result.prioritization,
|
|
...userConfig.prioritization
|
|
};
|
|
}
|
|
|
|
// Merge tier configuration
|
|
if (userConfig.tiers) {
|
|
result.tiers = {
|
|
...result.tiers,
|
|
...userConfig.tiers
|
|
};
|
|
}
|
|
|
|
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<IContextConfig>): Promise<void> {
|
|
// Merge with existing config
|
|
this.config = this.mergeConfigs(this.config, config);
|
|
|
|
// Invalidate cache
|
|
this.configCache = null;
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get cache configuration
|
|
*/
|
|
public getCacheConfig(): ICacheConfig {
|
|
return this.config.cache || { enabled: true, ttl: 3600, maxSize: 100 };
|
|
}
|
|
|
|
/**
|
|
* Get analyzer configuration
|
|
*/
|
|
public getAnalyzerConfig(): IAnalyzerConfig {
|
|
return this.config.analyzer || { enabled: true, useAIRefinement: false, aiModel: 'haiku' };
|
|
}
|
|
|
|
/**
|
|
* Get prioritization weights
|
|
*/
|
|
public getPrioritizationWeights(): IPrioritizationWeights {
|
|
return this.config.prioritization || {
|
|
dependencyWeight: 0.3,
|
|
relevanceWeight: 0.4,
|
|
efficiencyWeight: 0.2,
|
|
recencyWeight: 0.1
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get tier configuration
|
|
*/
|
|
public getTierConfig(): ITierConfig {
|
|
return this.config.tiers || {
|
|
essential: { minScore: 0.8, trimLevel: 'none' },
|
|
important: { minScore: 0.5, trimLevel: 'light' },
|
|
optional: { minScore: 0.2, trimLevel: 'aggressive' }
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Clear the config cache (force reload on next access)
|
|
*/
|
|
public clearCache(): void {
|
|
this.configCache = null;
|
|
}
|
|
} |