UNPKG

gitvan

Version:

Autonomic Git-native development automation platform with AI-powered workflows

389 lines (334 loc) 10.6 kB
/** * GitVan Template Learning System * Tracks template execution patterns and learns from success/failure */ import { promises as fs } from "node:fs"; import { join, dirname } from "pathe"; import { useGitVan } from "../core/context.mjs"; import { sha256Hex } from "../utils/crypto.mjs"; import { createLogger } from "../utils/logger.mjs"; const logger = createLogger("template-learning"); /** * Template Execution Metrics */ export class TemplateMetrics { constructor() { this.executions = new Map(); this.patterns = new Map(); this.userPreferences = new Map(); } /** * Record template execution */ async recordExecution(templatePath, executionResult, context = {}) { const executionId = sha256Hex(`${templatePath}-${Date.now()}`); const execution = { id: executionId, templatePath, timestamp: new Date().toISOString(), success: executionResult.ok, duration: executionResult.duration || 0, artifacts: executionResult.artifacts || [], errors: executionResult.errors || [], context: { projectType: context.projectType || "unknown", framework: context.framework || "unknown", userAgent: context.userAgent || "unknown", ...context, }, }; // Store execution this.executions.set(executionId, execution); // Update patterns await this.updatePatterns(execution); // Update user preferences await this.updateUserPreferences(execution); logger.info( `Recorded execution ${executionId} for template ${templatePath}` ); return executionId; } /** * Update success/failure patterns */ async updatePatterns(execution) { const templatePath = execution.templatePath; const patternKey = this.getPatternKey(execution); if (!this.patterns.has(patternKey)) { this.patterns.set(patternKey, { templatePath, pattern: patternKey, successCount: 0, failureCount: 0, totalExecutions: 0, avgDuration: 0, commonErrors: new Map(), successContexts: [], failureContexts: [], }); } const pattern = this.patterns.get(patternKey); pattern.totalExecutions++; if (execution.success) { pattern.successCount++; pattern.successContexts.push(execution.context); } else { pattern.failureCount++; pattern.failureContexts.push(execution.context); // Track common errors execution.errors.forEach((error) => { const errorKey = error.type || error.message; pattern.commonErrors.set( errorKey, (pattern.commonErrors.get(errorKey) || 0) + 1 ); }); } // Update average duration pattern.avgDuration = (pattern.avgDuration * (pattern.totalExecutions - 1) + execution.duration) / pattern.totalExecutions; this.patterns.set(patternKey, pattern); } /** * Update user preferences based on successful executions */ async updateUserPreferences(execution) { if (!execution.success) return; const preferences = this.userPreferences.get(execution.templatePath) || { templatePath: execution.templatePath, preferredPatterns: new Map(), avoidedPatterns: new Map(), contextPreferences: new Map(), lastUpdated: new Date().toISOString(), }; // Learn from successful context const contextKey = `${execution.context.projectType}-${execution.context.framework}`; preferences.contextPreferences.set( contextKey, (preferences.contextPreferences.get(contextKey) || 0) + 1 ); // Learn from successful patterns const patternKey = this.getPatternKey(execution); preferences.preferredPatterns.set( patternKey, (preferences.preferredPatterns.get(patternKey) || 0) + 1 ); preferences.lastUpdated = new Date().toISOString(); this.userPreferences.set(execution.templatePath, preferences); } /** * Get pattern key for execution */ getPatternKey(execution) { return `${execution.templatePath}-${execution.context.projectType}-${execution.context.framework}`; } /** * Get success rate for template */ getSuccessRate(templatePath) { const executions = Array.from(this.executions.values()).filter( (e) => e.templatePath === templatePath ); if (executions.length === 0) return 0; const successful = executions.filter((e) => e.success).length; return successful / executions.length; } /** * Get successful patterns for template */ getSuccessfulPatterns(templatePath) { return Array.from(this.patterns.values()) .filter((p) => p.templatePath === templatePath && p.successCount > 0) .sort((a, b) => b.successCount - a.successCount); } /** * Get failed patterns for template */ getFailedPatterns(templatePath) { return Array.from(this.patterns.values()) .filter((p) => p.templatePath === templatePath && p.failureCount > 0) .sort((a, b) => b.failureCount - a.failureCount); } /** * Get user preferences for template */ getUserPreferences(templatePath) { return this.userPreferences.get(templatePath); } /** * Persist metrics to file */ async persist() { try { const ctx = useGitVan(); const metricsPath = join( ctx.root, ".gitvan", "metrics", "template-learning.json" ); await fs.mkdir(dirname(metricsPath), { recursive: true }); const data = { executions: Object.fromEntries(this.executions), patterns: Object.fromEntries(this.patterns), userPreferences: Object.fromEntries(this.userPreferences), lastUpdated: new Date().toISOString(), }; await fs.writeFile(metricsPath, JSON.stringify(data, null, 2)); logger.info(`Persisted template learning metrics to ${metricsPath}`); } catch (error) { logger.error("Failed to persist template learning metrics:", error); } } /** * Load metrics from file */ async load() { try { const ctx = useGitVan(); const metricsPath = join( ctx.root, ".gitvan", "metrics", "template-learning.json" ); const data = JSON.parse(await fs.readFile(metricsPath, "utf8")); this.executions = new Map(Object.entries(data.executions || {})); this.patterns = new Map(Object.entries(data.patterns || {})); this.userPreferences = new Map( Object.entries(data.userPreferences || {}) ); logger.info(`Loaded template learning metrics from ${metricsPath}`); } catch (error) { logger.warn( "No existing template learning metrics found, starting fresh" ); } } } /** * Template Learning Manager */ export class TemplateLearningManager { constructor() { this.metrics = new TemplateMetrics(); this.isInitialized = false; } /** * Initialize the learning system */ async initialize() { if (this.isInitialized) return; await this.metrics.load(); this.isInitialized = true; logger.info("Template learning system initialized"); } /** * Record template execution */ async recordExecution(templatePath, executionResult, context = {}) { await this.initialize(); return await this.metrics.recordExecution( templatePath, executionResult, context ); } /** * Get learning insights for template */ async getInsights(templatePath) { await this.initialize(); const successRate = this.metrics.getSuccessRate(templatePath); const successfulPatterns = this.metrics.getSuccessfulPatterns(templatePath); const failedPatterns = this.metrics.getFailedPatterns(templatePath); const userPreferences = this.metrics.getUserPreferences(templatePath); return { templatePath, successRate, totalExecutions: Array.from(this.metrics.executions.values()).filter( (e) => e.templatePath === templatePath ).length, successfulPatterns: successfulPatterns.map((p) => ({ pattern: p.pattern, successCount: p.successCount, avgDuration: p.avgDuration, successRate: p.successCount / p.totalExecutions, })), failedPatterns: failedPatterns.map((p) => ({ pattern: p.pattern, failureCount: p.failureCount, commonErrors: Object.fromEntries(p.commonErrors), })), userPreferences: userPreferences ? { preferredPatterns: Object.fromEntries( userPreferences.preferredPatterns ), contextPreferences: Object.fromEntries( userPreferences.contextPreferences ), lastUpdated: userPreferences.lastUpdated, } : null, }; } /** * Get recommendations for template improvement */ async getRecommendations(templatePath) { const insights = await this.getInsights(templatePath); const recommendations = []; // Success rate recommendations if (insights.successRate < 0.7) { recommendations.push({ type: "success_rate", priority: "high", message: `Template has low success rate (${( insights.successRate * 100 ).toFixed(1)}%). Consider reviewing failed patterns.`, failedPatterns: insights.failedPatterns, }); } // Performance recommendations const slowPatterns = insights.successfulPatterns.filter( (p) => p.avgDuration > 5000 ); if (slowPatterns.length > 0) { recommendations.push({ type: "performance", priority: "medium", message: `Some patterns are slow (${slowPatterns.length} patterns > 5s). Consider optimization.`, slowPatterns, }); } // User preference recommendations if (insights.userPreferences) { const topPreferences = Object.entries( insights.userPreferences.preferredPatterns ) .sort(([, a], [, b]) => b - a) .slice(0, 3); if (topPreferences.length > 0) { recommendations.push({ type: "user_preferences", priority: "low", message: "Consider emphasizing these successful patterns in template generation.", topPreferences, }); } } return recommendations; } /** * Persist learning data */ async persist() { await this.metrics.persist(); } } // Export singleton instance export const templateLearning = new TemplateLearningManager();