feat(qa): add dynamic MCP tool injection based on project type

Implements context-aware MCP tool injection for QA agents to optimize
context window usage. Instead of including all browser automation tools
and documentation for every project, the system now detects project
capabilities and injects only relevant tools.

Key changes:
- Add project_context.py with capability detection (Electron, web
  frontend, API, database) from project_index.json
- Create modular MCP tool docs (prompts/mcp_tools/) that are injected
  dynamically based on detected capabilities
- Update permissions.py to filter MCP tools by project type
- Update client.py to pass capabilities through tool chain
- Add smart cache for project index refresh at spec creation

Context window savings:
- Electron apps: Only 4 Electron tools (not 12+ browser tools)
- Web frontends: Only 8 Puppeteer tools
- CLI projects: No browser tools at all

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
AndyMik90
2025-12-20 20:35:21 +01:00
parent 1d1e15446d
commit 757e5e04d2
12 changed files with 1045 additions and 155 deletions
+13
View File
@@ -19,6 +19,19 @@ TOOL_RECORD_GOTCHA = "mcp__auto-claude__record_gotcha"
TOOL_GET_SESSION_CONTEXT = "mcp__auto-claude__get_session_context"
TOOL_UPDATE_QA_STATUS = "mcp__auto-claude__update_qa_status"
# Puppeteer MCP tools for web browser automation
# Used for web frontend validation (non-Electron web apps)
PUPPETEER_TOOLS = [
"mcp__puppeteer__puppeteer_connect_active_tab",
"mcp__puppeteer__puppeteer_navigate",
"mcp__puppeteer__puppeteer_screenshot",
"mcp__puppeteer__puppeteer_click",
"mcp__puppeteer__puppeteer_fill",
"mcp__puppeteer__puppeteer_select",
"mcp__puppeteer__puppeteer_hover",
"mcp__puppeteer__puppeteer_evaluate",
]
# Electron MCP tools for desktop app automation (when ELECTRON_MCP_ENABLED is set)
# Uses electron-mcp-server to connect to Electron apps via Chrome DevTools Protocol.
# Electron app must be started with --remote-debugging-port=9222 (or ELECTRON_DEBUG_PORT).
+61 -5
View File
@@ -4,12 +4,17 @@ Agent Tool Permissions
Manages which tools are allowed for each agent type to prevent context
pollution and accidental misuse.
Supports dynamic tool filtering based on project capabilities to optimize
context window usage. For example, Electron tools are only included for
Electron projects, not for Next.js or CLI projects.
"""
from .models import (
BASE_READ_TOOLS,
BASE_WRITE_TOOLS,
ELECTRON_TOOLS,
PUPPETEER_TOOLS,
TOOL_GET_BUILD_PROGRESS,
TOOL_GET_SESSION_CONTEXT,
TOOL_RECORD_DISCOVERY,
@@ -20,15 +25,26 @@ from .models import (
)
def get_allowed_tools(agent_type: str) -> list[str]:
def get_allowed_tools(
agent_type: str,
project_capabilities: dict | None = None,
) -> list[str]:
"""
Get the list of allowed tools for a specific agent type.
This ensures each agent only sees tools relevant to their role,
preventing context pollution and accidental misuse.
When project_capabilities is provided, MCP tools are filtered based on
the project type. For example:
- Electron projects get Electron MCP tools
- Web frontends (non-Electron) get Puppeteer MCP tools
- CLI projects get neither
Args:
agent_type: One of 'planner', 'coder', 'qa_reviewer', 'qa_fixer'
project_capabilities: Optional dict from detect_project_capabilities()
containing flags like is_electron, is_web_frontend, etc.
Returns:
List of allowed tool names
@@ -79,9 +95,49 @@ def get_allowed_tools(agent_type: str) -> list[str]:
mapping = tool_mappings[agent_type]
tools = mapping["base"] + mapping["auto_claude"]
# Add Electron MCP tools for QA agents only (when enabled)
# This prevents context bloat for coder/planner agents who don't need desktop automation
if agent_type in ("qa_reviewer", "qa_fixer") and is_electron_mcp_enabled():
tools.extend(ELECTRON_TOOLS)
# Add MCP tools for QA agents only, based on project capabilities
if agent_type in ("qa_reviewer", "qa_fixer"):
tools.extend(
_get_qa_mcp_tools(project_capabilities)
)
return tools
def _get_qa_mcp_tools(project_capabilities: dict | None) -> list[str]:
"""
Get the list of MCP tools for QA agents based on project capabilities.
This function determines which MCP tools to include based on:
1. Project type detection (Electron, web frontend, etc.)
2. Environment variables (ELECTRON_MCP_ENABLED)
Args:
project_capabilities: Dict from detect_project_capabilities() or None
Returns:
List of MCP tool names to include
"""
tools = []
# If no capabilities provided, fall back to legacy behavior
# (check env var only)
if project_capabilities is None:
if is_electron_mcp_enabled():
tools.extend(ELECTRON_TOOLS)
return tools
# Project-capability-based tool selection
is_electron = project_capabilities.get("is_electron", False)
is_web_frontend = project_capabilities.get("is_web_frontend", False)
# Electron projects get Electron MCP tools (if enabled)
if is_electron and is_electron_mcp_enabled():
tools.extend(ELECTRON_TOOLS)
# Web frontends (non-Electron) get Puppeteer tools
# Puppeteer is always available, no env var check needed
if is_web_frontend and not is_electron:
tools.extend(PUPPETEER_TOOLS)
return tools
+60 -32
View File
@@ -20,6 +20,7 @@ from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient
from claude_agent_sdk.types import HookMatcher
from core.auth import get_sdk_env_vars, require_auth_token
from linear_updater import is_linear_enabled
from prompts_pkg.project_context import detect_project_capabilities, load_project_index
from security import bash_security_hook
@@ -174,10 +175,16 @@ def create_client(
# Check if custom auto-claude tools are available
auto_claude_tools_enabled = is_tools_available()
# Load project capabilities for dynamic MCP tool selection
# This enables context-aware tool injection based on project type
project_index = load_project_index(project_dir)
project_capabilities = detect_project_capabilities(project_index)
# Build the list of allowed tools
# Start with agent-specific tools (includes base tools + auto-claude tools)
# Pass project capabilities for dynamic MCP tool filtering
if auto_claude_tools_enabled:
allowed_tools_list = get_agent_allowed_tools(agent_type)
allowed_tools_list = get_agent_allowed_tools(agent_type, project_capabilities)
else:
allowed_tools_list = [*BUILTIN_TOOLS]
@@ -187,17 +194,30 @@ def create_client(
# Check if Electron MCP is enabled (for QA agents testing Electron apps)
electron_mcp_enabled = is_electron_mcp_enabled()
# Add external MCP tools
allowed_tools_list.extend(PUPPETEER_TOOLS)
allowed_tools_list.extend(CONTEXT7_TOOLS)
# Add external MCP tools based on project capabilities
# This saves context window by only including relevant tools
allowed_tools_list.extend(CONTEXT7_TOOLS) # Always available
if linear_enabled:
allowed_tools_list.extend(LINEAR_TOOLS)
if graphiti_mcp_enabled:
allowed_tools_list.extend(GRAPHITI_MCP_TOOLS)
# Add Electron MCP tools only for QA agents (qa_reviewer, qa_fixer) when enabled
# This prevents context bloat for coder/planner agents who don't need desktop automation
if electron_mcp_enabled and agent_type in ("qa_reviewer", "qa_fixer"):
allowed_tools_list.extend(ELECTRON_TOOLS)
# Add browser automation tools only for QA agents, based on project type
# Electron apps get Electron MCP tools; web frontends get Puppeteer tools
if agent_type in ("qa_reviewer", "qa_fixer"):
if project_capabilities.get("is_electron") and electron_mcp_enabled:
allowed_tools_list.extend(ELECTRON_TOOLS)
elif project_capabilities.get("is_web_frontend"):
allowed_tools_list.extend(PUPPETEER_TOOLS)
# CLI/backend-only projects get neither - saves context
# Determine which browser automation tools to allow based on project type
browser_tools_permissions = []
if agent_type in ("qa_reviewer", "qa_fixer"):
if project_capabilities.get("is_electron") and electron_mcp_enabled:
browser_tools_permissions = ELECTRON_TOOLS
elif project_capabilities.get("is_web_frontend"):
browser_tools_permissions = PUPPETEER_TOOLS
# Create comprehensive security settings
# Note: Using relative paths ("./**") restricts access to project directory
@@ -216,21 +236,14 @@ def create_client(
# Bash permission granted here, but actual commands are validated
# by the bash_security_hook (see security.py for allowed commands)
"Bash(*)",
# Allow Puppeteer MCP tools for browser automation
*PUPPETEER_TOOLS,
# Allow Context7 MCP tools for documentation lookup
*CONTEXT7_TOOLS,
# Allow Linear MCP tools for project management (if enabled)
*(LINEAR_TOOLS if linear_enabled else []),
# Allow Graphiti MCP tools for knowledge graph memory (if enabled)
*(GRAPHITI_MCP_TOOLS if graphiti_mcp_enabled else []),
# Allow Electron MCP tools for QA agents only (if enabled)
*(
ELECTRON_TOOLS
if electron_mcp_enabled
and agent_type in ("qa_reviewer", "qa_fixer")
else []
),
# Allow browser automation tools based on project type
*browser_tools_permissions,
],
},
}
@@ -249,26 +262,50 @@ def create_client(
else:
print(" - Extended thinking: disabled")
mcp_servers_list = ["puppeteer (browser automation)", "context7 (documentation)"]
# Build list of MCP servers for display
mcp_servers_list = ["context7 (documentation)"]
if agent_type in ("qa_reviewer", "qa_fixer"):
if project_capabilities.get("is_electron") and electron_mcp_enabled:
mcp_servers_list.append(
f"electron (desktop automation, port {get_electron_debug_port()})"
)
elif project_capabilities.get("is_web_frontend"):
mcp_servers_list.append("puppeteer (browser automation)")
if linear_enabled:
mcp_servers_list.append("linear (project management)")
if graphiti_mcp_enabled:
mcp_servers_list.append("graphiti-memory (knowledge graph)")
if electron_mcp_enabled:
mcp_servers_list.append(
f"electron (desktop automation, port {get_electron_debug_port()})"
)
if auto_claude_tools_enabled:
mcp_servers_list.append(f"auto-claude ({agent_type} tools)")
print(f" - MCP servers: {', '.join(mcp_servers_list)}")
# Show detected project capabilities for QA agents
if agent_type in ("qa_reviewer", "qa_fixer") and any(project_capabilities.values()):
caps = [k.replace("is_", "").replace("has_", "") for k, v in project_capabilities.items() if v]
print(f" - Project capabilities: {', '.join(caps)}")
print()
# Configure MCP servers
mcp_servers = {
"puppeteer": {"command": "npx", "args": ["puppeteer-mcp-server"]},
"context7": {"command": "npx", "args": ["-y", "@upstash/context7-mcp"]},
}
# Add browser automation MCP server based on project type
if agent_type in ("qa_reviewer", "qa_fixer"):
if project_capabilities.get("is_electron") and electron_mcp_enabled:
# Electron MCP for desktop apps
# Electron app must be started with --remote-debugging-port=<port>
mcp_servers["electron"] = {
"command": "npm",
"args": ["exec", "electron-mcp-server"],
}
elif project_capabilities.get("is_web_frontend"):
# Puppeteer for web frontends
mcp_servers["puppeteer"] = {
"command": "npx",
"args": ["puppeteer-mcp-server"],
}
# Add Linear MCP server if enabled
if linear_enabled:
mcp_servers["linear"] = {
@@ -285,15 +322,6 @@ def create_client(
"url": get_graphiti_mcp_url(),
}
# Add Electron MCP server if enabled
# Electron app must be started with --remote-debugging-port=<port> for connection
# Uses electron-mcp-server to connect to Electron via Chrome DevTools Protocol
if electron_mcp_enabled:
mcp_servers["electron"] = {
"command": "npm",
"args": ["exec", "electron-mcp-server"],
}
# Add custom auto-claude MCP server if available
auto_claude_mcp_server = None
if auto_claude_tools_enabled:
@@ -0,0 +1,122 @@
## API VALIDATION
For applications with API endpoints, verify routes, authentication, and response formats.
### Validation Steps
#### Step 1: Verify Endpoints Exist
Check that new/modified endpoints are properly registered:
**FastAPI:**
```bash
# Start server and check /docs or /openapi.json
curl http://localhost:8000/openapi.json | jq '.paths | keys'
```
**Express/Node:**
```bash
# Use route listing if available, or check source
grep -r "router\.\(get\|post\|put\|delete\)" --include="*.js" --include="*.ts" .
```
**Django REST:**
```bash
python manage.py show_urls
```
#### Step 2: Test Endpoint Responses
For each new/modified endpoint, verify:
**Success case:**
```bash
curl -X GET http://localhost:8000/api/resource \
-H "Content-Type: application/json" \
| jq .
```
**With authentication (if required):**
```bash
curl -X GET http://localhost:8000/api/resource \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json"
```
**POST with body:**
```bash
curl -X POST http://localhost:8000/api/resource \
-H "Content-Type: application/json" \
-d '{"field": "value"}'
```
#### Step 3: Verify Error Handling
Test error cases return appropriate status codes:
**400 - Bad Request (validation error):**
```bash
curl -X POST http://localhost:8000/api/resource \
-H "Content-Type: application/json" \
-d '{"invalid": "data"}'
# Should return 400 with error details
```
**401 - Unauthorized (missing auth):**
```bash
curl -X GET http://localhost:8000/api/protected-resource
# Should return 401
```
**404 - Not Found:**
```bash
curl -X GET http://localhost:8000/api/resource/nonexistent-id
# Should return 404
```
#### Step 4: Verify Response Format
Check that responses match expected schema:
```bash
# Verify JSON structure
curl http://localhost:8000/api/resource | jq 'keys'
# Check specific fields exist
curl http://localhost:8000/api/resource | jq '.data | has("id", "name")'
```
### Document Findings
```
API VERIFICATION:
- Endpoints registered: YES/NO
- Response formats: PASS/FAIL
- Error handling: PASS/FAIL
- Authentication: PASS/FAIL (if applicable)
- Issues: [list or "None"]
ENDPOINTS TESTED:
| Method | Path | Status | Notes |
|--------|------|--------|-------|
| GET | /api/resource | PASS | 200 OK |
| POST | /api/resource | PASS | 201 Created |
```
### Common Issues
**Missing Route Registration:**
Endpoint code exists but route not registered:
1. Check router imports
2. Verify middleware order
3. Check route prefix/base path
**Incorrect Status Codes:**
Wrong HTTP status returned:
1. 200 for created resources (should be 201)
2. 200 for errors (should be 4xx/5xx)
**Missing Validation:**
Invalid input accepted:
1. Add request body validation
2. Add parameter type checking
@@ -0,0 +1,105 @@
## DATABASE VALIDATION
For applications with database dependencies, verify migrations and schema integrity.
### Validation Steps
#### Step 1: Check Migrations Exist
Verify migration files were created for any schema changes:
**Django:**
```bash
python manage.py showmigrations
```
**Rails:**
```bash
rails db:migrate:status
```
**Prisma:**
```bash
npx prisma migrate status
```
**Alembic (SQLAlchemy):**
```bash
alembic history
alembic current
```
**Drizzle:**
```bash
npx drizzle-kit status
```
#### Step 2: Verify Migrations Apply
Test that migrations can be applied to a fresh database:
**Django:**
```bash
python manage.py migrate --plan
```
**Prisma:**
```bash
npx prisma migrate deploy --preview-feature
```
**Alembic:**
```bash
alembic upgrade head
```
#### Step 3: Verify Schema Matches Models
Check that database schema matches the model definitions:
**Prisma:**
```bash
npx prisma validate
npx prisma db pull --print
```
**Django:**
```bash
python manage.py makemigrations --check --dry-run
```
#### Step 4: Check for Data Integrity
If the feature modifies existing data:
1. Verify data migrations handle edge cases
2. Check for null constraints on new fields
3. Verify foreign key relationships
### Document Findings
```
DATABASE VERIFICATION:
- Migrations exist: YES/NO
- Migrations applied: YES/NO
- Schema correct: YES/NO
- Data integrity: PASS/FAIL
- Issues: [list or "None"]
```
### Common Issues
**Missing Migration:**
If a model changed but no migration file exists:
1. Flag as CRITICAL issue
2. Require developer to generate migration
**Migration Fails:**
If migration cannot be applied:
1. Check for dependency issues
2. Verify database connection
3. Check for conflicting migrations
**Schema Drift:**
If database schema doesn't match models:
1. Generate new migration
2. Review the diff for unexpected changes
@@ -0,0 +1,121 @@
## ELECTRON APP VALIDATION
For Electron/desktop applications, use the electron-mcp-server tools to validate the UI.
**Prerequisites:**
- `ELECTRON_MCP_ENABLED=true` in environment
- Electron app running with `--remote-debugging-port=9222`
- Start with: `pnpm run dev:mcp` or `pnpm run start:mcp`
### Available Tools
| Tool | Purpose |
|------|---------|
| `mcp__electron__get_electron_window_info` | Get info about running Electron windows |
| `mcp__electron__take_screenshot` | Capture screenshot of Electron window |
| `mcp__electron__send_command_to_electron` | Send commands (click, fill, evaluate JS) |
| `mcp__electron__read_electron_logs` | Read console logs from Electron app |
### Validation Flow
#### Step 1: Connect to Electron App
```
Tool: mcp__electron__get_electron_window_info
```
Verify the app is running and get window information. If no app found, document that Electron validation was skipped.
#### Step 2: Capture Screenshot
```
Tool: mcp__electron__take_screenshot
```
Take a screenshot to visually verify the current state of the application.
#### Step 3: Analyze Page Structure
```
Tool: mcp__electron__send_command_to_electron
Command: get_page_structure
```
Get an organized overview of all interactive elements (buttons, inputs, selects, links).
#### Step 4: Verify UI Elements
Use `send_command_to_electron` with specific commands:
**Click elements by text:**
```
Command: click_by_text
Args: {"text": "Button Text"}
```
**Click elements by selector:**
```
Command: click_by_selector
Args: {"selector": "button.submit-btn"}
```
**Fill input fields:**
```
Command: fill_input
Args: {"selector": "#email", "value": "test@example.com"}
# Or by placeholder:
Args: {"placeholder": "Enter email", "value": "test@example.com"}
```
**Send keyboard shortcuts:**
```
Command: send_keyboard_shortcut
Args: {"text": "Enter"}
# Or: {"text": "Ctrl+N"}, {"text": "Meta+N"}, {"text": "Escape"}
```
**Execute JavaScript:**
```
Command: eval
Args: {"code": "document.title"}
```
#### Step 5: Check Console Logs
```
Tool: mcp__electron__read_electron_logs
Args: {"logType": "console", "lines": 50}
```
Check for JavaScript errors, warnings, or failed operations.
### Document Findings
```
ELECTRON VALIDATION:
- App Connection: PASS/FAIL
- Debug port accessible: YES/NO
- Connected to correct window: YES/NO
- UI Verification: PASS/FAIL
- Screenshots captured: [list]
- Visual elements correct: PASS/FAIL
- Interactions working: PASS/FAIL
- Console Errors: [list or "None"]
- Electron-Specific Features: PASS/FAIL
- [Feature]: PASS/FAIL
- Issues: [list or "None"]
```
### Handling Common Issues
**App Not Running:**
If Electron app is not running or debug port is not accessible:
1. Document that Electron validation was skipped
2. Note reason: "App not running with --remote-debugging-port=9222"
3. Add to QA report as "Manual verification required"
**Headless Environment (CI/CD):**
If running in headless environment without display:
1. Skip interactive Electron validation
2. Document: "Electron UI validation skipped - headless environment"
3. Rely on unit/integration tests for validation
@@ -0,0 +1,99 @@
## WEB BROWSER VALIDATION
For web frontend applications, use Puppeteer MCP tools for browser automation and validation.
### Available Tools
| Tool | Purpose |
|------|---------|
| `mcp__puppeteer__puppeteer_connect_active_tab` | Connect to browser tab |
| `mcp__puppeteer__puppeteer_navigate` | Navigate to URL |
| `mcp__puppeteer__puppeteer_screenshot` | Take screenshot |
| `mcp__puppeteer__puppeteer_click` | Click element |
| `mcp__puppeteer__puppeteer_fill` | Fill input field |
| `mcp__puppeteer__puppeteer_select` | Select dropdown option |
| `mcp__puppeteer__puppeteer_hover` | Hover over element |
| `mcp__puppeteer__puppeteer_evaluate` | Execute JavaScript |
### Validation Flow
#### Step 1: Navigate to Page
```
Tool: mcp__puppeteer__puppeteer_navigate
Args: {"url": "http://localhost:3000"}
```
Navigate to the development server URL.
#### Step 2: Take Screenshot
```
Tool: mcp__puppeteer__puppeteer_screenshot
Args: {"name": "page-initial-state"}
```
Capture the initial page state for visual verification.
#### Step 3: Verify Elements Exist
```
Tool: mcp__puppeteer__puppeteer_evaluate
Args: {"script": "document.querySelector('[data-testid=\"feature\"]') !== null"}
```
Check that expected elements are present on the page.
#### Step 4: Test Interactions
**Click buttons/links:**
```
Tool: mcp__puppeteer__puppeteer_click
Args: {"selector": "[data-testid=\"submit-button\"]"}
```
**Fill form fields:**
```
Tool: mcp__puppeteer__puppeteer_fill
Args: {"selector": "input[name=\"email\"]", "value": "test@example.com"}
```
**Select dropdown options:**
```
Tool: mcp__puppeteer__puppeteer_select
Args: {"selector": "select[name=\"country\"]", "value": "US"}
```
#### Step 5: Check Console for Errors
```
Tool: mcp__puppeteer__puppeteer_evaluate
Args: {"script": "window.__consoleErrors || []"}
```
Or set up error capture before testing:
```
Tool: mcp__puppeteer__puppeteer_evaluate
Args: {
"script": "window.__consoleErrors = []; const origError = console.error; console.error = (...args) => { window.__consoleErrors.push(args); origError.apply(console, args); };"
}
```
### Document Findings
```
BROWSER VERIFICATION:
- [Page/Component]: PASS/FAIL
- Console errors: [list or "None"]
- Visual check: PASS/FAIL
- Interactions: PASS/FAIL
```
### Common Selectors
When testing UI elements, prefer these selector strategies:
1. `[data-testid="..."]` - Most reliable (if available)
2. `#id` - Element IDs
3. `button:contains("Text")` - By visible text
4. `.class-name` - CSS classes
5. `input[name="..."]` - Form fields by name
+8 -118
View File
@@ -164,122 +164,12 @@ BROWSER VERIFICATION:
---
## PHASE 4.5: ELECTRON VALIDATION (If Electron App)
For Electron/desktop applications, use the electron-mcp-server tools to validate the application.
**Prerequisites:**
- Electron app must be running with `--remote-debugging-port=9222`
- `ELECTRON_MCP_ENABLED=true` in environment
### 4.5.1: Connect to Electron App
```
# Use puppeteer to connect to the running Electron app
Tool: mcp__puppeteer__puppeteer_connect_active_tab
Input: { "debugPort": 9222 }
```
**Note:** The Electron app must be started with remote debugging enabled:
```bash
./your-electron-app --remote-debugging-port=9222
```
### 4.5.2: Navigate and Screenshot
```
# Navigate to specific routes/views within the Electron app
Tool: mcp__puppeteer__puppeteer_navigate
Input: { "url": "file:///path/or/route" }
# Take screenshot of current state (compressed to stay under SDK buffer limit)
Tool: mcp__puppeteer__puppeteer_screenshot
Input: { "name": "electron-app-main-view", "width": 1280, "height": 720, "quality": 60, "type": "jpeg" }
```
### 4.5.3: Verify UI Elements
```
# Check for specific elements
Tool: mcp__puppeteer__puppeteer_evaluate
Input: { "script": "document.querySelector('[data-testid=\"feature\"]') !== null" }
# Click on elements to test interactions
Tool: mcp__puppeteer__puppeteer_click
Input: { "selector": "[data-testid=\"button\"]" }
# Fill form fields
Tool: mcp__puppeteer__puppeteer_fill
Input: { "selector": "input[name=\"field\"]", "value": "test value" }
```
### 4.5.4: Check Console for Errors
```
# Execute script to capture console errors
Tool: mcp__puppeteer__puppeteer_evaluate
Input: { "script": "window.__consoleErrors || []" }
```
**CRITICAL**: Check for:
- JavaScript runtime errors
- Electron IPC communication errors
- Node.js integration errors (if nodeIntegration is enabled)
- Failed file system operations
### 4.5.5: Test Electron-Specific Features
Verify features unique to Electron:
- Window controls (minimize, maximize, close)
- Native menus and dialogs
- System tray integration (if applicable)
- File system access
- IPC communication between main and renderer processes
### 4.5.6: Document Findings
```
ELECTRON VALIDATION:
- App Connection: PASS/FAIL
- Debug port accessible: YES/NO
- Connected to correct window: YES/NO
- UI Verification: PASS/FAIL
- Screenshots captured: [list]
- Visual elements correct: PASS/FAIL
- Interactions working: PASS/FAIL
- Console Errors: [list or "None"]
- Electron-Specific Features: PASS/FAIL
- [Feature]: PASS/FAIL
- Issues: [list or "None"]
```
### 4.5.7: Handling Common Issues
**App Not Running:**
```
If Electron app is not running or debug port is not accessible:
1. Document that Electron validation was skipped
2. Note reason: "App not running with --remote-debugging-port=9222"
3. Add to QA report as "Manual verification required"
```
**Multiple Electron Instances:**
```
If multiple Electron apps are running:
1. Use targetUrl parameter to connect to specific app
Tool: mcp__puppeteer__puppeteer_connect_active_tab
Input: { "debugPort": 9222, "targetUrl": "file:///path/to/expected/url" }
```
**Headless Environment (CI/CD):**
```
If running in headless environment without display:
1. Skip interactive Electron validation
2. Document: "Electron UI validation skipped - headless environment"
3. Rely on unit/integration tests for validation
```
---
<!-- PROJECT-SPECIFIC VALIDATION TOOLS WILL BE INJECTED HERE -->
<!-- The following sections are dynamically added based on project type: -->
<!-- - Electron validation (for Electron apps) -->
<!-- - Puppeteer browser automation (for web frontends) -->
<!-- - Database validation (for projects with databases) -->
<!-- - API validation (for projects with API endpoints) -->
## PHASE 5: DATABASE VERIFICATION (If Applicable)
@@ -465,7 +355,7 @@ Create a comprehensive QA report:
| Integration Tests | ✓/✗ | X/Y passing |
| E2E Tests | ✓/✗ | X/Y passing |
| Browser Verification | ✓/✗ | [summary] |
| Electron Validation | ✓/✗ | [summary or "N/A - not Electron"] |
| Project-Specific Validation | ✓/✗ | [summary based on project type] |
| Database Verification | ✓/✗ | [summary] |
| Third-Party API Validation | ✓/✗ | [Context7 verification summary] |
| Security Review | ✓/✗ | [summary] |
@@ -631,7 +521,7 @@ All acceptance criteria verified:
- Integration tests: PASS
- E2E tests: PASS
- Browser verification: PASS
- Electron validation: PASS (or N/A)
- Project-specific validation: PASS (or N/A)
- Database verification: PASS
- Security review: PASS
- Regression check: PASS
+17
View File
@@ -20,9 +20,19 @@ from .prompts import (
get_coding_prompt,
get_followup_planner_prompt,
get_planner_prompt,
get_qa_fixer_prompt,
get_qa_reviewer_prompt,
is_first_run,
)
# Import project context utilities
from .project_context import (
detect_project_capabilities,
get_mcp_tools_for_project,
load_project_index,
should_refresh_project_index,
)
__all__ = [
# prompt_generator functions
"get_relative_spec_path",
@@ -35,5 +45,12 @@ __all__ = [
"get_planner_prompt",
"get_coding_prompt",
"get_followup_planner_prompt",
"get_qa_reviewer_prompt",
"get_qa_fixer_prompt",
"is_first_run",
# project_context functions
"load_project_index",
"detect_project_capabilities",
"get_mcp_tools_for_project",
"should_refresh_project_index",
]
+259
View File
@@ -0,0 +1,259 @@
"""
Project Context Detection
=========================
Detects project capabilities from project_index.json to determine which
MCP tools and validation sections are relevant for the project.
This enables dynamic prompt assembly where QA agents only receive documentation
for tools relevant to their project type (Electron, Expo, Next.js, etc.),
saving context window and keeping agents focused.
"""
import json
from pathlib import Path
def load_project_index(project_dir: Path) -> dict:
"""
Load project_index.json from the project's .auto-claude directory.
Args:
project_dir: Root directory of the project
Returns:
Parsed project index dict, or empty dict if not found
"""
index_file = project_dir / ".auto-claude" / "project_index.json"
if not index_file.exists():
return {}
try:
with open(index_file) as f:
return json.load(f)
except (json.JSONDecodeError, OSError):
return {}
def detect_project_capabilities(project_index: dict) -> dict:
"""
Detect what MCP tools and validation types are relevant for this project.
Analyzes the project_index.json to identify:
- Desktop app frameworks (Electron, Tauri)
- Mobile frameworks (Expo, React Native)
- Web frontend frameworks (React, Vue, Next.js, etc.)
- Backend capabilities (APIs, databases)
Args:
project_index: Parsed project_index.json dict
Returns:
Dictionary of capability flags:
- is_electron: True if project uses Electron
- is_tauri: True if project uses Tauri
- is_expo: True if project uses Expo
- is_react_native: True if project uses React Native
- is_web_frontend: True if project has web frontend (React, Vue, etc.)
- is_nextjs: True if project uses Next.js
- is_nuxt: True if project uses Nuxt
- has_api: True if project has API routes
- has_database: True if project has database connections
"""
capabilities = {
# Desktop app frameworks
"is_electron": False,
"is_tauri": False,
# Mobile frameworks
"is_expo": False,
"is_react_native": False,
# Web frontend frameworks
"is_web_frontend": False,
"is_nextjs": False,
"is_nuxt": False,
# Backend capabilities
"has_api": False,
"has_database": False,
}
services = project_index.get("services", {})
# Handle both dict format (services by name) and list format
if isinstance(services, dict):
service_list = services.values()
elif isinstance(services, list):
service_list = services
else:
service_list = []
for service in service_list:
if not isinstance(service, dict):
continue
# Collect all dependencies
deps = set()
for dep in service.get("dependencies", []):
if isinstance(dep, str):
deps.add(dep.lower())
for dep in service.get("dev_dependencies", []):
if isinstance(dep, str):
deps.add(dep.lower())
# Get framework (normalize to lowercase)
framework = str(service.get("framework", "")).lower()
# Desktop app detection
if "electron" in deps or any("@electron" in d for d in deps):
capabilities["is_electron"] = True
if "@tauri-apps/api" in deps or "tauri" in deps:
capabilities["is_tauri"] = True
# Mobile framework detection
if "expo" in deps:
capabilities["is_expo"] = True
if "react-native" in deps:
capabilities["is_react_native"] = True
# Web frontend detection
web_frameworks = ("react", "vue", "svelte", "angular", "solid")
if framework in web_frameworks:
capabilities["is_web_frontend"] = True
# Meta-framework detection
if framework in ("nextjs", "next.js", "next"):
capabilities["is_nextjs"] = True
capabilities["is_web_frontend"] = True
if framework in ("nuxt", "nuxt.js"):
capabilities["is_nuxt"] = True
capabilities["is_web_frontend"] = True
# Also check deps for framework indicators
if "next" in deps:
capabilities["is_nextjs"] = True
capabilities["is_web_frontend"] = True
if "nuxt" in deps:
capabilities["is_nuxt"] = True
capabilities["is_web_frontend"] = True
if "vite" in deps and not capabilities["is_electron"]:
# Vite usually indicates web frontend (unless Electron)
capabilities["is_web_frontend"] = True
# API detection
api_info = service.get("api", {})
if isinstance(api_info, dict) and api_info.get("routes"):
capabilities["has_api"] = True
# Database detection
if service.get("database"):
capabilities["has_database"] = True
# Also check for ORM/database deps
db_deps = {"prisma", "drizzle-orm", "typeorm", "sequelize", "mongoose",
"sqlalchemy", "alembic", "django", "peewee"}
if deps & db_deps:
capabilities["has_database"] = True
return capabilities
def should_refresh_project_index(project_dir: Path) -> bool:
"""
Check if project_index.json needs refresh based on dependency file changes.
Uses smart caching: only refresh if dependency files (package.json,
pyproject.toml, etc.) have been modified since the last index generation.
Args:
project_dir: Root directory of the project
Returns:
True if index should be regenerated, False if cache is still valid
"""
index_file = project_dir / ".auto-claude" / "project_index.json"
if not index_file.exists():
return True # No index, must generate
try:
index_mtime = index_file.stat().st_mtime
except OSError:
return True # Can't stat file, regenerate
# Check all dependency files that could change frameworks
dep_files = [
project_dir / "package.json",
project_dir / "pyproject.toml",
project_dir / "requirements.txt",
project_dir / "Gemfile",
project_dir / "go.mod",
project_dir / "Cargo.toml",
project_dir / "composer.json",
]
for dep_file in dep_files:
try:
if dep_file.exists() and dep_file.stat().st_mtime > index_mtime:
return True # Dependency file changed, refresh needed
except OSError:
continue # Skip files we can't stat
# Also check subdirectories for monorepos (first level only)
try:
for subdir in project_dir.iterdir():
if not subdir.is_dir():
continue
# Skip hidden dirs and common non-service dirs
if subdir.name.startswith(".") or subdir.name in (
"node_modules", "__pycache__", "dist", "build", ".git"
):
continue
subdir_pkg = subdir / "package.json"
try:
if subdir_pkg.exists() and subdir_pkg.stat().st_mtime > index_mtime:
return True
except OSError:
continue
subdir_pyproject = subdir / "pyproject.toml"
try:
if subdir_pyproject.exists() and subdir_pyproject.stat().st_mtime > index_mtime:
return True
except OSError:
continue
except OSError:
pass # Can't iterate dir, use cached index
return False # Cache is fresh
def get_mcp_tools_for_project(capabilities: dict) -> list[str]:
"""
Get list of MCP tool documentation files to include based on capabilities.
Args:
capabilities: Dict from detect_project_capabilities()
Returns:
List of prompt file paths (relative to prompts/) to include
"""
tools = []
# Desktop app validation
if capabilities.get("is_electron"):
tools.append("mcp_tools/electron_validation.md")
if capabilities.get("is_tauri"):
tools.append("mcp_tools/tauri_validation.md")
# Web browser automation (for non-Electron web apps)
if capabilities.get("is_web_frontend") and not capabilities.get("is_electron"):
tools.append("mcp_tools/puppeteer_browser.md")
# Database validation
if capabilities.get("has_database"):
tools.append("mcp_tools/database_validation.md")
# API testing
if capabilities.get("has_api"):
tools.append("mcp_tools/api_validation.md")
return tools
+147
View File
@@ -3,11 +3,18 @@ Prompt Loading Utilities
========================
Functions for loading agent prompts from markdown files.
Supports dynamic prompt assembly based on project type for context optimization.
"""
import json
from pathlib import Path
from .project_context import (
detect_project_capabilities,
get_mcp_tools_for_project,
load_project_index,
)
# Directory containing prompt files
# prompts/ is a sibling directory of prompts_pkg/, so go up one level first
PROMPTS_DIR = Path(__file__).parent.parent / "prompts"
@@ -267,3 +274,143 @@ def is_first_run(spec_dir: Path) -> bool:
except (OSError, json.JSONDecodeError):
# If we can't read the file, treat as first run
return True
def _load_prompt_file(filename: str) -> str:
"""
Load a prompt file from the prompts directory.
Args:
filename: Relative path to prompt file (e.g., "qa_reviewer.md" or "mcp_tools/electron_validation.md")
Returns:
Content of the prompt file
Raises:
FileNotFoundError: If prompt file doesn't exist
"""
prompt_file = PROMPTS_DIR / filename
if not prompt_file.exists():
raise FileNotFoundError(f"Prompt file not found: {prompt_file}")
return prompt_file.read_text()
def get_qa_reviewer_prompt(spec_dir: Path, project_dir: Path) -> str:
"""
Load the QA reviewer prompt with project-specific MCP tools dynamically injected.
This function:
1. Loads the base QA reviewer prompt
2. Detects project capabilities from project_index.json
3. Injects only relevant MCP tool documentation (Electron, Puppeteer, DB, API)
This saves context window by excluding irrelevant tool docs.
For example, a CLI Python project won't get Electron validation docs.
Args:
spec_dir: Directory containing the spec files
project_dir: Root directory of the project
Returns:
The QA reviewer prompt with project-specific tools injected
"""
# Load base QA reviewer prompt
base_prompt = _load_prompt_file("qa_reviewer.md")
# Load project index and detect capabilities
project_index = load_project_index(project_dir)
capabilities = detect_project_capabilities(project_index)
# Get list of MCP tool doc files to include
mcp_tool_files = get_mcp_tools_for_project(capabilities)
# Load and assemble MCP tool sections
mcp_sections = []
for tool_file in mcp_tool_files:
try:
section = _load_prompt_file(tool_file)
mcp_sections.append(section)
except FileNotFoundError:
# Skip missing files gracefully
pass
# Inject spec context at the beginning
spec_context = f"""## SPEC LOCATION
Your spec and progress files are located at:
- Spec: `{spec_dir}/spec.md`
- Implementation plan: `{spec_dir}/implementation_plan.json`
- Progress notes: `{spec_dir}/build-progress.txt`
- QA report output: `{spec_dir}/qa_report.md`
- Fix request output: `{spec_dir}/QA_FIX_REQUEST.md`
The project root is: `{project_dir}`
---
## PROJECT CAPABILITIES DETECTED
"""
# Add capability summary for transparency
active_caps = [k for k, v in capabilities.items() if v]
if active_caps:
spec_context += "Based on project analysis, the following capabilities were detected:\n"
for cap in active_caps:
cap_name = cap.replace("is_", "").replace("has_", "").replace("_", " ").title()
spec_context += f"- {cap_name}\n"
spec_context += "\nRelevant validation tools have been included below.\n\n"
else:
spec_context += "No special project capabilities detected. Using standard validation.\n\n"
spec_context += "---\n\n"
# Find injection point in base prompt (after PHASE 4, before PHASE 5)
injection_marker = "<!-- PROJECT-SPECIFIC VALIDATION TOOLS WILL BE INJECTED HERE -->"
if mcp_sections and injection_marker in base_prompt:
# Replace marker with actual MCP tool sections
mcp_content = "\n\n---\n\n## PROJECT-SPECIFIC VALIDATION TOOLS\n\n"
mcp_content += "The following validation tools are available based on your project type:\n\n"
mcp_content += "\n\n---\n\n".join(mcp_sections)
mcp_content += "\n\n---\n"
# Replace the multi-line marker comment block
import re
marker_pattern = r"<!-- PROJECT-SPECIFIC VALIDATION TOOLS WILL BE INJECTED HERE -->.*?<!-- - API validation \(for projects with API endpoints\) -->"
base_prompt = re.sub(marker_pattern, mcp_content, base_prompt, flags=re.DOTALL)
elif mcp_sections:
# Fallback: append at the end if marker not found
base_prompt += "\n\n---\n\n## PROJECT-SPECIFIC VALIDATION TOOLS\n\n"
base_prompt += "\n\n---\n\n".join(mcp_sections)
return spec_context + base_prompt
def get_qa_fixer_prompt(spec_dir: Path, project_dir: Path) -> str:
"""
Load the QA fixer prompt with spec paths injected.
Args:
spec_dir: Directory containing the spec files
project_dir: Root directory of the project
Returns:
The QA fixer prompt content with paths injected
"""
base_prompt = _load_prompt_file("qa_fixer.md")
spec_context = f"""## SPEC LOCATION
Your spec and progress files are located at:
- Spec: `{spec_dir}/spec.md`
- Implementation plan: `{spec_dir}/implementation_plan.json`
- QA fix request: `{spec_dir}/QA_FIX_REQUEST.md` (READ THIS FIRST!)
- QA report: `{spec_dir}/qa_report.md`
The project root is: `{project_dir}`
---
"""
return spec_context + base_prompt
+33
View File
@@ -9,7 +9,9 @@ import json
from collections.abc import Callable
from pathlib import Path
from analysis.analyzers import analyze_project
from phase_config import get_spec_phase_thinking_budget
from prompts_pkg.project_context import should_refresh_project_index
from review import run_review_checkpoint
from task_logger import (
LogEntryType,
@@ -185,6 +187,34 @@ class SpecOrchestrator:
# Don't fail the pipeline if summarization fails
print_status(f"Phase summarization skipped: {e}", "warning")
async def _ensure_fresh_project_index(self) -> None:
"""Ensure project_index.json is up-to-date before spec creation.
Uses smart caching: only regenerates if dependency files (package.json,
pyproject.toml, etc.) have been modified since the last index generation.
This ensures QA agents receive accurate project capability information
for dynamic MCP tool injection.
"""
index_file = self.project_dir / ".auto-claude" / "project_index.json"
if should_refresh_project_index(self.project_dir):
if index_file.exists():
print_status("Project dependencies changed, refreshing index...", "progress")
else:
print_status("Generating project index...", "progress")
try:
# Regenerate project index
analyze_project(self.project_dir, index_file)
print_status("Project index updated", "success")
except Exception as e:
print_status(f"Project index refresh failed: {e}", "warning")
# Don't fail spec creation if indexing fails - continue with cached/missing
else:
if index_file.exists():
print_status("Using cached project index", "info")
# If no index exists and no refresh needed, that's fine - capabilities will be empty
async def run(self, interactive: bool = True, auto_approve: bool = False) -> bool:
"""Run the spec creation process with dynamic phase selection.
@@ -212,6 +242,9 @@ class SpecOrchestrator:
)
)
# Smart cache: refresh project index if dependency files have changed
await self._ensure_fresh_project_index()
# Create phase executor
phase_executor = phases.PhaseExecutor(
project_dir=self.project_dir,