Skip to content

Commit 516768d

Browse files
gmccrackinclaude
andcommitted
Add in_progress tracking for crash recovery
When the agent script ends abruptly, there was no record of which feature was being worked on. This change adds: - New `in_progress` boolean column to Feature model - Schema migration for existing databases (ALTER TABLE) - feature_get_next() now checks for in-progress features first and returns them with "resumed": true flag - feature_mark_passing() and feature_skip() clear the in_progress flag - Coding prompt updated to check git status for uncommitted changes and handle resumed features appropriately This allows the agent to resume work on partially-implemented features after a crash, leveraging any uncommitted changes from the previous session. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 1690a61 commit 516768d

File tree

4 files changed

+99
-14
lines changed

4 files changed

+99
-14
lines changed

.claude/templates/coding_prompt.template.md

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,36 @@ pwd
1414
# 2. List files to understand project structure
1515
ls -la
1616

17-
# 3. Read the project specification to understand what you're building
17+
# 3. Check for uncommitted changes from previous session
18+
git status
19+
git diff --stat
20+
21+
# 4. Read the project specification to understand what you're building
1822
cat app_spec.txt
1923

20-
# 4. Read progress notes from previous sessions
24+
# 5. Read progress notes from previous sessions
2125
cat claude-progress.txt
2226

23-
# 5. Check recent git history
27+
# 6. Check recent git history
2428
git log --oneline -20
2529
```
2630

2731
Then use MCP tools to check feature status:
2832

2933
```
30-
# 6. Get progress statistics (passing/total counts)
34+
# 7. Get progress statistics (passing/total counts)
3135
Use the feature_get_stats tool
3236
33-
# 7. Get the next feature to work on
37+
# 8. Get the next feature to work on (may resume an in-progress feature)
3438
Use the feature_get_next tool
3539
```
3640

41+
**If feature_get_next returns a feature with `"resumed": true`:**
42+
- This feature was started in a previous session that crashed or was interrupted
43+
- Check uncommitted changes (`git status`) - they likely relate to this feature
44+
- Continue working on this feature, building on any existing progress
45+
- Do NOT start fresh - leverage the uncommitted changes
46+
3747
Understanding the `app_spec.txt` is critical - it contains the full requirements
3848
for the application you're building.
3949

api/database.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class Feature(Base):
2828
description = Column(Text, nullable=False)
2929
steps = Column(JSON, nullable=False) # Stored as JSON array
3030
passes = Column(Boolean, default=False, index=True)
31+
in_progress = Column(Boolean, default=False, index=True)
3132

3233
def to_dict(self) -> dict:
3334
"""Convert feature to dictionary for JSON serialization."""
@@ -39,6 +40,7 @@ def to_dict(self) -> dict:
3940
"description": self.description,
4041
"steps": self.steps,
4142
"passes": self.passes,
43+
"in_progress": self.in_progress,
4244
}
4345

4446

api/migration.py

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
"""
2-
JSON to SQLite Migration
3-
========================
2+
Database Migrations
3+
===================
44
5-
Automatically migrates existing feature_list.json files to SQLite database.
5+
Handles database migrations:
6+
- JSON to SQLite migration for legacy feature_list.json files
7+
- Schema migrations for adding new columns
68
"""
79

810
import json
@@ -11,6 +13,7 @@
1113
from pathlib import Path
1214
from typing import Optional
1315

16+
from sqlalchemy import text
1417
from sqlalchemy.orm import sessionmaker, Session
1518

1619
from api.database import Feature
@@ -152,3 +155,46 @@ def export_to_json(
152155

153156
finally:
154157
session.close()
158+
159+
160+
def migrate_add_in_progress_column(
161+
project_dir: Path,
162+
session_maker: sessionmaker,
163+
) -> bool:
164+
"""
165+
Add in_progress column to existing databases.
166+
167+
This migration adds the in_progress boolean column to track which feature
168+
is currently being worked on. This allows crash recovery by resuming
169+
work on in-progress features.
170+
171+
Args:
172+
project_dir: Directory containing the project
173+
session_maker: SQLAlchemy session maker
174+
175+
Returns:
176+
True if migration was performed, False if column already exists
177+
"""
178+
session: Session = session_maker()
179+
try:
180+
# Check if column already exists using PRAGMA
181+
result = session.execute(text("PRAGMA table_info(features)"))
182+
columns = [row[1] for row in result.fetchall()]
183+
184+
if "in_progress" in columns:
185+
return False # Column already exists
186+
187+
# Add the column with default value of 0 (False)
188+
session.execute(
189+
text("ALTER TABLE features ADD COLUMN in_progress BOOLEAN DEFAULT 0")
190+
)
191+
session.commit()
192+
print("Added in_progress column to features table")
193+
return True
194+
195+
except Exception as e:
196+
session.rollback()
197+
print(f"Error adding in_progress column: {e}")
198+
return False
199+
finally:
200+
session.close()

mcp_server/feature_mcp.py

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
sys.path.insert(0, str(Path(__file__).parent.parent))
3131

3232
from api.database import Feature, create_database
33-
from api.migration import migrate_json_to_sqlite
33+
from api.migration import migrate_json_to_sqlite, migrate_add_in_progress_column
3434

3535
# Configuration from environment
3636
PROJECT_DIR = Path(os.environ.get("PROJECT_DIR", ".")).resolve()
@@ -81,8 +81,9 @@ async def server_lifespan(server: FastMCP):
8181
# Initialize database
8282
_engine, _session_maker = create_database(PROJECT_DIR)
8383

84-
# Run migration if needed (converts legacy JSON to SQLite)
85-
migrate_json_to_sqlite(PROJECT_DIR, _session_maker)
84+
# Run migrations
85+
migrate_json_to_sqlite(PROJECT_DIR, _session_maker) # Legacy JSON to SQLite
86+
migrate_add_in_progress_column(PROJECT_DIR, _session_maker) # Add in_progress column
8687

8788
yield
8889

@@ -131,15 +132,34 @@ def feature_get_stats() -> str:
131132
def feature_get_next() -> str:
132133
"""Get the highest-priority pending feature to work on.
133134
134-
Returns the feature with the lowest priority number that has passes=false.
135-
Use this at the start of each coding session to determine what to implement next.
135+
If a feature is already marked as in_progress (from a previous crashed session),
136+
returns that feature to allow resuming interrupted work.
137+
138+
Otherwise, returns the feature with the lowest priority number that has passes=false
139+
and marks it as in_progress.
136140
137141
Returns:
138-
JSON with feature details (id, priority, category, name, description, steps, passes)
142+
JSON with feature details (id, priority, category, name, description, steps, passes, in_progress)
139143
or error message if all features are passing.
144+
If resuming, includes "resumed": true and a message.
140145
"""
141146
session = get_session()
142147
try:
148+
# First, check for any in-progress feature (resume interrupted work)
149+
in_progress_feature = (
150+
session.query(Feature)
151+
.filter(Feature.in_progress == True)
152+
.first()
153+
)
154+
155+
if in_progress_feature:
156+
return json.dumps({
157+
**in_progress_feature.to_dict(),
158+
"resumed": True,
159+
"message": "Resuming previously started feature"
160+
}, indent=2)
161+
162+
# Get next pending feature by priority
143163
feature = (
144164
session.query(Feature)
145165
.filter(Feature.passes == False)
@@ -150,6 +170,11 @@ def feature_get_next() -> str:
150170
if feature is None:
151171
return json.dumps({"error": "All features are passing! No more work to do."})
152172

173+
# Mark as in_progress
174+
feature.in_progress = True
175+
session.commit()
176+
session.refresh(feature)
177+
153178
return json.dumps(feature.to_dict(), indent=2)
154179
finally:
155180
session.close()
@@ -212,6 +237,7 @@ def feature_mark_passing(
212237
return json.dumps({"error": f"Feature with ID {feature_id} not found"})
213238

214239
feature.passes = True
240+
feature.in_progress = False # Clear in_progress flag
215241
session.commit()
216242
session.refresh(feature)
217243

@@ -257,6 +283,7 @@ def feature_skip(
257283
new_priority = (max_priority_result[0] + 1) if max_priority_result else 1
258284

259285
feature.priority = new_priority
286+
feature.in_progress = False # Clear in_progress flag
260287
session.commit()
261288
session.refresh(feature)
262289

0 commit comments

Comments
 (0)