-
-
Notifications
You must be signed in to change notification settings - Fork 30
feat: add Coderrr Doctor diagnostic tool #128
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9062727
8f3fbe6
04fa689
7d1e588
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| # Coderrr Doctor | ||
|
|
||
| A built-in diagnostic tool to ensure your environment is ready for AI coding. | ||
|
|
||
| ## Usage | ||
| Run the following to check your setup: | ||
| ```bash | ||
| coderrr doctor | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,36 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const fs = require('fs'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const path = require('path'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { execSync } = require('child_process'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class CoderrrDoctor { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checkEnv() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return fs.existsSync(path.join(process.cwd(), '.env')); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checkPython() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const version = execSync('python --version').toString().trim(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { status: true, version }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { status: false, version: 'Not found' }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+11
to
+16
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | |
| const version = execSync('python --version').toString().trim(); | |
| return { status: true, version }; | |
| } catch { | |
| return { status: false, version: 'Not found' }; | |
| } | |
| const commands = ['python', 'python3']; | |
| for (const cmd of commands) { | |
| try { | |
| const version = execSync(`${cmd} --version`).toString().trim(); | |
| return { status: true, version }; | |
| } catch { | |
| // Try next candidate command | |
| } | |
| } | |
| return { status: false, version: 'Not found' }; |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The codebase consistently uses axios for HTTP requests (as seen in src/agent.js). Using the native fetch API here introduces inconsistency and creates a dependency on Node.js 18+ without explicitly handling potential polyfill requirements. Consider using axios instead for consistency with the rest of the codebase and better error handling capabilities.
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The backend connectivity check may fail silently without providing useful diagnostic information. When the check fails, users only see "Backend: Unreachable" but don't know if it's due to timeout, network error, wrong URL, or server error. Consider capturing and displaying the error type (similar to how errorHandler.js categorizes network errors) to help users diagnose the issue.
| try { | |
| const controller = new AbortController(); | |
| const timeoutId = setTimeout(() => controller.abort(), 3000); | |
| const response = await fetch(url, { signal: controller.signal }); | |
| clearTimeout(timeoutId); | |
| return response.ok; | |
| } catch { | |
| return false; | |
| const controller = new AbortController(); | |
| const timeoutId = setTimeout(() => controller.abort(), 3000); | |
| try { | |
| const response = await fetch(url, { signal: controller.signal }); | |
| return response.ok; | |
| } catch (error) { | |
| clearTimeout(timeoutId); | |
| let reason = 'Unknown error'; | |
| if (error && error.name === 'AbortError') { | |
| reason = 'Timeout while connecting to backend'; | |
| } else if (error && typeof error.code === 'string') { | |
| if (error.code === 'ECONNREFUSED') { | |
| reason = 'Connection refused by backend (is it running and is CODERRR_BACKEND correct?)'; | |
| } else if (error.code === 'ENOTFOUND' || error.code === 'EAI_AGAIN') { | |
| reason = 'Backend host could not be resolved (check the CODERRR_BACKEND URL)'; | |
| } else { | |
| reason = `Network error (${error.code}) while connecting to backend`; | |
| } | |
| } else if (error && typeof error.message === 'string' && error.message) { | |
| reason = error.message; | |
| } | |
| console.error(`[Coderrr Doctor] Backend connectivity check failed for ${url}: ${reason}`); | |
| return false; | |
| } finally { | |
| clearTimeout(timeoutId); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| const chalk = require('chalk'); | ||
| const doctor = require('./doctor'); | ||
|
|
||
| async function runDiagnostics(backendUrl) { | ||
| console.log('\n' + chalk.blue.bold('🩺 CODERRR DOCTOR - SYSTEM DIAGNOSTICS')); | ||
| console.log(chalk.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')); | ||
|
|
||
| const python = doctor.checkPython(); | ||
| const node = doctor.checkNode(); | ||
| const hasEnv = doctor.checkEnv(); | ||
|
|
||
| console.log(`${python.status ? chalk.green('✔') : chalk.red('✘')} Python: ${python.version}`); | ||
| console.log(`${node.status ? chalk.green('✔') : chalk.red('✘')} Node.js: ${node.version}`); | ||
| console.log(`${hasEnv ? chalk.green('✔') : chalk.red('✘')} Local .env file detected`); | ||
|
|
||
| console.log(chalk.yellow('\nChecking Backend Connectivity...')); | ||
| const backendStatus = await doctor.checkBackend(backendUrl || 'https://coderrr-backend.vercel.app'); | ||
|
||
| console.log(`${backendStatus ? chalk.green('✔') : chalk.red('✘')} Backend: ${backendStatus ? 'Connected' : 'Unreachable'}`); | ||
|
|
||
| if (!backendStatus) { | ||
| console.log(chalk.red('\n[!] Advice: Check your internet or custom CODERRR_BACKEND variable.')); | ||
| } | ||
| console.log(chalk.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n')); | ||
| } | ||
|
|
||
| module.exports = { runDiagnostics }; | ||
|
Comment on lines
+1
to
+26
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,9 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Helps format diagnostic logs for potential export | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const generateReport = (results) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return `Coderrr Diagnostic Report - ${new Date().toISOString()}\n` + | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `Status: ${results.allPassed ? 'HEALTHY' : 'ISSUES DETECTED'}\n`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+4
to
+6
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const generateReport = (results) => { | |
| return `Coderrr Diagnostic Report - ${new Date().toISOString()}\n` + | |
| `Status: ${results.allPassed ? 'HEALTHY' : 'ISSUES DETECTED'}\n`; | |
| const generateReport = (results = {}) => { | |
| const lines = []; | |
| // Header and overall status (preserve existing behavior) | |
| lines.push(`Coderrr Diagnostic Report - ${new Date().toISOString()}`); | |
| lines.push( | |
| `Status: ${results.allPassed ? 'HEALTHY' : 'ISSUES DETECTED'}` | |
| ); | |
| // Detailed per-check results | |
| lines.push(''); | |
| lines.push('Checks:'); | |
| const entries = Object.entries(results); | |
| // If we only have allPassed or no entries, avoid printing an empty section | |
| const meaningfulEntries = entries.filter( | |
| ([key]) => key !== 'allPassed' | |
| ); | |
| if (meaningfulEntries.length === 0) { | |
| lines.push('- (no detailed checks available)'); | |
| } else { | |
| for (const [key, value] of meaningfulEntries) { | |
| // Handle structured check objects | |
| if (value && typeof value === 'object') { | |
| const statusValue = | |
| value.ok ?? | |
| value.passed ?? | |
| value.healthy ?? | |
| value.status; | |
| const messageValue = | |
| value.message ?? value.error ?? value.details; | |
| let line = `- ${key}:`; | |
| if (typeof statusValue !== 'undefined') { | |
| const statusLabel = | |
| typeof statusValue === 'boolean' | |
| ? statusValue | |
| ? 'OK' | |
| : 'FAIL' | |
| : String(statusValue); | |
| line += ` ${statusLabel}`; | |
| } | |
| if (messageValue) { | |
| line += ` - ${String(messageValue)}`; | |
| } | |
| // If neither status nor message was found, fall back to JSON | |
| if (line === `- ${key}:`) { | |
| line += ` ${JSON.stringify(value)}`; | |
| } | |
| lines.push(line); | |
| } else { | |
| // Simple scalar values | |
| lines.push(`- ${key}: ${String(value)}`); | |
| } | |
| } | |
| } | |
| return lines.join('\n'); |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The generateReport function in doctorHelpers.js is not being used anywhere in the codebase. The runDiagnostics function in doctorUI.js directly outputs to the console without calling generateReport. Either integrate this function into the diagnostic flow or remove it if it's not needed yet.
| * Helps format diagnostic logs for potential export | |
| */ | |
| const generateReport = (results) => { | |
| return `Coderrr Diagnostic Report - ${new Date().toISOString()}\n` + | |
| `Status: ${results.allPassed ? 'HEALTHY' : 'ISSUES DETECTED'}\n`; | |
| }; | |
| module.exports = { generateReport }; | |
| * Helper exports for doctor utilities. | |
| * | |
| * Note: This module currently does not expose any helpers. | |
| * If diagnostic reporting helpers are reintroduced in the future, | |
| * they should be added here and wired into the diagnostic flow. | |
| */ | |
| module.exports = {}; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| const doctor = require('../src/doctor'); | ||
|
|
||
| describe('Doctor Module', () => { | ||
| test('should detect node version', () => { | ||
| const node = doctor.checkNode(); | ||
| expect(node.status).toBe(true); | ||
| expect(node.version).toContain('v'); | ||
| }); | ||
| }); | ||
|
Comment on lines
+1
to
+9
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation is incomplete. It only shows the basic usage command but doesn't explain what the doctor command does, what checks it performs, what output to expect, or how to interpret the results. Compare this to other documentation in the project which typically includes more comprehensive information about features and their usage.