-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
fix: handle empty/greenfield projects in spec creation (#1426) #1841
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 1 commit
c393d60
09858dd
cdd8c31
55e174f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,12 +9,36 @@ | |
| from typing import TYPE_CHECKING | ||
|
|
||
| from .. import validator, writer | ||
| from ..discovery import get_project_index_stats | ||
| from .models import MAX_RETRIES, PhaseResult | ||
|
|
||
| if TYPE_CHECKING: | ||
| pass | ||
|
|
||
|
|
||
| def _is_greenfield_project(spec_dir) -> bool: | ||
| """Check if the project is empty/greenfield (0 discovered files).""" | ||
| stats = get_project_index_stats(spec_dir) | ||
| return stats.get("file_count", 0) == 0 | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
Comment on lines
+16
to
+21
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Good fix for the false-positive greenfield detection on missing/corrupt index. The One remaining edge case: if Proposed hardening def _is_greenfield_project(spec_dir: Path) -> bool:
"""Check if the project is empty/greenfield (0 discovered files)."""
stats = get_project_index_stats(spec_dir)
if not stats:
return False # Can't determine - don't assume greenfield
- return stats.get("file_count", 0) == 0
+ if "file_count" not in stats:
+ return False # Unrecognized format - don't assume greenfield
+ return stats["file_count"] == 0🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| def _greenfield_context() -> str: | ||
| """Return additional context for greenfield/empty projects.""" | ||
| return """ | ||
| **GREENFIELD PROJECT**: This is an empty or new project with no existing code. | ||
| There are no existing files to reference or modify. You are creating everything from scratch. | ||
|
|
||
| Adapt your approach: | ||
| - Do NOT reference existing files, patterns, or code structures | ||
| - Focus on what needs to be CREATED, not modified | ||
| - Define the initial project structure, files, and directories | ||
| - Specify the tech stack, frameworks, and dependencies to install | ||
| - Provide setup instructions for the new project | ||
| - For "Files to Modify" and "Files to Reference" sections, list files to CREATE instead | ||
| - For "Patterns to Follow", describe industry best practices rather than existing code | ||
| """ | ||
|
|
||
|
|
||
| class SpecPhaseMixin: | ||
| """Mixin for spec writing and critique phase methods.""" | ||
|
|
||
|
|
@@ -29,6 +53,13 @@ async def phase_quick_spec(self) -> PhaseResult: | |
| "quick_spec", True, [str(spec_file), str(plan_file)], [], 0 | ||
| ) | ||
|
|
||
| is_greenfield = _is_greenfield_project(self.spec_dir) | ||
| if is_greenfield: | ||
| self.ui.print_status( | ||
| "Greenfield project detected - adapting spec for new project", | ||
| "info", | ||
| ) | ||
|
|
||
| errors = [] | ||
| for attempt in range(MAX_RETRIES): | ||
| self.ui.print_status( | ||
|
|
@@ -42,7 +73,7 @@ async def phase_quick_spec(self) -> PhaseResult: | |
|
|
||
| This is a SIMPLE task. Create a minimal spec and implementation plan directly. | ||
| No research or extensive analysis needed. | ||
|
|
||
| {_greenfield_context() if is_greenfield else ""} | ||
| Create: | ||
| 1. A concise spec.md with just the essential sections | ||
| 2. A simple implementation_plan.json with 1-2 subtasks | ||
|
|
@@ -80,6 +111,15 @@ async def phase_spec_writing(self) -> PhaseResult: | |
| "spec.md exists but has issues, regenerating...", "warning" | ||
| ) | ||
|
|
||
| is_greenfield = _is_greenfield_project(self.spec_dir) | ||
| if is_greenfield: | ||
| self.ui.print_status( | ||
| "Greenfield project detected - adapting spec for new project", | ||
| "info", | ||
| ) | ||
|
||
|
|
||
| greenfield_ctx = _greenfield_context() if is_greenfield else "" | ||
|
|
||
| errors = [] | ||
| for attempt in range(MAX_RETRIES): | ||
| self.ui.print_status( | ||
|
|
@@ -88,6 +128,7 @@ async def phase_spec_writing(self) -> PhaseResult: | |
|
|
||
| success, output = await self.run_agent_fn( | ||
| "spec_writer.md", | ||
| additional_context=greenfield_ctx, | ||
| phase_name="spec_writing", | ||
| ) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -238,6 +238,41 @@ | |||||||||
| task_logger.start_phase(LogPhase.PLANNING, "Starting spec creation process") | ||||||||||
| TaskEventEmitter.from_spec_dir(self.spec_dir).emit("PLANNING_STARTED") | ||||||||||
|
|
||||||||||
| # Track whether we've already ended the planning phase (to avoid double-end) | ||||||||||
| planning_phase_ended = False | ||||||||||
|
|
||||||||||
| try: | ||||||||||
| return await self._run_phases(interactive, auto_approve, task_logger, ui) | ||||||||||
| except Exception as e: | ||||||||||
| # Ensure planning phase is always properly ended on unexpected errors | ||||||||||
| # This prevents the task from being stuck in "active" planning state | ||||||||||
| if not planning_phase_ended: | ||||||||||
| planning_phase_ended = True | ||||||||||
|
||||||||||
| task_logger.end_phase( | ||||||||||
| LogPhase.PLANNING, | ||||||||||
| success=False, | ||||||||||
| message=f"Spec creation failed with unexpected error: {e}", | ||||||||||
| ) | ||||||||||
| raise | ||||||||||
|
Comment on lines
243
to
258
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The To fix this,
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
|
|
||||||||||
| async def _run_phases( | ||||||||||
| self, | ||||||||||
| interactive: bool, | ||||||||||
| auto_approve: bool, | ||||||||||
| task_logger, | ||||||||||
| ui, | ||||||||||
|
||||||||||
| task_logger, | |
| ui, | |
| task_logger: "TaskLogger", | |
| ui: "ModuleType", |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For better type safety and code clarity, please add a type hint for the
spec_dirparameter. Based on its usage, it should bePath. You can use a forward reference"Path"and addfrom pathlib import Pathinside theif TYPE_CHECKING:block to avoid potential circular import issues while keeping the type checker happy.