Files
autocoder/api/migration.py
Auto 91cc00a9d0 fix: add explicit in_progress=False to all feature creation paths
Complete the defense-in-depth approach from PR #53 by adding explicit
in_progress=False to all remaining feature creation locations. This
ensures consistency with the MCP server pattern and prevents potential
NULL values in the in_progress field.

Changes:
- server/routers/features.py: Add in_progress=False to create_feature()
  and create_features_bulk() endpoints
- server/services/expand_chat_session.py: Add in_progress=False to
  _create_features_bulk() in the expand chat session
- api/migration.py: Add in_progress field handling in JSON migration,
  reading from source data with False as default

This follows up on PR #53 which added nullable=False constraints and
fixed existing NULL values, but only updated the MCP server creation
paths. Now all 6 feature creation locations explicitly set both
passes=False and in_progress=False.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 15:14:24 +02:00

156 lines
4.5 KiB
Python

"""
JSON to SQLite Migration
========================
Automatically migrates existing feature_list.json files to SQLite database.
"""
import json
import shutil
from datetime import datetime
from pathlib import Path
from typing import Optional
from sqlalchemy.orm import Session, sessionmaker
from api.database import Feature
def migrate_json_to_sqlite(
project_dir: Path,
session_maker: sessionmaker,
) -> bool:
"""
Detect existing feature_list.json, import to SQLite, rename to backup.
This function:
1. Checks if feature_list.json exists
2. Checks if database already has data (skips if so)
3. Imports all features from JSON
4. Renames JSON file to feature_list.json.backup.<timestamp>
Args:
project_dir: Directory containing the project
session_maker: SQLAlchemy session maker
Returns:
True if migration was performed, False if skipped
"""
json_file = project_dir / "feature_list.json"
if not json_file.exists():
return False # No JSON file to migrate
# Check if database already has data
session: Session = session_maker()
try:
existing_count = session.query(Feature).count()
if existing_count > 0:
print(
f"Database already has {existing_count} features, skipping migration"
)
return False
finally:
session.close()
# Load JSON data
try:
with open(json_file, "r", encoding="utf-8") as f:
features_data = json.load(f)
except json.JSONDecodeError as e:
print(f"Error parsing feature_list.json: {e}")
return False
except IOError as e:
print(f"Error reading feature_list.json: {e}")
return False
if not isinstance(features_data, list):
print("Error: feature_list.json must contain a JSON array")
return False
# Import features into database
session = session_maker()
try:
imported_count = 0
for i, feature_dict in enumerate(features_data):
# Handle both old format (no id/priority/name) and new format
feature = Feature(
id=feature_dict.get("id", i + 1),
priority=feature_dict.get("priority", i + 1),
category=feature_dict.get("category", "uncategorized"),
name=feature_dict.get("name", f"Feature {i + 1}"),
description=feature_dict.get("description", ""),
steps=feature_dict.get("steps", []),
passes=feature_dict.get("passes", False),
in_progress=feature_dict.get("in_progress", False),
)
session.add(feature)
imported_count += 1
session.commit()
# Verify import
final_count = session.query(Feature).count()
print(f"Migrated {final_count} features from JSON to SQLite")
except Exception as e:
session.rollback()
print(f"Error during migration: {e}")
return False
finally:
session.close()
# Rename JSON file to backup
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_file = project_dir / f"feature_list.json.backup.{timestamp}"
try:
shutil.move(json_file, backup_file)
print(f"Original JSON backed up to: {backup_file.name}")
except IOError as e:
print(f"Warning: Could not backup JSON file: {e}")
# Continue anyway - the data is in the database
return True
def export_to_json(
project_dir: Path,
session_maker: sessionmaker,
output_file: Optional[Path] = None,
) -> Path:
"""
Export features from database back to JSON format.
Useful for debugging or if you need to revert to the old format.
Args:
project_dir: Directory containing the project
session_maker: SQLAlchemy session maker
output_file: Output file path (default: feature_list_export.json)
Returns:
Path to the exported file
"""
if output_file is None:
output_file = project_dir / "feature_list_export.json"
session: Session = session_maker()
try:
features = (
session.query(Feature)
.order_by(Feature.priority.asc(), Feature.id.asc())
.all()
)
features_data = [f.to_dict() for f in features]
with open(output_file, "w", encoding="utf-8") as f:
json.dump(features_data, f, indent=2)
print(f"Exported {len(features_data)} features to {output_file}")
return output_file
finally:
session.close()