/** * Ollama Client * Handles communication with local Ollama instance for AI analysis */ import { Ollama } from 'ollama'; import type { ClassInfo } from './scanner.js'; /** * Options for OllamaClient */ export interface OllamaClientOptions { model?: string; host?: string; } /** * OllamaClient - Wrapper for Ollama API with error handling */ export class OllamaClient { private model: string; private host: string; private client: Ollama; constructor(options: OllamaClientOptions = {}) { this.model = options.model || 'llama3'; this.host = options.host || 'http://localhost:11434'; this.client = new Ollama({ host: this.host }); } /** * Check if Ollama is running and model is available * @returns True if connected and model available */ async checkConnection(): Promise { try { const models = await this.client.list(); // Get full model names (with tags) and base names (without tags) const fullNames = models.models.map((m) => m.name); const baseNames = models.models.map((m) => m.name.split(':')[0]); const requestedBase = this.model.split(':')[0]; // Check if exact match or base name match return fullNames.includes(this.model) || baseNames.includes(this.model) || baseNames.includes(requestedBase); } catch { return false; } } /** * Send a prompt to Ollama and get response * @param systemPrompt - System instructions * @param userPrompt - User content (PHP code) * @returns AI response */ async chat(systemPrompt: string, userPrompt: string): Promise { try { const response = await this.client.chat({ model: this.model, messages: [ { role: 'system', content: systemPrompt }, { role: 'user', content: userPrompt }, ], stream: false, }); return response.message.content; } catch (error) { const err = error as NodeJS.ErrnoException; if (err.code === 'ECONNREFUSED') { throw new Error( 'Cannot connect to Ollama. Make sure Ollama is running:\n' + ' 1. Install: https://ollama.ai\n' + ' 2. Start: ollama serve\n' + ` 3. Pull model: ollama pull ${this.model}` ); } throw new Error(`Ollama error: ${err.message}`); } } /** * Analyze PHP code for deprecations * @param code - PHP source code * @param phpVersion - Target PHP version * @returns Analysis result in Markdown */ async analyzeCode(code: string, phpVersion: string = '8.3'): Promise { // Determine available features based on PHP version const majorVersion = parseFloat(phpVersion); const features = this.getPhpFeatures(majorVersion); const systemPrompt = `Analyze this PHP code for issues. Target: PHP ${phpVersion} Output format: ## Issues Found | Line | Issue | Fix | |------|-------|-----| | 10 | mysql_query deprecated | Use PDO | | 20 | array() old syntax | Use [] | ## Refactored Code (key parts only) \`\`\`php // Show fixed code using PHP ${phpVersion} syntax: // - Use [] not array() // - Use type hints // - Use ?-> null safe operator // - Use match instead of switch \`\`\` RULES for PHP ${phpVersion}: - ALWAYS use [] instead of array() - Add return types (: void, : array, : string) - Add parameter types - Use null coalescing ?? and ?-> Find: deprecated functions, old array() syntax, missing types, security issues.`; return this.chat(systemPrompt, code); } /** * Get available PHP features based on version * @param version - PHP major version number * @returns String listing available features */ private getPhpFeatures(version: number): string { const features: string[] = []; if (version < 5.4) { features.push('- No short array syntax [], use array()'); features.push('- No traits'); } if (version >= 5.4) { features.push('- Short array syntax [] available'); features.push('- Traits available'); } if (version < 7.0) { features.push('- NO return type declarations'); features.push('- NO scalar type hints (int, string, bool)'); features.push('- NO null coalescing operator ??'); features.push('- mysql_* functions still available (but discouraged)'); } if (version >= 7.0) { features.push('- Return type declarations available'); features.push('- Scalar type hints available'); features.push('- Null coalescing operator ?? available'); features.push('- mysql_* functions REMOVED, use PDO'); } if (version >= 7.1) { features.push('- Nullable types ?Type available'); features.push('- Void return type available'); } if (version >= 7.4) { features.push('- Typed properties available'); features.push('- Arrow functions available'); } if (version >= 8.0) { features.push('- Named arguments available'); features.push('- Match expression available'); features.push('- Constructor property promotion available'); features.push('- Union types available'); } if (version >= 8.1) { features.push('- Readonly properties available'); features.push('- Enums available'); } if (version >= 8.2) { features.push('- Readonly classes available'); } return features.join('\n'); } /** * Generate PHPUnit tests for a class * @param code - PHP class source code * @param classInfo - Extracted class information * @returns PHPUnit test code */ async generateTests(code: string, classInfo: ClassInfo): Promise { const systemPrompt = `Generate PHPUnit tests for this PHP class. YOUR RESPONSE MUST BE EXACTLY THIS FORMAT (no other text): instance = new ${classInfo.className || 'Example'}(); } public function testExample(): void { $this->assertTrue(true); } } ABSOLUTE RULES: 1. Start with m.name).join(', ') || 'analyze the code'} Generate real test methods for the actual class methods. Output ONLY PHP code.`; return this.chat(systemPrompt, code); } /** * Generate DocBlock documentation * @param code - PHP source code * @returns Code with DocBlocks added */ async generateDocBlocks(code: string): Promise { const systemPrompt = `You are an expert PHP developer specializing in documentation. Add comprehensive DocBlock comments to the provided PHP code. Requirements: 1. Add class-level DocBlocks with @package, @author, @since 2. Add method DocBlocks with: - Brief description - @param for each parameter with type and description - @return with type and description - @throws for exceptions that may be thrown 3. Add property DocBlocks with @var 4. Follow PSR-5 PHPDoc standards Output ONLY the complete PHP code with DocBlocks added, starting with { const systemPrompt = `You are an expert in API documentation and OpenAPI 3.0.3 specifications. Generate a COMPLETE and VALID OpenAPI spec from the provided PHP code. OUTPUT THIS EXACT STRUCTURE (fill in based on PHP code): openapi: 3.0.3 info: title: API Title description: API Description version: 1.0.0 license: name: MIT servers: - url: http://localhost:8080 description: Local server paths: /users: get: operationId: getUsers summary: Get all users security: - apiKey: [] responses: '200': description: Success content: application/json: schema: type: array items: type: object '400': description: Bad request '500': description: Server error /users/{id}: get: operationId: getUserById summary: Get user by ID security: - apiKey: [] parameters: - in: path name: id required: true schema: type: integer responses: '200': description: Success '404': description: Not found put: operationId: updateUser summary: Update user security: - apiKey: [] parameters: - in: path name: id required: true schema: type: integer requestBody: required: true content: application/json: schema: type: object responses: '200': description: Updated '400': description: Bad request '404': description: Not found components: securitySchemes: apiKey: type: apiKey in: header name: X-API-Key CRITICAL RULES: 1. MUST include: info.license, servers, operationId on every operation 2. MUST include security on every operation 3. MUST include at least one 4XX response (400 or 404) on every operation 4. All path parameters in URL MUST be defined in parameters array 5. securitySchemes MUST be inside components (not at root) 6. All paths at same indentation level under paths: 7. Response codes as quoted strings: '200', '404' Output ONLY valid YAML. NO text before or after. NO markdown.`; return this.chat(systemPrompt, code); } }