mirror of
https://github.com/leonvanzyl/autocoder.git
synced 2026-02-01 15:03:36 +00:00
feat: Add YOLO mode for rapid prototyping without browser testing
Add a new YOLO (You Only Live Once) mode that skips all browser testing and regression tests for faster feature iteration during prototyping. Changes made: **Core YOLO Mode Implementation:** - Add --yolo CLI flag to autonomous_agent_demo.py - Update agent.py to accept yolo_mode parameter and select appropriate prompt - Modify client.py to conditionally include Playwright MCP server (excluded in YOLO mode) - Add coding_prompt_yolo.template.md with static analysis only verification - Add get_coding_prompt_yolo() to prompts.py **Server/API Updates:** - Add AgentStartRequest schema with yolo_mode field - Update AgentStatus to include yolo_mode - Modify process_manager.py to pass --yolo flag to subprocess - Update agent router to accept yolo_mode in start request **UI Updates:** - Add YOLO toggle button (lightning bolt icon) in AgentControl - Show YOLO mode indicator when agent is running in YOLO mode - Add useAgentStatus hook to track current mode - Update startAgent API to accept yoloMode parameter - Add YOLO toggle in SpecCreationChat completion flow **Spec Creation Improvements:** - Fix create-spec.md to properly replace [FEATURE_COUNT] placeholder - Add REQUIRED FEATURE COUNT section to initializer_prompt.template.md - Fix spec_chat_session.py to create security settings file for Claude SDK - Delete app_spec.txt before spec creation to allow fresh creation **Documentation:** - Add YOLO mode section to CLAUDE.md with usage examples - Add checkpoint.md slash command for creating detailed commits 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
40
.claude/commands/checkpoint.md
Normal file
40
.claude/commands/checkpoint.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
description: Create commit with detailed comment
|
||||||
|
---
|
||||||
|
|
||||||
|
Please create a comprehensive checkpoint commit with the following steps:
|
||||||
|
|
||||||
|
1. **Initialize Git if needed**: Run `git init` if git has not been instantiated for the project yet.
|
||||||
|
|
||||||
|
2. **Analyze all changes**:
|
||||||
|
|
||||||
|
- Run `git status` to see all tracked and untracked files
|
||||||
|
- Run `git diff` to see detailed changes in tracked files
|
||||||
|
- Run `git log -5 --oneline` to understand the commit message style of this repository
|
||||||
|
|
||||||
|
3. **Stage everything**:
|
||||||
|
|
||||||
|
- Add ALL tracked changes (modified and deleted files)
|
||||||
|
- Add ALL untracked files (new files)
|
||||||
|
- Use `git add -A` or `git add .` to stage everything
|
||||||
|
|
||||||
|
4. **Create a detailed commit message**:
|
||||||
|
|
||||||
|
- **First line**: Write a clear, concise summary (50-72 chars) describing the primary change
|
||||||
|
- Use imperative mood (e.g., "Add feature" not "Added feature")
|
||||||
|
- Examples: "feat: add user authentication", "fix: resolve database connection issue", "refactor: improve API route structure"
|
||||||
|
- **Body**: Provide a detailed description including:
|
||||||
|
- What changes were made (list of key modifications)
|
||||||
|
- Why these changes were made (purpose/motivation)
|
||||||
|
- Any important technical details or decisions
|
||||||
|
- Breaking changes or migration notes if applicable
|
||||||
|
- **Footer**: Include co-author attribution as shown in the Git Safety Protocol
|
||||||
|
|
||||||
|
5. **Execute the commit**: Create the commit with the properly formatted message following this repository's conventions.
|
||||||
|
|
||||||
|
IMPORTANT:
|
||||||
|
|
||||||
|
- Do NOT skip any files - include everything
|
||||||
|
- Make the commit message descriptive enough that someone reviewing the git log can understand what was accomplished
|
||||||
|
- Follow the project's existing commit message conventions (check git log first)
|
||||||
|
- Include the Claude Code co-author attribution in the commit message
|
||||||
@@ -459,10 +459,19 @@ Create a new file using this XML structure:
|
|||||||
If the output directory has an existing `initializer_prompt.md`, read it and update the feature count.
|
If the output directory has an existing `initializer_prompt.md`, read it and update the feature count.
|
||||||
If not, copy from `.claude/templates/initializer_prompt.template.md` first, then update.
|
If not, copy from `.claude/templates/initializer_prompt.template.md` first, then update.
|
||||||
|
|
||||||
Update the feature count references to match the derived count from Phase 4L:
|
**CRITICAL: You MUST update the feature count placeholder:**
|
||||||
|
|
||||||
- Line containing "create ... test cases" - update to the derived feature count
|
1. Find the line containing `**[FEATURE_COUNT]**` in the "REQUIRED FEATURE COUNT" section
|
||||||
- Line containing "Minimum ... features" - update to the derived feature count
|
2. Replace `[FEATURE_COUNT]` with the exact number agreed upon in Phase 4L (e.g., `25`)
|
||||||
|
3. The result should read like: `You must create exactly **25** features using the...`
|
||||||
|
|
||||||
|
**Example edit:**
|
||||||
|
```
|
||||||
|
Before: **CRITICAL:** You must create exactly **[FEATURE_COUNT]** features using the `feature_create_bulk` tool.
|
||||||
|
After: **CRITICAL:** You must create exactly **25** features using the `feature_create_bulk` tool.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verify the update:** After editing, read the file again to confirm the feature count appears correctly. If `[FEATURE_COUNT]` still appears in the file, the update failed and you must try again.
|
||||||
|
|
||||||
**Note:** You do NOT need to update `coding_prompt.md` - the coding agent works through features one at a time regardless of total count.
|
**Note:** You do NOT need to update `coding_prompt.md` - the coding agent works through features one at a time regardless of total count.
|
||||||
|
|
||||||
|
|||||||
274
.claude/templates/coding_prompt_yolo.template.md
Normal file
274
.claude/templates/coding_prompt_yolo.template.md
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
<!-- YOLO MODE PROMPT - Keep synchronized with coding_prompt.template.md -->
|
||||||
|
<!-- Last synced: 2026-01-01 -->
|
||||||
|
|
||||||
|
## YOLO MODE - Rapid Prototyping (Testing Disabled)
|
||||||
|
|
||||||
|
**WARNING:** This mode skips all browser testing and regression tests.
|
||||||
|
Features are marked as passing after lint/type-check succeeds.
|
||||||
|
Use for rapid prototyping only - not for production-quality development.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## YOUR ROLE - CODING AGENT (YOLO MODE)
|
||||||
|
|
||||||
|
You are continuing work on a long-running autonomous development task.
|
||||||
|
This is a FRESH context window - you have no memory of previous sessions.
|
||||||
|
|
||||||
|
### STEP 1: GET YOUR BEARINGS (MANDATORY)
|
||||||
|
|
||||||
|
Start by orienting yourself:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. See your working directory
|
||||||
|
pwd
|
||||||
|
|
||||||
|
# 2. List files to understand project structure
|
||||||
|
ls -la
|
||||||
|
|
||||||
|
# 3. Read the project specification to understand what you're building
|
||||||
|
cat app_spec.txt
|
||||||
|
|
||||||
|
# 4. Read progress notes from previous sessions
|
||||||
|
cat claude-progress.txt
|
||||||
|
|
||||||
|
# 5. Check recent git history
|
||||||
|
git log --oneline -20
|
||||||
|
```
|
||||||
|
|
||||||
|
Then use MCP tools to check feature status:
|
||||||
|
|
||||||
|
```
|
||||||
|
# 6. Get progress statistics (passing/total counts)
|
||||||
|
Use the feature_get_stats tool
|
||||||
|
|
||||||
|
# 7. Get the next feature to work on
|
||||||
|
Use the feature_get_next tool
|
||||||
|
```
|
||||||
|
|
||||||
|
Understanding the `app_spec.txt` is critical - it contains the full requirements
|
||||||
|
for the application you're building.
|
||||||
|
|
||||||
|
### STEP 2: START SERVERS (IF NOT RUNNING)
|
||||||
|
|
||||||
|
If `init.sh` exists, run it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x init.sh
|
||||||
|
./init.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Otherwise, start servers manually and document the process.
|
||||||
|
|
||||||
|
### STEP 3: CHOOSE ONE FEATURE TO IMPLEMENT
|
||||||
|
|
||||||
|
Get the next feature to implement:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Get the highest-priority pending feature
|
||||||
|
Use the feature_get_next tool
|
||||||
|
```
|
||||||
|
|
||||||
|
Once you've retrieved the feature, **immediately mark it as in-progress**:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Mark feature as in-progress to prevent other sessions from working on it
|
||||||
|
Use the feature_mark_in_progress tool with feature_id=42
|
||||||
|
```
|
||||||
|
|
||||||
|
Focus on completing one feature in this session before moving on to other features.
|
||||||
|
It's ok if you only complete one feature in this session, as there will be more sessions later that continue to make progress.
|
||||||
|
|
||||||
|
#### When to Skip a Feature (EXTREMELY RARE)
|
||||||
|
|
||||||
|
**Skipping should almost NEVER happen.** Only skip for truly external blockers you cannot control:
|
||||||
|
|
||||||
|
- **External API not configured**: Third-party service credentials missing (e.g., Stripe keys, OAuth secrets)
|
||||||
|
- **External service unavailable**: Dependency on service that's down or inaccessible
|
||||||
|
- **Environment limitation**: Hardware or system requirement you cannot fulfill
|
||||||
|
|
||||||
|
**NEVER skip because:**
|
||||||
|
|
||||||
|
| Situation | Wrong Action | Correct Action |
|
||||||
|
|-----------|--------------|----------------|
|
||||||
|
| "Page doesn't exist" | Skip | Create the page |
|
||||||
|
| "API endpoint missing" | Skip | Implement the endpoint |
|
||||||
|
| "Database table not ready" | Skip | Create the migration |
|
||||||
|
| "Component not built" | Skip | Build the component |
|
||||||
|
| "No data to test with" | Skip | Create test data or build data entry flow |
|
||||||
|
| "Feature X needs to be done first" | Skip | Build feature X as part of this feature |
|
||||||
|
|
||||||
|
If a feature requires building other functionality first, **build that functionality**. You are the coding agent - your job is to make the feature work, not to defer it.
|
||||||
|
|
||||||
|
If you must skip (truly external blocker only):
|
||||||
|
|
||||||
|
```
|
||||||
|
Use the feature_skip tool with feature_id={id}
|
||||||
|
```
|
||||||
|
|
||||||
|
Document the SPECIFIC external blocker in `claude-progress.txt`. "Functionality not built" is NEVER a valid reason.
|
||||||
|
|
||||||
|
### STEP 4: IMPLEMENT THE FEATURE
|
||||||
|
|
||||||
|
Implement the chosen feature thoroughly:
|
||||||
|
|
||||||
|
1. Write the code (frontend and/or backend as needed)
|
||||||
|
2. Ensure proper error handling
|
||||||
|
3. Follow existing code patterns in the codebase
|
||||||
|
|
||||||
|
### STEP 5: VERIFY WITH LINT AND TYPE CHECK (YOLO MODE)
|
||||||
|
|
||||||
|
**In YOLO mode, verification is done through static analysis only.**
|
||||||
|
|
||||||
|
Run the appropriate lint and type-check commands for your project:
|
||||||
|
|
||||||
|
**For TypeScript/JavaScript projects:**
|
||||||
|
```bash
|
||||||
|
npm run lint
|
||||||
|
npm run typecheck # or: npx tsc --noEmit
|
||||||
|
```
|
||||||
|
|
||||||
|
**For Python projects:**
|
||||||
|
```bash
|
||||||
|
ruff check .
|
||||||
|
mypy .
|
||||||
|
```
|
||||||
|
|
||||||
|
**If lint/type-check passes:** Proceed to mark the feature as passing.
|
||||||
|
|
||||||
|
**If lint/type-check fails:** Fix the errors before proceeding.
|
||||||
|
|
||||||
|
### STEP 6: UPDATE FEATURE STATUS
|
||||||
|
|
||||||
|
**YOU CAN ONLY MODIFY ONE FIELD: "passes"**
|
||||||
|
|
||||||
|
After lint/type-check passes, mark the feature as passing:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Mark feature #42 as passing (replace 42 with the actual feature ID)
|
||||||
|
Use the feature_mark_passing tool with feature_id=42
|
||||||
|
```
|
||||||
|
|
||||||
|
**NEVER:**
|
||||||
|
|
||||||
|
- Delete features
|
||||||
|
- Edit feature descriptions
|
||||||
|
- Modify feature steps
|
||||||
|
- Combine or consolidate features
|
||||||
|
- Reorder features
|
||||||
|
|
||||||
|
### STEP 7: COMMIT YOUR PROGRESS
|
||||||
|
|
||||||
|
Make a descriptive git commit:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .
|
||||||
|
git commit -m "Implement [feature name] - YOLO mode
|
||||||
|
|
||||||
|
- Added [specific changes]
|
||||||
|
- Lint/type-check passing
|
||||||
|
- Marked feature #X as passing
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
### STEP 8: UPDATE PROGRESS NOTES
|
||||||
|
|
||||||
|
Update `claude-progress.txt` with:
|
||||||
|
|
||||||
|
- What you accomplished this session
|
||||||
|
- Which feature(s) you completed
|
||||||
|
- Any issues discovered or fixed
|
||||||
|
- What should be worked on next
|
||||||
|
- Current completion status (e.g., "45/200 features passing")
|
||||||
|
|
||||||
|
### STEP 9: END SESSION CLEANLY
|
||||||
|
|
||||||
|
Before context fills up:
|
||||||
|
|
||||||
|
1. Commit all working code
|
||||||
|
2. Update claude-progress.txt
|
||||||
|
3. Mark features as passing if lint/type-check verified
|
||||||
|
4. Ensure no uncommitted changes
|
||||||
|
5. Leave app in working state
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## FEATURE TOOL USAGE RULES (CRITICAL - DO NOT VIOLATE)
|
||||||
|
|
||||||
|
The feature tools exist to reduce token usage. **DO NOT make exploratory queries.**
|
||||||
|
|
||||||
|
### ALLOWED Feature Tools (ONLY these):
|
||||||
|
|
||||||
|
```
|
||||||
|
# 1. Get progress stats (passing/in_progress/total counts)
|
||||||
|
feature_get_stats
|
||||||
|
|
||||||
|
# 2. Get the NEXT feature to work on (one feature only)
|
||||||
|
feature_get_next
|
||||||
|
|
||||||
|
# 3. Mark a feature as in-progress (call immediately after feature_get_next)
|
||||||
|
feature_mark_in_progress with feature_id={id}
|
||||||
|
|
||||||
|
# 4. Mark a feature as passing (after lint/type-check succeeds)
|
||||||
|
feature_mark_passing with feature_id={id}
|
||||||
|
|
||||||
|
# 5. Skip a feature (moves to end of queue) - ONLY when blocked by dependency
|
||||||
|
feature_skip with feature_id={id}
|
||||||
|
|
||||||
|
# 6. Clear in-progress status (when abandoning a feature)
|
||||||
|
feature_clear_in_progress with feature_id={id}
|
||||||
|
```
|
||||||
|
|
||||||
|
### RULES:
|
||||||
|
|
||||||
|
- Do NOT try to fetch lists of all features
|
||||||
|
- Do NOT query features by category
|
||||||
|
- Do NOT list all pending features
|
||||||
|
|
||||||
|
**You do NOT need to see all features.** The feature_get_next tool tells you exactly what to work on. Trust it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## EMAIL INTEGRATION (DEVELOPMENT MODE)
|
||||||
|
|
||||||
|
When building applications that require email functionality (password resets, email verification, notifications, etc.), you typically won't have access to a real email service or the ability to read email inboxes.
|
||||||
|
|
||||||
|
**Solution:** Configure the application to log emails to the terminal instead of sending them.
|
||||||
|
|
||||||
|
- Password reset links should be printed to the console
|
||||||
|
- Email verification links should be printed to the console
|
||||||
|
- Any notification content should be logged to the terminal
|
||||||
|
|
||||||
|
**During testing:**
|
||||||
|
|
||||||
|
1. Trigger the email action (e.g., click "Forgot Password")
|
||||||
|
2. Check the terminal/server logs for the generated link
|
||||||
|
3. Use that link directly to verify the functionality works
|
||||||
|
|
||||||
|
This allows you to fully test email-dependent flows without needing external email services.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## IMPORTANT REMINDERS (YOLO MODE)
|
||||||
|
|
||||||
|
**Your Goal:** Rapidly prototype the application with all features implemented
|
||||||
|
|
||||||
|
**This Session's Goal:** Complete at least one feature
|
||||||
|
|
||||||
|
**Quality Bar (YOLO Mode):**
|
||||||
|
|
||||||
|
- Code compiles without errors (lint/type-check passing)
|
||||||
|
- Follows existing code patterns
|
||||||
|
- Basic error handling in place
|
||||||
|
- Features are implemented according to spec
|
||||||
|
|
||||||
|
**Note:** Browser testing and regression testing are SKIPPED in YOLO mode.
|
||||||
|
Features may have bugs that would be caught by manual testing.
|
||||||
|
Use standard mode for production-quality verification.
|
||||||
|
|
||||||
|
**You have unlimited time.** Take as long as needed to implement features correctly.
|
||||||
|
The most important thing is that you leave the code base in a clean state before
|
||||||
|
terminating the session (Step 9).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Begin by running Step 1 (Get Your Bearings).
|
||||||
@@ -9,6 +9,16 @@ Start by reading `app_spec.txt` in your working directory. This file contains
|
|||||||
the complete specification for what you need to build. Read it carefully
|
the complete specification for what you need to build. Read it carefully
|
||||||
before proceeding.
|
before proceeding.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## REQUIRED FEATURE COUNT
|
||||||
|
|
||||||
|
**CRITICAL:** You must create exactly **[FEATURE_COUNT]** features using the `feature_create_bulk` tool.
|
||||||
|
|
||||||
|
This number was determined during spec creation and must be followed precisely. Do not create more or fewer features than specified.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### CRITICAL FIRST TASK: Create Features
|
### CRITICAL FIRST TASK: Create Features
|
||||||
|
|
||||||
Based on `app_spec.txt`, create features using the feature_create_bulk tool. The features are stored in a SQLite database,
|
Based on `app_spec.txt`, create features using the feature_create_bulk tool. The features are stored in a SQLite database,
|
||||||
|
|||||||
27
CLAUDE.md
27
CLAUDE.md
@@ -42,8 +42,35 @@ python start.py
|
|||||||
# Run agent directly for a project (use absolute path or registered name)
|
# Run agent directly for a project (use absolute path or registered name)
|
||||||
python autonomous_agent_demo.py --project-dir C:/Projects/my-app
|
python autonomous_agent_demo.py --project-dir C:/Projects/my-app
|
||||||
python autonomous_agent_demo.py --project-dir my-app # if registered
|
python autonomous_agent_demo.py --project-dir my-app # if registered
|
||||||
|
|
||||||
|
# YOLO mode: rapid prototyping without browser testing
|
||||||
|
python autonomous_agent_demo.py --project-dir my-app --yolo
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### YOLO Mode (Rapid Prototyping)
|
||||||
|
|
||||||
|
YOLO mode skips all testing for faster feature iteration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# CLI
|
||||||
|
python autonomous_agent_demo.py --project-dir my-app --yolo
|
||||||
|
|
||||||
|
# UI: Toggle the lightning bolt button before starting the agent
|
||||||
|
```
|
||||||
|
|
||||||
|
**What's different in YOLO mode:**
|
||||||
|
- No regression testing (skips `feature_get_for_regression`)
|
||||||
|
- No Playwright MCP server (browser automation disabled)
|
||||||
|
- Features marked passing after lint/type-check succeeds
|
||||||
|
- Faster iteration for prototyping
|
||||||
|
|
||||||
|
**What's the same:**
|
||||||
|
- Lint and type-check still run to verify code compiles
|
||||||
|
- Feature MCP server for tracking progress
|
||||||
|
- All other development tools available
|
||||||
|
|
||||||
|
**When to use:** Early prototyping when you want to quickly scaffold features without verification overhead. Switch back to standard mode for production-quality development.
|
||||||
|
|
||||||
### React UI (in ui/ directory)
|
### React UI (in ui/ directory)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
15
agent.py
15
agent.py
@@ -24,6 +24,7 @@ from progress import print_session_header, print_progress_summary, has_features
|
|||||||
from prompts import (
|
from prompts import (
|
||||||
get_initializer_prompt,
|
get_initializer_prompt,
|
||||||
get_coding_prompt,
|
get_coding_prompt,
|
||||||
|
get_coding_prompt_yolo,
|
||||||
copy_spec_to_project,
|
copy_spec_to_project,
|
||||||
has_project_prompts,
|
has_project_prompts,
|
||||||
)
|
)
|
||||||
@@ -111,6 +112,7 @@ async def run_autonomous_agent(
|
|||||||
project_dir: Path,
|
project_dir: Path,
|
||||||
model: str,
|
model: str,
|
||||||
max_iterations: Optional[int] = None,
|
max_iterations: Optional[int] = None,
|
||||||
|
yolo_mode: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Run the autonomous agent loop.
|
Run the autonomous agent loop.
|
||||||
@@ -119,12 +121,17 @@ async def run_autonomous_agent(
|
|||||||
project_dir: Directory for the project
|
project_dir: Directory for the project
|
||||||
model: Claude model to use
|
model: Claude model to use
|
||||||
max_iterations: Maximum number of iterations (None for unlimited)
|
max_iterations: Maximum number of iterations (None for unlimited)
|
||||||
|
yolo_mode: If True, skip browser testing and use YOLO prompt
|
||||||
"""
|
"""
|
||||||
print("\n" + "=" * 70)
|
print("\n" + "=" * 70)
|
||||||
print(" AUTONOMOUS CODING AGENT DEMO")
|
print(" AUTONOMOUS CODING AGENT DEMO")
|
||||||
print("=" * 70)
|
print("=" * 70)
|
||||||
print(f"\nProject directory: {project_dir}")
|
print(f"\nProject directory: {project_dir}")
|
||||||
print(f"Model: {model}")
|
print(f"Model: {model}")
|
||||||
|
if yolo_mode:
|
||||||
|
print("Mode: YOLO (testing disabled)")
|
||||||
|
else:
|
||||||
|
print("Mode: Standard (full testing)")
|
||||||
if max_iterations:
|
if max_iterations:
|
||||||
print(f"Max iterations: {max_iterations}")
|
print(f"Max iterations: {max_iterations}")
|
||||||
else:
|
else:
|
||||||
@@ -170,7 +177,7 @@ async def run_autonomous_agent(
|
|||||||
print_session_header(iteration, is_first_run)
|
print_session_header(iteration, is_first_run)
|
||||||
|
|
||||||
# Create client (fresh context)
|
# Create client (fresh context)
|
||||||
client = create_client(project_dir, model)
|
client = create_client(project_dir, model, yolo_mode=yolo_mode)
|
||||||
|
|
||||||
# Choose prompt based on session type
|
# Choose prompt based on session type
|
||||||
# Pass project_dir to enable project-specific prompts
|
# Pass project_dir to enable project-specific prompts
|
||||||
@@ -178,7 +185,11 @@ async def run_autonomous_agent(
|
|||||||
prompt = get_initializer_prompt(project_dir)
|
prompt = get_initializer_prompt(project_dir)
|
||||||
is_first_run = False # Only use initializer once
|
is_first_run = False # Only use initializer once
|
||||||
else:
|
else:
|
||||||
prompt = get_coding_prompt(project_dir)
|
# Use YOLO prompt if in YOLO mode
|
||||||
|
if yolo_mode:
|
||||||
|
prompt = get_coding_prompt_yolo(project_dir)
|
||||||
|
else:
|
||||||
|
prompt = get_coding_prompt(project_dir)
|
||||||
|
|
||||||
# Run session with async context manager
|
# Run session with async context manager
|
||||||
async with client:
|
async with client:
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ Example Usage:
|
|||||||
|
|
||||||
# Limit iterations for testing
|
# Limit iterations for testing
|
||||||
python autonomous_agent_demo.py --project-dir my-app --max-iterations 5
|
python autonomous_agent_demo.py --project-dir my-app --max-iterations 5
|
||||||
|
|
||||||
|
# YOLO mode: rapid prototyping without browser testing
|
||||||
|
python autonomous_agent_demo.py --project-dir my-app --yolo
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -57,6 +60,9 @@ Examples:
|
|||||||
# Limit iterations for testing
|
# Limit iterations for testing
|
||||||
python autonomous_agent_demo.py --project-dir my-app --max-iterations 5
|
python autonomous_agent_demo.py --project-dir my-app --max-iterations 5
|
||||||
|
|
||||||
|
# YOLO mode: rapid prototyping without browser testing
|
||||||
|
python autonomous_agent_demo.py --project-dir my-app --yolo
|
||||||
|
|
||||||
Authentication:
|
Authentication:
|
||||||
Uses Claude CLI credentials from ~/.claude/.credentials.json
|
Uses Claude CLI credentials from ~/.claude/.credentials.json
|
||||||
Run 'claude login' to authenticate (handled by start.bat/start.sh)
|
Run 'claude login' to authenticate (handled by start.bat/start.sh)
|
||||||
@@ -84,6 +90,13 @@ Authentication:
|
|||||||
help=f"Claude model to use (default: {DEFAULT_MODEL})",
|
help=f"Claude model to use (default: {DEFAULT_MODEL})",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--yolo",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="Enable YOLO mode: rapid prototyping without browser testing",
|
||||||
|
)
|
||||||
|
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
@@ -122,6 +135,7 @@ def main() -> None:
|
|||||||
project_dir=project_dir,
|
project_dir=project_dir,
|
||||||
model=args.model,
|
model=args.model,
|
||||||
max_iterations=args.max_iterations,
|
max_iterations=args.max_iterations,
|
||||||
|
yolo_mode=args.yolo,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
|||||||
90
client.py
90
client.py
@@ -72,13 +72,14 @@ BUILTIN_TOOLS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def create_client(project_dir: Path, model: str):
|
def create_client(project_dir: Path, model: str, yolo_mode: bool = False):
|
||||||
"""
|
"""
|
||||||
Create a Claude Agent SDK client with multi-layered security.
|
Create a Claude Agent SDK client with multi-layered security.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
project_dir: Directory for the project
|
project_dir: Directory for the project
|
||||||
model: Claude model to use
|
model: Claude model to use
|
||||||
|
yolo_mode: If True, skip Playwright MCP server for rapid prototyping
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Configured ClaudeSDKClient (from claude_agent_sdk)
|
Configured ClaudeSDKClient (from claude_agent_sdk)
|
||||||
@@ -92,6 +93,30 @@ def create_client(project_dir: Path, model: str):
|
|||||||
Note: Authentication is handled by start.bat/start.sh before this runs.
|
Note: Authentication is handled by start.bat/start.sh before this runs.
|
||||||
The Claude SDK auto-detects credentials from ~/.claude/.credentials.json
|
The Claude SDK auto-detects credentials from ~/.claude/.credentials.json
|
||||||
"""
|
"""
|
||||||
|
# Build allowed tools list based on mode
|
||||||
|
# In YOLO mode, exclude Playwright tools for faster prototyping
|
||||||
|
allowed_tools = [*BUILTIN_TOOLS, *FEATURE_MCP_TOOLS]
|
||||||
|
if not yolo_mode:
|
||||||
|
allowed_tools.extend(PLAYWRIGHT_TOOLS)
|
||||||
|
|
||||||
|
# Build permissions list
|
||||||
|
permissions_list = [
|
||||||
|
# Allow all file operations within the project directory
|
||||||
|
"Read(./**)",
|
||||||
|
"Write(./**)",
|
||||||
|
"Edit(./**)",
|
||||||
|
"Glob(./**)",
|
||||||
|
"Grep(./**)",
|
||||||
|
# Bash permission granted here, but actual commands are validated
|
||||||
|
# by the bash_security_hook (see security.py for allowed commands)
|
||||||
|
"Bash(*)",
|
||||||
|
# Allow Feature MCP tools for feature management
|
||||||
|
*FEATURE_MCP_TOOLS,
|
||||||
|
]
|
||||||
|
if not yolo_mode:
|
||||||
|
# Allow Playwright MCP tools for browser automation (standard mode only)
|
||||||
|
permissions_list.extend(PLAYWRIGHT_TOOLS)
|
||||||
|
|
||||||
# Create comprehensive security settings
|
# Create comprehensive security settings
|
||||||
# Note: Using relative paths ("./**") restricts access to project directory
|
# Note: Using relative paths ("./**") restricts access to project directory
|
||||||
# since cwd is set to project_dir
|
# since cwd is set to project_dir
|
||||||
@@ -99,21 +124,7 @@ def create_client(project_dir: Path, model: str):
|
|||||||
"sandbox": {"enabled": True, "autoAllowBashIfSandboxed": True},
|
"sandbox": {"enabled": True, "autoAllowBashIfSandboxed": True},
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"defaultMode": "acceptEdits", # Auto-approve edits within allowed directories
|
"defaultMode": "acceptEdits", # Auto-approve edits within allowed directories
|
||||||
"allow": [
|
"allow": permissions_list,
|
||||||
# Allow all file operations within the project directory
|
|
||||||
"Read(./**)",
|
|
||||||
"Write(./**)",
|
|
||||||
"Edit(./**)",
|
|
||||||
"Glob(./**)",
|
|
||||||
"Grep(./**)",
|
|
||||||
# Bash permission granted here, but actual commands are validated
|
|
||||||
# by the bash_security_hook (see security.py for allowed commands)
|
|
||||||
"Bash(*)",
|
|
||||||
# Allow Playwright MCP tools for browser automation
|
|
||||||
*PLAYWRIGHT_TOOLS,
|
|
||||||
# Allow Feature MCP tools for feature management
|
|
||||||
*FEATURE_MCP_TOOLS,
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +140,10 @@ def create_client(project_dir: Path, model: str):
|
|||||||
print(" - Sandbox enabled (OS-level bash isolation)")
|
print(" - Sandbox enabled (OS-level bash isolation)")
|
||||||
print(f" - Filesystem restricted to: {project_dir.resolve()}")
|
print(f" - Filesystem restricted to: {project_dir.resolve()}")
|
||||||
print(" - Bash commands restricted to allowlist (see security.py)")
|
print(" - Bash commands restricted to allowlist (see security.py)")
|
||||||
print(" - MCP servers: playwright (browser), features (database)")
|
if yolo_mode:
|
||||||
|
print(" - MCP servers: features (database) - YOLO MODE (no Playwright)")
|
||||||
|
else:
|
||||||
|
print(" - MCP servers: playwright (browser), features (database)")
|
||||||
print(" - Project settings enabled (skills, commands, CLAUDE.md)")
|
print(" - Project settings enabled (skills, commands, CLAUDE.md)")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
@@ -140,6 +154,27 @@ def create_client(project_dir: Path, model: str):
|
|||||||
else:
|
else:
|
||||||
print(" - Warning: System Claude CLI not found, using bundled CLI")
|
print(" - Warning: System Claude CLI not found, using bundled CLI")
|
||||||
|
|
||||||
|
# Build MCP servers config - features is always included, playwright only in standard mode
|
||||||
|
mcp_servers = {
|
||||||
|
"features": {
|
||||||
|
"command": sys.executable, # Use the same Python that's running this script
|
||||||
|
"args": ["-m", "mcp_server.feature_mcp"],
|
||||||
|
"env": {
|
||||||
|
# Inherit parent environment (PATH, ANTHROPIC_API_KEY, etc.)
|
||||||
|
**os.environ,
|
||||||
|
# Add custom variables
|
||||||
|
"PROJECT_DIR": str(project_dir.resolve()),
|
||||||
|
"PYTHONPATH": str(Path(__file__).parent.resolve()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if not yolo_mode:
|
||||||
|
# Include Playwright MCP server for browser automation (standard mode only)
|
||||||
|
mcp_servers["playwright"] = {
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["@playwright/mcp@latest", "--viewport-size", "1280x720"],
|
||||||
|
}
|
||||||
|
|
||||||
return ClaudeSDKClient(
|
return ClaudeSDKClient(
|
||||||
options=ClaudeAgentOptions(
|
options=ClaudeAgentOptions(
|
||||||
model=model,
|
model=model,
|
||||||
@@ -147,25 +182,8 @@ def create_client(project_dir: Path, model: str):
|
|||||||
system_prompt="You are an expert full-stack developer building a production-quality web application.",
|
system_prompt="You are an expert full-stack developer building a production-quality web application.",
|
||||||
setting_sources=["project"], # Enable skills, commands, and CLAUDE.md from project dir
|
setting_sources=["project"], # Enable skills, commands, and CLAUDE.md from project dir
|
||||||
max_buffer_size=10 * 1024 * 1024, # 10MB for large Playwright screenshots
|
max_buffer_size=10 * 1024 * 1024, # 10MB for large Playwright screenshots
|
||||||
allowed_tools=[
|
allowed_tools=allowed_tools,
|
||||||
*BUILTIN_TOOLS,
|
mcp_servers=mcp_servers,
|
||||||
*PLAYWRIGHT_TOOLS,
|
|
||||||
*FEATURE_MCP_TOOLS,
|
|
||||||
],
|
|
||||||
mcp_servers={
|
|
||||||
"playwright": {"command": "npx", "args": ["@playwright/mcp@latest", "--viewport-size", "1280x720"]},
|
|
||||||
"features": {
|
|
||||||
"command": sys.executable, # Use the same Python that's running this script
|
|
||||||
"args": ["-m", "mcp_server.feature_mcp"],
|
|
||||||
"env": {
|
|
||||||
# Inherit parent environment (PATH, ANTHROPIC_API_KEY, etc.)
|
|
||||||
**os.environ,
|
|
||||||
# Add custom variables
|
|
||||||
"PROJECT_DIR": str(project_dir.resolve()),
|
|
||||||
"PYTHONPATH": str(Path(__file__).parent.resolve()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hooks={
|
hooks={
|
||||||
"PreToolUse": [
|
"PreToolUse": [
|
||||||
HookMatcher(matcher="Bash", hooks=[bash_security_hook]),
|
HookMatcher(matcher="Bash", hooks=[bash_security_hook]),
|
||||||
|
|||||||
@@ -75,6 +75,11 @@ def get_coding_prompt(project_dir: Path | None = None) -> str:
|
|||||||
return load_prompt("coding_prompt", project_dir)
|
return load_prompt("coding_prompt", project_dir)
|
||||||
|
|
||||||
|
|
||||||
|
def get_coding_prompt_yolo(project_dir: Path | None = None) -> str:
|
||||||
|
"""Load the YOLO mode coding agent prompt (project-specific if available)."""
|
||||||
|
return load_prompt("coding_prompt_yolo", project_dir)
|
||||||
|
|
||||||
|
|
||||||
def get_app_spec(project_dir: Path) -> str:
|
def get_app_spec(project_dir: Path) -> str:
|
||||||
"""
|
"""
|
||||||
Load the app spec from the project.
|
Load the app spec from the project.
|
||||||
@@ -131,6 +136,7 @@ def scaffold_project_prompts(project_dir: Path) -> Path:
|
|||||||
templates = [
|
templates = [
|
||||||
("app_spec.template.txt", "app_spec.txt"),
|
("app_spec.template.txt", "app_spec.txt"),
|
||||||
("coding_prompt.template.md", "coding_prompt.md"),
|
("coding_prompt.template.md", "coding_prompt.md"),
|
||||||
|
("coding_prompt_yolo.template.md", "coding_prompt_yolo.md"),
|
||||||
("initializer_prompt.template.md", "initializer_prompt.md"),
|
("initializer_prompt.template.md", "initializer_prompt.md"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
|
|
||||||
from ..schemas import AgentStatus, AgentActionResponse
|
from ..schemas import AgentStatus, AgentActionResponse, AgentStartRequest
|
||||||
from ..services.process_manager import get_manager
|
from ..services.process_manager import get_manager
|
||||||
|
|
||||||
|
|
||||||
@@ -68,15 +68,19 @@ async def get_agent_status(project_name: str):
|
|||||||
status=manager.status,
|
status=manager.status,
|
||||||
pid=manager.pid,
|
pid=manager.pid,
|
||||||
started_at=manager.started_at,
|
started_at=manager.started_at,
|
||||||
|
yolo_mode=manager.yolo_mode,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/start", response_model=AgentActionResponse)
|
@router.post("/start", response_model=AgentActionResponse)
|
||||||
async def start_agent(project_name: str):
|
async def start_agent(
|
||||||
|
project_name: str,
|
||||||
|
request: AgentStartRequest = AgentStartRequest(),
|
||||||
|
):
|
||||||
"""Start the agent for a project."""
|
"""Start the agent for a project."""
|
||||||
manager = get_project_manager(project_name)
|
manager = get_project_manager(project_name)
|
||||||
|
|
||||||
success, message = await manager.start()
|
success, message = await manager.start(yolo_mode=request.yolo_mode)
|
||||||
|
|
||||||
return AgentActionResponse(
|
return AgentActionResponse(
|
||||||
success=success,
|
success=success,
|
||||||
|
|||||||
@@ -99,11 +99,17 @@ class FeatureListResponse(BaseModel):
|
|||||||
# Agent Schemas
|
# Agent Schemas
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
class AgentStartRequest(BaseModel):
|
||||||
|
"""Request schema for starting the agent."""
|
||||||
|
yolo_mode: bool = False
|
||||||
|
|
||||||
|
|
||||||
class AgentStatus(BaseModel):
|
class AgentStatus(BaseModel):
|
||||||
"""Current agent status."""
|
"""Current agent status."""
|
||||||
status: Literal["stopped", "running", "paused", "crashed"]
|
status: Literal["stopped", "running", "paused", "crashed"]
|
||||||
pid: int | None = None
|
pid: int | None = None
|
||||||
started_at: datetime | None = None
|
started_at: datetime | None = None
|
||||||
|
yolo_mode: bool = False
|
||||||
|
|
||||||
|
|
||||||
class AgentActionResponse(BaseModel):
|
class AgentActionResponse(BaseModel):
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ class AgentProcessManager:
|
|||||||
self._status: Literal["stopped", "running", "paused", "crashed"] = "stopped"
|
self._status: Literal["stopped", "running", "paused", "crashed"] = "stopped"
|
||||||
self.started_at: datetime | None = None
|
self.started_at: datetime | None = None
|
||||||
self._output_task: asyncio.Task | None = None
|
self._output_task: asyncio.Task | None = None
|
||||||
|
self.yolo_mode: bool = False # YOLO mode for rapid prototyping
|
||||||
|
|
||||||
# Support multiple callbacks (for multiple WebSocket clients)
|
# Support multiple callbacks (for multiple WebSocket clients)
|
||||||
self._output_callbacks: Set[Callable[[str], Awaitable[None]]] = set()
|
self._output_callbacks: Set[Callable[[str], Awaitable[None]]] = set()
|
||||||
@@ -214,10 +215,13 @@ class AgentProcessManager:
|
|||||||
self.status = "stopped"
|
self.status = "stopped"
|
||||||
self._remove_lock()
|
self._remove_lock()
|
||||||
|
|
||||||
async def start(self) -> tuple[bool, str]:
|
async def start(self, yolo_mode: bool = False) -> tuple[bool, str]:
|
||||||
"""
|
"""
|
||||||
Start the agent as a subprocess.
|
Start the agent as a subprocess.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
yolo_mode: If True, run in YOLO mode (no browser testing)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple of (success, message)
|
Tuple of (success, message)
|
||||||
"""
|
"""
|
||||||
@@ -227,6 +231,9 @@ class AgentProcessManager:
|
|||||||
if not self._check_lock():
|
if not self._check_lock():
|
||||||
return False, "Another agent instance is already running for this project"
|
return False, "Another agent instance is already running for this project"
|
||||||
|
|
||||||
|
# Store YOLO mode for status queries
|
||||||
|
self.yolo_mode = yolo_mode
|
||||||
|
|
||||||
# Build command - pass absolute path to project directory
|
# Build command - pass absolute path to project directory
|
||||||
cmd = [
|
cmd = [
|
||||||
sys.executable,
|
sys.executable,
|
||||||
@@ -235,6 +242,10 @@ class AgentProcessManager:
|
|||||||
str(self.project_dir.resolve()),
|
str(self.project_dir.resolve()),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Add --yolo flag if YOLO mode is enabled
|
||||||
|
if yolo_mode:
|
||||||
|
cmd.append("--yolo")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Start subprocess with piped stdout/stderr
|
# Start subprocess with piped stdout/stderr
|
||||||
# Use project_dir as cwd so Claude SDK sandbox allows access to project files
|
# Use project_dir as cwd so Claude SDK sandbox allows access to project files
|
||||||
@@ -295,6 +306,7 @@ class AgentProcessManager:
|
|||||||
self.status = "stopped"
|
self.status = "stopped"
|
||||||
self.process = None
|
self.process = None
|
||||||
self.started_at = None
|
self.started_at = None
|
||||||
|
self.yolo_mode = False # Reset YOLO mode
|
||||||
|
|
||||||
return True, "Agent stopped"
|
return True, "Agent stopped"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -375,6 +387,7 @@ class AgentProcessManager:
|
|||||||
"status": self.status,
|
"status": self.status,
|
||||||
"pid": self.pid,
|
"pid": self.pid,
|
||||||
"started_at": self.started_at.isoformat() if self.started_at else None,
|
"started_at": self.started_at.isoformat() if self.started_at else None,
|
||||||
|
"yolo_mode": self.yolo_mode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ Uses the create-spec.md skill to guide users through app spec creation.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import shutil
|
import shutil
|
||||||
import threading
|
import threading
|
||||||
@@ -87,6 +88,33 @@ class SpecChatSession:
|
|||||||
# Ensure project directory exists (like CLI does in start.py)
|
# Ensure project directory exists (like CLI does in start.py)
|
||||||
self.project_dir.mkdir(parents=True, exist_ok=True)
|
self.project_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Delete app_spec.txt so Claude can create it fresh
|
||||||
|
# The SDK requires reading existing files before writing, but app_spec.txt is created new
|
||||||
|
# Note: We keep initializer_prompt.md so Claude can read and update the template
|
||||||
|
prompts_dir = self.project_dir / "prompts"
|
||||||
|
app_spec_path = prompts_dir / "app_spec.txt"
|
||||||
|
if app_spec_path.exists():
|
||||||
|
app_spec_path.unlink()
|
||||||
|
logger.info("Deleted scaffolded app_spec.txt for fresh spec creation")
|
||||||
|
|
||||||
|
# Create security settings file (like client.py does)
|
||||||
|
# This grants permissions for file operations in the project directory
|
||||||
|
security_settings = {
|
||||||
|
"sandbox": {"enabled": False}, # Disable sandbox for spec creation
|
||||||
|
"permissions": {
|
||||||
|
"defaultMode": "acceptEdits",
|
||||||
|
"allow": [
|
||||||
|
"Read(./**)",
|
||||||
|
"Write(./**)",
|
||||||
|
"Edit(./**)",
|
||||||
|
"Glob(./**)",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
settings_file = self.project_dir / ".claude_settings.json"
|
||||||
|
with open(settings_file, "w") as f:
|
||||||
|
json.dump(security_settings, f, indent=2)
|
||||||
|
|
||||||
# Replace $ARGUMENTS with absolute project path (like CLI does in start.py:184)
|
# Replace $ARGUMENTS with absolute project path (like CLI does in start.py:184)
|
||||||
# Using absolute path avoids confusion when project folder name differs from app name
|
# Using absolute path avoids confusion when project folder name differs from app name
|
||||||
project_path = str(self.project_dir.resolve())
|
project_path = str(self.project_dir.resolve())
|
||||||
@@ -111,6 +139,7 @@ class SpecChatSession:
|
|||||||
permission_mode="acceptEdits", # Auto-approve file writes for spec creation
|
permission_mode="acceptEdits", # Auto-approve file writes for spec creation
|
||||||
max_turns=100,
|
max_turns=100,
|
||||||
cwd=str(self.project_dir.resolve()),
|
cwd=str(self.project_dir.resolve()),
|
||||||
|
settings=str(settings_file.resolve()),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
# Enter the async context and track it
|
# Enter the async context and track it
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect, useCallback } from 'react'
|
import { useState, useEffect, useCallback } from 'react'
|
||||||
import { useProjects, useFeatures } from './hooks/useProjects'
|
import { useProjects, useFeatures, useAgentStatus } from './hooks/useProjects'
|
||||||
import { useProjectWebSocket } from './hooks/useWebSocket'
|
import { useProjectWebSocket } from './hooks/useWebSocket'
|
||||||
import { useFeatureSound } from './hooks/useFeatureSound'
|
import { useFeatureSound } from './hooks/useFeatureSound'
|
||||||
import { useCelebration } from './hooks/useCelebration'
|
import { useCelebration } from './hooks/useCelebration'
|
||||||
@@ -34,6 +34,7 @@ function App() {
|
|||||||
|
|
||||||
const { data: projects, isLoading: projectsLoading } = useProjects()
|
const { data: projects, isLoading: projectsLoading } = useProjects()
|
||||||
const { data: features } = useFeatures(selectedProject)
|
const { data: features } = useFeatures(selectedProject)
|
||||||
|
const { data: agentStatusData } = useAgentStatus(selectedProject)
|
||||||
const wsState = useProjectWebSocket(selectedProject)
|
const wsState = useProjectWebSocket(selectedProject)
|
||||||
|
|
||||||
// Play sounds when features move between columns
|
// Play sounds when features move between columns
|
||||||
@@ -151,6 +152,7 @@ function App() {
|
|||||||
<AgentControl
|
<AgentControl
|
||||||
projectName={selectedProject}
|
projectName={selectedProject}
|
||||||
status={wsState.agentStatus}
|
status={wsState.agentStatus}
|
||||||
|
yoloMode={agentStatusData?.yolo_mode ?? false}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Play, Pause, Square, Loader2 } from 'lucide-react'
|
import { useState } from 'react'
|
||||||
|
import { Play, Pause, Square, Loader2, Zap } from 'lucide-react'
|
||||||
import {
|
import {
|
||||||
useStartAgent,
|
useStartAgent,
|
||||||
useStopAgent,
|
useStopAgent,
|
||||||
@@ -10,9 +11,12 @@ import type { AgentStatus } from '../lib/types'
|
|||||||
interface AgentControlProps {
|
interface AgentControlProps {
|
||||||
projectName: string
|
projectName: string
|
||||||
status: AgentStatus
|
status: AgentStatus
|
||||||
|
yoloMode?: boolean // From server status - whether currently running in YOLO mode
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AgentControl({ projectName, status }: AgentControlProps) {
|
export function AgentControl({ projectName, status, yoloMode = false }: AgentControlProps) {
|
||||||
|
const [yoloEnabled, setYoloEnabled] = useState(false)
|
||||||
|
|
||||||
const startAgent = useStartAgent(projectName)
|
const startAgent = useStartAgent(projectName)
|
||||||
const stopAgent = useStopAgent(projectName)
|
const stopAgent = useStopAgent(projectName)
|
||||||
const pauseAgent = usePauseAgent(projectName)
|
const pauseAgent = usePauseAgent(projectName)
|
||||||
@@ -24,7 +28,7 @@ export function AgentControl({ projectName, status }: AgentControlProps) {
|
|||||||
pauseAgent.isPending ||
|
pauseAgent.isPending ||
|
||||||
resumeAgent.isPending
|
resumeAgent.isPending
|
||||||
|
|
||||||
const handleStart = () => startAgent.mutate()
|
const handleStart = () => startAgent.mutate(yoloEnabled)
|
||||||
const handleStop = () => stopAgent.mutate()
|
const handleStop = () => stopAgent.mutate()
|
||||||
const handlePause = () => pauseAgent.mutate()
|
const handlePause = () => pauseAgent.mutate()
|
||||||
const handleResume = () => resumeAgent.mutate()
|
const handleResume = () => resumeAgent.mutate()
|
||||||
@@ -34,21 +38,43 @@ export function AgentControl({ projectName, status }: AgentControlProps) {
|
|||||||
{/* Status Indicator */}
|
{/* Status Indicator */}
|
||||||
<StatusIndicator status={status} />
|
<StatusIndicator status={status} />
|
||||||
|
|
||||||
|
{/* YOLO Mode Indicator - shown when running in YOLO mode */}
|
||||||
|
{(status === 'running' || status === 'paused') && yoloMode && (
|
||||||
|
<div className="flex items-center gap-1 px-2 py-1 bg-[var(--color-neo-pending)] border-3 border-[var(--color-neo-border)]">
|
||||||
|
<Zap size={14} className="text-yellow-900" />
|
||||||
|
<span className="font-display font-bold text-xs uppercase text-yellow-900">
|
||||||
|
YOLO
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Control Buttons */}
|
{/* Control Buttons */}
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
{status === 'stopped' || status === 'crashed' ? (
|
{status === 'stopped' || status === 'crashed' ? (
|
||||||
<button
|
<>
|
||||||
onClick={handleStart}
|
{/* YOLO Toggle - only shown when stopped */}
|
||||||
disabled={isLoading}
|
<button
|
||||||
className="neo-btn neo-btn-success text-sm py-2 px-3"
|
onClick={() => setYoloEnabled(!yoloEnabled)}
|
||||||
title="Start Agent"
|
className={`neo-btn text-sm py-2 px-3 ${
|
||||||
>
|
yoloEnabled ? 'neo-btn-warning' : 'neo-btn-secondary'
|
||||||
{isLoading ? (
|
}`}
|
||||||
<Loader2 size={18} className="animate-spin" />
|
title="YOLO Mode: Skip testing for rapid prototyping"
|
||||||
) : (
|
>
|
||||||
<Play size={18} />
|
<Zap size={18} className={yoloEnabled ? 'text-yellow-900' : ''} />
|
||||||
)}
|
</button>
|
||||||
</button>
|
<button
|
||||||
|
onClick={handleStart}
|
||||||
|
disabled={isLoading}
|
||||||
|
className="neo-btn neo-btn-success text-sm py-2 px-3"
|
||||||
|
title={yoloEnabled ? "Start Agent (YOLO Mode)" : "Start Agent"}
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<Loader2 size={18} className="animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Play size={18} />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
) : status === 'running' ? (
|
) : status === 'running' ? (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export function NewProjectModal({
|
|||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
const [initializerStatus, setInitializerStatus] = useState<InitializerStatus>('idle')
|
const [initializerStatus, setInitializerStatus] = useState<InitializerStatus>('idle')
|
||||||
const [initializerError, setInitializerError] = useState<string | null>(null)
|
const [initializerError, setInitializerError] = useState<string | null>(null)
|
||||||
|
const [yoloModeSelected, setYoloModeSelected] = useState(false)
|
||||||
|
|
||||||
// Suppress unused variable warning - specMethod may be used in future
|
// Suppress unused variable warning - specMethod may be used in future
|
||||||
void _specMethod
|
void _specMethod
|
||||||
@@ -116,11 +117,13 @@ export function NewProjectModal({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSpecComplete = async () => {
|
const handleSpecComplete = async (_specPath: string, yoloMode: boolean = false) => {
|
||||||
|
// Save yoloMode for retry
|
||||||
|
setYoloModeSelected(yoloMode)
|
||||||
// Auto-start the initializer agent
|
// Auto-start the initializer agent
|
||||||
setInitializerStatus('starting')
|
setInitializerStatus('starting')
|
||||||
try {
|
try {
|
||||||
await startAgent(projectName.trim())
|
await startAgent(projectName.trim(), yoloMode)
|
||||||
// Success - navigate to project
|
// Success - navigate to project
|
||||||
setStep('complete')
|
setStep('complete')
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -136,7 +139,7 @@ export function NewProjectModal({
|
|||||||
const handleRetryInitializer = () => {
|
const handleRetryInitializer = () => {
|
||||||
setInitializerError(null)
|
setInitializerError(null)
|
||||||
setInitializerStatus('idle')
|
setInitializerStatus('idle')
|
||||||
handleSpecComplete()
|
handleSpecComplete('', yoloModeSelected)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleChatCancel = () => {
|
const handleChatCancel = () => {
|
||||||
@@ -153,6 +156,7 @@ export function NewProjectModal({
|
|||||||
setError(null)
|
setError(null)
|
||||||
setInitializerStatus('idle')
|
setInitializerStatus('idle')
|
||||||
setInitializerError(null)
|
setInitializerError(null)
|
||||||
|
setYoloModeSelected(false)
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { Send, X, CheckCircle2, AlertCircle, Wifi, WifiOff, RotateCcw, Loader2, ArrowRight } from 'lucide-react'
|
import { Send, X, CheckCircle2, AlertCircle, Wifi, WifiOff, RotateCcw, Loader2, ArrowRight, Zap } from 'lucide-react'
|
||||||
import { useSpecChat } from '../hooks/useSpecChat'
|
import { useSpecChat } from '../hooks/useSpecChat'
|
||||||
import { ChatMessage } from './ChatMessage'
|
import { ChatMessage } from './ChatMessage'
|
||||||
import { QuestionOptions } from './QuestionOptions'
|
import { QuestionOptions } from './QuestionOptions'
|
||||||
@@ -16,7 +16,7 @@ type InitializerStatus = 'idle' | 'starting' | 'error'
|
|||||||
|
|
||||||
interface SpecCreationChatProps {
|
interface SpecCreationChatProps {
|
||||||
projectName: string
|
projectName: string
|
||||||
onComplete: (specPath: string) => void
|
onComplete: (specPath: string, yoloMode?: boolean) => void
|
||||||
onCancel: () => void
|
onCancel: () => void
|
||||||
initializerStatus?: InitializerStatus
|
initializerStatus?: InitializerStatus
|
||||||
initializerError?: string | null
|
initializerError?: string | null
|
||||||
@@ -33,6 +33,7 @@ export function SpecCreationChat({
|
|||||||
}: SpecCreationChatProps) {
|
}: SpecCreationChatProps) {
|
||||||
const [input, setInput] = useState('')
|
const [input, setInput] = useState('')
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
const [yoloEnabled, setYoloEnabled] = useState(false)
|
||||||
const messagesEndRef = useRef<HTMLDivElement>(null)
|
const messagesEndRef = useRef<HTMLDivElement>(null)
|
||||||
const inputRef = useRef<HTMLInputElement>(null)
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
@@ -257,7 +258,9 @@ export function SpecCreationChat({
|
|||||||
{initializerStatus === 'starting' ? (
|
{initializerStatus === 'starting' ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 size={20} className="animate-spin" />
|
<Loader2 size={20} className="animate-spin" />
|
||||||
<span className="font-bold">Starting agent...</span>
|
<span className="font-bold">
|
||||||
|
Starting agent{yoloEnabled ? ' (YOLO mode)' : ''}...
|
||||||
|
</span>
|
||||||
</>
|
</>
|
||||||
) : initializerStatus === 'error' ? (
|
) : initializerStatus === 'error' ? (
|
||||||
<>
|
<>
|
||||||
@@ -284,13 +287,28 @@ export function SpecCreationChat({
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{initializerStatus === 'idle' && (
|
{initializerStatus === 'idle' && (
|
||||||
<button
|
<>
|
||||||
onClick={() => onComplete('')}
|
{/* YOLO Mode Toggle */}
|
||||||
className="neo-btn neo-btn-primary"
|
<button
|
||||||
>
|
onClick={() => setYoloEnabled(!yoloEnabled)}
|
||||||
Continue to Project
|
className={`neo-btn text-sm py-2 px-3 ${
|
||||||
<ArrowRight size={16} />
|
yoloEnabled ? 'neo-btn-warning' : 'bg-white'
|
||||||
</button>
|
}`}
|
||||||
|
title="YOLO Mode: Skip testing for rapid prototyping"
|
||||||
|
>
|
||||||
|
<Zap size={16} className={yoloEnabled ? 'text-yellow-900' : ''} />
|
||||||
|
<span className={yoloEnabled ? 'text-yellow-900 font-bold' : ''}>
|
||||||
|
YOLO
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => onComplete('', yoloEnabled)}
|
||||||
|
className="neo-btn neo-btn-primary"
|
||||||
|
>
|
||||||
|
Continue to Project
|
||||||
|
<ArrowRight size={16} />
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ export function useStartAgent(projectName: string) {
|
|||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: () => api.startAgent(projectName),
|
mutationFn: (yoloMode: boolean = false) => api.startAgent(projectName, yoloMode),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['agent-status', projectName] })
|
queryClient.invalidateQueries({ queryKey: ['agent-status', projectName] })
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -117,9 +117,13 @@ export async function getAgentStatus(projectName: string): Promise<AgentStatusRe
|
|||||||
return fetchJSON(`/projects/${encodeURIComponent(projectName)}/agent/status`)
|
return fetchJSON(`/projects/${encodeURIComponent(projectName)}/agent/status`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function startAgent(projectName: string): Promise<AgentActionResponse> {
|
export async function startAgent(
|
||||||
|
projectName: string,
|
||||||
|
yoloMode: boolean = false
|
||||||
|
): Promise<AgentActionResponse> {
|
||||||
return fetchJSON(`/projects/${encodeURIComponent(projectName)}/agent/start`, {
|
return fetchJSON(`/projects/${encodeURIComponent(projectName)}/agent/start`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ yolo_mode: yoloMode }),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ export interface AgentStatusResponse {
|
|||||||
status: AgentStatus
|
status: AgentStatus
|
||||||
pid: number | null
|
pid: number | null
|
||||||
started_at: string | null
|
started_at: string | null
|
||||||
|
yolo_mode: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AgentActionResponse {
|
export interface AgentActionResponse {
|
||||||
|
|||||||
Reference in New Issue
Block a user