Skip to content

Commit 667b996

Browse files
authored
feat: cf import ralph — one-command migration from ralph-claude-code (#615)
## Summary - cf import ralph [path] [--workspace] [--dry-run]: parses .ralphrc / fix_plan.md / PROMPT.md / specs / AGENT.md and maps them to tasks, PRD, and AGENTS.md - Optional fix_plan sections (OPTIONAL_SECTIONS or defaults) import as BACKLOG; checked items skipped; file order preserved - Idempotent re-runs via content-hash external_url keys + UNIQUE index (#565 pattern); PRD versioned on change; AGENTS.md never overwritten - Rich dry-run mapping report with zero writes; ralph state files never read - Docs: QUICKSTART "Coming from ralph?", CLI_WIREFRAME mapping, README command list ## Validation - Tests: 55 new tests (TDD, real SQLite workspaces, no mocks); full backend suite green locally (chunked) + CI - Lint: Clean - Test mutation check: Passed (Phase 7d) - Internal review: Completed (advisory) — findings applied - Cross-family review: codex (gpt-5.5) — no correctness issues found - Review feedback: 6 items fixed across 3 rounds, 1 rebutted then resolved by refactor; CodeRabbit CLI false-positive rebutted with evidence - Demo: All 4 acceptance criteria verified with outcome evidence against ralph's real templates/ Closes #615
1 parent ce7d6da commit 667b996

18 files changed

Lines changed: 1448 additions & 0 deletions

File tree

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,11 @@ cf tasks show <id> # Task details with dependencies
188188
cf schedule show # Task schedule with dependencies
189189
cf schedule predict # Completion date estimates
190190
cf schedule bottlenecks # Identify blocking tasks
191+
192+
# Migration / on-ramps
193+
cf import ralph [path] # Import a ralph-claude-code project
194+
cf import ralph [path] --dry-run # Preview mapping without changes
195+
cf import ralph [path] -w <workspace> # Import into a specific workspace
191196
```
192197

193198
### BUILD -- Execution

codeframe/cli/app.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from codeframe.cli.engines_commands import engines_app
3131
from codeframe.cli.hooks_commands import hooks_app
3232
from codeframe.cli.stats_commands import stats_app
33+
from codeframe.cli.import_commands import import_app
3334

3435
# Load environment variables from .env files
3536
# Priority: workspace .env > home .env
@@ -5651,6 +5652,8 @@ def templates_apply(
56515652

56525653
app.add_typer(dashboard_app, name="dashboard")
56535654

5655+
app.add_typer(import_app, name="import")
5656+
56545657

56555658
# =============================================================================
56565659
# Version command

codeframe/cli/import_commands.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
"""Import commands for migrating projects from other tools.
2+
3+
Usage:
4+
cf import ralph [path] [--workspace PATH] [--dry-run]
5+
"""
6+
7+
from pathlib import Path
8+
from typing import Optional
9+
10+
import typer
11+
from rich.console import Console
12+
from rich.table import Table
13+
14+
console = Console()
15+
16+
import_app = typer.Typer(
17+
name="import",
18+
help="Import projects from other tools into CodeFRAME",
19+
no_args_is_help=True,
20+
)
21+
22+
23+
@import_app.command("ralph")
24+
def import_ralph(
25+
path: Optional[Path] = typer.Argument(
26+
None,
27+
help="Path to the ralph project root (defaults to current directory)",
28+
),
29+
workspace: Optional[Path] = typer.Option(
30+
None,
31+
"--workspace",
32+
"-w",
33+
help="Target CodeFRAME workspace (defaults to the ralph project root)",
34+
),
35+
dry_run: bool = typer.Option(
36+
False,
37+
"--dry-run",
38+
help="Show the mapping report without making any changes",
39+
),
40+
) -> None:
41+
"""Import a ralph-claude-code project (.ralph/ + .ralphrc).
42+
43+
Maps .ralph/fix_plan.md to tasks (optional sections become BACKLOG),
44+
.ralph/PROMPT.md + specs/ to a PRD, and .ralph/AGENT.md + ALLOWED_TOOLS
45+
to AGENTS.md. Re-runs are idempotent.
46+
"""
47+
from codeframe.core.importers.ralph import (
48+
RalphProjectNotFoundError,
49+
import_ralph_project,
50+
)
51+
from codeframe.core.state_machine import TaskStatus
52+
53+
ralph_path = (path or Path.cwd()).resolve()
54+
55+
try:
56+
report = import_ralph_project(
57+
ralph_path, workspace_path=workspace, dry_run=dry_run
58+
)
59+
except RalphProjectNotFoundError as exc:
60+
console.print(f"[red]Error:[/red] {exc}")
61+
raise typer.Exit(1)
62+
63+
if dry_run:
64+
console.print(
65+
"[yellow]DRY RUN[/yellow] — mapping report only, no changes made\n"
66+
)
67+
68+
ready = sum(
69+
1 for t in report.tasks_created if t["status"] == TaskStatus.READY
70+
)
71+
backlog = len(report.tasks_created) - ready
72+
summary = Table(title=f"Ralph import: {ralph_path}")
73+
summary.add_column("Source", style="cyan")
74+
summary.add_column("Destination")
75+
summary.add_row(
76+
".ralph/fix_plan.md",
77+
f"{len(report.tasks_created)} tasks "
78+
f"({ready} READY, {backlog} BACKLOG), "
79+
f"{len(report.tasks_skipped)} skipped",
80+
)
81+
prd_label = {
82+
"created": "PRD created",
83+
"new_version": "PRD updated (new version)",
84+
"skipped_identical": "PRD unchanged (skipped)",
85+
"none": "—",
86+
}[report.prd_action]
87+
summary.add_row(".ralph/PROMPT.md + specs/", prd_label)
88+
agents_label = {
89+
"written": "AGENTS.md written",
90+
"skipped_exists": "AGENTS.md exists (skipped)",
91+
"none": "—",
92+
}[report.agents_md_action]
93+
summary.add_row(".ralph/AGENT.md + ALLOWED_TOOLS", agents_label)
94+
console.print(summary)
95+
96+
if report.tasks_created:
97+
task_table = Table(title="Tasks")
98+
task_table.add_column("Title")
99+
task_table.add_column("Section", style="dim")
100+
task_table.add_column("Status")
101+
for task in report.tasks_created:
102+
status = task["status"].value
103+
color = "green" if status == "READY" else "yellow"
104+
task_table.add_row(
105+
task["title"], task["section"], f"[{color}]{status}[/{color}]"
106+
)
107+
console.print(task_table)
108+
109+
if report.tasks_skipped:
110+
skip_table = Table(title="Skipped")
111+
skip_table.add_column("Title")
112+
skip_table.add_column("Reason", style="dim")
113+
for item in report.tasks_skipped:
114+
skip_table.add_row(item["title"], item["reason"])
115+
console.print(skip_table)
116+
117+
if report.state_files_ignored:
118+
ignored = ", ".join(report.state_files_ignored)
119+
console.print(f"[dim]Ignored ralph state files: {ignored}[/dim]")
120+
121+
if dry_run:
122+
console.print(
123+
"\nRun again without [bold]--dry-run[/bold] to perform the import."
124+
)
125+
else:
126+
console.print(
127+
f"\n[green]✓[/green] Imported into workspace at "
128+
f"{report.workspace_path}"
129+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Importers that migrate projects from other tools into CodeFRAME."""

0 commit comments

Comments
 (0)