#!/usr/bin/env python3
"""Cleave CLI - Thin wrapper around cleave library.

This CLI provides command-line access to the cleave library's functionality.
All core logic lives in cleave.core.* modules.

Usage:
    cleave init --directive "..." --children '["A", "B", "C"]'
    cleave assess --directive "Add JWT auth..."
    cleave match --directive "Add Stripe payment processing"
    cleave context --manifest .cleave/manifest.yaml
    cleave conflicts --results ".cleave/0-task.md,.cleave/1-task.md"
    cleave reunify --workspace .cleave
    cleave install-skill
"""

from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path

from cleave.core.assessment import (
    PATTERNS,
    assess_directive,
    calculate_complexity,
    effective_complexity,
    match_pattern,
)
from cleave.core.install import install_skill
from cleave.core.metrics import record_assessment, get_metrics_summary
from cleave.core.permissions import (
    check_missing_permissions,
    get_settings_path,
    infer_permissions,
)
from cleave.core.probe import probe_codebase, format_probe_result
from cleave.core.reunify import reunify_workspace
from cleave.core.settings import (
    CleaveSettings,
    get_settings_file,
    load_settings,
    save_settings,
    set_setting,
)
from cleave.core.workspace import init_workspace, reconstruct_context
from cleave.core.yaml_utils import parse_yaml_simple, to_yaml


def cmd_init(args: argparse.Namespace) -> int:
    """Initialize a new cleave workspace with manifest and task files.

    Creates the workspace directory structure including:
    - manifest.yaml: Root configuration and intent
    - siblings.yaml: Sibling coordination metadata
    - N-task.md: Task files for each child
    - metrics.yaml: Telemetry tracking

    Args:
        args: Parsed CLI arguments including directive, children, output path

    Returns:
        0 on success, 1 on error (e.g., MAX_DEPTH exceeded)
    """
    children = json.loads(args.children)

    created = init_workspace(
        directive=args.directive,
        children=children,
        output_dir=args.output,
        mode=args.mode,
        threshold=args.threshold,
        max_depth=args.max_depth,
        parent_manifest_path=args.parent,
        node_id=args.node_id,
        depth=args.depth,
        include_permissions=args.infer_permissions,
        tdd=not args.no_tdd,
    )

    # Handle MAX_DEPTH error
    if "error" in created:
        print(f"Error: {created['error']}", file=sys.stderr)
        print(f"  {created['message']}", file=sys.stderr)
        print(f"  Guidance: {created['guidance']}", file=sys.stderr)
        return 1

    workspace_dir = created.get("workspace", args.output)
    print(f"Workspace initialized at {workspace_dir}/")
    if args.depth > 0:
        print(f"  Nested cleave at depth {args.depth}, node {args.node_id}")
        print(f"  Remaining budget: {args.max_depth - args.depth} levels")

    print("\nFiles created:")
    for name in created:
        if name != "workspace":  # Skip the metadata key
            print(f"  - {name}")

    assessment = assess_directive(args.directive, args.threshold)
    print(f"\nAssessment:")
    print(f"  Complexity: {assessment.complexity}")
    print(f"  Decision: {assessment.decision}")
    print(f"  Method: {assessment.method}")
    if assessment.pattern:
        print(f"  Pattern: {assessment.pattern} ({assessment.confidence:.0%})")
    else:
        print(
            "⚠ No pattern matched. Success criteria in manifest are TODO placeholders.",
            file=sys.stderr,
        )
        print(
            "  Define testable outcomes in manifest.yaml before executing child tasks.",
            file=sys.stderr,
        )

    # Warn if approaching MAX_DEPTH
    remaining = args.max_depth - args.depth
    if remaining == 1:
        print(f"\n⚠ WARNING: At depth {args.depth}, only 1 level of cleaving remains.", file=sys.stderr)
        print("  Child tasks should be atomic (execute without further cleaving).", file=sys.stderr)

    # Check permissions against settings if requested
    if args.infer_permissions:
        match = match_pattern(args.directive)
        pattern_id = match.pattern_id if match else None
        perms = infer_permissions(args.directive, pattern_id)
        settings_check = check_missing_permissions(perms)

        if not settings_check["all_covered"]:
            print(f"\n⚠ MISSING PERMISSIONS:", file=sys.stderr)
            print(f"  {settings_check['missing_count']} permission(s) not in settings.local.json", file=sys.stderr)
            for perm in settings_check["missing"]:
                print(f"    - {perm}", file=sys.stderr)
            print("\n  Add these to ~/.claude/settings.local.json for fire-and-forget execution.", file=sys.stderr)
        else:
            print(f"\n✓ All {settings_check['covered_count']} inferred permissions are configured.")

    return 0


def cmd_assess(args: argparse.Namespace) -> int:
    """Assess complexity of a directive or manual system/modifier specification.

    Supports two modes:
    1. Directive mode: Pass --directive to analyze a task description
    2. Manual mode: Pass --systems and --modifiers for explicit calculation

    Args:
        args: Parsed CLI arguments

    Returns:
        0 always (assessment output goes to stdout)
    """
    if args.directive:
        result = assess_directive(args.directive, args.threshold, args.validate)
        match = match_pattern(args.directive)
        pattern_id = match.pattern_id if match else None

        output = {
            "complexity": result.complexity,
            "systems": result.systems,
            "modifiers": result.modifiers,
            "method": result.method,
            "pattern": result.pattern,
            "confidence": result.confidence,
            "decision": result.decision,
            "reasoning": result.reasoning,
            "skip_interrogation": result.skip_interrogation,
            "tier": 0 if result.skip_interrogation else (1 if result.decision == "execute" else 2),
        }

        if args.infer_permissions:
            perms = infer_permissions(args.directive, pattern_id)
            output["inferred_permissions"] = perms
            settings_check = check_missing_permissions(perms)
            output["settings_check"] = settings_check
    else:
        systems_list = [s.strip() for s in args.systems.split(",")] if args.systems else []
        modifiers = [m.strip() for m in args.modifiers.split(",")] if args.modifiers else []

        complexity = calculate_complexity(len(systems_list), modifiers)
        eff_complexity = effective_complexity(complexity, args.validate)
        decision = "execute" if eff_complexity <= args.threshold else "cleave"

        output = {
            "complexity": complexity,
            "effective_complexity": eff_complexity,
            "systems": len(systems_list),
            "systems_list": systems_list,
            "modifiers": modifiers,
            "threshold": args.threshold,
            "decision": decision,
            "reasoning": f"(1 + {len(systems_list)}) × (1 + 0.5 × {len(modifiers)}) = {complexity}. Effective: {eff_complexity}",
        }

    if args.format == "yaml":
        print(to_yaml(output))
    else:
        print(json.dumps(output, indent=2))

    return 0


def cmd_match(args: argparse.Namespace) -> int:
    """Match a directive against the 7 core complexity patterns.

    Patterns include: full_stack_crud, authentication, external_integration,
    database_migration, performance_optimization, breaking_api_change, simple_refactor.

    Args:
        args: Parsed CLI arguments with directive

    Returns:
        0 always (match output goes to stdout)
    """
    match = match_pattern(args.directive)

    if match:
        output = {
            "matched": True,
            "pattern": match.name,
            "pattern_id": match.pattern_id,
            "confidence": match.confidence,
            "keywords_matched": match.keywords_matched,
            "systems": match.systems,
            "default_modifiers": match.modifiers,
            "split_strategy": PATTERNS[match.pattern_id]["split_strategy"],
        }
    else:
        directive_lower = args.directive.lower()
        partial_matches = []
        for pid, pattern in PATTERNS.items():
            keywords_matched = [kw for kw in pattern["keywords"] if kw in directive_lower]
            if keywords_matched:
                partial_matches.append(
                    {
                        "pattern": pattern["name"],
                        "keywords_matched": keywords_matched,
                        "count": len(keywords_matched),
                    }
                )
        partial_matches.sort(key=lambda x: x["count"], reverse=True)

        output = {
            "matched": False,
            "reason": "No pattern matched with confidence >= 0.80",
            "recommendation": "Use sequential thinking for assessment",
            "partial_matches": partial_matches[:3] if partial_matches else [],
        }

    if args.format == "yaml":
        print(to_yaml(output))
    else:
        print(json.dumps(output, indent=2))

    return 0


def cmd_context(args: argparse.Namespace) -> int:
    """Reconstruct full context from a manifest file.

    Useful for understanding the current state of a cleave workspace,
    including ancestry, intent, siblings, and assessment data.

    Args:
        args: Parsed CLI arguments with manifest path

    Returns:
        0 always (context output goes to stdout)
    """
    context = reconstruct_context(args.manifest)

    if args.format == "yaml":
        print(to_yaml(context))
    else:
        print(json.dumps(context, indent=2, default=str))

    return 0


def cmd_check_permissions(args: argparse.Namespace) -> int:
    """Check which permissions are needed vs configured in settings.local.json.

    Compares inferred permissions from a directive against the user's
    Claude Code settings to identify gaps for fire-and-forget execution.

    Args:
        args: Parsed CLI arguments with directive

    Returns:
        0 if all permissions covered, 1 if permissions missing
    """
    match = match_pattern(args.directive)
    pattern_id = match.pattern_id if match else None

    perms = infer_permissions(args.directive, pattern_id)
    settings_check = check_missing_permissions(perms)

    output = {
        "directive": args.directive,
        "pattern": match.name if match else None,
        "inferred": {
            "bundles": perms["bundles"],
            "permissions": perms["permissions"],
            "count": len(perms["permissions"]),
        },
        "settings": {
            "path": str(get_settings_path()),
            "current_permissions": settings_check["current_count"],
            "covered": settings_check["covered"],
            "missing": settings_check["missing"],
            "all_covered": settings_check["all_covered"],
        },
    }

    if args.snippet and settings_check["missing"]:
        print("# Add these to ~/.claude/settings.local.json under permissions.allow:\n")
        for perm in settings_check["missing"]:
            print(f'"{perm}",')
        return 0

    if args.format == "yaml":
        print(to_yaml(output))
    else:
        print(json.dumps(output, indent=2))

    if settings_check["all_covered"]:
        print(f"\n✓ All {len(perms['permissions'])} inferred permissions are configured.", file=sys.stderr)
    else:
        print(
            f"\n⚠ {len(settings_check['missing'])}/{len(perms['permissions'])} permissions missing from settings.local.json",
            file=sys.stderr,
        )
        print("\nTo enable fire-and-forget execution, add these permissions:", file=sys.stderr)
        for perm in settings_check["missing"]:
            print(f"  {perm}", file=sys.stderr)

    return 0 if settings_check["all_covered"] else 1


def cmd_install_skill(args: argparse.Namespace) -> int:
    """Handle install-skill subcommand."""
    result = install_skill()

    if result.success:
        if result.action == "created":
            print(f"Skill installed: {result.target_path} -> {result.source_path}")
        elif result.action == "updated":
            print(f"Skill symlink updated: {result.target_path} -> {result.source_path}")
        elif result.action == "skipped":
            print(f"Skill already installed at {result.target_path}")
        return 0
    else:
        print(f"Error: {result.message}", file=sys.stderr)
        return 1


def cmd_probe(args: argparse.Namespace) -> int:
    """Probe codebase before decomposition (Socratic interrogation phase).

    Scans the codebase to:
    - Detect technology stack (language, framework, database, etc.)
    - Find files relevant to the directive
    - Generate context-aware questions based on what's found

    This is the pre-decomposition assessment that helps users who understand
    the high-level problem but need help articulating the decomposition.

    Args:
        args: Parsed CLI arguments with directive and optional root path

    Returns:
        0 always (probe output goes to stdout)
    """
    result = probe_codebase(args.directive, args.root)
    output = format_probe_result(result)

    if args.format == "yaml":
        print(to_yaml(output))
    else:
        print(json.dumps(output, indent=2))

    # Summary to stderr for human readability
    if not args.quiet:
        print("\n--- Probe Summary ---", file=sys.stderr)
        stack_items = [f"{k}={v}" for k, v in result.detected_stack.items() if v]
        print(f"Stack: {', '.join(stack_items) or 'unknown'}", file=sys.stderr)
        print(f"Relevant files: {len(result.relevant_files)}", file=sys.stderr)
        if result.pattern_match:
            print(f"Pattern: {result.pattern_match} ({result.pattern_confidence:.0%})", file=sys.stderr)
        print(f"Questions generated: {len(result.triggered_questions)}", file=sys.stderr)

        if result.triggered_questions:
            print("\nSocratic Questions:", file=sys.stderr)
            for i, q in enumerate(result.triggered_questions, 1):
                print(f"  {i}. {q.question}", file=sys.stderr)
                if q.options != ["freeform"]:
                    print(f"     Options: {', '.join(q.options)}", file=sys.stderr)

    return 0


def cmd_metrics(args: argparse.Namespace) -> int:
    """Show metrics and feedback loop data.

    Displays accuracy of complexity predictions vs actual execution effort,
    pattern match statistics, and calibration recommendations.

    Args:
        args: Parsed CLI arguments

    Returns:
        0 always (metrics output goes to stdout)
    """
    summary = get_metrics_summary(args.workspace)

    if args.format == "yaml":
        print(to_yaml(summary))
    else:
        print(json.dumps(summary, indent=2))

    if not args.quiet and summary.get("assessments"):
        print("\n--- Metrics Summary ---", file=sys.stderr)
        print(f"Total assessments: {summary.get('total_assessments', 0)}", file=sys.stderr)
        print(f"Average accuracy: {summary.get('average_accuracy', 0):.0%}", file=sys.stderr)
        if summary.get("recommendations"):
            print("\nCalibration recommendations:", file=sys.stderr)
            for rec in summary["recommendations"]:
                print(f"  - {rec}", file=sys.stderr)

    return 0


def cmd_reunify(args: argparse.Namespace) -> int:
    """Reunify child tasks in a workspace.

    Aggregates results from all task files, runs 4-step conflict detection,
    generates merge.md for conflict resolution, and review.md for validation.
    Auto-extracts effort from task results for metrics feedback loop.

    Args:
        args: Parsed CLI arguments with workspace path

    Returns:
        0 if ready to close (no conflicts, all SUCCESS)
        1 on error (workspace not found, no tasks)
        2 if review required (conflicts or non-SUCCESS status)
    """
    result = reunify_workspace(
        workspace_path=args.workspace,
        write_merge=args.write_merge,
        write_review=not args.no_review,
        write_report=not getattr(args, 'no_report', False),
        explicit_effort=getattr(args, 'effort', None),
    )

    if "error" in result:
        print(f"Error: {result['error']}", file=sys.stderr)
        return 1

    if args.format == "yaml":
        print(to_yaml(result))
    else:
        print(json.dumps(result, indent=2))

    if not args.quiet:
        print("\n--- Reunification Summary ---", file=sys.stderr)
        print(f"Tasks: {result['tasks_found']}", file=sys.stderr)
        print(f"Status: {result['rollup_status']}", file=sys.stderr)
        print(f"Conflicts: {result['conflicts_found']}", file=sys.stderr)
        print(f"Verification: {result['verification']['coverage']} tasks provided evidence", file=sys.stderr)
        if result["merge_file"]:
            print(f"Merge file: {result['merge_file']}", file=sys.stderr)
        if result["review_file"]:
            print(f"Review file: {result['review_file']}", file=sys.stderr)
        if result.get("report_file"):
            print(f"Report file: {result['report_file']}", file=sys.stderr)
        # Warn on zero verification coverage
        verified = result["verification"]
        if verified["tasks_with_evidence"] == 0 and verified["tasks_total"] > 0:
            print(
                "⚠ No tasks provided verification evidence. Reunification contract requires proof of function.",
                file=sys.stderr,
            )

        # Warn if manifest still has unresolved TODO success criteria.
        # Uses prefix "TODO:" rather than an exact string so the warning
        # remains valid if template wording changes.
        manifest_path = Path(args.workspace) / "manifest.yaml"
        if manifest_path.exists():
            manifest_text = manifest_path.read_text()
            if any(
                line.strip().startswith("- TODO:")
                for line in manifest_text.splitlines()
            ):
                print(
                    "⚠ Success criteria were never defined (still TODO in manifest.yaml).",
                    file=sys.stderr,
                )
                print(
                    "  Accuracy and calibration claims for this workspace are unreliable.",
                    file=sys.stderr,
                )

        if result["ready_to_close"]:
            print("✓ Ready to close - no conflicts, all tasks succeeded", file=sys.stderr)
        else:
            print("⚠ Review required before closing", file=sys.stderr)

    return 0 if result["ready_to_close"] else 2


def cmd_analytics(args: argparse.Namespace) -> int:
    """Display performance analytics dashboard.

    Shows performance summary, trends, slow operations, and memory hotspots.
    Supports multiple export formats: JSON, YAML, Markdown, Prometheus.

    Args:
        args: Parsed CLI arguments

    Returns:
        0 always (analytics output goes to stdout)
    """
    from cleave.analytics import (
        detect_regressions,
        generate_analytics,
        get_memory_hotspots,
        get_slow_operations,
        get_trends,
    )
    from cleave.analytics.exporters import (
        export_json,
        export_markdown,
        export_prometheus,
        export_yaml,
    )

    analytics = generate_analytics()

    # Add additional analysis sections
    analytics["slow_operations"] = get_slow_operations(threshold_seconds=args.slow_threshold)
    analytics["memory_hotspots"] = get_memory_hotspots(threshold_mb=args.memory_threshold)
    analytics["trends"] = get_trends()
    analytics["regressions"] = detect_regressions()

    # Export in requested format
    if args.format == "json":
        output = export_json(analytics)
    elif args.format == "yaml":
        output = export_yaml(analytics)
    elif args.format == "markdown":
        output = export_markdown(analytics)
    elif args.format == "prometheus":
        output = export_prometheus(analytics)
    else:
        output = export_json(analytics)

    print(output)

    # Summary to stderr if not quiet
    if not args.quiet:
        print("\n--- Analytics Summary ---", file=sys.stderr)
        print(f"Total operations: {analytics['total_operations']}", file=sys.stderr)
        print(f"Total executions: {analytics['total_executions']}", file=sys.stderr)
        print(f"Total time: {analytics['summary']['total_time']:.4f}s", file=sys.stderr)

        if analytics["slow_operations"]:
            print(
                f"\nSlow operations (>{args.slow_threshold}s): {len(analytics['slow_operations'])}",
                file=sys.stderr,
            )
            for op in analytics["slow_operations"][:3]:
                print(f"  - {op['name']}: {op['avg_time']:.4f}s", file=sys.stderr)

        if analytics["memory_hotspots"]:
            print(
                f"\nMemory hotspots (>{args.memory_threshold}MB): {len(analytics['memory_hotspots'])}",
                file=sys.stderr,
            )
            for op in analytics["memory_hotspots"][:3]:
                print(f"  - {op['name']}: {op['peak_memory_mb']:.2f}MB", file=sys.stderr)

        if analytics["regressions"]:
            print(
                f"\nPerformance regressions detected: {len(analytics['regressions'])}",
                file=sys.stderr,
            )
            for reg in analytics["regressions"][:3]:
                print(
                    f"  - {reg['operation']}: {reg['regression_factor']:.1f}x slower",
                    file=sys.stderr,
                )

    return 0


def cmd_tui(args: argparse.Namespace) -> int:
    """Launch interactive TUI mode.

    Requires optional dependencies: pip install styrene-cleave[tui]

    Args:
        args: Parsed CLI arguments with optional directory

    Returns:
        0 on success, 1 if TUI dependencies not installed
    """
    try:
        from cleave.tui.app import CleaveTUI
    except ImportError as e:
        print("TUI dependencies not installed.", file=sys.stderr)
        print("Install with: pip install styrene-cleave[tui]", file=sys.stderr)
        print(f"\nMissing: {e}", file=sys.stderr)
        return 1

    from pathlib import Path

    working_directory = Path(args.directory).resolve()
    if not working_directory.exists():
        print(f"Directory not found: {working_directory}", file=sys.stderr)
        return 1

    app = CleaveTUI(working_directory=working_directory)
    app.run()
    return 0


def cmd_config(args: argparse.Namespace) -> int:
    """Manage cleave settings in ~/.cleave/settings.yaml.

    Subcommands:
    - show: Display current settings
    - set: Set a setting value
    - path: Show settings file path

    Args:
        args: Parsed CLI arguments

    Returns:
        0 on success, 1 on error
    """
    if args.config_action == "show":
        settings = load_settings()
        output = settings.to_dict()

        if args.format == "yaml":
            print(to_yaml(output))
        else:
            print(json.dumps(output, indent=2))

        print(f"\nSettings file: {get_settings_file()}", file=sys.stderr)
        return 0

    elif args.config_action == "set":
        key = args.key
        value = args.value

        # Validate known keys
        valid_keys = ["cloven_report_path", "cloven_report_naming", "backend"]
        if key not in valid_keys:
            print(f"Unknown setting: {key}", file=sys.stderr)
            print(f"Valid settings: {', '.join(valid_keys)}", file=sys.stderr)
            return 1

        # Validate cloven_report_naming values
        if key == "cloven_report_naming":
            valid_naming = ["descriptor", "timestamp", "workspace"]
            if value not in valid_naming:
                print(f"Invalid value for {key}: {value}", file=sys.stderr)
                print(f"Valid values: {', '.join(valid_naming)}", file=sys.stderr)
                return 1

        # Validate backend values
        if key == "backend":
            valid_backends = ["claude", "ollama", "openai", "vllm", "llamacpp"]
            if value not in valid_backends:
                print(f"Invalid backend: {value}", file=sys.stderr)
                print(f"Valid backends: {', '.join(valid_backends)}", file=sys.stderr)
                return 1

        # Handle "none" or "null" for path reset
        if key == "cloven_report_path" and value.lower() in ("none", "null", ""):
            value = None

        set_setting(key, value)
        print(f"Set {key} = {value}")
        print(f"Settings saved to: {get_settings_file()}", file=sys.stderr)
        return 0

    elif args.config_action == "path":
        print(get_settings_file())
        return 0

    else:
        print(f"Unknown config action: {args.config_action}", file=sys.stderr)
        return 1


def main() -> int:
    """Main entry point."""
    parser = argparse.ArgumentParser(
        description="Cleave CLI - Task decomposition automation",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  cleave init --directive "Add JWT auth" --children '["Auth Core", "Middleware", "Database"]'
  cleave assess --directive "Add JWT auth to API"
  cleave match --directive "Add Stripe payment processing"
  cleave context --manifest .cleave/manifest.yaml
  cleave reunify --workspace .cleave
  cleave analytics --format markdown  # Show performance analytics
  cleave install-skill
  cleave tui [directory]  # Launch interactive TUI (requires: pip install styrene-cleave[tui])
  cleave config show      # Show current settings
  cleave config set cloven_report_naming descriptor  # Set report naming strategy
        """,
    )

    subparsers = parser.add_subparsers(dest="command", required=True)

    # init
    init_parser = subparsers.add_parser("init", help="Initialize cleave workspace")
    init_parser.add_argument("--directive", "-d", required=True)
    init_parser.add_argument("--children", "-c", required=True, help="JSON array of child labels")
    init_parser.add_argument(
        "--output",
        "-o",
        default=None,
        help="Workspace directory (default: auto-generate from directive)",
    )
    init_parser.add_argument("--mode", "-m", default="lean", choices=["lean", "robust"])
    init_parser.add_argument("--threshold", "-t", type=float, default=2.0)
    init_parser.add_argument("--max-depth", type=int, default=5)
    init_parser.add_argument("--infer-permissions", action="store_true")
    init_parser.add_argument("--no-tdd", action="store_true")
    init_parser.add_argument("--parent", "-p", help="Path to parent manifest.yaml")
    init_parser.add_argument("--node-id", default="root")
    init_parser.add_argument("--depth", type=int, default=0)
    init_parser.set_defaults(func=cmd_init)

    # assess
    assess_parser = subparsers.add_parser("assess", help="Assess directive complexity")
    assess_parser.add_argument("--directive", "-d")
    assess_parser.add_argument("--systems", "-s")
    assess_parser.add_argument("--modifiers", "-m")
    assess_parser.add_argument("--threshold", "-t", type=float, default=2.0)
    assess_parser.add_argument("--validate/--no-validate", dest="validate", default=True)
    assess_parser.add_argument("--infer-permissions", action="store_true")
    assess_parser.add_argument("--format", "-f", default="json", choices=["json", "yaml"])
    assess_parser.set_defaults(func=cmd_assess)

    # match
    match_parser = subparsers.add_parser("match", help="Match directive against patterns")
    match_parser.add_argument("--directive", "-d", required=True)
    match_parser.add_argument("--format", "-f", default="json", choices=["json", "yaml"])
    match_parser.set_defaults(func=cmd_match)

    # context
    context_parser = subparsers.add_parser("context", help="Reconstruct context from manifest")
    context_parser.add_argument("--manifest", "-m", required=True)
    context_parser.add_argument("--format", "-f", default="json", choices=["json", "yaml"])
    context_parser.set_defaults(func=cmd_context)

    # check-permissions
    check_parser = subparsers.add_parser("check-permissions", help="Check permission gaps")
    check_parser.add_argument("--directive", "-d", required=True)
    check_parser.add_argument("--snippet", action="store_true")
    check_parser.add_argument("--format", "-f", default="json", choices=["json", "yaml"])
    check_parser.set_defaults(func=cmd_check_permissions)

    # reunify
    reunify_parser = subparsers.add_parser("reunify", help="Reunify child tasks")
    reunify_parser.add_argument("--workspace", "-w", required=True)
    reunify_parser.add_argument("--write-merge/--no-write-merge", dest="write_merge", default=True)
    reunify_parser.add_argument("--no-review", action="store_true")
    reunify_parser.add_argument("--no-report", action="store_true",
                                help="Skip generating .cloven.md session report")
    reunify_parser.add_argument("--effort", "-e", choices=["low", "medium", "high"],
                                help="Override inferred effort for metrics feedback")
    reunify_parser.add_argument("--quiet", "-q", action="store_true")
    reunify_parser.add_argument("--format", "-f", default="json", choices=["json", "yaml"])
    reunify_parser.set_defaults(func=cmd_reunify)

    # install-skill
    install_skill_parser = subparsers.add_parser(
        "install-skill", help="Install cleave skill to ~/.claude/skills/"
    )
    install_skill_parser.set_defaults(func=cmd_install_skill)

    # probe (Socratic interrogation phase)
    probe_parser = subparsers.add_parser("probe", help="Probe codebase before decomposition")
    probe_parser.add_argument("--directive", "-d", required=True, help="Task directive to analyze")
    probe_parser.add_argument("--root", "-r", default=".", help="Root directory to probe")
    probe_parser.add_argument("--quiet", "-q", action="store_true", help="Suppress summary output")
    probe_parser.add_argument("--format", "-f", default="json", choices=["json", "yaml"])
    probe_parser.set_defaults(func=cmd_probe)

    # metrics (feedback loop)
    metrics_parser = subparsers.add_parser("metrics", help="Show assessment accuracy metrics")
    metrics_parser.add_argument("--workspace", "-w", default=".", help="Workspace or project root")
    metrics_parser.add_argument("--quiet", "-q", action="store_true")
    metrics_parser.add_argument("--format", "-f", default="json", choices=["json", "yaml"])
    metrics_parser.set_defaults(func=cmd_metrics)

    # analytics (performance dashboard)
    analytics_parser = subparsers.add_parser(
        "analytics",
        help="Display performance analytics dashboard with trends and slow operations",
    )
    analytics_parser.add_argument(
        "--format",
        "-f",
        default="json",
        choices=["json", "yaml", "markdown", "prometheus"],
        help="Output format (default: json)",
    )
    analytics_parser.add_argument(
        "--slow-threshold",
        "-s",
        type=float,
        default=1.0,
        help="Threshold in seconds for slow operations (default: 1.0)",
    )
    analytics_parser.add_argument(
        "--memory-threshold",
        "-m",
        type=float,
        default=100.0,
        help="Threshold in MB for memory hotspots (default: 100.0)",
    )
    analytics_parser.add_argument("--quiet", "-q", action="store_true")
    analytics_parser.set_defaults(func=cmd_analytics)

    # tui (interactive terminal interface)
    tui_parser = subparsers.add_parser(
        "tui",
        help="Launch interactive TUI (requires: pip install styrene-cleave[tui])",
    )
    tui_parser.add_argument(
        "directory",
        nargs="?",
        default=".",
        help="Working directory for .calf files (default: current directory)",
    )
    tui_parser.set_defaults(func=cmd_tui)

    # config (settings management)
    config_parser = subparsers.add_parser(
        "config",
        help="Manage cleave settings (~/.cleave/settings.yaml)",
    )
    config_subparsers = config_parser.add_subparsers(dest="config_action", required=True)

    # config show
    config_show = config_subparsers.add_parser("show", help="Show current settings")
    config_show.add_argument("--format", "-f", default="yaml", choices=["json", "yaml"])

    # config set
    config_set = config_subparsers.add_parser("set", help="Set a setting value")
    config_set.add_argument("key", help="Setting key (e.g., cloven_report_path)")
    config_set.add_argument("value", help="Setting value")

    # config path
    config_subparsers.add_parser("path", help="Show settings file path")

    config_parser.set_defaults(func=cmd_config)

    args = parser.parse_args()
    return args.func(args)


if __name__ == "__main__":
    sys.exit(main())
