Add in-progress status tracking for features

Implements feature locking to prevent multiple agent sessions from working
on the same feature simultaneously. This is essential for parallel agent
execution.

Database changes:
- Add `in_progress` boolean column to Feature model
- Add migration function to handle existing databases

MCP Server tools:
- Add `feature_mark_in_progress` - lock feature when starting work
- Add `feature_clear_in_progress` - unlock feature when abandoning
- Update `feature_get_next` to skip in-progress features
- Update `feature_get_stats` to include in_progress count
- Update `feature_mark_passing` and `feature_skip` to clear in_progress

Backend updates:
- Update progress.py to track and display in_progress count
- Update features router to properly categorize in-progress features
- Update WebSocket to broadcast in_progress in progress updates
- Add in_progress to FeatureResponse schema

Frontend updates:
- Add in_progress to TypeScript types (Feature, ProjectStats, WSProgressMessage)
- Update useWebSocket hook to track in_progress state

Prompt template:
- Add instructions for agents to mark features in-progress immediately
- Document new MCP tools in allowed tools section

Also fixes spec_chat_session.py to use absolute project path instead of
relative path for consistency with CLI behavior.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Auto
2025-12-30 19:00:49 +02:00
parent d6c736261b
commit f180e1933d
12 changed files with 179 additions and 30 deletions

View File

@@ -93,6 +93,7 @@ def feature_to_response(f) -> FeatureResponse:
description=f.description,
steps=f.steps if isinstance(f.steps, list) else [],
passes=f.passes,
in_progress=f.in_progress,
)
@@ -123,18 +124,21 @@ async def list_features(project_name: str):
all_features = session.query(Feature).order_by(Feature.priority).all()
pending = []
in_progress = []
done = []
for f in all_features:
feature_response = feature_to_response(f)
if f.passes:
done.append(feature_response)
elif f.in_progress:
in_progress.append(feature_response)
else:
pending.append(feature_response)
return FeatureListResponse(
pending=pending,
in_progress=[],
in_progress=in_progress,
done=done,
)
except HTTPException:

View File

@@ -73,7 +73,7 @@ def validate_project_name(name: str) -> str:
def get_project_stats(project_dir: Path) -> ProjectStats:
"""Get statistics for a project."""
_init_imports()
passing, total = _count_passing_tests(project_dir)
passing, _, total = _count_passing_tests(project_dir)
percentage = (passing / total * 100) if total > 0 else 0.0
return ProjectStats(passing=passing, total=total, percentage=round(percentage, 1))

View File

@@ -78,6 +78,7 @@ class FeatureResponse(FeatureBase):
id: int
priority: int
passes: bool
in_progress: bool
class Config:
from_attributes = True

View File

@@ -83,8 +83,12 @@ class SpecChatSession:
except UnicodeDecodeError:
skill_content = skill_path.read_text(encoding="utf-8", errors="replace")
# Replace $ARGUMENTS with the project path (use forward slashes for consistency)
project_path = f"generations/{self.project_name}"
# Ensure project directory exists (like CLI does in start.py)
self.project_dir.mkdir(parents=True, exist_ok=True)
# 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
project_path = str(self.project_dir.resolve())
system_prompt = skill_content.replace("$ARGUMENTS", project_path)
# Create Claude SDK client with limited tools for spec creation

View File

@@ -117,21 +117,24 @@ async def poll_progress(websocket: WebSocket, project_name: str):
project_dir = _get_generations_dir() / project_name
count_passing_tests = _get_count_passing_tests()
last_passing = -1
last_in_progress = -1
last_total = -1
while True:
try:
passing, total = count_passing_tests(project_dir)
passing, in_progress, total = count_passing_tests(project_dir)
# Only send if changed
if passing != last_passing or total != last_total:
if passing != last_passing or in_progress != last_in_progress or total != last_total:
last_passing = passing
last_in_progress = in_progress
last_total = total
percentage = (passing / total * 100) if total > 0 else 0
await websocket.send_json({
"type": "progress",
"passing": passing,
"in_progress": in_progress,
"total": total,
"percentage": round(percentage, 1),
})
@@ -204,11 +207,12 @@ async def project_websocket(websocket: WebSocket, project_name: str):
# Send initial progress
count_passing_tests = _get_count_passing_tests()
passing, total = count_passing_tests(project_dir)
passing, in_progress, total = count_passing_tests(project_dir)
percentage = (passing / total * 100) if total > 0 else 0
await websocket.send_json({
"type": "progress",
"passing": passing,
"in_progress": in_progress,
"total": total,
"percentage": round(percentage, 1),
})