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:
@@ -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).
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user