cntx-ui
Version:
Autonomous Repository Intelligence engine with web UI and MCP server. Unified semantic code understanding, local RAG, and agent working memory.
492 lines (491 loc) • 20.4 kB
JavaScript
/**
* Heuristics Manager - Centralized service for loading and applying heuristics
* Manages configuration-based code categorization logic
*/
import { readFileSync, writeFileSync, existsSync, watchFile } from 'fs';
export default class HeuristicsManager {
configPath;
config;
cache;
isWatching;
lastLoaded;
constructor(configPath = './heuristics-config.json') {
this.configPath = configPath;
this.config = null;
this.cache = new Map();
this.isWatching = false;
this.lastLoaded = null;
}
/**
* Load heuristics configuration from file
*/
loadConfig() {
try {
if (!existsSync(this.configPath)) {
console.warn(`Heuristics config not found at ${this.configPath}, using fallback`);
this.config = this.getFallbackConfig();
return;
}
const configContent = readFileSync(this.configPath, 'utf8');
const newConfig = JSON.parse(configContent);
// Validate config structure
this.validateConfig(newConfig);
this.config = newConfig;
this.lastLoaded = Date.now();
this.cache.clear(); // Clear cache when config changes
// Set up file watching if not already watching
if (!this.isWatching) {
this.setupFileWatcher();
}
console.log('✅ Heuristics configuration loaded successfully');
}
catch (error) {
console.error('❌ Failed to load heuristics config:', error.message);
console.log('📦 Falling back to hardcoded heuristics');
this.config = this.getFallbackConfig();
}
}
/**
* Validate heuristics configuration structure
*/
validateConfig(config) {
const required = ['purposeHeuristics', 'bundleHeuristics', 'semanticTypeMapping'];
for (const field of required) {
if (!config[field]) {
throw new Error(`Missing required field: ${field}`);
}
}
// Validate purpose heuristics structure
if (!config.purposeHeuristics.patterns || !config.purposeHeuristics.fallback) {
throw new Error('Invalid purposeHeuristics structure');
}
// Validate bundle heuristics structure
if (!config.bundleHeuristics.patterns || !config.bundleHeuristics.fallback) {
throw new Error('Invalid bundleHeuristics structure');
}
}
/**
* Set up file watcher for config changes
*/
setupFileWatcher() {
if (!existsSync(this.configPath))
return;
watchFile(this.configPath, (curr, prev) => {
if (curr.mtime !== prev.mtime) {
console.log('📝 Heuristics config file changed, reloading...');
this.loadConfig();
}
});
this.isWatching = true;
}
/**
* Get configuration, loading if necessary
*/
getConfig() {
if (!this.config) {
this.loadConfig();
}
return this.config;
}
/**
* Determine function purpose using configured heuristics
*/
determinePurpose(func) {
const config = this.getConfig();
const name = (func.name || '').toLowerCase();
const pathParts = func.pathParts || [];
// Check each purpose pattern
for (const [patternName, pattern] of Object.entries(config.purposeHeuristics.patterns)) {
if (this.evaluateConditions(pattern.conditions, { func, name, pathParts })) {
return pattern.purpose;
}
}
// Return fallback
return config.purposeHeuristics.fallback.purpose;
}
/**
* Infer business domains (e.g. auth, editing, file-mgmt)
*/
inferBusinessDomains(func) {
const domains = new Set();
const name = (func.name || '').toLowerCase();
const path = (func.pathParts || []).join('/').toLowerCase();
const imports = (func.includes?.imports || []).filter((i) => typeof i === 'string');
// Path-based domains
if (path.includes('auth'))
domains.add('authentication');
if (path.includes('component') || path.includes('ui'))
domains.add('ui-layer');
if (path.includes('service') || path.includes('api'))
domains.add('api-integration');
if (path.includes('test') || path.includes('spec'))
domains.add('testing');
// Import-based domains
if (imports.some(i => i.includes('tauri')))
domains.add('desktop-runtime');
if (imports.some(i => i.includes('tiptap') || i.includes('prosemirror')))
domains.add('text-editing');
if (imports.some(i => i.includes('react')))
domains.add('frontend-ui');
// Name-based domains
if (/file|save|export|read|write/i.test(name))
domains.add('file-management');
if (/login|user|session/i.test(name))
domains.add('authentication');
return Array.from(domains);
}
/**
* Infer technical patterns (e.g. hooks, async-io, event-handlers)
*/
inferTechnicalPatterns(func) {
const patterns = new Set();
const name = (func.name || '').toLowerCase();
const code = func.code || '';
if (name.startsWith('use'))
patterns.add('react-hooks');
if (code.includes('async') || code.includes('await'))
patterns.add('async-io');
if (code.includes('on(') || code.includes('addListener') || name.startsWith('handle'))
patterns.add('event-driven');
if (code.includes('new ') || code.includes('class '))
patterns.add('object-oriented');
if (func.isExported)
patterns.add('public-api');
return Array.from(patterns);
}
/**
* Suggest bundles for file using configured heuristics
*/
suggestBundlesForFile(filePath) {
const config = this.getConfig();
const fileName = filePath.toLowerCase();
const pathParts = fileName.split('/');
const suggestions = [];
// Check each bundle pattern
for (const [patternName, pattern] of Object.entries(config.bundleHeuristics.patterns)) {
if (this.evaluateConditions(pattern.conditions, { fileName, filePath, pathParts })) {
suggestions.push(pattern.bundle);
// Check sub-patterns
if (pattern.subPatterns) {
for (const [subName, subPattern] of Object.entries(pattern.subPatterns)) {
if (this.evaluateConditions(subPattern.conditions, { fileName, filePath, pathParts })) {
suggestions.push(subPattern.bundle);
}
}
}
}
}
// Apply fallback logic if no suggestions
if (suggestions.length === 0) {
const fallback = config.bundleHeuristics.fallback;
if (fallback.webFallback && this.evaluateConditions(fallback.webFallback.conditions, { fileName, filePath, pathParts })) {
suggestions.push(fallback.webFallback.bundle);
}
else if (fallback.defaultFallback) {
suggestions.push(...fallback.defaultFallback.bundles);
}
}
return [...new Set(suggestions)]; // Remove duplicates
}
/**
* Get semantic type cluster mapping
*/
getSemanticTypeMapping() {
const config = this.getConfig();
const mapping = {};
for (const [clusterName, cluster] of Object.entries(config.semanticTypeMapping.clusters)) {
for (const type of cluster.types) {
mapping[type] = cluster.clusterId;
}
}
return mapping;
}
/**
* Evaluate condition strings against context
*/
evaluateConditions(conditions, context) {
const conds = Array.isArray(conditions) ? conditions : [conditions];
// For purpose heuristics, we generally want high precision, so use AND logic
// if there are multiple conditions
const needsAndLogic = conds.length > 1 || this.requiresAndLogic(conds);
if (needsAndLogic) {
return conds.every(condition => {
try {
return this.evaluateCondition(condition, context);
}
catch (error) {
console.warn(`Failed to evaluate condition: ${condition}`, error);
return false;
}
});
}
else {
return conds.some(condition => {
try {
return this.evaluateCondition(condition, context);
}
catch (error) {
console.warn(`Failed to evaluate condition: ${condition}`, error);
return false;
}
});
}
}
/**
* Determine if conditions require AND logic vs OR logic
*/
requiresAndLogic(conditions) {
// React hook pattern specifically needs AND logic
if (conditions.length === 2 &&
conditions.some(c => c.includes('name.startsWith')) &&
conditions.some(c => c.includes('func.type'))) {
return true;
}
// Frontend pattern needs AND logic for web + src
if (conditions.length === 2 &&
conditions.some(c => c.includes("pathParts.includes('web')")) &&
conditions.some(c => c.includes("pathParts.includes('src')"))) {
return true;
}
// Default to OR logic for other patterns
return false;
}
/**
* Evaluate a single condition
*/
evaluateCondition(condition, context) {
const { func, name, fileName, filePath, pathParts } = context;
// Handle function type conditions
if (condition.includes('func.type ===')) {
const typeMatch = condition.match(/func\.type === ['"]([^'"]+)['"]/);
if (typeMatch && func) {
return func.type === typeMatch[1];
}
}
// Handle name-based conditions
if (condition.includes('name.startsWith(')) {
const prefixMatch = condition.match(/name\.startsWith\(['"]([^'"]+)['"]\)/);
if (prefixMatch && name) {
return name.startsWith(prefixMatch[1]);
}
}
if (condition.includes('name.includes(')) {
const includesMatch = condition.match(/name\.includes\(['"]([^'"]+)['"]\)/);
if (includesMatch && name) {
return name.includes(includesMatch[1]);
}
}
// Handle fileName conditions
if (condition.includes('fileName.includes(')) {
const includesMatch = condition.match(/fileName\.includes\(['"]([^'"]+)['"]\)/);
if (includesMatch && fileName) {
return fileName.includes(includesMatch[1]);
}
}
if (condition.includes('fileName.endsWith(')) {
const endsWithMatch = condition.match(/fileName\.endsWith\(['"]([^'"]+)['"]\)/);
if (endsWithMatch && fileName) {
return fileName.endsWith(endsWithMatch[1]);
}
}
// Handle pathParts conditions
if (condition.includes('pathParts.includes(')) {
const includesMatch = condition.match(/pathParts\.includes\(['"]([^'"]+)['"]\)/);
if (includesMatch && pathParts) {
return pathParts.includes(includesMatch[1]);
}
}
// New: Check imports in the chunk context
if (condition.includes('chunk.imports.includes(')) {
const importMatch = condition.match(/chunk\.imports\.includes\(['"]([^'"]+)['"]\)/);
if (importMatch && func?.includes?.imports) {
return func.includes.imports
.filter((i) => typeof i === 'string')
.some((imp) => imp.includes(importMatch[1]));
}
}
// New: Check for specific naming patterns (case-insensitive)
if (condition.includes('name.matches(')) {
const regexMatch = condition.match(/name\.matches\(['"]([^'"]+)['"]\)/);
if (regexMatch && name) {
return new RegExp(regexMatch[1], 'i').test(name);
}
}
return false;
}
/**
* Fallback configuration for when config file is unavailable
*/
getFallbackConfig() {
return {
version: "1.0.0",
purposeHeuristics: {
patterns: {
reactComponent: {
conditions: ["func.type === 'react_component'"],
purpose: "React component",
confidence: 0.95
},
reactHook: {
conditions: ["name.startsWith('use')", "func.type === 'function'"],
purpose: "React hook",
confidence: 0.9
},
serviceLayer: {
conditions: ["pathParts.includes('services')"],
purpose: "Service layer logic",
confidence: 0.7
},
componentLayer: {
conditions: ["pathParts.includes('components')"],
purpose: "UI component logic",
confidence: 0.7
},
tauriCommand: {
conditions: ["name.matches('command')", "chunk.imports.includes('tauri')"],
purpose: "Tauri backend command",
confidence: 0.9
},
textEditing: {
conditions: ["chunk.imports.includes('tiptap')", "chunk.imports.includes('prosemirror')"],
purpose: "Rich text editing logic",
confidence: 0.95
},
fileManagement: {
conditions: ["pathParts.includes('services')", "name.matches('file|export|save')"],
purpose: "File system service",
confidence: 0.85
},
uiComponent: {
conditions: ["pathParts.includes('components')", "name.matches('modal|button|menu|bar')"],
purpose: "UI component logic",
confidence: 0.85
},
apiHandler: {
conditions: ["name.includes('api')", "name.includes('endpoint')"],
purpose: "API handler",
confidence: 0.85
},
dataRetrieval: {
conditions: ["name.includes('get')", "name.includes('fetch')"],
purpose: "Data retrieval",
confidence: 0.8
},
dataCreation: {
conditions: ["name.includes('create')", "name.includes('add')"],
purpose: "Data creation",
confidence: 0.8
},
dataModification: {
conditions: ["name.includes('update')", "name.includes('edit')"],
purpose: "Data modification",
confidence: 0.8
},
dataDeletion: {
conditions: ["name.includes('delete')", "name.includes('remove')"],
purpose: "Data deletion",
confidence: 0.8
},
validation: {
conditions: ["name.includes('validate')", "name.includes('check')"],
purpose: "Validation",
confidence: 0.75
},
dataProcessing: {
conditions: ["name.includes('parse')", "name.includes('format')"],
purpose: "Data processing",
confidence: 0.75
}
},
fallback: {
purpose: "Utility function",
confidence: 0.5
}
},
bundleHeuristics: {
patterns: {
frontend: {
conditions: ["pathParts.includes('web')", "pathParts.includes('src')"],
bundle: "frontend",
confidence: 0.8,
subPatterns: {
uiComponents: {
conditions: ["pathParts.includes('components')"],
bundle: "ui-components",
confidence: 0.9
}
}
},
server: {
conditions: ["fileName.includes('server')", "fileName.includes('api')", "pathParts.includes('bin')"],
bundle: "server",
confidence: 0.85
},
configuration: {
conditions: ["fileName.includes('config')", "fileName.includes('setup')", "fileName.endsWith('.json')", "fileName.endsWith('.sh')", "fileName.includes('package')"],
bundle: "config",
confidence: 0.9
},
documentation: {
conditions: ["fileName.endsWith('.md')", "fileName.includes('doc')", "fileName.includes('readme')"],
bundle: "docs",
confidence: 0.95
}
},
fallback: {
webFallback: {
conditions: ["pathParts.includes('web')"],
bundle: "frontend",
confidence: 0.6
},
defaultFallback: {
bundles: ["server", "config"],
confidence: 0.4
}
}
},
semanticTypeMapping: {
clusters: {
businessLogic: { types: ["business_logic", "algorithm"], clusterId: 0 },
dataLayer: { types: ["data_processing", "database"], clusterId: 1 },
apiLayer: { types: ["api_integration", "middleware", "routing"], clusterId: 2 },
uiLayer: { types: ["ui_component", "page_component", "layout_component", "hook"], clusterId: 3 },
utilities: { types: ["utility", "configuration", "function", "type_definition"], clusterId: 4 },
testing: { types: ["testing", "documentation", "monitoring"], clusterId: 5 },
infrastructure: { types: ["error_handling", "performance", "security"], clusterId: 6 },
unknown: { types: ["unknown"], clusterId: 7 }
}
}
};
}
/**
* Update configuration (for API endpoints) and persist to disk
*/
async updateConfig(newConfig) {
try {
this.validateConfig(newConfig);
// Write to disk
writeFileSync(this.configPath, JSON.stringify(newConfig, null, 2), 'utf8');
this.config = newConfig;
this.cache.clear();
this.lastLoaded = Date.now();
console.log('📝 Heuristics configuration updated and saved to disk');
return true;
}
catch (error) {
console.error('❌ Failed to update heuristics config:', error.message);
throw error;
}
}
/**
* Get performance metrics
*/
getPerformanceMetrics() {
// TODO: Implement performance tracking
return {
totalEvaluations: 0,
accuracyScore: 0.0,
lastUpdated: this.lastLoaded
};
}
}