"""Codebase adoption — bring any existing codebase under pact governance.

Bridges the gap between test-gen (which analyzes) and the daemon (which
monitors and fixes). Creates the full project state the daemon expects:
DecompositionTree, contracts, test suites, RunState, implementations.

The existing code IS the spec. Contracts describe what it actually does.
Tests verify it. The daemon then monitors for regressions.
"""

from __future__ import annotations

import logging
import re
import shutil
from datetime import datetime
from pathlib import Path
from uuid import uuid4

from pact.agents.base import AgentBase
from pact.budget import BudgetTracker
from pact.codebase_analyzer import analyze_codebase
from pact.project import ProjectManager
from pact.schemas import (
    ComponentContract,
    ComponentTask,
    ContractTestSuite,
    DecompositionNode,
    DecompositionTree,
    InterviewResult,
    RunState,
)
from pact.schemas_testgen import (
    CodebaseAnalysis,
    TestGenPlan,
)
from pact.test_gen import (
    plan_test_generation,
    render_security_audit,
    reverse_engineer_contract,
)

logger = logging.getLogger(__name__)


# ── Extension Helpers ──────────────────────────────────────────────

_SOURCE_EXTENSIONS = (".py", ".ts", ".js", ".tsx", ".jsx", ".rs")


def _strip_source_ext(name: str) -> str:
    """Strip a known source-file extension from *name*."""
    for ext in _SOURCE_EXTENSIONS:
        if name.endswith(ext):
            return name[:-len(ext)]
    return name


# ── Mechanical Smoke Tests ────────────────────────────────────────


def _is_module_level(func, source_path: Path) -> bool:
    """Check if a function is defined at module level (not nested/closure)."""
    if func.is_method:
        return False
    if func.line_number <= 0:
        return True  # No line info — assume module-level
    try:
        source_lines = source_path.read_text(encoding="utf-8", errors="replace").splitlines()
        if func.line_number <= len(source_lines):
            line = source_lines[func.line_number - 1]
            return line.startswith("def ") or line.startswith("async def ")
    except (OSError, IndexError):
        pass
    return True  # Assume module-level if we can't check


def _detect_src_layout(root: Path) -> bool:
    """Detect if project uses src/ layout (PEP 517 convention)."""
    src_dir = root / "src"
    if not src_dir.is_dir():
        return False
    pyproject = root / "pyproject.toml"
    setup_py = root / "setup.py"
    return pyproject.exists() or setup_py.exists()


def generate_smoke_tests(
    analysis: CodebaseAnalysis,
    language: str = "python",
) -> dict[str, str]:
    """Generate mechanical smoke tests from AST-extracted function signatures.

    No LLM required. Tests verify:
    - Module imports successfully
    - Each public, module-level function is callable

    Returns dict of component_id -> test code string.
    """
    if language == "rust":
        return _generate_rust_smoke_tests(analysis)
    if language in ("typescript", "javascript"):
        return _generate_typescript_smoke_tests(analysis)
    return _generate_python_smoke_tests(analysis)


def _generate_python_smoke_tests(analysis: CodebaseAnalysis) -> dict[str, str]:
    """Generate Python smoke tests from AST-extracted function signatures."""
    suites: dict[str, str] = {}
    root = Path(analysis.root_path)
    src_layout = _detect_src_layout(root)

    for sf in analysis.source_files:
        if not sf.functions:
            continue

        # Strip src/ prefix for module path when using src layout
        rel_path = sf.path
        if src_layout and rel_path.startswith("src/"):
            rel_path = rel_path[4:]

        # Component ID for the tree (flat, underscore-separated)
        component_id = sf.path.replace("/", "_").replace("\\", "_")
        if component_id.endswith(".py"):
            component_id = component_id[:-3]

        # Module import path (dot-separated, no .py)
        module_path = rel_path.replace("/", ".").replace("\\", ".")
        if module_path.endswith(".py"):
            module_path = module_path[:-3]

        # Flat test filename using dotted module path (avoids package collisions)
        test_file_rel = f"test_{module_path.replace('.', '_')}.py"

        # Safe identifier for test function names
        safe_id = module_path.replace(".", "_")

        lines = [
            f'"""Smoke tests for {sf.path} (auto-generated by pact adopt)."""',
            "",
            "import pytest",
            "",
        ]

        # Import test
        lines.append(f"def test_import_{safe_id}():")
        lines.append(f'    """Module imports without error."""')
        lines.append(f"    import importlib")
        lines.append(f'    mod = importlib.import_module("{module_path}")')
        lines.append(f"    assert mod is not None")
        lines.append("")

        # Per-function callable checks (public, module-level only)
        source_path = root / sf.path
        for func in sf.functions:
            if func.name.startswith("_"):
                continue
            if not _is_module_level(func, source_path):
                continue
            safe_name = func.name.replace(".", "_")
            lines.append(f"def test_{safe_name}_is_callable():")
            lines.append(f'    """Function {func.name} exists and is callable."""')
            lines.append(f"    import importlib")
            lines.append(f'    mod = importlib.import_module("{module_path}")')
            lines.append(f'    fn = getattr(mod, "{func.name}", None)')
            lines.append(f"    assert fn is not None, "
                         f"\"'{func.name}' not found in {module_path}\"")
            lines.append(f"    assert callable(fn)")
            lines.append("")

        suites[test_file_rel] = "\n".join(lines)

    return suites


def _generate_rust_smoke_tests(analysis: CodebaseAnalysis) -> dict[str, str]:
    """Generate Rust smoke tests from AST-extracted function signatures.

    For each source file with public functions, generates a .rs test file
    with #[cfg(test)] and #[test] attributes. Tests verify that each public
    function exists and is callable (compilation check).

    Output goes to tests/smoke/ directory as individual .rs files.
    """
    suites: dict[str, str] = {}
    root = Path(analysis.root_path)

    for sf in analysis.source_files:
        if not sf.functions:
            continue

        # Derive module path from file: src/foo/bar.rs -> foo::bar
        rel_path = sf.path
        if rel_path.startswith("src/"):
            rel_path = rel_path[4:]

        # Component ID for the tree
        component_id = sf.path.replace("/", "_").replace("\\", "_")
        if component_id.endswith(".rs"):
            component_id = component_id[:-3]

        # Rust module path (:: separated)
        module_path = rel_path.replace("/", "::").replace("\\", "::")
        if module_path.endswith(".rs"):
            module_path = module_path[:-3]
        # lib.rs and main.rs are crate root, not submodules
        if module_path.endswith("::lib") or module_path.endswith("::main"):
            module_path = module_path.rsplit("::", 1)[0] if "::" in module_path else "crate"
        if module_path in ("lib", "main"):
            module_path = "crate"

        # Flat test filename
        safe_module = module_path.replace("::", "_")
        test_file_rel = f"test_{safe_module}.rs"

        # Collect public functions (skip private ones starting with _)
        pub_funcs = [
            f for f in sf.functions
            if not f.name.startswith("_")
        ]

        lines = [
            f"//! Smoke tests for {sf.path} (auto-generated by pact adopt).",
            "",
            "#[cfg(test)]",
            f"mod smoke_{safe_module} {{",
        ]

        # Use the appropriate import path
        if module_path == "crate":
            use_prefix = "crate"
        else:
            use_prefix = f"crate::{module_path}"

        # Compilation check: use the module
        lines.append("    #[test]")
        lines.append(f"    fn test_module_{safe_module}_compiles() {{")
        lines.append(f"        // Verify that the module compiles and is reachable.")
        if module_path != "crate":
            lines.append(f"        use {use_prefix};")
        lines.append(f"        // Module compiled successfully.")
        lines.append("    }")
        lines.append("")

        # Per-function existence checks
        for func in pub_funcs:
            safe_name = func.name.replace(".", "_")
            lines.append("    #[test]")
            lines.append(f"    fn test_{safe_name}_exists() {{")
            lines.append(f"        // Verify {func.name} is a callable symbol.")
            lines.append(f"        let _fn_ref = {use_prefix}::{func.name};")
            lines.append("    }")
            lines.append("")

        lines.append("}")
        lines.append("")

        suites[test_file_rel] = "\n".join(lines)

    return suites


def _generate_typescript_smoke_tests(analysis: CodebaseAnalysis) -> dict[str, str]:
    """Generate TypeScript smoke tests (Vitest) from extracted definitions.

    For each source file with exported definitions, generates a ``.test.ts``
    file that verifies the module imports and each export is defined.
    Handles Effect-TS patterns: layers, services, schemas, tagged errors.
    """
    suites: dict[str, str] = {}

    for sf in analysis.source_files:
        if not sf.functions:
            continue

        # Strip src/ prefix for import path
        rel_path = sf.path
        if rel_path.startswith("src/"):
            rel_path = rel_path[4:]

        # Module import path (no extension)
        import_path = rel_path
        for ext in (".ts", ".js", ".tsx", ".jsx"):
            if import_path.endswith(ext):
                import_path = import_path[:-len(ext)]
                break

        # Component ID for the tree (flat, underscore-separated)
        component_id = sf.path.replace("/", "_").replace("\\", "_")
        for ext in (".ts", ".js", ".tsx", ".jsx"):
            if component_id.endswith(ext.replace(".", "_")):
                component_id = component_id[:-len(ext) + 1]  # remove _ts etc
                break
        # Clean up any remaining extension
        component_id = re.sub(r"_(?:ts|js|tsx|jsx)$", "", component_id)

        # Flat test filename
        safe_id = import_path.replace("/", "_").replace("\\", "_")
        test_file_rel = f"test_{safe_id}.test.ts"

        lines = [
            f'// Smoke tests for {sf.path} (auto-generated by pact adopt)',
            f"import {{ describe, it, expect }} from 'vitest'",
            "",
            f"describe('{import_path}', () => {{",
        ]

        # Import test
        lines.append(f"  it('imports without error', async () => {{")
        lines.append(f"    const mod = await import('../src/{import_path}')")
        lines.append(f"    expect(mod).toBeDefined()")
        lines.append(f"  }})")
        lines.append("")

        # Per-export checks
        for func in sf.functions:
            if func.name.startswith("_"):
                continue
            if "export" not in func.decorators:
                continue
            safe_name = func.name.replace(".", "_")

            # Determine the right type check
            is_fn = (
                not func.decorators
                or not any(d in func.decorators for d in (
                    "class", "interface", "type", "service", "tagged_error",
                    "tagged_data", "schema", "layer", "effect_gen", "pipe",
                ))
            )

            lines.append(f"  it('exports {func.name}', async () => {{")
            lines.append(f"    const mod = await import('../src/{import_path}')")
            lines.append(f"    expect(mod.{func.name}).toBeDefined()")
            if is_fn:
                lines.append(f"    expect(typeof mod.{func.name}).toBe('function')")
            lines.append(f"  }})")
            lines.append("")

        lines.append("})")
        lines.append("")

        suites[test_file_rel] = "\n".join(lines)

    return suites


# ── Tree Construction ──────────────────────────────────────────────


def build_decomposition_tree(analysis: CodebaseAnalysis) -> DecompositionTree:
    """Convert a CodebaseAnalysis into a DecompositionTree.

    Each source file with functions becomes a leaf component.
    Packages (directories) become intermediate nodes.
    Root is the project itself.
    """
    nodes: dict[str, DecompositionNode] = {}

    # Root node
    root_id = "root"
    nodes[root_id] = DecompositionNode(
        component_id=root_id,
        name="Project Root",
        description=f"Adopted codebase at {analysis.root_path}",
        depth=0,
        children=[],
    )

    # Track intermediate package nodes
    package_nodes: dict[str, str] = {}  # "src/utils" -> component_id

    for sf in analysis.source_files:
        if not sf.functions:
            continue

        # Derive component_id from file path
        component_id = _strip_source_ext(
            sf.path.replace("/", "_").replace("\\", "_")
        )

        # Derive a clean name
        name = Path(sf.path).stem.replace("_", " ").title()

        # Determine parent: package directory or root
        parent_parts = Path(sf.path).parent.parts
        parent_id = root_id

        if parent_parts and str(Path(sf.path).parent) != ".":
            # Create intermediate package nodes as needed
            for i in range(len(parent_parts)):
                pkg_path = "/".join(parent_parts[: i + 1])
                if pkg_path not in package_nodes:
                    pkg_id = pkg_path.replace("/", "_")
                    pkg_name = parent_parts[i].replace("_", " ").title()
                    nodes[pkg_id] = DecompositionNode(
                        component_id=pkg_id,
                        name=pkg_name,
                        description=f"Package: {pkg_path}",
                        depth=i + 1,
                        parent_id=parent_id if i == 0 else package_nodes.get(
                            "/".join(parent_parts[:i]), root_id
                        ),
                        children=[],
                    )
                    package_nodes[pkg_path] = pkg_id

                    # Add to parent's children
                    actual_parent = nodes[pkg_id].parent_id
                    if actual_parent and actual_parent in nodes:
                        if pkg_id not in nodes[actual_parent].children:
                            nodes[actual_parent].children.append(pkg_id)

                parent_id = package_nodes[pkg_path]

        # Create leaf node for the source file
        depth = len(parent_parts) + 1 if parent_parts and str(Path(sf.path).parent) != "." else 1
        func_names = [f.name for f in sf.functions]
        nodes[component_id] = DecompositionNode(
            component_id=component_id,
            name=name,
            description=f"Module {sf.path}: {', '.join(func_names[:5])}"
            + (f" (+{len(func_names) - 5} more)" if len(func_names) > 5 else ""),
            depth=depth,
            parent_id=parent_id,
            children=[],
        )

        # Add to parent's children
        if parent_id in nodes and component_id not in nodes[parent_id].children:
            nodes[parent_id].children.append(component_id)

    return DecompositionTree(root_id=root_id, nodes=nodes)


# ── Implementation Linking ─────────────────────────────────────────


def link_existing_implementations(
    project: ProjectManager,
    analysis: CodebaseAnalysis,
    tree: DecompositionTree,
) -> None:
    """Link existing source files as implementations.

    Instead of generating code, we symlink or copy the existing source
    into the implementation directories the daemon expects.
    """
    root = Path(analysis.root_path)

    for sf in analysis.source_files:
        if not sf.functions:
            continue

        component_id = _strip_source_ext(
            sf.path.replace("/", "_").replace("\\", "_")
        )

        if component_id not in tree.nodes:
            continue

        # Create impl directory and copy/symlink source
        src_dir = project.impl_src_dir(component_id)
        source_path = root / sf.path
        if source_path.exists():
            dest_path = src_dir / source_path.name
            shutil.copy2(source_path, dest_path)

        # Save metadata
        project.save_impl_metadata(component_id, {
            "adopted": True,
            "source_path": sf.path,
            "functions": len(sf.functions),
            "adopted_at": datetime.now().isoformat(),
        })


# ── Adoption Pipeline ─────────────────────────────────────────────


async def adopt_codebase(
    project_path: str | Path,
    language: str = "python",
    budget: float = 10.0,
    model: str = "claude-sonnet-4-5-20250929",
    backend: str = "anthropic",
    complexity_threshold: int = 5,
    dry_run: bool = False,
    include: list[str] | None = None,
    exclude: list[str] | None = None,
) -> AdoptionResult:
    """Adopt an existing codebase into a full pact project.

    Steps:
    1. Mechanical analysis (reuses test-gen)
    2. Build decomposition tree from modules
    3. Initialize project state
    4. Reverse-engineer contracts from source (LLM)
    5. Generate tests from contracts (LLM)
    6. Link existing code as implementations
    7. Create RunState ready for daemon

    Args:
        project_path: Root of the codebase to adopt.
        language: Programming language.
        budget: LLM budget in dollars.
        model: LLM model for generation.
        backend: LLM backend.
        complexity_threshold: Priority threshold for complexity.
        dry_run: If True, only do mechanical phases (no LLM).
        include: Directories, file-lists, or ``-`` for stdin file paths.
        exclude: Additional directory names to skip.

    Returns:
        AdoptionResult with summary.
    """
    project_path = Path(project_path).resolve()

    # Phase 1: Mechanical analysis
    logger.info("Analyzing codebase at %s...", project_path)
    analysis = analyze_codebase(project_path, language, include=include, exclude=exclude)
    logger.info(
        "Found %d source files, %d functions, %d test files",
        analysis.total_source_files, analysis.total_functions, analysis.total_test_files,
    )

    # Phase 2: Build decomposition tree
    tree = build_decomposition_tree(analysis)
    # Exclude root-only tree (empty project) from leaf count
    leaves = tree.leaves()
    leaf_count = len(leaves) if any(n.component_id != "root" for n in leaves) else 0
    logger.info("Built decomposition tree: %d nodes, %d leaves", len(tree.nodes), leaf_count)

    # Phase 3: Initialize project
    project = ProjectManager(project_path)
    project.init(budget=budget)

    # Write task.md describing the adoption
    project.task_path.write_text(
        f"# Adopted Codebase\n\n"
        f"This project was adopted from an existing codebase.\n\n"
        f"## Analysis\n"
        f"- Source files: {analysis.total_source_files}\n"
        f"- Functions: {analysis.total_functions}\n"
        f"- Test files: {analysis.total_test_files}\n"
        f"- Coverage: {analysis.coverage.coverage_ratio:.0%}\n"
        f"- Security findings: {len(analysis.security.findings)}\n"
    )

    # Save tree and interview (auto-approved for adoption)
    project.save_tree(tree)
    project.save_interview(InterviewResult(
        risks=[f"Adopted codebase with {len(analysis.security.findings)} security findings"],
        assumptions=["Existing code is the specification", "Contracts describe actual behavior"],
        approved=True,
    ))

    # Build plan for prioritization
    plan = plan_test_generation(analysis, complexity_threshold, skip_covered=False)

    # Save security audit
    audit_dir = project_path / ".pact" / "test-gen"
    audit_dir.mkdir(parents=True, exist_ok=True)
    (audit_dir / "security_audit.md").write_text(render_security_audit(analysis.security))
    (audit_dir / "analysis.json").write_text(analysis.model_dump_json(indent=2))

    result = AdoptionResult(
        components=leaf_count,
        total_functions=analysis.total_functions,
        coverage_before=analysis.coverage.coverage_ratio,
        security_findings=len(analysis.security.findings),
    )

    # Generate mechanical smoke tests (no LLM) for all modes
    # Flat files in tests/smoke/ with dotted module names (e.g. test_pact_adopt.py)
    smoke_suites = generate_smoke_tests(analysis, language)
    smoke_dir = project_path / "tests" / "smoke"
    smoke_dir.mkdir(parents=True, exist_ok=True)
    for test_filename, code in smoke_suites.items():
        (smoke_dir / test_filename).write_text(code)
    result.smoke_tests_generated = len(smoke_suites)
    if smoke_suites:
        project.append_audit("adopt_smoke_tests", f"{len(smoke_suites)} smoke test files generated")
        logger.info("Generated %d smoke test files", len(smoke_suites))

    if dry_run:
        result.dry_run = True
        # Still create state so user can inspect
        state = _create_adoption_state(project, tree, "interview")
        state.pause("Dry run — adoption not finalized")
        project.save_state(state)
        project.append_audit("adopt_dry_run", f"{leaf_count} components analyzed")
        return result

    # Phase 4-5: LLM-powered contract + test generation
    budget_tracker = BudgetTracker(per_project_cap=budget)
    budget_tracker.set_model_pricing(model)
    budget_tracker.start_project()

    agent = AgentBase(budget_tracker, model=model, backend=backend)

    # Group source files by component for processing
    from pact.budget import BudgetExceeded

    resumed = 0
    skipped = 0
    try:
        for sf in analysis.source_files:
            if not sf.functions:
                continue

            if budget_tracker.is_exceeded():
                logger.warning("Budget exceeded, stopping contract generation")
                break

            component_id = _strip_source_ext(
                sf.path.replace("/", "_").replace("\\", "_")
            )

            if component_id not in tree.nodes:
                continue

            # Resume: skip modules that already have both contract + tests
            existing_contract = project.load_contract(component_id)
            existing_suite = project.load_test_suite(component_id)
            if existing_contract and existing_suite:
                resumed += 1
                result.contracts_generated += 1
                result.tests_generated += 1
                continue

            # Read source
            source_path = project_path / sf.path
            if not source_path.exists():
                continue
            source_code = source_path.read_text(encoding="utf-8", errors="replace")

            module_name = _strip_source_ext(
                sf.path.replace("/", ".").replace("\\", ".")
            )
            function_names = [f.name for f in sf.functions]

            # Phase 4: Reverse-engineer contract (reuse if already saved)
            contract = existing_contract
            if not contract:
                logger.info("Contracting %s (%d functions)...", module_name, len(function_names))
                try:
                    contract = await reverse_engineer_contract(
                        agent, source_code, module_name, function_names,
                        tool_index=analysis.tool_index,
                    )
                except Exception as exc:
                    if "stalled" in str(exc) or "APIStatusError" in type(exc).__name__ or "500" in str(exc):
                        logger.warning("API error on %s: %s — skipping", module_name, exc)
                        skipped += 1
                        continue
                    raise
                contract.component_id = component_id
                project.save_contract(contract)
                project.append_audit(
                    "adopt_contract", f"Contract for {component_id}",
                    component_id=component_id,
                )
            result.contracts_generated += 1

            if budget_tracker.is_exceeded():
                break

            # Phase 5: Generate tests
            logger.info("Testing %s...", module_name)
            from pact.agents.test_author import author_tests
            try:
                suite, _research, _plan = await author_tests(
                    agent, contract, language=language,
                )
            except Exception as exc:
                if "stalled" in str(exc) or "APIStatusError" in type(exc).__name__ or "500" in str(exc):
                    logger.warning("API error on tests for %s: %s — skipping", module_name, exc)
                    skipped += 1
                    continue
                raise
            # Fix import paths for the actual module location
            if suite.generated_code:
                old_import = f"from {contract.component_id} import"
                new_import = f"from {module_name} import"
                suite.generated_code = suite.generated_code.replace(old_import, new_import)
            suite.component_id = component_id
            project.save_test_suite(suite)
            result.tests_generated += 1
            project.append_audit(
                "adopt_tests", f"Tests for {component_id}: {len(suite.test_cases)} cases",
                component_id=component_id,
            )

    except BudgetExceeded:
        logger.warning("Budget exhausted — progress saved, re-run to continue")

    finally:
        if resumed:
            logger.info("Resumed: %d modules from previous run", resumed)
        if skipped:
            logger.warning("Skipped %d modules due to API errors", skipped)
        await agent.close()
        result.total_cost_usd = budget_tracker.project_spend

    # Phase 6: Link existing code as implementations
    link_existing_implementations(project, analysis, tree)

    # Phase 7: Create RunState
    state = _create_adoption_state(project, tree, "implement")
    project.save_state(state)
    project.append_audit(
        "adopt_complete",
        f"{result.contracts_generated} contracts, {result.tests_generated} tests, ${result.total_cost_usd:.4f}",
    )

    return result


def _create_adoption_state(
    project: ProjectManager,
    tree: DecompositionTree,
    target_phase: str,
) -> RunState:
    """Create a RunState for an adopted project.

    Sets the phase past interview/decompose since those are handled
    by the adoption process. Components are marked as 'contracted'
    (have contracts but not yet validated).
    """
    component_tasks = []
    for node_id, node in tree.nodes.items():
        if not node.children:  # Leaf nodes only
            component_tasks.append(ComponentTask(
                component_id=node_id,
                status="pending",
            ))

    return RunState(
        id=uuid4().hex[:12],
        project_dir=str(project.project_dir),
        status="active",
        phase=target_phase,
        created_at=datetime.now().isoformat(),
        component_tasks=component_tasks,
    )


# ── Result Model ───────────────────────────────────────────────────


class AdoptionResult:
    """Summary of a codebase adoption."""

    def __init__(
        self,
        components: int = 0,
        total_functions: int = 0,
        coverage_before: float = 0.0,
        security_findings: int = 0,
    ):
        self.components = components
        self.total_functions = total_functions
        self.coverage_before = coverage_before
        self.security_findings = security_findings
        self.contracts_generated = 0
        self.tests_generated = 0
        self.smoke_tests_generated = 0
        self.total_cost_usd = 0.0
        self.dry_run = False

    def summary(self) -> str:
        lines = []
        if self.dry_run:
            lines.append("=== Adoption Dry Run ===")
        else:
            lines.append("=== Adoption Complete ===")

        lines.append(f"Components: {self.components}")
        lines.append(f"Functions: {self.total_functions}")
        lines.append(f"Existing coverage: {self.coverage_before:.0%}")
        lines.append(f"Security findings: {self.security_findings}")
        if self.smoke_tests_generated:
            lines.append(f"Smoke tests: {self.smoke_tests_generated} files")

        if not self.dry_run:
            lines.append(f"Contracts generated: {self.contracts_generated}")
            lines.append(f"Tests generated: {self.tests_generated}")
            lines.append(f"Cost: ${self.total_cost_usd:.4f}")
            lines.append("")
            lines.append("Next steps:")
            lines.append("  pact status .          # Review project state")
            lines.append("  pact tree .            # View component tree")
            lines.append("  pact daemon .          # Start monitoring daemon")

        return "\n".join(lines)
