UNPKG

@robinson_ai_systems/free-agent-mcp

Version:

Free Agent MCP - Portable, workspace-agnostic code generation using FREE models (Ollama)

1,436 lines (1,388 loc) 683 kB
#!/usr/bin/env node import { fileURLToPath } from 'url'; import { dirname } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); var __defProp = Object.defineProperty; var __getOwnPropNames = Object.getOwnPropertyNames; var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { if (typeof require !== "undefined") return require.apply(this, arguments); throw Error('Dynamic require of "' + x + '" is not supported'); }); var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __commonJS = (cb, mod) => function __require2() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // src/shared/shared-llm/ollama-client.ts async function pingOllama(timeoutMs = 1e3) { const urls = [ `${BASE}/api/tags`, `${BASE.replace("localhost", "127.0.0.1")}/api/tags` ]; for (const url of urls) { try { const r = await fetch(url, { method: "GET", signal: AbortSignal.timeout(timeoutMs), headers: { "Accept": "application/json" } }); if (r.ok) { return true; } } catch (error) { console.error(`[pingOllama] Failed to ping ${url}: ${error?.message || error}`); } } return false; } async function ollamaGenerate(opts) { const { model, prompt, format, timeoutMs = 12e4, retries = 2 } = opts; console.error(`[sharedGenerate] Starting generation with model: ${model}, timeout: ${timeoutMs}ms`); let lastErr; for (let i = 0; i <= retries; i++) { try { console.error(`[sharedGenerate] Attempt ${i + 1}/${retries + 1}`); const body = { model, prompt, stream: false }; if (format === "json") { body.format = "json"; } console.error(`[sharedGenerate] Sending fetch to ${BASE}/api/generate`); const r = await fetch(`${BASE}/api/generate`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), signal: AbortSignal.timeout(timeoutMs) }); console.error(`[sharedGenerate] Fetch completed with status: ${r.status}`); if (!r.ok) throw new Error(`HTTP ${r.status}`); console.error("[sharedGenerate] Parsing JSON response..."); const json = await r.json(); console.error("[sharedGenerate] JSON parsed successfully"); return json.response || ""; } catch (e) { console.error(`[sharedGenerate] Error on attempt ${i + 1}:`, e); lastErr = e; if (i < retries) { console.error(`[sharedGenerate] Retrying in ${500 * (i + 1)}ms...`); await sleep(500 * (i + 1)); } } } throw new Error(`Ollama generate failed after ${retries + 1} attempt(s): ${lastErr?.message || lastErr}`); } async function warmModels(models) { for (const model of models) { try { await ollamaGenerate({ model, prompt: "test", timeoutMs: 1e4, retries: 0 }); } catch { } } } var BASE, sleep; var init_ollama_client = __esm({ "src/shared/shared-llm/ollama-client.ts"() { "use strict"; BASE = (process.env.OLLAMA_BASE_URL || "http://localhost:11434").replace(/\/+$/, ""); sleep = (ms) => new Promise((r) => setTimeout(r, ms)); } }); // src/utils/model-manager.ts import { Ollama } from "ollama"; function getModelManager(baseUrl) { if (!modelManager) { modelManager = new ModelManager(baseUrl); } return modelManager; } var ModelManager, modelManager; var init_model_manager = __esm({ "src/utils/model-manager.ts"() { "use strict"; ModelManager = class { ollama; models = /* @__PURE__ */ new Map(); lastDiscovery = 0; discoveryInterval = 6e4; // Re-discover every 60 seconds baseUrl; constructor(baseUrl = "http://localhost:11434") { this.baseUrl = baseUrl; this.ollama = new Ollama({ host: baseUrl }); } /** * Discover all available models from Ollama */ async discoverModels(force = false) { const now = Date.now(); if (!force && this.models.size > 0 && now - this.lastDiscovery < this.discoveryInterval) { return Array.from(this.models.values()); } try { const response = await this.ollama.list(); this.models.clear(); for (const model of response.models) { const info = this.parseModelInfo(model); this.models.set(info.name, info); } this.lastDiscovery = now; console.error(`[ModelManager] Discovered ${this.models.size} models`); return Array.from(this.models.values()); } catch (error) { console.error("[ModelManager] Failed to discover models:", error); return []; } } /** * Parse model information from Ollama response */ parseModelInfo(model) { const name = model.name; const size = model.size || 0; const sizeGB = size / (1024 * 1024 * 1024); const parts = name.split(":"); const baseName = parts[0] || name; const paramSize = parts[1] || "unknown"; let family = "unknown"; if (baseName.includes("qwen")) family = "qwen"; else if (baseName.includes("deepseek")) family = "deepseek"; else if (baseName.includes("llama")) family = "llama"; else if (baseName.includes("codellama")) family = "codellama"; else if (baseName.includes("mistral")) family = "mistral"; else if (baseName.includes("phi")) family = "phi"; else if (baseName.includes("gemma")) family = "gemma"; const capabilities = ["chat"]; const isCodeCapable = baseName.includes("coder") || baseName.includes("code") || family === "codellama" || family === "mistral" || // Mistral is excellent for code family === "deepseek" || // DeepSeek is code-focused family === "qwen"; if (isCodeCapable) { capabilities.push("code"); } if (baseName.includes("vision") || baseName.includes("llava")) { capabilities.push("vision"); } if (baseName.includes("embed")) { capabilities.push("embedding"); } let speed; if (sizeGB < 2) speed = "fast"; else if (sizeGB < 5) speed = "medium"; else speed = "slow"; let quality; const paramNum = parseInt(paramSize); if (isNaN(paramNum)) quality = "good"; else if (paramNum < 7) quality = "good"; else if (paramNum < 20) quality = "better"; else quality = "best"; return { name, size, sizeGB, family, parameter_size: paramSize, capabilities, speed, quality, modified_at: model.modified_at || (/* @__PURE__ */ new Date()).toISOString() }; } /** * Select the best model for a task */ async selectModel(criteria) { const models = await this.discoverModels(); if (models.length === 0) { console.error("[ModelManager] No models available"); return null; } let candidates = models; if (criteria.requiredCapabilities && criteria.requiredCapabilities.length > 0) { candidates = models.filter( (m) => criteria.requiredCapabilities.every((cap) => m.capabilities.includes(cap)) ); } if (criteria.task === "code" || criteria.task === "refactor" || criteria.task === "test") { const codeModels = candidates.filter((m) => m.capabilities.includes("code")); if (codeModels.length > 0) { candidates = codeModels; } } candidates.sort((a, b) => { if (criteria.preferSpeed) { if (a.speed !== b.speed) { const speedOrder = { fast: 0, medium: 1, slow: 2 }; return speedOrder[a.speed] - speedOrder[b.speed]; } } const targetQuality = this.getTargetQuality(criteria.complexity); const aMatch = a.quality === targetQuality ? 0 : 1; const bMatch = b.quality === targetQuality ? 0 : 1; if (aMatch !== bMatch) return aMatch - bMatch; if (criteria.complexity === "complex") { return b.size - a.size; } if (criteria.complexity === "simple") { return a.size - b.size; } return Math.abs(a.sizeGB - 5) - Math.abs(b.sizeGB - 5); }); return candidates[0]?.name || models[0]?.name || null; } /** * Get fallback chain for a model */ async getFallbackChain(primaryModel, criteria) { const models = await this.discoverModels(); const chain = [primaryModel]; let candidates = models.filter( (m) => m.name !== primaryModel && (!criteria.requiredCapabilities || criteria.requiredCapabilities.every((cap) => m.capabilities.includes(cap))) ); candidates.sort((a, b) => a.size - b.size); chain.push(...candidates.slice(0, 2).map((m) => m.name)); return chain; } /** * Get adaptive timeout for a model */ async getAdaptiveTimeout(modelName, isColdStart = false) { const model = this.models.get(modelName); if (!model) { return isColdStart ? 18e4 : 6e4; } let baseTimeout; if (model.sizeGB < 2) { baseTimeout = 3e4; } else if (model.sizeGB < 5) { baseTimeout = 6e4; } else if (model.sizeGB < 10) { baseTimeout = 12e4; } else { baseTimeout = 18e4; } if (isColdStart) { baseTimeout *= 2; } return baseTimeout; } /** * Get model info */ getModelInfo(modelName) { return this.models.get(modelName); } /** * List all models */ listModels() { return Array.from(this.models.values()); } /** * List available model names (for quick lookup) */ async listAvailableModels() { await this.discoverModels(); return Array.from(this.models.keys()); } /** * Get models by capability */ getModelsByCapability(capability) { return Array.from(this.models.values()).filter( (m) => m.capabilities.includes(capability) ); } /** * Get target quality for complexity */ getTargetQuality(complexity) { switch (complexity) { case "simple": return "good"; case "medium": return "better"; case "complex": return "best"; default: return "good"; } } /** * Check if Ollama is running */ async isOllamaRunning() { try { await this.ollama.list(); return true; } catch { return false; } } }; modelManager = null; } }); // src/ollama-client.ts import { Ollama as Ollama2 } from "ollama"; import { spawn } from "child_process"; var OllamaClient; var init_ollama_client2 = __esm({ "src/ollama-client.ts"() { "use strict"; init_ollama_client(); init_model_manager(); OllamaClient = class { ollama; models; baseUrl; autoStart; ollamaProcess = null; startedByUs = false; constructor(autoStart = true) { this.baseUrl = process.env.OLLAMA_BASE_URL || "http://localhost:11434"; this.ollama = new Ollama2({ host: this.baseUrl }); this.models = this.initializeModels(); this.autoStart = autoStart; } /** * Get the current Ollama base URL */ getBaseUrl() { return this.baseUrl; } initializeModels() { return /* @__PURE__ */ new Map([ // PRIMARY: Qwen 2.5 Coder 7B - BEST 7B code model available [ "qwen2.5-coder:7b", { name: "qwen2.5-coder:7b", speed: "medium", quality: "best", useCase: ["code", "medium", "refactoring", "tests", "features", "complex"] } ], // ANALYSIS: Mistral 7B - Excellent for API integration, analysis, planning [ "mistral:7b", { name: "mistral:7b", speed: "medium", quality: "better", useCase: ["analysis", "api-integration", "research", "planning", "tool-setup", "configuration"] } ], // FALLBACK: DeepSeek Coder 1.3B - Small but code-specific [ "deepseek-coder:1.3b", { name: "deepseek-coder:1.3b", speed: "fast", quality: "good", useCase: ["code", "simple", "quick-generation"] } ], // ROUTER: Qwen 3B - Fast for routing/intent detection only [ "qwen2.5:3b", { name: "qwen2.5:3b", speed: "fast", quality: "good", useCase: ["router", "intent", "scaffolding", "simple"] } ], // EMBEDDINGS: Nomic Embed Text - For semantic search [ "nomic-embed-text", { name: "nomic-embed-text", speed: "fast", quality: "good", useCase: ["embeddings", "semantic-search"] } ] ]); } /** * Select the best model for the task */ async selectModel(options) { const modelManager2 = getModelManager(this.baseUrl); if (options.model && options.model !== "auto") { const modelMap = { "primary": "qwen2.5-coder:7b", "fallback": "deepseek-coder:1.3b", "router": "qwen2.5:3b", "qwen-coder": "qwen2.5-coder:7b", "deepseek": "deepseek-coder:1.3b" }; const requestedModel = modelMap[options.model] || options.model; const modelInfo = modelManager2.getModelInfo(requestedModel); if (modelInfo) { return requestedModel; } console.warn(`[OllamaClient] Requested model ${requestedModel} not found, auto-selecting...`); } const modelManager_models = await modelManager2.listAvailableModels(); if (options.complexity === "simple" && (options.model?.includes("analysis") || options.model?.includes("research"))) { if (modelManager_models.includes("mistral:7b")) { return "mistral:7b"; } } if (modelManager_models.includes("qwen2.5-coder:7b")) { return "qwen2.5-coder:7b"; } if (modelManager_models.includes("mistral:7b")) { return "mistral:7b"; } if (modelManager_models.includes("deepseek-coder:1.3b")) { return "deepseek-coder:1.3b"; } const criteria = { task: "code", // Default to code task complexity: options.complexity || "medium", preferSpeed: false, // Prefer quality over speed for code requiredCapabilities: ["code"] }; const selectedModel = await modelManager2.selectModel(criteria); if (!selectedModel) { throw new Error("No Ollama models available. Please pull mistral:7b or qwen2.5-coder:7b."); } return selectedModel; } /** * Generate text using Ollama (with auto-start, dynamic model selection, and adaptive timeouts!) */ async generate(prompt, options = {}) { await this.ensureRunning(); const modelManager2 = getModelManager(this.baseUrl); await modelManager2.discoverModels(); const model = await this.selectModel(options); const startTime = Date.now(); const isColdStart = false; let timeout = await modelManager2.getAdaptiveTimeout(model, isColdStart); if (process.env.OLLAMA_REQUEST_TIMEOUT) { timeout = parseInt(process.env.OLLAMA_REQUEST_TIMEOUT, 10) * 1e3; } else if (isColdStart && process.env.OLLAMA_STARTUP_TIMEOUT) { timeout = parseInt(process.env.OLLAMA_STARTUP_TIMEOUT, 10) * 1e3; } else if (!isColdStart && process.env.OLLAMA_WARMUP_TIMEOUT) { timeout = parseInt(process.env.OLLAMA_WARMUP_TIMEOUT, 10) * 1e3; } console.error(`[OllamaClient] Using model: ${model} (timeout: ${timeout}ms, cold_start: ${isColdStart})`); console.error(`[OllamaClient] Prompt length: ${prompt.length} chars`); try { console.error("[OllamaClient] Calling sharedGenerate..."); const text = await ollamaGenerate({ model, prompt, format: "text", timeoutMs: timeout, retries: 2 }); const timeMs = Date.now() - startTime; console.error(`[OllamaClient] sharedGenerate completed in ${timeMs}ms`); const tokensInput = Math.ceil(prompt.length / 4); const tokensGenerated = Math.ceil(text.length / 4); return { text, model, tokensGenerated, tokensInput, tokensTotal: tokensInput + tokensGenerated, timeMs }; } catch (error) { if (error.message?.includes("not found") || error.message?.includes("404")) { console.warn(`[OllamaClient] Model ${model} failed, trying fallback...`); const criteria = { task: "code", complexity: options.complexity || "simple" }; const fallbackChain = await modelManager2.getFallbackChain(model, criteria); if (fallbackChain.length > 1) { const fallbackModel = fallbackChain[1]; console.error(`[OllamaClient] Retrying with fallback model: ${fallbackModel}`); const fallbackTimeout = await modelManager2.getAdaptiveTimeout(fallbackModel, isColdStart); const text = await ollamaGenerate({ model: fallbackModel, prompt, format: "text", timeoutMs: fallbackTimeout, retries: 1 }); const timeMs = Date.now() - startTime; const tokensInput = Math.ceil(prompt.length / 4); const tokensGenerated = Math.ceil(text.length / 4); return { text, model: fallbackModel, tokensGenerated, tokensInput, tokensTotal: tokensInput + tokensGenerated, timeMs }; } throw new Error( `No available models found. Please pull at least one model: ollama pull qwen2.5:3b` ); } throw error; } } /** * Check if Ollama is running and models are available */ async checkHealth() { const errors = []; let running = false; let availableModels = []; try { const response = await this.ollama.list(); running = true; availableModels = response.models.map((m) => m.name); const recommendedModels = [ "qwen2.5-coder:7b", // PRIMARY: Best 7B code model "mistral:7b", // ANALYSIS: API integration, research, planning "deepseek-coder:1.3b", // FALLBACK: Code-specific, small "qwen2.5:3b", // ROUTER: Fast for routing "nomic-embed-text" // EMBEDDINGS: For semantic search ]; for (const model of recommendedModels) { if (!availableModels.includes(model)) { errors.push(`Model ${model} not found. Run: ollama pull ${model}`); } } } catch (error) { errors.push(`Ollama not running. Please start Ollama.`); } return { running, models: availableModels, errors }; } /** * Get model info */ getModelInfo(modelName) { return this.models.get(modelName); } /** * List all configured models */ listModels() { return Array.from(this.models.values()); } /** * Auto-start Ollama if not running (saves Augment credits!) * Enhanced with better detection, configurable timeout, and exponential backoff */ async startOllama() { console.error("\u{1F680} Auto-starting Ollama..."); const timeoutSeconds = parseInt(process.env.OLLAMA_STARTUP_TIMEOUT || "180", 10); console.error(`\u23F1\uFE0F Using startup timeout: ${timeoutSeconds} seconds`); const ollamaPath = process.env.OLLAMA_PATH || (process.platform === "win32" ? "C:\\Users\\chris\\AppData\\Local\\Programs\\Ollama\\ollama.exe" : "ollama"); try { console.error("\u{1F50D} Checking if Ollama is already running..."); console.error(`\u{1F517} Base URL: ${this.baseUrl}`); const isRunning = await pingOllama(5e3); if (isRunning) { console.error("\u2705 Ollama is already running!"); return; } console.error("\u274C Ollama not responding, attempting to start..."); console.error(`\u{1F680} Spawning Ollama process: ${ollamaPath}`); this.ollamaProcess = spawn(ollamaPath, ["serve"], { detached: true, stdio: "ignore", windowsHide: true }); this.ollamaProcess.unref(); this.startedByUs = true; console.error(`\u23F3 Waiting for Ollama to be ready (timeout: ${timeoutSeconds}s)...`); const delays = [1e3, 2e3, 4e3, 8e3]; let totalWait = 0; let attemptCount = 0; while (totalWait < timeoutSeconds * 1e3) { const delay = attemptCount < delays.length ? delays[attemptCount] : 1e3; await new Promise((resolve3) => setTimeout(resolve3, delay)); totalWait += delay; attemptCount++; try { const ready = await pingOllama(2e3); if (ready) { console.error(`\u2705 Ollama ready after ${totalWait}ms!`); return; } } catch { console.error(`\u23F3 Still waiting... (${Math.floor(totalWait / 1e3)}s / ${timeoutSeconds}s)`); } } throw new Error(`Ollama started but not ready within ${timeoutSeconds} seconds. Try increasing OLLAMA_START_TIMEOUT.`); } catch (error) { if (error.code === "ENOENT") { throw new Error( `Ollama not found at: ${ollamaPath} Please install Ollama from https://ollama.com or set OLLAMA_PATH environment variable.` ); } if (error.code === "EADDRINUSE" || error.message?.includes("address already in use")) { throw new Error( `Port 11434 is already in use. Another Ollama instance may be running. Try: pkill ollama (Linux/Mac) or taskkill /F /IM ollama.exe (Windows)` ); } throw new Error(`Failed to auto-start Ollama: ${error.message}`); } } /** * Ensure Ollama is running (auto-start if needed) * Enhanced with better health checking using pingOllama */ async ensureRunning() { console.error(`[OllamaClient] Ensuring Ollama is running at ${this.baseUrl}...`); try { console.error("[OllamaClient] Checking Ollama health..."); const isRunning = await pingOllama(1e4); if (isRunning) { console.error("[OllamaClient] \u2705 Ollama is running and healthy!"); return; } console.error("[OllamaClient] \u274C Ollama not responding"); if (this.autoStart) { console.error("[OllamaClient] Auto-start enabled, attempting to start Ollama..."); await this.startOllama(); const isNowRunning = await pingOllama(5e3); if (!isNowRunning) { throw new Error("Ollama started but still not responding to health checks"); } console.error("[OllamaClient] \u2705 Ollama started successfully!"); } else { throw new Error( "Ollama is not running. Please start Ollama with: ollama serve\nOr enable auto-start by setting autoStart=true in constructor." ); } } catch (error) { console.error(`[OllamaClient] Error in ensureRunning: ${error.message}`); if (this.autoStart && !error.message?.includes("auto-start") && !error.message?.includes("started but still not responding")) { console.error("[OllamaClient] Ping failed, trying auto-start as fallback..."); await this.startOllama(); } else { throw error; } } } /** * Cleanup spawned Ollama process on shutdown */ async cleanup() { if (this.ollamaProcess && this.startedByUs) { console.error("\u{1F9F9} Cleaning up spawned Ollama process..."); try { this.ollamaProcess.kill(); console.error("\u2705 Ollama process terminated"); } catch (error) { console.error(`\u26A0\uFE0F Failed to kill Ollama process: ${error.message}`); } } } }; } }); // src/utils/prompt-builder.ts var PromptBuilder; var init_prompt_builder = __esm({ "src/utils/prompt-builder.ts"() { "use strict"; PromptBuilder = class { /** * Build code generation prompt (alias for buildGenerationPrompt) */ buildCodePrompt(request) { return this.buildGenerationPrompt(request); } /** * Build code generation prompt */ buildGenerationPrompt(request) { const { task, context, template } = request; let prompt = `You are an expert software engineer. Generate COMPLETE, PRODUCTION-READY code. Task: ${task} Context: ${context} CRITICAL: DO NOT INVENT OR HALLUCINATE APIs! - If the task asks for simple logic (like adding numbers), write the logic directly - DO NOT use AWS, external services, or imaginary APIs unless explicitly required - DO NOT add comments like "for demonstration purposes" or "imaginary service" - Write REAL, WORKING code that actually does what the task asks STRICT REQUIREMENTS (MANDATORY): 1. NO PLACEHOLDERS - No TODO, FIXME, PLACEHOLDER, STUB, TBD, MOCK, or "implement this later" 2. SIMPLE LOGIC FIRST - For simple tasks, write simple code. Don't overcomplicate with external APIs 3. REAL APIs ONLY - If you must use external APIs, only use real, documented ones 4. COMPLETE IMPLEMENTATIONS - Every function must have full logic, no empty bodies 5. PROPER IMPORTS - Use ES6 imports (import X from 'Y'), never require() 6. SYNTACTICALLY CORRECT - Balanced braces, brackets, parentheses 7. ERROR HANDLING - Include try/catch for async operations and external calls 8. TYPE SAFETY - Add TypeScript types for all parameters, returns, and variables 9. PRODUCTION READY - Code must compile and run without modifications FORBIDDEN PATTERNS (WILL FAIL VALIDATION): - throw new Error('Not implemented') - // TODO: implement this - // FIXME: ... - function foo() { } // empty body - const x = PLACEHOLDER; - // ... (ellipsis indicating incomplete code) - new AWS.RestifyClient() // This doesn't exist! - "for demonstration purposes" // This means fake code! - "imaginary service" // This means fake code! EXAMPLE - CORRECT (simple task, simple code): Task: "Create a function that adds two numbers" \`\`\`typescript export function addNumbers(a: number, b: number): number { return a + b; } \`\`\` EXAMPLE - WRONG (overcomplicated with fake APIs): \`\`\`typescript import { sum } from '@aws-sdk/client-cognito-identity'; // \u274C WRONG! Not needed! export async function addNumbers(a: number, b: number): Promise<number> { const result = await sum(...); // \u274C WRONG! Fake API! return result; } \`\`\` CODE QUALITY STANDARDS: - Write clean, maintainable code following DRY principles - Follow best practices for the framework/language specified in context - Add JSDoc comments for public functions/classes only - Use meaningful variable and function names - Keep functions focused and single-purpose `; if (template && template !== "none") { prompt += `Template Guide: Use the ${template} template pattern as a structural reference. `; } prompt += `Generate the COMPLETE code now. Wrap code in triple backticks with the file path as a comment: \`\`\`typescript // path/to/file.ts // Your complete, production-ready code here \`\`\` If multiple files are needed, provide each in separate code blocks with their paths. REMEMBER: Code will be validated. Any placeholders, fake APIs, or incomplete implementations will be rejected.`; return prompt; } /** * Build code analysis prompt */ buildAnalysisPrompt(request) { const { code, files, question } = request; let prompt = `You are an expert code reviewer. Analyze this code for issues. Question: ${question} `; if (code) { prompt += `Code to analyze: \`\`\` ${code} \`\`\` `; } if (files && files.length > 0) { prompt += `Files to analyze: ${files.map((f, i) => `${i + 1}. ${f}`).join("\n")} `; } prompt += `Analyze for: - Performance issues - Security vulnerabilities - Code smells - Best practice violations - Potential bugs - Maintainability concerns Provide detailed analysis with: 1. Summary of findings 2. Specific issues found (use format: [TYPE] (SEVERITY) location: description) 3. Recommendations for improvement Be specific and actionable.`; return prompt; } /** * Build refactoring prompt */ buildRefactorPrompt(request) { const { code, instructions, style } = request; let prompt = `You are an expert software engineer. Refactor this code to PRODUCTION QUALITY. Code to refactor: \`\`\` ${code} \`\`\` Refactoring Instructions: ${instructions} `; if (style) { prompt += `Code Style: ${style} `; } prompt += `STRICT REQUIREMENTS (MANDATORY): 1. MAINTAIN FUNCTIONALITY - Same behavior, improved structure 2. NO PLACEHOLDERS - No TODO, FIXME, STUB, or incomplete implementations 3. COMPLETE CODE - All functions must have full implementations 4. REAL APIs ONLY - Use only documented, real APIs 5. IMPROVE QUALITY - Better naming, structure, and maintainability 6. PRESERVE TESTS - Keep existing test compatibility 7. ${style || "modern"} CODING PRACTICES - Follow industry standards FORBIDDEN IN REFACTORED CODE: - Empty function bodies - Placeholder comments (TODO, FIXME, etc.) - Fake or non-existent APIs - throw new Error('Not implemented') - Incomplete logic or ellipsis comments Provide: 1. COMPLETE refactored code (in triple backticks) 2. List of changes made (bullet points starting with -) Format: \`\`\`typescript // Complete refactored code here \`\`\` Changes made: - Extracted UserProfile component with full implementation - Renamed handleClick to handleUserClick for clarity - Applied dependency injection pattern with proper types REMEMBER: Refactored code will be validated. Any placeholders or incomplete code will be rejected.`; return prompt; } /** * Build test generation prompt */ buildTestPrompt(request) { const { code, framework, coverage } = request; let prompt = `You are an expert test engineer. Generate COMPLETE, RUNNABLE tests. Code to test: \`\`\` ${code} \`\`\` Test Framework: ${framework} Coverage Level: ${coverage || "comprehensive"} STRICT REQUIREMENTS (MANDATORY): 1. COMPLETE TESTS - All test cases must be fully implemented 2. NO PLACEHOLDERS - No TODO, FIXME, or "add test here" comments 3. REAL ASSERTIONS - Use actual ${framework} assertion methods (no fake APIs) 4. RUNNABLE - Tests must execute without modifications 5. PROPER SETUP - Include all necessary imports, mocks, and setup/teardown 6. DESCRIPTIVE NAMES - Use clear, specific test names (not "test1", "test2") 7. FOLLOW ${framework} BEST PRACTICES - Use framework conventions TEST COVERAGE REQUIREMENTS: - Test ALL public methods/functions - Include happy path scenarios - Include error/exception handling - Test edge cases (null, undefined, empty, boundary values) - Test async operations with proper await/promises - Include setup/teardown if needed `; if (coverage === "edge-cases") { prompt += `EXTRA FOCUS ON EDGE CASES: - Null and undefined inputs - Empty arrays, objects, and strings - Boundary values (min, max, zero, negative) - Invalid input types - Error conditions and exceptions - Race conditions for async code - Timeout scenarios `; } prompt += `FORBIDDEN IN TESTS: - Empty test bodies: it('should work', () => { }) - Placeholder comments: // TODO: add test - Fake assertion methods - Incomplete async handling Generate the COMPLETE test file now. Wrap in triple backticks: \`\`\`typescript // Complete, runnable test file here \`\`\` REMEMBER: Tests will be validated and must be executable without modifications.`; return prompt; } /** * Build documentation prompt */ buildDocPrompt(request) { const { code, style, detail } = request; let prompt = `You are a technical writer. Generate clear documentation for this code. Code to document: \`\`\` ${code} \`\`\` Documentation style: ${style || "tsdoc"} Detail level: ${detail || "detailed"} `; if (style === "jsdoc" || style === "tsdoc") { prompt += `Requirements: - Document all public functions/methods - Include @param tags with types - Include @returns tag - Include @throws for errors - Add @example for complex functions - Keep descriptions clear and concise `; } else if (style === "markdown" || style === "readme") { prompt += `Requirements: - Create a README-style document - Include overview/description - List all functions/methods - Provide usage examples - Include installation/setup if applicable - Add troubleshooting section `; } prompt += `Generate the documentation now.`; return prompt; } }; } }); // src/pipeline/types.ts function calculateWeightedScore(scores, weights = DEFAULT_PIPELINE_CONFIG.weights) { const w = weights; return scores.compilation * w.compilation + scores.tests_functional * w.tests_functional + scores.tests_edge * w.tests_edge + scores.types * w.types + scores.style * w.style + scores.security * w.security + (scores.conventions || 0) * (w.conventions || 0); } function meetsAcceptanceCriteria(verdict, config = DEFAULT_PIPELINE_CONFIG) { const score = calculateWeightedScore(verdict.scores, config.weights); if (score < (config.acceptThreshold ?? DEFAULT_PIPELINE_CONFIG.acceptThreshold)) { return false; } if (verdict.scores.compilation === 0) return false; if (verdict.scores.security === 0) return false; return true; } var DEFAULT_PIPELINE_CONFIG; var init_types = __esm({ "src/pipeline/types.ts"() { "use strict"; DEFAULT_PIPELINE_CONFIG = { maxAttempts: 5, acceptThreshold: 0.9, weights: { compilation: 0.15, tests_functional: 0.25, tests_edge: 0.15, types: 0.1, security: 0.1, style: 0.05, conventions: 0.2 }, allowedLibraries: [ // Node.js built-ins "fs", "path", "util", "crypto", "stream", "events", "buffer", // Common safe libraries "lodash", "axios", "express", "react", "vue", "next", // Testing "jest", "@jest/globals", "vitest", "mocha", "chai", // TypeScript "typescript", "@types/*" ], minCoverage: 80, testTimeout: 5e3, globalTimeout: 3e4, memoryLimit: 512, spec: "", provider: "ollama", model: "qwen2.5-coder:7b" }; } }); // src/utils/repo-tools.ts import * as fs from "fs"; import * as path from "path"; import { execSync } from "child_process"; async function runRepoTools(root, files) { const result = { passed: true, lintErrors: [], typeErrors: [], boundaryErrors: [], customRuleErrors: [], allErrors: [] }; const lintResult = await runESLint(root, files); result.lintErrors = lintResult.errors; if (lintResult.errors.length > 0) result.passed = false; const typeResult = await runTypeScript(root, files); result.typeErrors = typeResult.errors; if (typeResult.errors.length > 0) result.passed = false; const boundaryResult = await checkBoundaries(root, files); result.boundaryErrors = boundaryResult.errors; if (boundaryResult.errors.length > 0) result.passed = false; const customResult = await runCustomRules(root, files); result.customRuleErrors = customResult.errors; if (customResult.errors.length > 0) result.passed = false; result.allErrors = [ ...result.lintErrors, ...result.typeErrors, ...result.boundaryErrors, ...result.customRuleErrors ]; return result; } async function runESLint(root, files) { const errors = []; const eslintPath = path.join(root, "node_modules", ".bin", "eslint"); if (!fs.existsSync(eslintPath) && !fs.existsSync(eslintPath + ".cmd")) { return { errors: [] }; } try { const fileArgs = files.map((f) => `"${f}"`).join(" "); const cmd = process.platform === "win32" ? `"${eslintPath}.cmd" --format json ${fileArgs}` : `"${eslintPath}" --format json ${fileArgs}`; const output = execSync(cmd, { cwd: root, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }); const results = JSON.parse(output); for (const result of results) { for (const message of result.messages) { errors.push(`${result.filePath}:${message.line}:${message.column} - ${message.message} (${message.ruleId})`); } } } catch (error) { if (error.stdout) { try { const results = JSON.parse(error.stdout); for (const result of results) { for (const message of result.messages) { errors.push(`${result.filePath}:${message.line}:${message.column} - ${message.message} (${message.ruleId})`); } } } catch (parseError) { errors.push(`ESLint error: ${error.message}`); } } } return { errors: errors.slice(0, 10) }; } async function runTypeScript(root, files) { const errors = []; const tscPath = path.join(root, "node_modules", ".bin", "tsc"); if (!fs.existsSync(tscPath) && !fs.existsSync(tscPath + ".cmd")) { return { errors: [] }; } try { const cmd = process.platform === "win32" ? `"${tscPath}.cmd" --noEmit --pretty false` : `"${tscPath}" --noEmit --pretty false`; execSync(cmd, { cwd: root, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }); } catch (error) { if (error.stderr || error.stdout) { const output = error.stderr || error.stdout; const lines = output.split("\n"); for (const line of lines) { const match = line.match(/^(.+?)\((\d+),(\d+)\):\s*error\s+TS\d+:\s*(.+)$/); if (match) { const [, file, line2, col, message] = match; errors.push(`${file}:${line2}:${col} - ${message}`); } } } } return { errors: errors.slice(0, 10) }; } async function checkBoundaries(root, files) { const errors = []; const eslintConfigPath = findESLintConfig(root); if (!eslintConfigPath) { return { errors: [] }; } try { const configContent = fs.readFileSync(eslintConfigPath, "utf-8"); if (!configContent.includes("boundaries")) { return { errors: [] }; } const boundaryRules = parseBoundaryRules(configContent); for (const file of files) { const violations = checkFileBoundaries(root, file, boundaryRules); errors.push(...violations); } } catch (error) { } return { errors: errors.slice(0, 10) }; } function findESLintConfig(root) { const configFiles = [ ".eslintrc.js", ".eslintrc.cjs", ".eslintrc.json", ".eslintrc", "eslint.config.js" ]; for (const file of configFiles) { const fullPath = path.join(root, file); if (fs.existsSync(fullPath)) { return fullPath; } } return null; } function parseBoundaryRules(configContent) { const rules = []; const ruleMatches = configContent.matchAll(/"from":\s*\[([^\]]+)\][^}]*"allow":\s*\[([^\]]+)\]/g); for (const match of ruleMatches) { const from = match[1].split(",").map((s) => s.trim().replace(/['"]/g, "")); const allow = match[2].split(",").map((s) => s.trim().replace(/['"]/g, "")); rules.push({ from, allow }); } return rules; } function checkFileBoundaries(root, file, rules) { const errors = []; try { const content = fs.readFileSync(path.join(root, file), "utf-8"); const imports = extractImports(content); const fileLayer = determineLayer(file); if (!fileLayer) return errors; const rule = rules.find((r) => r.from.includes(fileLayer)); if (!rule) return errors; for (const imp of imports) { const importLayer = determineLayer(imp); if (!importLayer) continue; if (!rule.allow.includes(importLayer)) { errors.push(`${file}: Cannot import from ${importLayer} (${imp}). ${fileLayer} can only import from: ${rule.allow.join(", ")}`); } } } catch (error) { } return errors; } function extractImports(content) { const imports = []; const importMatches = content.matchAll(/import\s+.*?\s+from\s+['"]([^'"]+)['"]/g); for (const match of importMatches) { imports.push(match[1]); } const requireMatches = content.matchAll(/require\(['"]([^'"]+)['"]\)/g); for (const match of requireMatches) { imports.push(match[1]); } return imports; } function determineLayer(filePath) { if (filePath.includes("/features/") || filePath.includes("\\features\\")) return "feature"; if (filePath.includes("/domain/") || filePath.includes("\\domain\\")) return "domain"; if (filePath.includes("/infra/") || filePath.includes("\\infra\\")) return "infra"; if (filePath.includes("/utils/") || filePath.includes("\\utils\\")) return "utils"; if (filePath.includes("/lib/") || filePath.includes("\\lib\\")) return "lib"; if (filePath.includes("/core/") || filePath.includes("\\core\\")) return "core"; return null; } async function runCustomRules(root, files) { const errors = []; for (const file of files) { try { const content = fs.readFileSync(path.join(root, file), "utf-8"); const snakeCaseMatches = content.matchAll(/\b(const|let|var)\s+([a-z]+_[a-z_]+)\s*=/g); for (const match of snakeCaseMatches) { const varName = match[2]; if (!/^[A-Z_]+$/.test(varName)) { errors.push(`${file}: Use camelCase instead of snake_case for variable '${varName}'`); } } const anyTypeMatches = content.matchAll(/export\s+(function|const|class|interface|type)\s+[^:]+:\s*any/g); for (const match of anyTypeMatches) { errors.push(`${file}: Do not use 'any' type in public exports`); } if (!file.includes(".test.") && !file.includes(".spec.")) { const consoleMatches = content.matchAll(/console\.log\(/g); for (const match of consoleMatches) { errors.push(`${file}: Remove console.log() from production code`); } } } catch (error) { } } return { errors: errors.slice(0, 10) }; } var init_repo_tools = __esm({ "src/utils/repo-tools.ts"() { "use strict"; } }); // src/utils/edit-constraints.ts import * as path2 from "path"; async function checkEditConstraints(root, files, constraints) { const violations = []; for (const file of files) { const isAllowed = constraints.allowedPaths.some( (allowed) => file.path.startsWith(allowed) || matchesGlob(file.path, allowed) ); if (!isAllowed) { const isReadOnly = constraints.readOnlyPaths.some( (readonly) => file.path.startsWith(readonly) || matchesGlob(file.path, readonly) ); if (isReadOnly) { violations.push({ file: file.path, violation: "File is read-only and cannot be modified", severity: "error" }); continue; } } if (!constraints.allowPublicRenames) { const renameViolations = await checkPublicRenames(root, file); violations.push(...renameViolations); } } return violations; } function matchesGlob(filePath, pattern) { const regexPattern = pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\./g, "\\."); const regex = new RegExp(`^${regexPattern}$`); return regex.test(filePath); } async function checkPublicRenames(root, file) { const violations = []; try { const newSymbols = extractSymbolsFromContent(file.content, file.path); const fs23 = await import("fs"); const fullPath = path2.join(root, file.path); if (!fs23.existsSync(fullPath)) { return violations; } const originalContent = fs23.readFileSync(fullPath, "utf-8"); const originalSymbols = extractSymbolsFromContent(originalContent, file.path); const originalPublic = originalSymbols.filter((s) => s.isPublic); const newPublic = newSymbols.filter((s) => s.isPublic); const originalNames = new Set(originalPublic.map((s) => s.name)); const newNames = new Set(newPublic.map((s) => s.name)); for (const name of originalNames) { if (!newNames.has(name)) { violations.push({ file: file.path, violation: `Public symbol '${name}' was removed or renamed. This is a breaking change.`, severity: "error" }); } } } catch (error) { } return violations; } function extractSymbolsFromContent(content, filePath) { const symbols = []; const lines = content.split("\n"); const patterns = [ // export function/const/class/interface/type { regex: /export\s+(async\s+)?function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g, type: "function", isPublic: true }, { regex: /export\s+const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g, type: "const", isPublic: true }, { regex: /export\s+class\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g, type: "class", isPublic: true }, { regex: /export\s+interface\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g, type: "interface", isPublic: true }, { regex: /export\s+type\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g, type: "type", isPublic: true }, { regex: /export\s+enum\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g, type: "enum", isPublic: true } ]; for (const pattern of patterns) { let match; while ((match = pattern.regex.exec(content)) !== null) { const name = match[2] || match[1]; if (!name) continue; const lineNumber = content.substring(0, match.index).split("\n").length; symbols.push({ name, type: pattern.type, file: filePath, line: lineNumber, isPublic: pattern.isPublic }); } } return symbols; } function inferAllowedPaths(spec, root) { const allowedPaths = []; const pathMatches = spec.matchAll(/(?:^|\s)([a-zA-Z0-9_\-/]+\.[a-zA-Z]+)/g); for (const match of pathMatches) { allowedPaths.push(match[1]); } const dirMatches = spec.matchAll(/(?:^|\s)([a-zA-Z0-9_\-/]+\/)/g); for (const match of dirMatches) { allowedPaths.push(match[1] + "**/*"); } if (allowedPaths.length === 0) { allowedPaths.push("src/**/*"); } return allowedPaths; } function getDefaultReadOnlyPaths() { return [ "node_modules/**/*", "dist/**/*", "build/**/*", ".next/**/*", "coverage/**/*", "__generated__/**/*", "src/gen/**/*", "src/generated/**/*", "prisma/client/**/*" ]; } function calculateDiffSize(originalContent, newContent) { const originalLines = originalContent.split("\n"); const newLines = newContent.split("\n"); let changes = 0; const maxLines = Math.max(originalLines.length, newLines.length); for (let i = 0; i < maxLines; i++) { const originalLine = originalLines[i] || ""; const newLine = newLines[i] || ""; if (originalLine !== newLine) { changes++; } } return changes; } function isMinimalDiff(originalContent, newContent) { const diffSize = calculateDiffSize(originalContent, newContent); const totalLines = originalContent.split("\n").length; if (totalLines === 0) return true; const changePercentage = diffSize / totalLines * 100; return changePercentage < 50; } async function enforceMinimalDiffs(root, files) { const violations = []; const fs23 = await import("fs"); for (const file of files) { const fullPath = path2.join(root, file.path); if (!fs23.existsSync(fullPath)) { continue; } const originalContent = fs23.readFileSync(fullPath, "utf-8"); if (!isMinimalDiff(originalContent, file.content)) { const diffSize = calculateDiffSize(originalContent, file.content); const totalLines = originalContent.split("\n").length; const changePercentage = (diffSize / totalLines * 100).toFixed(1); violations.push({ file: file.path, viol