-
-
Notifications
You must be signed in to change notification settings - Fork 29
Dockerized the project #112
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
c8c1ae7
aa21bb4
c31f762
147a90b
f041536
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,37 @@ | ||||
| # Git | ||||
| .git | ||||
| .gitignore | ||||
|
|
||||
| # Node | ||||
| node_modules | ||||
| npm-debug.log | ||||
| dist | ||||
| coverage | ||||
|
|
||||
| # Python | ||||
| __pycache__ | ||||
| *.pyc | ||||
| *.pyo | ||||
| *.pyd | ||||
| .Python | ||||
| env | ||||
| venv | ||||
| pip-log.txt | ||||
| pip-delete-this-directory.txt | ||||
| .tox | ||||
| .coverage | ||||
| .coverage.* | ||||
| .cache | ||||
| nosetests.xml | ||||
| coverage.xml | ||||
| *.cover | ||||
| *.log | ||||
| .pytest_cache | ||||
|
|
||||
| # Docker | ||||
| docker-compose.yml | ||||
| Dockerfile | ||||
| .dockerignore | ||||
|
|
||||
| # Environment | ||||
| .env | ||||
|
||||
| .env |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -352,7 +352,17 @@ This section explains how to run Coderrr locally from source for development or | |
| git clone https://github.com/Akash-nath29/Coderrr.git | ||
| cd Coderrr | ||
| ``` | ||
| ### 2. Backend Setup (FastAPI) | ||
| ### 2. Backend Setup | ||
| You can run the backend using Docker (recommended) or set it up manually. | ||
|
|
||
| #### Option A: Docker (Recommended) | ||
|
|
||
| ```bash | ||
| docker compose up --build | ||
| ``` | ||
| The backend will be started at `http://localhost:5000` with hot-reloading enabled. | ||
|
Comment on lines
+358
to
+363
|
||
|
|
||
| #### Option B: Manual Setup (FastAPI) | ||
|
|
||
| ```bash | ||
| cd backend | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| # Use official Python 3.11 slim image | ||
| FROM python:3.11-slim | ||
|
|
||
| # Set working directory | ||
| WORKDIR /app | ||
|
|
||
| # Set environment variables | ||
| ENV PYTHONDONTWRITEBYTECODE=1 \ | ||
| PYTHONUNBUFFERED=1 | ||
|
|
||
| # Install system dependencies (if any needed, e.g. for build tools) | ||
| # RUN apt-get update && apt-get install -y --no-install-recommends gcc && rm -rf /var/lib/apt/lists/* | ||
|
|
||
| # Copy requirements first to leverage cache | ||
| COPY requirements.txt . | ||
|
|
||
| # Install Python dependencies | ||
| RUN pip install --no-cache-dir -r requirements.txt | ||
|
|
||
| # Copy application code | ||
| COPY . . | ||
|
|
||
| # Expose the API port | ||
| EXPOSE 5000 | ||
|
|
||
| # Run the application | ||
| CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "5000"] | ||
|
Comment on lines
+26
to
+27
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,21 @@ | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
|
Comment on lines
+1
to
+2
|
||||||||||||||
| version: '3.8' |
Copilot
AI
Jan 27, 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 PR title is "Dockerized the project" and the description extensively mentions Skills.md prompt enhancements, test suite additions, and changes to agent.js and fileOps.js. However, the actual code changes in this PR only include Docker configuration files (Dockerfile, docker-compose.yml, .dockerignore) and documentation updates. The Skills.md-related changes mentioned in the description are not present in this PR's diffs, suggesting they may have been merged previously or are being mistakenly included in this PR's description. The PR description should be updated to accurately reflect only the Docker-related changes.
Copilot
AI
Jan 27, 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 port configuration is inconsistent. The docker-compose.yml maps port 5000:5000 and the backend Dockerfile exposes port 5000, but the root .env.example file shows CODERRR_BACKEND=http://localhost:8000. According to the coding guidelines, the backend runs on port 5000, not 8000. When users run the backend via Docker, the CLI will try to connect to port 8000 by default (from .env.example), which will fail to connect to the Dockerized backend on port 5000.
| - "5000:5000" | |
| - "8000:5000" |
Copilot
AI
Jan 27, 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 volume mount "./backend:/app" creates a bind mount that overwrites the /app directory in the container with the local backend directory. This means that the Python dependencies installed during the Docker build (in the image) will be overwritten by the local directory contents, which doesn't include the installed packages. This will cause import errors when the backend tries to import dependencies like fastapi, uvicorn, etc.
A common solution is to use a named volume for the Python packages directory or exclude it from the bind mount. Alternatively, dependencies could be installed in the command, but this defeats the purpose of using Docker layers for caching.
Copilot
AI
Jan 27, 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 environment variables HOST and PORT are set in docker-compose.yml but these are not standard environment variables used by uvicorn. Uvicorn reads command-line arguments or standard variables. The --host and --port flags in the command override these environment variables anyway, making them redundant. If the intention was to make these configurable, they should be used in the command like: command: uvicorn main:app --host ${HOST:-0.0.0.0} --port ${PORT:-5000} --reload
| environment: | |
| - HOST=0.0.0.0 | |
| - PORT=5000 |
Copilot
AI
Jan 27, 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 .env file path is specified as a root-level file, but based on the project structure, API keys and secrets should be in backend/.env (as indicated by backend/.env.example). The env_file path should be "./backend/.env" to correctly load the backend's environment variables containing GITHUB_TOKEN, MISTRAL_API_KEY, etc. The current configuration will fail to load API credentials needed for the AI backend to function.
| # Load environment variables from .env file if it exists | |
| env_file: | |
| - .env | |
| # Load environment variables from backend/.env file if it exists | |
| env_file: | |
| - ./backend/.env |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,194 @@ | ||
| const fs = require('fs'); | ||
| const path = require('path'); | ||
| const Agent = require('../../src/agent'); | ||
|
|
||
| // Mock dependencies | ||
| jest.mock('../../src/ui', () => ({ | ||
| info: jest.fn(), | ||
| warning: jest.fn(), | ||
| success: jest.fn(), | ||
| error: jest.fn(), | ||
| spinner: jest.fn(() => ({ start: jest.fn(), stop: jest.fn() })), | ||
| section: jest.fn(), | ||
| space: jest.fn(), | ||
| confirm: jest.fn(), | ||
| displayFileOp: jest.fn(), | ||
| displayDiff: jest.fn() | ||
| })); | ||
|
|
||
| jest.mock('../../src/configManager', () => ({ | ||
| initializeProjectStorage: jest.fn(), | ||
| loadProjectMemory: jest.fn(() => []), | ||
| saveProjectMemory: jest.fn(), | ||
| clearProjectMemory: jest.fn(), | ||
| getConfig: jest.fn() | ||
| })); | ||
|
|
||
| jest.mock('axios'); | ||
|
|
||
| describe('Skills.md Loading', () => { | ||
| let tempDir; | ||
| const originalCwd = process.cwd(); | ||
|
|
||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| // Create a temporary directory for testing | ||
| tempDir = path.join(originalCwd, 'test-temp-skills'); | ||
| if (!fs.existsSync(tempDir)) { | ||
| fs.mkdirSync(tempDir, { recursive: true }); | ||
| } | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| // Clean up temporary directory | ||
| if (fs.existsSync(tempDir)) { | ||
| const files = fs.readdirSync(tempDir); | ||
| for (const file of files) { | ||
| fs.unlinkSync(path.join(tempDir, file)); | ||
| } | ||
| fs.rmdirSync(tempDir); | ||
| } | ||
| }); | ||
|
|
||
| describe('loadSkillsPrompt', () => { | ||
| it('should load Skills.md when it exists', () => { | ||
| const skillsContent = '# Frontend Skills\n- Use modern design patterns'; | ||
| fs.writeFileSync(path.join(tempDir, 'Skills.md'), skillsContent); | ||
|
|
||
| const agent = new Agent({ workingDir: tempDir }); | ||
| agent.loadSkillsPrompt(); | ||
|
|
||
| expect(agent.skillsPrompt).toBe(skillsContent); | ||
| }); | ||
|
|
||
| it('should not set skillsPrompt when Skills.md does not exist', () => { | ||
| const agent = new Agent({ workingDir: tempDir }); | ||
| agent.loadSkillsPrompt(); | ||
|
|
||
| expect(agent.skillsPrompt).toBeNull(); | ||
| }); | ||
|
|
||
| it('should handle empty Skills.md gracefully', () => { | ||
| fs.writeFileSync(path.join(tempDir, 'Skills.md'), ' '); | ||
|
|
||
| const agent = new Agent({ workingDir: tempDir }); | ||
| agent.loadSkillsPrompt(); | ||
|
|
||
| expect(agent.skillsPrompt).toBe(''); | ||
| }); | ||
| }); | ||
|
|
||
| describe('loadCustomPrompt', () => { | ||
| it('should load Coderrr.md when it exists', () => { | ||
| const taskContent = '# Task: Build landing page'; | ||
| fs.writeFileSync(path.join(tempDir, 'Coderrr.md'), taskContent); | ||
|
|
||
| const agent = new Agent({ workingDir: tempDir }); | ||
| agent.loadCustomPrompt(); | ||
|
|
||
| expect(agent.customPrompt).toBe(taskContent); | ||
| }); | ||
|
|
||
| it('should not set customPrompt when Coderrr.md does not exist', () => { | ||
| const agent = new Agent({ workingDir: tempDir }); | ||
| agent.loadCustomPrompt(); | ||
|
|
||
| expect(agent.customPrompt).toBeNull(); | ||
| }); | ||
| }); | ||
|
|
||
| describe('Prompt Priority Order', () => { | ||
| it('should construct prompt with Skills.md before Coderrr.md', () => { | ||
| const skillsContent = '# Skills\n- Be modern'; | ||
| const taskContent = '# Task\n- Build a form'; | ||
| fs.writeFileSync(path.join(tempDir, 'Skills.md'), skillsContent); | ||
| fs.writeFileSync(path.join(tempDir, 'Coderrr.md'), taskContent); | ||
|
|
||
| const agent = new Agent({ workingDir: tempDir, scanOnFirstRequest: false }); | ||
| agent.loadSkillsPrompt(); | ||
| agent.loadCustomPrompt(); | ||
|
|
||
| // Simulate prompt construction (from chat method logic) | ||
| let enhancedPrompt = 'User request'; | ||
|
|
||
| // Task guidance first (will be wrapped by skills) | ||
| if (agent.customPrompt) { | ||
| enhancedPrompt = `[TASK GUIDANCE]\n${agent.customPrompt}\n\n[USER REQUEST]\n${enhancedPrompt}`; | ||
| } | ||
|
|
||
| // Skills prepended (comes before everything else) | ||
| if (agent.skillsPrompt) { | ||
| enhancedPrompt = `[SKILLS]\n${agent.skillsPrompt}\n\n${enhancedPrompt}`; | ||
| } | ||
|
|
||
| // Verify priority order: Skills comes first | ||
| expect(enhancedPrompt.startsWith('[SKILLS]')).toBe(true); | ||
| expect(enhancedPrompt.indexOf('[SKILLS]')).toBeLessThan(enhancedPrompt.indexOf('[TASK GUIDANCE]')); | ||
| expect(enhancedPrompt.indexOf('[TASK GUIDANCE]')).toBeLessThan(enhancedPrompt.indexOf('[USER REQUEST]')); | ||
| }); | ||
|
|
||
| it('should work with only Skills.md (no Coderrr.md)', () => { | ||
| const skillsContent = '# Skills\n- Be creative'; | ||
| fs.writeFileSync(path.join(tempDir, 'Skills.md'), skillsContent); | ||
|
|
||
| const agent = new Agent({ workingDir: tempDir, scanOnFirstRequest: false }); | ||
| agent.loadSkillsPrompt(); | ||
| agent.loadCustomPrompt(); | ||
|
|
||
| let enhancedPrompt = 'User request'; | ||
|
|
||
| if (agent.customPrompt) { | ||
| enhancedPrompt = `[TASK GUIDANCE]\n${agent.customPrompt}\n\n[USER REQUEST]\n${enhancedPrompt}`; | ||
| } | ||
|
|
||
| if (agent.skillsPrompt) { | ||
| enhancedPrompt = `[SKILLS]\n${agent.skillsPrompt}\n\n${enhancedPrompt}`; | ||
| } | ||
|
|
||
| expect(enhancedPrompt).toContain('[SKILLS]'); | ||
| expect(enhancedPrompt).not.toContain('[TASK GUIDANCE]'); | ||
| expect(enhancedPrompt).toContain('User request'); | ||
| }); | ||
|
|
||
| it('should work with only Coderrr.md (no Skills.md)', () => { | ||
| const taskContent = '# Task\n- Do something'; | ||
| fs.writeFileSync(path.join(tempDir, 'Coderrr.md'), taskContent); | ||
|
|
||
| const agent = new Agent({ workingDir: tempDir, scanOnFirstRequest: false }); | ||
| agent.loadSkillsPrompt(); | ||
| agent.loadCustomPrompt(); | ||
|
|
||
| let enhancedPrompt = 'User request'; | ||
|
|
||
| if (agent.customPrompt) { | ||
| enhancedPrompt = `[TASK GUIDANCE]\n${agent.customPrompt}\n\n[USER REQUEST]\n${enhancedPrompt}`; | ||
| } | ||
|
|
||
| if (agent.skillsPrompt) { | ||
| enhancedPrompt = `[SKILLS]\n${agent.skillsPrompt}\n\n${enhancedPrompt}`; | ||
| } | ||
|
|
||
| expect(enhancedPrompt).not.toContain('[SKILLS]'); | ||
| expect(enhancedPrompt).toContain('[TASK GUIDANCE]'); | ||
| expect(enhancedPrompt).toContain('User request'); | ||
| }); | ||
|
|
||
| it('should work with neither Skills.md nor Coderrr.md', () => { | ||
| const agent = new Agent({ workingDir: tempDir, scanOnFirstRequest: false }); | ||
| agent.loadSkillsPrompt(); | ||
| agent.loadCustomPrompt(); | ||
|
|
||
| let enhancedPrompt = 'User request'; | ||
|
|
||
| if (agent.customPrompt) { | ||
| enhancedPrompt = `[TASK GUIDANCE]\n${agent.customPrompt}\n\n[USER REQUEST]\n${enhancedPrompt}`; | ||
| } | ||
|
|
||
| if (agent.skillsPrompt) { | ||
| enhancedPrompt = `[SKILLS]\n${agent.skillsPrompt}\n\n${enhancedPrompt}`; | ||
| } | ||
|
|
||
| expect(enhancedPrompt).toBe('User request'); | ||
| }); | ||
| }); | ||
| }); |
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 .dockerignore file excludes docker-compose.yml, Dockerfile, and .dockerignore itself from the Docker build context. However, these files are in the root directory while the Docker build context is set to "./backend" in docker-compose.yml. These exclusions have no effect since the files aren't in the backend directory. Additionally, excluding docker-compose.yml and Dockerfile from the build context is unnecessary since they wouldn't be copied anyway - the COPY commands in the Dockerfile only copy requirements.txt and the application code.