Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 92 additions & 2 deletions src/shelfai/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,10 +269,10 @@ def _resolve_shelf_root(path: str) -> Optional[Path]:
base = Path(path).expanduser().resolve()
if not base.exists():
return None
if (base / "index.md").exists() or (base / "chunks").exists():
if (base / "index.md").exists() or (base / "chunks").exists() or (base / ".chunk-defaults.yaml").exists():
return base
nested = base / "shelf"
if (nested / "index.md").exists() or (nested / "chunks").exists():
if (nested / "index.md").exists() or (nested / "chunks").exists() or (nested / ".chunk-defaults.yaml").exists():
return nested
return None

Expand Down Expand Up @@ -311,6 +311,19 @@ def _learn_event_count(db_path: Path) -> int:
return conn.execute("SELECT COUNT(*) FROM loads").fetchone()[0]


def _warn_if_migration_needed(shelf_root: Path) -> None:
from shelfai.core.migrate import ShelfMigrator

migrator = ShelfMigrator()
detected = migrator.detect_version(str(shelf_root))
if detected == migrator.CURRENT_VERSION:
return
console.print(
f"[yellow]⚠ This shelf uses ShelfAI {detected} format. "
"Run `shelfai migrate` to upgrade.[/yellow]"
)


def main_cli() -> None:
"""Entry point for installed `shelfai` and `python -m shelfai` usage."""
if len(sys.argv) == 2 and sys.argv[1] == "--version":
Expand Down Expand Up @@ -430,6 +443,7 @@ def add(
if not shelf.exists():
console.print(f"[red]No shelf found at {shelf_path}. Run `shelfai init` first.[/red]")
raise typer.Exit(1)
_warn_if_migration_needed(shelf.path)

config = shelf.load_config()
allowed_categories = set(config.categories or [])
Expand Down Expand Up @@ -557,6 +571,7 @@ def index_cmd(
if not shelf.exists():
console.print(f"[red]No shelf found at {shelf_path}. Run `shelfai init` first.[/red]")
raise typer.Exit(1)
_warn_if_migration_needed(shelf.path)

if manual:
editor = os.environ.get("EDITOR", "nano")
Expand Down Expand Up @@ -690,6 +705,7 @@ def session(
if not shelf.exists():
console.print(f"[red]No shelf found at {shelf_path}. Run `shelfai init` first.[/red]")
raise typer.Exit(1)
_warn_if_migration_needed(shelf.path)

agent_dir = None
if agent:
Expand Down Expand Up @@ -755,6 +771,7 @@ def extract(
if not shelf.exists():
console.print(f"[red]No shelf found at {shelf_path}. Run `shelfai init` first.[/red]")
raise typer.Exit(1)
_warn_if_migration_needed(shelf.path)

agent_dir = None
if agent:
Expand Down Expand Up @@ -811,6 +828,7 @@ def search(
f"[red]No shelf found at {resolved_shelf_path}. Run `shelfai init` first.[/red]"
)
raise typer.Exit(1)
_warn_if_migration_needed(shelf.path)

searcher = ChunkSearch(resolved_shelf_path)
if searcher._index:
Expand Down Expand Up @@ -881,6 +899,7 @@ def status(
except FileNotFoundError:
display_error(f"No shelf found at {shelf_path or path or '.'}. Run `shelfai init` first.")
raise typer.Exit(1)
_warn_if_migration_needed(shelf.path)

shelf = Shelf(str(shelf_path))
stats = shelf.status()
Expand Down Expand Up @@ -1125,6 +1144,7 @@ def prune(
if not shelf.exists():
console.print(f"[red]No shelf found at {shelf_path}. Run `shelfai init` first.[/red]")
raise typer.Exit(1)
_warn_if_migration_needed(shelf.path)

stats = shelf.status()

Expand Down Expand Up @@ -1497,6 +1517,7 @@ def review_cmd(
if not shelf.exists():
console.print(f"[red]No shelf found at {shelf_path}. Run `shelfai init` first.[/red]")
raise typer.Exit(1)
_warn_if_migration_needed(shelf.path)

stage_dir = shelf.path / "generated" / "review" / "new_context"
stage_dir.mkdir(parents=True, exist_ok=True)
Expand Down Expand Up @@ -2399,6 +2420,75 @@ def version(
console.print()


# ──────────────────────────────────────────────
# shelfai migrate
# ──────────────────────────────────────────────


@app.command("migrate")
def migrate_cmd(
shelf_path: str = typer.Argument(".", help="Path to the shelf directory"),
dry_run: bool = typer.Option(False, "--dry-run", help="Show plan without writing changes"),
source_version: Optional[str] = typer.Option(None, "--from", help="Force source version"),
backup: bool = typer.Option(True, "--backup/--no-backup", help="Create a backup before migrating"),
):
"""Detect the shelf format and migrate it to the current version."""
from shelfai.core.migrate import ShelfMigrator

migrator = ShelfMigrator()
root = Path(shelf_path).expanduser().resolve()
if not root.exists():
console.print(f"[red]Path not found: {root}[/red]")
raise typer.Exit(1)

if source_version is None:
plan = migrator.plan_migration(str(root))
else:
plan = migrator._plan_from_version(str(root), migrator._normalize_version(source_version))

_print_migration_plan(plan)

if dry_run:
return

if plan.current_version == migrator.CURRENT_VERSION:
return

if backup:
backup_path = migrator.backup(str(root))
console.print(f"[dim]Backup created at {backup_path}[/dim]")

try:
migrator.migrate(str(root), source_version=source_version)
except FileExistsError as exc:
console.print(f"[red]Migration failed: {exc}[/red]")
raise typer.Exit(1)
console.print(f"[green]Shelf migrated to {migrator.CURRENT_VERSION}[/green]")


def _print_migration_plan(plan) -> None:
console.print(f"\n[bold]Migration plan[/bold]\n")
console.print(f" Current version: {plan.current_version}")
console.print(f" Target version : {plan.target_version}\n")

if not plan.steps:
console.print("[green]No migration needed.[/green]\n")
return

for i, step in enumerate(plan.steps, 1):
console.print(f" {i}. {step.from_version} -> {step.to_version}")
console.print(f" {step.description}")
for change in step.changes:
console.print(f" - {change}")
console.print()

if plan.breaking_changes:
console.print("[yellow]Breaking changes:[/yellow]")
for change in plan.breaking_changes:
console.print(f" - {change}")
console.print()


# ──────────────────────────────────────────────
# Helpers
# ──────────────────────────────────────────────
Expand Down
4 changes: 4 additions & 0 deletions src/shelfai/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from shelfai.core.conditions import ConditionalLoader, LoadCondition, LoadContext
from shelfai.core.diff_report import ChunkDiff, ShelfDiffReport, compare_before_after, compare_shelves
from shelfai.core.migrate import MigrationPlan, MigrationStep, ShelfMigrator
from shelfai.core.priority import ChunkPriority, PriorityManager

__all__ = [
Expand All @@ -10,8 +11,11 @@
"ConditionalLoader",
"LoadCondition",
"LoadContext",
"MigrationPlan",
"MigrationStep",
"PriorityManager",
"ShelfDiffReport",
"ShelfMigrator",
"compare_before_after",
"compare_shelves",
]
Loading
Loading