Commit ed0a5ffc authored by Jan Reimes's avatar Jan Reimes
Browse files

feat(ai): add spec checkout support to workspace add-members

- Add checkout_spec_to_workspace() function for spec checkout
- Add --release option for adding specs (allows multiple versions)
- Update add-members CLI to checkout specs using existing checkout-specs functionality
- Import checkout_spec_to_workspace in __init__.py and cli/ai.py
- Remove duplicate code from workspace functions
parent 5d90246d
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ from tdoc_crawler.ai.operations.summarize import SummarizeResult, summarize_tdoc
from tdoc_crawler.ai.operations.workspaces import (
    DEFAULT_WORKSPACE,
    add_workspace_members,
    checkout_spec_to_workspace,
    checkout_tdoc_to_workspace,
    create_workspace,
    delete_workspace,
@@ -103,6 +104,7 @@ __all__ = [
    "ProcessingStatus",
    "SummarizeResult",
    "add_workspace_members",
    "checkout_spec_to_workspace",
    "checkout_tdoc_to_workspace",
    "convert_tdoc",
    "create_workspace",
+54 −0
Original line number Diff line number Diff line
@@ -7,6 +7,8 @@ from pathlib import Path
from typing import Any

from tdoc_crawler.ai.models import WorkspaceMember
from tdoc_crawler.database.specs import SpecDatabase
from tdoc_crawler.specs.operations.checkout import checkout_specs
from tdoc_crawler.tdocs.operations.checkout import checkout_tdoc
from tdoc_crawler.tdocs.sources.whatthespec import resolve_via_whatthespec

@@ -200,6 +202,57 @@ def checkout_tdoc_to_workspace(
        return None


def checkout_spec_to_workspace(
    spec_number: str,
    checkout_base: Path,
    workspace: str | None,
    release: str = "latest",
    ) -> Path | None:
    """Checkout a spec and add it to a workspace.

    Args:
        spec_number: Spec number (e.g., "26260" for 26.260)
        checkout_base: Base checkout directory
        workspace: Workspace name
        release: Spec release version (e.g., "16.3.0", "17.0.0") or "latest"

    Returns:
        Path to the checked out spec folder, or None if checkout failed
    """
    # First check if already checked out
    specs_dir = checkout_base / "Specs"
    if specs_dir.exists():
        for spec_dir in specs_dir.rglob(f"*{spec_number}*"):
            if spec_dir.is_dir():
                _logger.debug(f"Spec {spec_number} already checked out at {spec_dir}")
                return spec_dir

    # Need to checkout the spec
    try:
        # Open the spec database
        db_path = checkout_base.parent / "specs.db"
        with SpecDatabase(db_path) as db:
            # Checkout the spec
            checkout_paths = checkout_specs(
                spec_numbers=[spec_number],
                checkout_dir=checkout_base,
                database=db,
                release=release,
            )

            if checkout_paths and checkout_paths[0] and checkout_paths[0].exists():
                _logger.info(f"Checked out spec {spec_number} to {checkout_paths[0]}")
                return checkout_paths[0]

        _logger.warning(f"Failed to checkout spec {spec_number}")
        return None

    except Exception as e:
        _logger.warning(f"Error checking out spec {spec_number}: {e}")
        return None



def ensure_ai_subfolder(checkout_path: Path) -> Path:
    """Ensure the .ai subfolder exists for processed outputs.

@@ -218,6 +271,7 @@ def ensure_ai_subfolder(checkout_path: Path) -> Path:
__all__ = [
    "DEFAULT_WORKSPACE",
    "add_workspace_members",
    "checkout_spec_to_workspace",
    "checkout_tdoc_to_workspace",
    "create_workspace",
    "delete_workspace",
+10 −5
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@ from rich.table import Table

from tdoc_crawler.ai import (
    AiStorage,
    checkout_spec_to_workspace,
    checkout_tdoc_to_workspace,
    convert_tdoc,
    create_workspace,
@@ -313,7 +314,8 @@ def workspace_add_members(
    workspace: Annotated[str | None, typer.Option("--workspace", "-w", help="Workspace name")] = None,
    items: Annotated[list[str], typer.Argument(..., help="Source item IDs to add")] = None,  # type: ignore[assignment]
    kind: Annotated[str, typer.Option("--kind", help="Source kind (tdoc, spec, other)"),] = "tdoc",
    checkout: Annotated[bool, typer.Option("--checkout/--no-checkout", help="Checkout/download TDocs if not present")] = True,
    checkout: Annotated[bool, typer.Option("--checkout/--no-checkout", help="Checkout/download documents if not present")] = True,
    release: Annotated[str | None, typer.Option("--release", help="Spec release version (e.g., 16.3.0, 17.0.0). Only applies to specs.")] = None,
    json_output: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
) -> None:
    """Add source items to a workspace."""
@@ -331,10 +333,13 @@ def workspace_add_members(
    for item in items:
        source_path = item

        # For TDocs, try to resolve/checkout to get actual path
        # Specs are handled differently - store the spec number for now
        if checkout and source_kind == SourceKind.TDOC:
        # For TDocs and specs, try to resolve/checkout to get actual path
        if checkout:
            checkout_path = None
            if source_kind == SourceKind.TDOC:
                checkout_path = checkout_tdoc_to_workspace(item, checkout_base, storage, workspace)
            elif source_kind == SourceKind.SPEC:
                checkout_path = checkout_spec_to_workspace(item, checkout_base, workspace, release or "latest")
            if checkout_path:
                source_path = str(checkout_path)
                # Ensure .ai subfolder exists