Section-02 hasn't been written yet either, but I know its purpose from the index. I have all the information needed to write this section.

# Section 03 — CLI Module

## Overview

This section creates the new `src/godotiq/cli.py` module with argparse-based CLI routing and updates `src/godotiq/__main__.py` to use it. The CLI supports three modes:

- `godotiq` (no args) -- starts the MCP server (backward compatible with existing `.mcp.json` configs)
- `godotiq --version` -- prints the package version
- `godotiq install-addon <path>` -- copies addon files to a Godot project (skeleton only; full implementation is in section-04)

The critical design constraint is **lazy imports**: `cli.py` must never import `server.py` at module scope because that module performs heavy side-effectful imports (registering all 35 MCP tools, loading parsers, initializing the bridge subsystem). The `install-addon` code path must not pay that startup cost.

## Dependencies

- **Section 01 (packaging-config):** The `[project.scripts]` entry point in `pyproject.toml` must already point to `godotiq.cli:cli_main`. Section 01 handles this change.
- **Section 02 (version-and-init):** `src/godotiq/__init__.py` must export `__version__` so the CLI can use it for `--version`. The version string comes from `importlib.metadata` with a `"0.0.0-dev"` fallback.

## Tests First

All tests go in `/Users/salvatorefarruggio/Desktop/godotiq/tests/test_cli.py`. This section covers TDD sections 5 (CLI argument parsing), 8 (lazy import behavior), and 9 (__main__ entry point) from the TDD plan.

### Test Group 1: CLI Argument Parsing (TDD section 5)

These tests verify that `cli_main` and its argument parser behave correctly for all modes of invocation. Use `monkeypatch` to intercept the actual server startup and install-addon execution so tests stay fast and side-effect-free.

```python
"""Tests for godotiq.cli argument parsing and dispatch."""

import subprocess
import sys

import pytest


class TestCliParsing:
    """Test argparse routing in cli.py."""

    # Test: no args defaults to serve action (does not crash).
    # Mock the _serve function so it returns immediately.
    # Verify _serve was called.
    def test_no_args_defaults_to_serve(self, monkeypatch):
        """When invoked with no arguments, cli_main should dispatch to _serve."""
        ...

    # Test: --version flag prints version string and exits.
    # Capture stdout and check it contains the version.
    def test_version_flag(self, capsys):
        """godotiq --version should print the version and exit."""
        ...

    # Test: --help flag prints help text and exits.
    # Capture stdout and verify it contains expected subcommand names.
    def test_help_flag(self, capsys):
        """godotiq --help should print help text and exit."""
        ...

    # Test: install-addon subcommand requires a path argument.
    # Invoking install-addon with no path should raise SystemExit (argparse error).
    def test_install_addon_requires_path(self):
        """install-addon with no path argument should fail."""
        ...

    # Test: install-addon with valid path is accepted by the parser.
    # Mock the actual install_addon function; just verify the parser accepts the args.
    def test_install_addon_accepts_path(self, monkeypatch, tmp_path):
        """install-addon with a path argument should be accepted."""
        ...

    # Test: install-addon --dry-run flag is recognized.
    # Verify the dry_run attribute is True when --dry-run is passed.
    def test_install_addon_dry_run_flag(self, monkeypatch, tmp_path):
        """install-addon --dry-run should set dry_run=True."""
        ...

    # Test: unknown subcommand shows error.
    def test_unknown_subcommand_errors(self):
        """Unknown subcommand should produce an error exit."""
        ...
```

### Test Group 2: Lazy Import Behavior (TDD section 8)

These tests are critical for ensuring the CLI module does not drag in the heavy server module when it is not needed.

```python
class TestLazyImports:
    """Verify cli.py does not eagerly import server.py."""

    # Test: importing cli module does NOT import server module.
    # Check sys.modules after importing godotiq.cli.
    def test_cli_import_does_not_import_server(self):
        """Importing godotiq.cli should not trigger import of godotiq.server."""
        ...

    # Test: importing cli module does NOT trigger MCP tool registration.
    # After importing cli, godotiq.server should not be in sys.modules.
    def test_cli_import_does_not_register_tools(self):
        """Importing cli should not cause MCP tool modules to load."""
        ...

    # Test: install-addon code path does NOT import server module.
    # Call the install-addon dispatch path (mocked) and verify server is still not imported.
    def test_install_addon_path_does_not_import_server(self, monkeypatch, tmp_path):
        """The install-addon code path should not import godotiq.server."""
        ...
```

### Test Group 3: __main__.py Entry Point (TDD section 9)

```python
class TestMainModule:
    """Test python -m godotiq routing."""

    # Test: python -m godotiq --version prints version.
    def test_python_m_version(self):
        """python -m godotiq --version should print the version."""
        result = subprocess.run(
            [sys.executable, "-m", "godotiq", "--version"],
            capture_output=True,
            text=True,
            timeout=10,
        )
        assert result.returncode == 0
        # Version string should be in stdout
        ...

    # Test: python -m godotiq --help works.
    def test_python_m_help(self):
        """python -m godotiq --help should succeed."""
        result = subprocess.run(
            [sys.executable, "-m", "godotiq", "--help"],
            capture_output=True,
            text=True,
            timeout=10,
        )
        assert result.returncode == 0
        ...
```

### Notes on Lazy Import Tests

The lazy import tests require careful setup. To verify that importing `godotiq.cli` does not also import `godotiq.server`, you need either:

1. **Fresh subprocess approach** (recommended for `TestMainModule`): run `python -c "import godotiq.cli; import sys; assert 'godotiq.server' not in sys.modules"` in a subprocess.
2. **Module cleanup approach** (for in-process tests): remove `godotiq.server` from `sys.modules` before the test, import `godotiq.cli`, then check. This is trickier because other test modules may have already imported server. Use `monkeypatch` to manage `sys.modules` state.

The subprocess approach is more reliable. Consider using `subprocess.run` for the lazy import tests as well.

## Implementation Details

### File: `/Users/salvatorefarruggio/Desktop/godotiq/src/godotiq/cli.py` (CREATE)

This is the core deliverable of this section. The module implements:

1. **`cli_main()`** -- the top-level entry point called by the `[project.scripts]` console script. Parses arguments with `argparse` and dispatches to the appropriate handler.

2. **`_serve()`** -- starts the MCP server. Contains a **local import** of `godotiq.server` inside the function body. This is the critical lazy-import pattern.

3. **Argument parser structure:**
   - Top-level parser with `--version` flag (using `from godotiq import __version__`)
   - Subparsers (optional, meaning no subcommand defaults to serve):
     - `install-addon` with positional `path` argument and `--dry-run` flag

Key design decisions:

- **`argparse` only** -- no third-party CLI frameworks. Stdlib is sufficient and adds zero dependencies.
- **Default to serve when no args given** -- this is essential for backward compatibility. Existing `.mcp.json` configs invoke `godotiq` with no arguments and expect the MCP server to start. If `argparse` subparsers are used, the default behavior when no subcommand is given must be to call `_serve()`. This is achieved by checking if no subcommand was provided (the `func` attribute is not set) and defaulting to `_serve()`.
- **`__version__` import at module scope is acceptable** -- importing `godotiq.__init__` is lightweight (it only reads `importlib.metadata`). The heavy import to avoid is `godotiq.server`.
- **`install_addon` function signature** -- the actual `install_addon(target_dir, dry_run)` implementation is defined in this module but fully implemented in section-04. For this section, add the function with a placeholder body that raises `NotImplementedError` or prints a message. The argparse handler should wire the subcommand to call this function.

#### Module structure sketch

```python
"""GodotIQ CLI — argument parsing and subcommand dispatch."""

from __future__ import annotations

import argparse
import sys
from pathlib import Path


def cli_main() -> None:
    """Parse arguments and dispatch to the appropriate handler."""
    # Import __version__ here (lightweight, no server import)
    # Build argparse parser with --version
    # Add subparsers: install-addon with path + --dry-run
    # If no subcommand given, default to _serve()
    ...


def _serve() -> None:
    """Start the MCP server (lazy import to avoid loading all tools for other subcommands)."""
    from godotiq.server import main  # Local import!
    main()


def install_addon(target_dir: str, dry_run: bool = False) -> None:
    """Copy addon files and GODOTIQ_RULES.md to a Godot project.
    
    Full implementation in section-04. This section provides the skeleton.
    """
    ...
```

#### argparse pattern for default subcommand

The tricky part is making `godotiq` (no args) default to `_serve()`. The standard pattern:

```python
parser = argparse.ArgumentParser(prog="godotiq", description="...")
parser.add_argument("--version", action="version", version=f"godotiq {__version__}")

subparsers = parser.add_subparsers(dest="command")

install_parser = subparsers.add_parser("install-addon", help="...")
install_parser.add_argument("path", help="Path to Godot project root")
install_parser.add_argument("--dry-run", action="store_true", help="...")

args = parser.parse_args()

if args.command is None:
    _serve()
elif args.command == "install-addon":
    install_addon(args.path, dry_run=args.dry_run)
```

Using `dest="command"` on subparsers means `args.command` is `None` when no subcommand is given. This naturally defaults to serve behavior.

### File: `/Users/salvatorefarruggio/Desktop/godotiq/src/godotiq/__main__.py` (MODIFY)

Change from:

```python
from godotiq.server import main
main()
```

To:

```python
from godotiq.cli import cli_main
cli_main()
```

This is a two-line change. After this, `python -m godotiq` goes through `cli_main()` which then lazily imports `server.main` only when the serve path is taken.

**Note about the `sys.modules.setdefault` hack in `server.py` (line 61):** After this change, `python -m godotiq` loads `__main__.py` -> `cli_main()` -> (if serving) `_serve()` -> `from godotiq.server import main`. Because `server.py` is now always loaded under its canonical name `godotiq.server` (not as `__main__`), the `sys.modules.setdefault` hack becomes a harmless no-op. Do NOT remove it in this section to avoid scope creep -- just verify it does not interfere.

### File: `/Users/salvatorefarruggio/Desktop/godotiq/tests/test_cli.py` (CREATE)

Full test file as described in the "Tests First" section above.

## Verification Checklist

After implementation, verify:

1. `python -m godotiq --version` prints the version string and exits with code 0
2. `python -m godotiq --help` shows help text including `install-addon` subcommand
3. `python -c "import godotiq.cli; import sys; print('godotiq.server' in sys.modules)"` prints `False`
4. `python -m godotiq` still starts the MCP server (backward compatibility)
5. All existing tests continue to pass (`pytest -v`)
6. New tests in `tests/test_cli.py` pass

## Files Changed

| File | Action | Purpose |
|------|--------|---------|
| `src/godotiq/cli.py` | CREATE | argparse CLI with serve default, --version, install-addon skeleton |
| `src/godotiq/__main__.py` | MODIFY | Route through `cli_main()` instead of `server.main()` |
| `tests/test_cli.py` | CREATE | Tests for argument parsing, lazy imports, __main__ entry point |

## Implementation Notes (Post-Build)

### Deviations from plan
- **install_addon skeleton includes validation**: Added path resolution, directory existence check, and project.godot check ahead of section-04. Section-04 will build on these.
- **Defensive else clause**: Added `else: parser.print_help(); sys.exit(1)` in dispatch for future-proofing (not in plan).
- **Subprocess-based lazy import tests**: Used tmp_path script file instead of inline `-c` for the install-addon lazy import test (syntax limitation with try/except in one-liner).
- **Omitted `test_cli_import_does_not_register_tools`**: Plan specified this test but it's redundant — `test_cli_import_does_not_import_server` already covers this since server.py is what triggers tool registration.

### Files created/modified
- `src/godotiq/cli.py` — created (argparse CLI with lazy server import)
- `src/godotiq/__main__.py` — modified (routes through cli_main)
- `tests/test_cli.py` — created (11 tests: parsing, lazy imports, __main__)

### Test results
- 1149 passed, 2 skipped (full suite, no regressions)