# Makefile 整合 Phase 2 & 3 實施計劃

## Overview

本計劃涵蓋 Makefile 整合的 Phase 2 (配置系統) 和 Phase 3 (架構重構),目標是建立完整的配置體系、改善開發者體驗,並透過 Adapter Pattern 支援多種 toolchain。

**Phase 1 現況** (已完成):
- build_node 和 flash_node 已改為呼叫 Makefile
- 使用 `.fwauto/build_output.toml` 傳遞結構化資訊
- 保持原有的 CLI 和 init_node (專案編號查找模式)

**Phase 2 目標** (本計劃):
- 建立 `.fwauto/` 配置體系
- 重構 init_node 為錨點模式
- 實作 `fwauto init` 互動式配置
- 向後相容舊專案

**Phase 3 目標** (本計劃):
- 實作 Adapter Pattern
- 支援多 toolchain (Keil, GCC, IAR)
- 統一工具鏈介面

## Current State Analysis

### 現有架構

**init_node** (`src/fwauto/nodes/init.py:23`):
```python
base_path = "C:\\STM32F407\\examples"
project_pattern = f"{base_path}\\實例{int(project_num):02d} *"
```

**CLI** (`src/fwauto/cli.py`):
- 所有指令都需要 `project_number` 參數
- 必須在特定位置執行

**build/flash nodes**:
- Phase 1 已改為呼叫 Makefile
- 尚未使用配置檔案
- 硬編碼部分路徑資訊

### 關鍵限制

1. ❌ 必須提供專案編號才能執行
2. ❌ 無法在專案目錄內直接執行指令
3. ❌ 無專案配置檔案,難以客製化
4. ❌ 工具鏈差異直接寫在 node 邏輯中
5. ❌ 擴展新 toolchain 需要修改 Python 程式碼

## Desired End State

### Phase 2 完成後

**開發者工作流程**:
```bash
# 1. 在任意新專案目錄執行初始化
cd /path/to/my-stm32-project
fwauto init
# → 互動式向導產生 .fwauto/ 配置

# 2. 直接在專案目錄執行指令 (不需專案編號)
fwauto build
fwauto run
```

**專案結構**:
```
my-stm32-project/
├── .fwauto/
│   ├── config.toml         # 專案配置
│   ├── toolchain.toml      # 工具鏈配置
│   └── build_output.toml   # Build 輸出 (自動生成)
├── src/
└── ...
```

**向後相容**:
- 偵測舊專案 (無 `.fwauto/`)
- Fallback 到 Phase 1 行為 (專案編號查找)
- 提供 `fwauto migrate` 指令轉換舊專案

### Phase 3 完成後

**Adapter 架構**:
```python
# 工具鏈抽象
class ToolchainAdapter(Protocol):
    def build(self, config_path: str) -> Result: ...

class FlasherAdapter(Protocol):
    def flash(self, config_path: str, artifact_path: str) -> Result: ...

# 支援多種實作
KeilToolchainAdapter()
GCCToolchainAdapter()
STM32CubeProgrammerFlasherAdapter()
```

**動態選擇**:
```toml
# .fwauto/toolchain.toml
[toolchain]
type = "keil"  # 或 "gcc", "iar"

[flasher]
type = "stm32cubeprogrammer"  # 或 "jlink"
```

**驗證方式**:
- 執行 `uv run fwauto build` 成功編譯
- 執行 `uv run fwauto run` 成功編譯+燒錄
- 在專案目錄任意位置執行指令都能正常運作
- 切換 toolchain 只需修改配置檔案

## What We're NOT Doing

### Phase 2 不做的事
- ❌ 不實作 Adapter Pattern (留給 Phase 3)
- ❌ 不支援多 toolchain (仍維持 Keil + STM32CubeProgrammer)
- ❌ 不修改 Graph 流程結構
- ❌ 不改變 FirmwareState 的核心屬性 (保持最小化)

### Phase 3 不做的事
- ❌ 不支援自動偵測工具鏈 (由使用者選擇)
- ❌ 不實作跨平台路徑轉換 (WSL 支援留給後續)
- ❌ 不實作 clean/version 等進階 Adapter 方法 (僅 build/flash)
- ❌ 不改變 LangGraph 流程架構

## Implementation Approach

### Phase 2 策略
採用 **漸進式遷移** 策略:
1. 先建立配置系統基礎設施
2. 重構 init_node 支援錨點模式
3. 保持向後相容,不破壞現有使用者工作流程
4. 使用 KISS 原則,避免過度設計

### Phase 3 策略
採用 **先抽象再擴展** 策略:
1. 先實作 Adapter Protocol 和現有工具鏈 (Keil)
2. 驗證架構可行性
3. 再擴展到其他 toolchain (GCC, IAR)
4. 使用 Registry Pattern 管理 Adapter 實例

---

## Phase 2: 配置系統與 init_node 重構

### Overview

Phase 2 專注於建立 `.fwauto/` 配置體系,改善開發者體驗。核心改動包括:
1. 配置檔案讀取模組
2. init_node 重構 (錨點模式)
3. `fwauto init` 互動式指令
4. 向後相容處理

### Phase 2.1: 配置檔案讀取模組

#### Overview

建立統一的配置檔案讀取/驗證模組,作為後續所有 Phase 的基礎。

#### Changes Required

##### 1. 新增配置模組

**File**: `src/fwauto/config.py` (新檔案)

**Changes**: 建立配置讀取/驗證模組

```python
"""Configuration management for FWAuto."""

import sys
from pathlib import Path
from typing import Any

if sys.version_info >= (3, 11):
    import tomllib
else:
    import tomli as tomllib

import tomli_w


class ConfigError(Exception):
    """Configuration related errors."""
    pass


class ProjectConfig:
    """Project configuration from .fwauto/config.toml."""

    def __init__(self, config_path: Path):
        self.config_path = config_path
        self.data = self._load()
        self._validate()

    def _load(self) -> dict[str, Any]:
        """Load TOML configuration."""
        try:
            with open(self.config_path, "rb") as f:
                return tomllib.load(f)
        except FileNotFoundError:
            raise ConfigError(f"Config file not found: {self.config_path}")
        except Exception as e:
            raise ConfigError(f"Failed to load config: {e}")

    def _validate(self) -> None:
        """Validate configuration schema."""
        # 驗證必要 section
        if "project" not in self.data:
            raise ConfigError("Missing [project] section")
        if "build" not in self.data:
            raise ConfigError("Missing [build] section")
        if "flash" not in self.data:
            raise ConfigError("Missing [flash] section")

        # 驗證必要欄位
        project = self.data["project"]
        if "name" not in project:
            raise ConfigError("Missing project.name")

        build = self.data["build"]
        if "makefile" not in build:
            raise ConfigError("Missing build.makefile")

        flash = self.data["flash"]
        if "makefile" not in flash:
            raise ConfigError("Missing flash.makefile")

    @property
    def project_name(self) -> str:
        return self.data["project"]["name"]

    @property
    def build_makefile(self) -> str:
        return self.data["build"]["makefile"]

    @property
    def build_target(self) -> str:
        return self.data["build"].get("target", "build")

    @property
    def flash_makefile(self) -> str:
        return self.data["flash"]["makefile"]

    @property
    def flash_target(self) -> str:
        return self.data["flash"].get("target", "flash")

    @property
    def hex_arg(self) -> str:
        return self.data["flash"].get("hex_arg", "HEX_FILE")


class ToolchainConfig:
    """Toolchain configuration from .fwauto/toolchain.toml."""

    def __init__(self, config_path: Path):
        self.config_path = config_path
        self.data = self._load()
        self._validate()

    def _load(self) -> dict[str, Any]:
        """Load TOML configuration."""
        try:
            with open(self.config_path, "rb") as f:
                return tomllib.load(f)
        except FileNotFoundError:
            raise ConfigError(f"Config file not found: {self.config_path}")
        except Exception as e:
            raise ConfigError(f"Failed to load config: {e}")

    def _validate(self) -> None:
        """Validate configuration schema."""
        if "toolchain" not in self.data:
            raise ConfigError("Missing [toolchain] section")
        if "flasher" not in self.data:
            raise ConfigError("Missing [flasher] section")

        toolchain = self.data["toolchain"]
        if "type" not in toolchain:
            raise ConfigError("Missing toolchain.type")

        flasher = self.data["flasher"]
        if "type" not in flasher:
            raise ConfigError("Missing flasher.type")

    @property
    def toolchain_type(self) -> str:
        return self.data["toolchain"]["type"]

    @property
    def flasher_type(self) -> str:
        return self.data["flasher"]["type"]

    def get_toolchain_config(self) -> dict[str, Any]:
        """Get complete toolchain configuration."""
        return self.data["toolchain"]

    def get_flasher_config(self) -> dict[str, Any]:
        """Get complete flasher configuration."""
        return self.data["flasher"]


def find_project_root(start_path: Path | None = None) -> Path | None:
    """
    向上搜尋 .fwauto/ 目錄,找到專案根目錄.

    Args:
        start_path: 搜尋起點,預設為當前目錄

    Returns:
        專案根目錄 Path,若未找到則返回 None
    """
    if start_path is None:
        start_path = Path.cwd()

    current = start_path.resolve()

    # 向上搜尋,最多到根目錄
    while True:
        fwauto_dir = current / ".fwauto"
        if fwauto_dir.is_dir():
            return current

        # 到達根目錄
        if current.parent == current:
            return None

        current = current.parent


def load_project_config(project_root: Path) -> ProjectConfig:
    """Load project configuration."""
    config_path = project_root / ".fwauto" / "config.toml"
    return ProjectConfig(config_path)


def load_toolchain_config(project_root: Path) -> ToolchainConfig:
    """Load toolchain configuration."""
    config_path = project_root / ".fwauto" / "toolchain.toml"
    return ToolchainConfig(config_path)


def create_default_config(project_root: Path, project_name: str) -> None:
    """Create default .fwauto/config.toml."""
    config_dir = project_root / ".fwauto"
    config_dir.mkdir(exist_ok=True)

    config_data = {
        "project": {
            "name": project_name,
            "description": "STM32 Firmware Project",
        },
        "build": {
            "makefile": ".fwauto/build/Makefile",
            "target": "build",
        },
        "flash": {
            "makefile": ".fwauto/flash/Makefile",
            "target": "flash",
            "hex_arg": "HEX_FILE",
        },
    }

    config_path = config_dir / "config.toml"
    with open(config_path, "wb") as f:
        tomli_w.dump(config_data, f)


def create_default_toolchain_config(
    project_root: Path,
    toolchain_type: str,
    flasher_type: str,
    toolchain_params: dict[str, Any] | None = None,
    flasher_params: dict[str, Any] | None = None,
) -> None:
    """Create default .fwauto/toolchain.toml."""
    config_dir = project_root / ".fwauto"
    config_dir.mkdir(exist_ok=True)

    toolchain_data = {"type": toolchain_type}
    if toolchain_params:
        toolchain_data.update(toolchain_params)

    flasher_data = {"type": flasher_type}
    if flasher_params:
        flasher_data.update(flasher_params)

    config_data = {
        "toolchain": toolchain_data,
        "flasher": flasher_data,
    }

    config_path = config_dir / "toolchain.toml"
    with open(config_path, "wb") as f:
        tomli_w.dump(config_data, f)
```

##### 2. 更新 pyproject.toml

**File**: `pyproject.toml`

**Changes**: 新增 `tomli` 和 `tomli-w` 依賴

```toml
dependencies = [
    # ... 現有依賴
    "tomli>=2.0.1; python_version < '3.11'",
    "tomli-w>=1.0.0",
]
```

#### Success Criteria

##### Automated Verification

- [x] 配置模組通過型別檢查: `uv run pyright src/fwauto/config.py`
- [ ] 單元測試通過: `uv run pytest tests/test_config.py -v` (測試檔案尚未建立)
- [x] Import 測試: `uv run python -c "from fwauto.config import ProjectConfig, find_project_root"`

##### Manual Verification

- [ ] 建立測試配置檔案並成功讀取
- [ ] 驗證錯誤處理 (缺少必要欄位)
- [ ] 測試 `find_project_root()` 在不同目錄層級

**Implementation Note**: 完成此 Phase 並通過所有自動化驗證後,暫停並確認測試結果,再進入下一階段。

---

### Phase 2.2: 重構 init_node (錨點模式)

#### Overview

重構 `init_node` 從專案編號查找模式改為 `.fwauto/` 錨點模式。

#### Changes Required

##### 1. 重構 init_project_node

**File**: `src/fwauto/nodes/init.py`

**Changes**: 完全重寫初始化邏輯

```python
"""Initialize project node to find project root."""

from pathlib import Path
from typing import Any

from ..config import find_project_root, load_project_config, ConfigError
from ..state import FirmwareState


def init_project_node(state: FirmwareState) -> dict[str, Any]:
    """
    Initialize project paths using .fwauto/ anchor.

    New behavior (Phase 2):
    - Search for .fwauto/ directory from current working directory
    - Load project configuration from .fwauto/config.toml
    - Fallback to legacy mode if .fwauto/ not found and project_number provided
    """
    project_num = state.get("project_number")

    # 嘗試新模式: 錨點搜尋
    project_root = find_project_root()

    if project_root:
        # 新模式: 找到 .fwauto/ 目錄
        try:
            config = load_project_config(project_root)
            print(f"✅ Found project root: {project_root}")
            print(f"📦 Project: {config.project_name}")

            return {
                **state,
                "project_root": str(project_root),
                "project_initialized": True,
            }

        except ConfigError as e:
            print(f"❌ Config error: {e}")
            return {
                **state,
                "project_root": "",
                "project_initialized": False,
                "build_status": "error",
                "build_errors": [f"Configuration error: {str(e)}"],
            }

    # Fallback: 舊模式 (專案編號查找)
    if project_num:
        print("⚠️  No .fwauto/ found, using legacy project number lookup")
        return _legacy_init(state, project_num)

    # 兩種模式都失敗
    return {
        **state,
        "project_root": "",
        "project_initialized": False,
        "build_status": "error",
        "build_errors": [
            "No .fwauto/ directory found. Please run 'fwauto init' first, "
            "or provide project number for legacy mode."
        ],
    }


def _legacy_init(state: FirmwareState, project_num: str) -> dict[str, Any]:
    """Legacy initialization using project number (Phase 1 behavior)."""
    import glob

    base_path = "C:\\STM32F407\\examples"

    try:
        # Find project directory
        project_pattern = f"{base_path}\\實例{int(project_num):02d} *"
        project_dirs = glob.glob(project_pattern)

        print(f"🔍 Looking for project: {project_pattern}")

        if not project_dirs:
            # Also try without zero padding
            project_pattern = f"{base_path}\\實例{int(project_num)} *"
            project_dirs = glob.glob(project_pattern)

        if not project_dirs:
            raise FileNotFoundError(f"No project directory found for pattern: {project_pattern}")

        project_root = project_dirs[0]
        print(f"✅ Found project root (legacy): {project_root}")

        return {**state, "project_root": project_root, "project_initialized": True}

    except Exception as e:
        print(f"❌ Failed to initialize project: {e}")
        return {
            **state,
            "project_root": "",
            "project_initialized": False,
            "build_status": "error",
            "build_errors": [f"Project initialization failed: {str(e)}"],
        }
```

##### 2. 更新 FirmwareState

**File**: `src/fwauto/state.py`

**Changes**: `project_number` 改為 optional

```python
project_number: Annotated[str | None, "Project identifier (optional in Phase 2+)"]
```

#### Success Criteria

##### Automated Verification

- [x] 型別檢查通過: `uv run pyright src/fwauto/nodes/init.py`
- [ ] 單元測試通過: `uv run pytest tests/test_init_node.py -v`

##### Manual Verification

- [ ] 在有 `.fwauto/` 的專案目錄執行,成功初始化
- [ ] 在無 `.fwauto/` 但有專案編號的情況下,Fallback 到舊模式
- [ ] 在無 `.fwauto/` 且無專案編號時,回傳適當錯誤訊息

**Implementation Note**: 完成此 Phase 並通過所有驗證後,暫停確認 init_node 行為正確。

---

### Phase 2.3: 實作 fwauto init 指令

#### Overview

建立互動式配置向導,協助使用者初始化 `.fwauto/` 配置。

#### Changes Required

##### 1. 新增 init 指令模組

**File**: `src/fwauto/commands/init.py` (新檔案)

**Changes**: 實作互動式配置向導

```python
"""Interactive initialization wizard for FWAuto projects."""

from pathlib import Path
from rich.console import Console
from rich.prompt import Prompt, Confirm

from ..config import create_default_config, create_default_toolchain_config

console = Console()


def run_init_wizard(project_root: Path | None = None) -> bool:
    """
    Run interactive initialization wizard.

    Returns:
        True if initialization succeeded, False otherwise
    """
    if project_root is None:
        project_root = Path.cwd()

    console.print("\n[cyan]🚀 FWAuto Project Initialization[/cyan]\n")

    # 檢查是否已存在 .fwauto/
    fwauto_dir = project_root / ".fwauto"
    if fwauto_dir.exists():
        if not Confirm.ask(
            "[yellow].fwauto/ directory already exists. Overwrite?[/yellow]",
            default=False
        ):
            console.print("[yellow]Initialization cancelled.[/yellow]")
            return False

    # 1. 專案資訊
    console.print("[bold]Step 1: Project Information[/bold]")
    project_name = Prompt.ask(
        "Project name",
        default=project_root.name
    )

    # 2. 工具鏈選擇
    console.print("\n[bold]Step 2: Toolchain Configuration[/bold]")
    console.print("Available toolchains:")
    console.print("  1. Keil uVision")
    console.print("  2. GCC ARM (TODO: Phase 3)")
    console.print("  3. IAR (TODO: Phase 3)")

    toolchain_choice = Prompt.ask(
        "Select toolchain",
        choices=["1", "2", "3"],
        default="1"
    )

    toolchain_type = "keil"  # Phase 2 僅支援 Keil
    toolchain_params = {}

    if toolchain_choice == "1":
        # Keil 配置
        keil_path = Prompt.ask(
            "Keil UV4 executable path",
            default="C:\\Keil_v5\\UV4\\uv4.exe"
        )
        project_file = Prompt.ask(
            "Project file path (relative to project root)",
            default=".\\USER\\Project.uvprojx"
        )
        artifact_path = Prompt.ask(
            "Artifact (hex) output path",
            default=".\\OBJ\\Template.hex"
        )

        toolchain_params = {
            "uv4_path": keil_path,
            "project_file": project_file,
            "artifact_path": artifact_path,
        }

    # 3. Flasher 選擇
    console.print("\n[bold]Step 3: Flasher Configuration[/bold]")
    console.print("Available flashers:")
    console.print("  1. STM32 ST-LINK Utility")
    console.print("  2. STM32CubeProgrammer")
    console.print("  3. J-Link (TODO: Phase 3)")

    flasher_choice = Prompt.ask(
        "Select flasher",
        choices=["1", "2", "3"],
        default="2"
    )

    flasher_type = "stm32cubeprogrammer"
    flasher_params = {}

    if flasher_choice == "2":
        flasher_exe = Prompt.ask(
            "STM32_Programmer_CLI.exe path",
            default="C:\\Program Files\\STMicroelectronics\\STM32Cube\\STM32CubeProgrammer\\bin\\STM32_Programmer_CLI.exe"
        )
        interface = Prompt.ask(
            "Programming interface",
            choices=["SWD", "JTAG"],
            default="SWD"
        )

        flasher_params = {
            "exe_path": flasher_exe,
            "interface": interface,
            "verify": True,
            "reset": True,
        }

    # 4. 建立配置檔案
    console.print("\n[bold]Step 4: Creating configuration files...[/bold]")

    try:
        # 建立 .fwauto/ 目錄結構
        fwauto_dir.mkdir(exist_ok=True)
        (fwauto_dir / "build").mkdir(exist_ok=True)
        (fwauto_dir / "flash").mkdir(exist_ok=True)

        # 建立 config.toml
        create_default_config(project_root, project_name)
        console.print("[green]✓ Created .fwauto/config.toml[/green]")

        # 建立 toolchain.toml
        create_default_toolchain_config(
            project_root,
            toolchain_type,
            flasher_type,
            toolchain_params,
            flasher_params,
        )
        console.print("[green]✓ Created .fwauto/toolchain.toml[/green]")

        # 提示建立 Makefile
        console.print("\n[yellow]⚠️  Don't forget to create:[/yellow]")
        console.print(f"  - {fwauto_dir / 'build' / 'Makefile'}")
        console.print(f"  - {fwauto_dir / 'flash' / 'Makefile'}")
        console.print("\n[cyan]💡 See docs/makefile-integration-design.md for Makefile examples.[/cyan]")

        console.print("\n[green]✅ Initialization complete![/green]")
        return True

    except Exception as e:
        console.print(f"[red]❌ Initialization failed: {e}[/red]")
        return False
```

##### 2. 在 CLI 註冊 init 指令

**File**: `src/fwauto/cli.py`

**Changes**: 新增 `init` 指令

```python
from fwauto.commands.init import run_init_wizard

@app.command()
def init():
    """
    🔧 Initialize FWAuto configuration for current project.

    Creates .fwauto/ directory with configuration files through
    an interactive wizard.

    Examples:
        cd /path/to/project
        fwauto init
    """
    from pathlib import Path
    success = run_init_wizard(Path.cwd())
    if not success:
        raise typer.Exit(code=1)
```

##### 3. 更新現有指令 (移除必填 project 參數)

**File**: `src/fwauto/cli.py`

**Changes**: `build`, `flash`, `run` 指令的 project 參數改為 optional

```python
@app.command()
def build(
    project: str | None = typer.Argument(None, help="Project number (optional in Phase 2+)"),
    verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose output"),
):
    """
    🔨 Build STM32 firmware project.

    Phase 2+: If no project number provided, searches for .fwauto/ in current directory.

    Examples:
        # New mode (with .fwauto/)
        cd /path/to/project
        fwauto build

        # Legacy mode (with project number)
        fwauto build 04
    """
    console.print("[cyan]🔨 Building firmware...[/cyan]")

    state: FirmwareState = {
        "user_prompt": f"build {project}" if project else "build",
        "project_root": "",
        "project_number": project.zfill(2) if project else None,
        "project_initialized": False,
        # ... 其他欄位
    }

    run_workflow(state)

# 同樣修改 flash() 和 run()
```

#### Success Criteria

##### Automated Verification

- [ ] 型別檢查通過: `uv run mypy src/fwauto/commands/init.py`
- [ ] CLI 指令可執行: `uv run fwauto init --help`

##### Manual Verification

- [ ] 執行 `fwauto init` 成功產生 `.fwauto/config.toml`
- [ ] 執行 `fwauto init` 成功產生 `.fwauto/toolchain.toml`
- [ ] 配置向導的互動流程順暢
- [ ] 執行 `fwauto build` (無專案編號) 成功初始化專案

**Implementation Note**: 完成此 Phase 後確認 init 指令可正常使用。

---

### Phase 2.4: 向後相容與 migrate 指令

#### Overview

確保舊專案 (無 `.fwauto/`) 仍可使用,並提供遷移工具。

#### Changes Required

##### 1. 實作 migrate 指令

**File**: `src/fwauto/commands/migrate.py` (新檔案)

**Changes**: 實作舊專案遷移工具

```python
"""Migrate legacy projects to .fwauto/ structure."""

from pathlib import Path
from rich.console import Console
from rich.prompt import Confirm

from ..config import create_default_config, create_default_toolchain_config

console = Console()


def migrate_legacy_project(project_root: Path) -> bool:
    """
    Migrate a legacy project to .fwauto/ structure.

    Detects project structure and creates appropriate configuration.
    """
    console.print(f"\n[cyan]🔄 Migrating project: {project_root}[/cyan]\n")

    # 檢查專案結構
    user_dir = project_root / "USER"
    obj_dir = project_root / "OBJ"

    if not user_dir.exists():
        console.print("[red]❌ USER/ directory not found. Is this a valid STM32 project?[/red]")
        return False

    # 查找 .uvprojx 檔案
    uvprojx_files = list(user_dir.glob("*.uvprojx"))
    if not uvprojx_files:
        console.print("[yellow]⚠️  No .uvprojx file found in USER/[/yellow]")
        project_file = ".\\USER\\Project.uvprojx"
    else:
        project_file = f".\\USER\\{uvprojx_files[0].name}"
        console.print(f"[green]✓ Found project file: {project_file}[/green]")

    # 查找 .hex 檔案
    if obj_dir.exists():
        hex_files = list(obj_dir.glob("*.hex"))
        if hex_files:
            artifact_path = f".\\OBJ\\{hex_files[0].name}"
            console.print(f"[green]✓ Found hex file: {artifact_path}[/green]")
        else:
            artifact_path = ".\\OBJ\\Template.hex"
    else:
        artifact_path = ".\\OBJ\\Template.hex"

    # 確認遷移
    console.print("\n[bold]Migration will create:[/bold]")
    console.print("  - .fwauto/config.toml")
    console.print("  - .fwauto/toolchain.toml")
    console.print("  - .fwauto/build/ (directory)")
    console.print("  - .fwauto/flash/ (directory)")

    if not Confirm.ask("\nProceed with migration?", default=True):
        console.print("[yellow]Migration cancelled.[/yellow]")
        return False

    try:
        # 建立目錄結構
        fwauto_dir = project_root / ".fwauto"
        fwauto_dir.mkdir(exist_ok=True)
        (fwauto_dir / "build").mkdir(exist_ok=True)
        (fwauto_dir / "flash").mkdir(exist_ok=True)

        # 建立配置檔案
        project_name = project_root.name
        create_default_config(project_root, project_name)
        console.print("[green]✓ Created .fwauto/config.toml[/green]")

        # 建立 toolchain 配置 (預設 Keil)
        toolchain_params = {
            "uv4_path": "C:\\Keil_v5\\UV4\\uv4.exe",
            "project_file": project_file,
            "artifact_path": artifact_path,
        }
        flasher_params = {
            "exe_path": "C:\\Program Files\\STMicroelectronics\\STM32Cube\\STM32CubeProgrammer\\bin\\STM32_Programmer_CLI.exe",
            "interface": "SWD",
            "verify": True,
            "reset": True,
        }

        create_default_toolchain_config(
            project_root,
            "keil",
            "stm32cubeprogrammer",
            toolchain_params,
            flasher_params,
        )
        console.print("[green]✓ Created .fwauto/toolchain.toml[/green]")

        console.print("\n[yellow]⚠️  Don't forget to create Makefile templates:[/yellow]")
        console.print(f"  - {fwauto_dir / 'build' / 'Makefile'}")
        console.print(f"  - {fwauto_dir / 'flash' / 'Makefile'}")

        console.print("\n[green]✅ Migration complete![/green]")
        console.print("[cyan]You can now use 'fwauto build' without project number.[/cyan]")
        return True

    except Exception as e:
        console.print(f"[red]❌ Migration failed: {e}[/red]")
        return False
```

##### 2. 註冊 migrate 指令

**File**: `src/fwauto/cli.py`

**Changes**: 新增 `migrate` 指令

```python
from fwauto.commands.migrate import migrate_legacy_project

@app.command()
def migrate(
    project: str | None = typer.Argument(None, help="Legacy project number (e.g., 04)"),
):
    """
    🔄 Migrate legacy project to .fwauto/ structure.

    If project number provided, locates project in C:\\STM32F407\\examples.
    Otherwise, migrates current directory.

    Examples:
        # Migrate current directory
        cd C:\\STM32F407\\examples\\實例04 UART
        fwauto migrate

        # Migrate by project number
        fwauto migrate 04
    """
    from pathlib import Path

    if project:
        # 使用舊模式查找專案
        import glob
        base_path = "C:\\STM32F407\\examples"
        project_pattern = f"{base_path}\\實例{int(project):02d} *"
        project_dirs = glob.glob(project_pattern)

        if not project_dirs:
            project_pattern = f"{base_path}\\實例{int(project)} *"
            project_dirs = glob.glob(project_pattern)

        if not project_dirs:
            console.print(f"[red]❌ Project {project} not found[/red]")
            raise typer.Exit(code=1)

        project_root = Path(project_dirs[0])
    else:
        project_root = Path.cwd()

    success = migrate_legacy_project(project_root)
    if not success:
        raise typer.Exit(code=1)
```

#### Success Criteria

##### Automated Verification

- [ ] 型別檢查通過: `uv run mypy src/fwauto/commands/migrate.py`
- [ ] CLI 指令可執行: `uv run fwauto migrate --help`

##### Manual Verification

- [ ] 對舊專案執行 `fwauto migrate` 成功產生配置
- [ ] 遷移後的專案可使用 `fwauto build` (不需專案編號)
- [ ] 未遷移的舊專案仍可使用 `fwauto build 04` (舊模式)

**Implementation Note**: 完成 Phase 2 所有階段後,執行完整測試確認向後相容性。

---

## Phase 3: Adapter Pattern 與多 toolchain 支援

### Overview

Phase 3 專注於抽象化工具鏈差異,透過 Adapter Pattern 支援多種 toolchain 和 flasher。

### Phase 3.1: 建立 Adapter 基礎架構

#### Overview

定義 Adapter Protocol 和統一的 Result 格式,建立 Adapter Registry。

#### Changes Required

##### 1. 定義 Adapter Protocol

**File**: `src/fwauto/adapters/base.py` (新檔案)

**Changes**: 定義 Adapter 介面

```python
"""Base adapter protocols and result types."""

from typing import Protocol, TypedDict, Literal


class BuildResult(TypedDict):
    """Result from toolchain build operation."""
    success: bool
    message: str
    artifact_path: str | None
    exit_code: int | None
    stdout: str | None
    stderr: str | None


class FlashResult(TypedDict):
    """Result from flasher operation."""
    success: bool
    message: str
    exit_code: int | None
    stdout: str | None
    stderr: str | None


class ToolchainAdapter(Protocol):
    """Protocol for toolchain adapters (build operations)."""

    def build(self, project_root: str, config: dict) -> BuildResult:
        """
        Build firmware using the toolchain.

        Args:
            project_root: Absolute path to project root
            config: Toolchain configuration from toolchain.toml

        Returns:
            BuildResult with success status and outputs
        """
        ...


class FlasherAdapter(Protocol):
    """Protocol for flasher adapters (flash operations)."""

    def flash(self, project_root: str, artifact_path: str, config: dict) -> FlashResult:
        """
        Flash firmware to device.

        Args:
            project_root: Absolute path to project root
            artifact_path: Absolute path to firmware artifact (hex/bin)
            config: Flasher configuration from toolchain.toml

        Returns:
            FlashResult with success status and outputs
        """
        ...
```

##### 2. 實作 Adapter Registry

**File**: `src/fwauto/adapters/registry.py` (新檔案)

**Changes**: 建立 Adapter 註冊與查找機制

```python
"""Adapter registry for dynamic adapter selection."""

from typing import Type
from .base import ToolchainAdapter, FlasherAdapter


class AdapterRegistry:
    """Registry for toolchain and flasher adapters."""

    _toolchain_adapters: dict[str, Type[ToolchainAdapter]] = {}
    _flasher_adapters: dict[str, Type[FlasherAdapter]] = {}

    @classmethod
    def register_toolchain(cls, name: str, adapter_class: Type[ToolchainAdapter]) -> None:
        """Register a toolchain adapter."""
        cls._toolchain_adapters[name] = adapter_class

    @classmethod
    def register_flasher(cls, name: str, adapter_class: Type[FlasherAdapter]) -> None:
        """Register a flasher adapter."""
        cls._flasher_adapters[name] = adapter_class

    @classmethod
    def get_toolchain_adapter(cls, name: str) -> Type[ToolchainAdapter]:
        """Get toolchain adapter by name."""
        if name not in cls._toolchain_adapters:
            raise ValueError(f"Unknown toolchain adapter: {name}")
        return cls._toolchain_adapters[name]

    @classmethod
    def get_flasher_adapter(cls, name: str) -> Type[FlasherAdapter]:
        """Get flasher adapter by name."""
        if name not in cls._flasher_adapters:
            raise ValueError(f"Unknown flasher adapter: {name}")
        return cls._flasher_adapters[name]

    @classmethod
    def list_toolchains(cls) -> list[str]:
        """List registered toolchain adapters."""
        return list(cls._toolchain_adapters.keys())

    @classmethod
    def list_flashers(cls) -> list[str]:
        """List registered flasher adapters."""
        return list(cls._flasher_adapters.keys())
```

#### Success Criteria

##### Automated Verification

- [ ] 型別檢查通過: `uv run mypy src/fwauto/adapters/`
- [ ] Import 測試: `uv run python -c "from fwauto.adapters.base import ToolchainAdapter"`

##### Manual Verification

- [ ] Protocol 定義清晰,符合設計文件
- [ ] Registry 可正常註冊和查找 Adapter

**Implementation Note**: 完成基礎架構後進入 Phase 3.2 實作具體 Adapter。

---

### Phase 3.2: 實作現有工具鏈 Adapter

#### Overview

實作 Keil toolchain 和 STM32CubeProgrammer flasher 的 Adapter。

#### Changes Required

##### 1. 實作 KeilToolchainAdapter

**File**: `src/fwauto/adapters/keil.py` (新檔案)

**Changes**: 實作 Keil 編譯 Adapter

```python
"""Keil uVision toolchain adapter."""

import subprocess
import os
from pathlib import Path
from datetime import datetime
from .base import BuildResult


class KeilToolchainAdapter:
    """Adapter for Keil uVision toolchain."""

    def build(self, project_root: str, config: dict) -> BuildResult:
        """Build using Keil uVision."""
        try:
            # 讀取配置
            uv4_path = config.get("uv4_path", "C:\\Keil_v5\\UV4\\uv4.exe")
            project_file = config.get("project_file")
            artifact_path = config.get("artifact_path")

            if not project_file or not artifact_path:
                return {
                    "success": False,
                    "message": "Missing project_file or artifact_path in config",
                    "artifact_path": None,
                    "exit_code": None,
                    "stdout": None,
                    "stderr": None,
                }

            # 解析路徑
            project_root_path = Path(project_root)
            project_file_path = project_root_path / project_file
            artifact_path_full = project_root_path / artifact_path

            if not project_file_path.exists():
                return {
                    "success": False,
                    "message": f"Project file not found: {project_file_path}",
                    "artifact_path": None,
                    "exit_code": None,
                    "stdout": None,
                    "stderr": None,
                }

            # 記錄開始時間
            start_time = datetime.now()

            # 執行編譯
            log_file = artifact_path_full.with_suffix(".log")
            cmd = f'"{uv4_path}" -b "{project_file_path}" -j0 -o "{log_file}"'

            result = subprocess.run(
                cmd,
                shell=True,
                capture_output=True,
                text=True,
            )

            # 檢查成功條件
            success = (
                result.returncode == 0
                and artifact_path_full.exists()
                and datetime.fromtimestamp(artifact_path_full.stat().st_mtime) > start_time
            )

            return {
                "success": success,
                "message": "Build succeeded" if success else "Build failed",
                "artifact_path": str(artifact_path_full) if success else None,
                "exit_code": result.returncode,
                "stdout": result.stdout,
                "stderr": result.stderr,
            }

        except Exception as e:
            return {
                "success": False,
                "message": f"Build exception: {str(e)}",
                "artifact_path": None,
                "exit_code": None,
                "stdout": None,
                "stderr": None,
            }
```

##### 2. 實作 STM32CubeProgrammerFlasherAdapter

**File**: `src/fwauto/adapters/stm32cubeprogrammer.py` (新檔案)

**Changes**: 實作 STM32CubeProgrammer flasher Adapter

```python
"""STM32CubeProgrammer flasher adapter."""

import subprocess
from pathlib import Path
from .base import FlashResult


class STM32CubeProgrammerFlasherAdapter:
    """Adapter for STM32CubeProgrammer CLI."""

    def flash(self, project_root: str, artifact_path: str, config: dict) -> FlashResult:
        """Flash using STM32CubeProgrammer."""
        try:
            # 讀取配置
            exe_path = config.get("exe_path", "STM32_Programmer_CLI.exe")
            interface = config.get("interface", "SWD")
            verify = config.get("verify", True)
            reset = config.get("reset", True)

            # 檢查 hex 檔案
            artifact = Path(artifact_path)
            if not artifact.exists():
                return {
                    "success": False,
                    "message": f"Artifact not found: {artifact_path}",
                    "exit_code": None,
                    "stdout": None,
                    "stderr": None,
                }

            # 組合命令
            cmd = f'"{exe_path}" -c port={interface} -w "{artifact}"'
            if verify:
                cmd += " -v"
            if reset:
                cmd += " -rst"

            # 執行燒錄
            result = subprocess.run(
                cmd,
                shell=True,
                capture_output=True,
                text=True,
                encoding="utf-8",
                errors="replace",
            )

            # 檢查成功 (exit code + 輸出檢查)
            success = result.returncode == 0
            if success and result.stdout:
                # 二次檢查: 輸出中是否包含錯誤關鍵字
                stdout_lower = result.stdout.lower()
                if any(kw in stdout_lower for kw in ["error", "fail", "not found"]):
                    success = False

            return {
                "success": success,
                "message": "Flash succeeded" if success else "Flash failed",
                "exit_code": result.returncode,
                "stdout": result.stdout,
                "stderr": result.stderr,
            }

        except Exception as e:
            return {
                "success": False,
                "message": f"Flash exception: {str(e)}",
                "exit_code": None,
                "stdout": None,
                "stderr": None,
            }
```

##### 3. 註冊 Adapter

**File**: `src/fwauto/adapters/__init__.py` (新檔案)

**Changes**: 自動註冊所有 Adapter

```python
"""Adapter module initialization and registration."""

from .registry import AdapterRegistry
from .keil import KeilToolchainAdapter
from .stm32cubeprogrammer import STM32CubeProgrammerFlasherAdapter

# 註冊 toolchain adapters
AdapterRegistry.register_toolchain("keil", KeilToolchainAdapter)

# 註冊 flasher adapters
AdapterRegistry.register_flasher("stm32cubeprogrammer", STM32CubeProgrammerFlasherAdapter)

__all__ = [
    "AdapterRegistry",
    "KeilToolchainAdapter",
    "STM32CubeProgrammerFlasherAdapter",
]
```

#### Success Criteria

##### Automated Verification

- [ ] 型別檢查通過: `uv run mypy src/fwauto/adapters/`
- [ ] 單元測試通過: `uv run pytest tests/test_adapters.py -v`

##### Manual Verification

- [ ] Keil Adapter 可成功編譯測試專案
- [ ] STM32CubeProgrammer Adapter 可成功燒錄
- [ ] Adapter 回傳的 Result 格式正確

**Implementation Note**: 完成 Adapter 實作後進入 Phase 3.3 重構 node。

---

### Phase 3.3: 重構 build_node 和 flash_node

#### Overview

修改 `build_node` 和 `flash_node` 使用 Adapter Pattern。

#### Changes Required

##### 1. 重構 build_node

**File**: `src/fwauto/nodes/build.py`

**Changes**: 改用 Adapter 架構

```python
"""Build node using Adapter pattern."""

from pathlib import Path
from typing import Any

from ..state import FirmwareState
from ..config import load_toolchain_config, ConfigError
from ..adapters import AdapterRegistry


def build_node(state: FirmwareState) -> dict[str, Any]:
    """Build firmware using toolchain adapter."""

    if not state.get("project_initialized"):
        return {
            **state,
            "build_status": "error",
            "build_errors": ["Project not initialized"],
        }

    project_root = state.get("project_root")
    if not project_root:
        return {
            **state,
            "build_status": "error",
            "build_errors": ["Project root not set"],
        }

    try:
        # 讀取 toolchain 配置
        toolchain_config = load_toolchain_config(Path(project_root))
        toolchain_type = toolchain_config.toolchain_type

        # 取得 Adapter
        adapter_class = AdapterRegistry.get_toolchain_adapter(toolchain_type)
        adapter = adapter_class()

        # 執行 build
        print(f"🔨 Building with {toolchain_type} toolchain...")
        result = adapter.build(
            project_root=project_root,
            config=toolchain_config.get_toolchain_config(),
        )

        # 處理結果
        if result["success"]:
            print("✅ Build succeeded")
            return {
                **state,
                "build_status": "success",
                "build_errors": [],
                "build_log": (result.get("stdout") or "") + (result.get("stderr") or ""),
                "firmware_path": result.get("artifact_path"),
            }
        else:
            print(f"❌ Build failed: {result['message']}")
            iteration = state.get("iteration", 0) + 1
            return {
                **state,
                "build_status": "error",
                "build_errors": [result["message"]],
                "build_log": (result.get("stdout") or "") + (result.get("stderr") or ""),
                "iteration": iteration,
            }

    except ConfigError as e:
        return {
            **state,
            "build_status": "error",
            "build_errors": [f"Config error: {str(e)}"],
        }

    except ValueError as e:
        return {
            **state,
            "build_status": "error",
            "build_errors": [f"Adapter error: {str(e)}"],
        }

    except Exception as e:
        return {
            **state,
            "build_status": "error",
            "build_errors": [f"Build error: {str(e)}"],
        }
```

##### 2. 重構 flash_node

**File**: `src/fwauto/nodes/flash.py`

**Changes**: 改用 Adapter 架構

```python
"""Flash node using Adapter pattern."""

from pathlib import Path
from typing import Any

from ..state import FirmwareState
from ..config import load_toolchain_config, ConfigError
from ..adapters import AdapterRegistry


def flash_node(state: FirmwareState) -> dict[str, Any]:
    """Flash firmware using flasher adapter."""

    if not state.get("project_initialized"):
        return {
            **state,
            "flash_status": "error",
            "flash_errors": ["Project not initialized"],
        }

    project_root = state.get("project_root")
    if not project_root:
        return {
            **state,
            "flash_status": "error",
            "flash_errors": ["Project root not set"],
        }

    # 取得 firmware path
    firmware_path = state.get("firmware_path")
    if not firmware_path:
        return {
            **state,
            "flash_status": "error",
            "flash_errors": ["No firmware path. Build first?"],
        }

    try:
        # 讀取 flasher 配置
        toolchain_config = load_toolchain_config(Path(project_root))
        flasher_type = toolchain_config.flasher_type

        # 取得 Adapter
        adapter_class = AdapterRegistry.get_flasher_adapter(flasher_type)
        adapter = adapter_class()

        # 執行 flash
        print(f"📥 Flashing with {flasher_type}...")
        result = adapter.flash(
            project_root=project_root,
            artifact_path=firmware_path,
            config=toolchain_config.get_flasher_config(),
        )

        # 處理結果
        if result["success"]:
            print("✅ Flash succeeded")
            return {
                **state,
                "flash_status": "success",
                "flash_errors": [],
                "flash_log": (result.get("stdout") or "") + (result.get("stderr") or ""),
            }
        else:
            print(f"❌ Flash failed: {result['message']}")
            return {
                **state,
                "flash_status": "error",
                "flash_errors": [result["message"]],
                "flash_log": (result.get("stdout") or "") + (result.get("stderr") or ""),
            }

    except ConfigError as e:
        return {
            **state,
            "flash_status": "error",
            "flash_errors": [f"Config error: {str(e)}"],
        }

    except ValueError as e:
        return {
            **state,
            "flash_status": "error",
            "flash_errors": [f"Adapter error: {str(e)}"],
        }

    except Exception as e:
        return {
            **state,
            "flash_status": "error",
            "flash_errors": [f"Flash error: {str(e)}"],
        }
```

#### Success Criteria

##### Automated Verification

- [ ] 型別檢查通過: `uv run mypy src/fwauto/nodes/`
- [ ] 整合測試通過: `uv run pytest tests/test_nodes_integration.py -v`

##### Manual Verification

- [ ] 執行 `uv run fwauto build` 使用 Adapter 成功編譯
- [ ] 執行 `uv run fwauto run` 成功編譯+燒錄
- [ ] 錯誤處理正確 (Adapter 失敗時)

**Implementation Note**: 完成 node 重構後進入 Phase 3.4 擴展多 toolchain。

---

### Phase 3.4: 擴展多 toolchain 支援

#### Overview

實作 GCC-ARM toolchain adapter,驗證架構擴展性。

#### Changes Required

##### 1. 實作 GCCARMToolchainAdapter

**File**: `src/fwauto/adapters/gcc_arm.py` (新檔案)

**Changes**: 實作 GCC-ARM Adapter

```python
"""GCC ARM toolchain adapter."""

import subprocess
from pathlib import Path
from datetime import datetime
from .base import BuildResult


class GCCARMToolchainAdapter:
    """Adapter for GCC ARM toolchain (arm-none-eabi-gcc)."""

    def build(self, project_root: str, config: dict) -> BuildResult:
        """Build using GCC ARM toolchain via make."""
        try:
            # 讀取配置
            make_path = config.get("make_path", "make")
            makefile = config.get("makefile", "Makefile")
            target = config.get("target", "all")
            artifact_path = config.get("artifact_path")

            if not artifact_path:
                return {
                    "success": False,
                    "message": "Missing artifact_path in config",
                    "artifact_path": None,
                    "exit_code": None,
                    "stdout": None,
                    "stderr": None,
                }

            # 解析路徑
            project_root_path = Path(project_root)
            makefile_path = project_root_path / makefile
            artifact_path_full = project_root_path / artifact_path

            if not makefile_path.exists():
                return {
                    "success": False,
                    "message": f"Makefile not found: {makefile_path}",
                    "artifact_path": None,
                    "exit_code": None,
                    "stdout": None,
                    "stderr": None,
                }

            # 記錄開始時間
            start_time = datetime.now()

            # 執行 make
            cmd = f'"{make_path}" -f "{makefile_path}" {target}'

            result = subprocess.run(
                cmd,
                shell=True,
                capture_output=True,
                text=True,
                cwd=project_root,
            )

            # 檢查成功條件
            success = (
                result.returncode == 0
                and artifact_path_full.exists()
                and datetime.fromtimestamp(artifact_path_full.stat().st_mtime) > start_time
            )

            return {
                "success": success,
                "message": "Build succeeded" if success else "Build failed",
                "artifact_path": str(artifact_path_full) if success else None,
                "exit_code": result.returncode,
                "stdout": result.stdout,
                "stderr": result.stderr,
            }

        except Exception as e:
            return {
                "success": False,
                "message": f"Build exception: {str(e)}",
                "artifact_path": None,
                "exit_code": None,
                "stdout": None,
                "stderr": None,
            }
```

##### 2. 註冊 GCC-ARM Adapter

**File**: `src/fwauto/adapters/__init__.py`

**Changes**: 註冊新 Adapter

```python
from .gcc_arm import GCCARMToolchainAdapter

# 註冊 GCC-ARM toolchain
AdapterRegistry.register_toolchain("gcc-arm", GCCARMToolchainAdapter)
```

##### 3. 更新 init 指令選項

**File**: `src/fwauto/commands/init.py`

**Changes**: 新增 GCC-ARM 選項

```python
# 在 toolchain 選擇中
console.print("Available toolchains:")
console.print("  1. Keil uVision")
console.print("  2. GCC ARM")  # 更新狀態
console.print("  3. IAR (TODO)")

if toolchain_choice == "2":
    # GCC-ARM 配置
    make_path = Prompt.ask(
        "Make executable path",
        default="make"
    )
    makefile = Prompt.ask(
        "Makefile path (relative to project root)",
        default="Makefile"
    )
    artifact_path = Prompt.ask(
        "Artifact (hex) output path",
        default="build/firmware.hex"
    )

    toolchain_type = "gcc-arm"
    toolchain_params = {
        "make_path": make_path,
        "makefile": makefile,
        "artifact_path": artifact_path,
    }
```

#### Success Criteria

##### Automated Verification

- [ ] 型別檢查通過: `uv run mypy src/fwauto/adapters/gcc_arm.py`
- [ ] Adapter 註冊成功: `uv run python -c "from fwauto.adapters import AdapterRegistry; print(AdapterRegistry.list_toolchains())"`

##### Manual Verification

- [ ] 使用 GCC-ARM 的專案可成功初始化
- [ ] 執行 `fwauto build` 使用 GCC-ARM 成功編譯
- [ ] 切換 toolchain 只需修改 `toolchain.toml`

**Implementation Note**: 完成 Phase 3 所有階段後,執行完整的端到端測試。

---

## Testing Strategy

### Unit Tests

**Phase 2 測試**:
- `tests/test_config.py` - 配置讀取/驗證
- `tests/test_init_node.py` - init_node 新舊模式
- `tests/test_commands_init.py` - init/migrate 指令

**Phase 3 測試**:
- `tests/test_adapters.py` - 所有 Adapter 實作
- `tests/test_adapter_registry.py` - Registry 功能
- `tests/test_nodes_with_adapters.py` - build/flash node

### Integration Tests

**Phase 2 整合測試**:
1. 完整初始化流程: `fwauto init` → `fwauto build`
2. 舊專案遷移: `fwauto migrate` → `fwauto build`
3. 向後相容: 舊專案使用 `fwauto build 04`

**Phase 3 整合測試**:
1. Keil 專案: `init` → `build` → `flash`
2. GCC-ARM 專案: `init` → `build` → `flash`
3. 切換 toolchain: 修改 config → rebuild

### Manual Testing Steps

**Phase 2 手動測試**:
1. 在新專案目錄執行 `fwauto init`,驗證配置產生
2. 執行 `fwauto build` (不需專案編號),驗證編譯成功
3. 在專案子目錄執行 `fwauto build`,驗證向上搜尋
4. 對舊專案執行 `fwauto migrate`,驗證遷移成功

**Phase 3 手動測試**:
1. 建立 Keil 專案並使用 Adapter 編譯
2. 建立 GCC-ARM 專案並使用 Adapter 編譯
3. 修改 `toolchain.toml` 切換 toolchain,驗證動態載入
4. 驗證錯誤處理 (工具不存在、配置錯誤)

---

## Performance Considerations

### Phase 2 效能考量

1. **配置檔案快取**
   - 目前每次呼叫都重新讀取 TOML
   - 未來可考慮快取機制 (檔案 mtime 檢查)

2. **向上搜尋深度**
   - `find_project_root()` 最多搜尋到根目錄
   - 對深層目錄結構可能較慢
   - 建議:限制搜尋深度或提供明確路徑參數

### Phase 3 效能考量

1. **Adapter 實例化**
   - 每次 build/flash 都建立新 Adapter 實例
   - 對簡單 Adapter 影響不大
   - 未來可考慮 singleton pattern

2. **subprocess 執行**
   - Keil/GCC 編譯本身耗時較長 (分鐘級)
   - subprocess overhead 可忽略

---

## Migration Notes

### 從 Phase 1 遷移到 Phase 2

**現有使用者工作流程**:
```bash
# Phase 1 方式 (仍可使用)
fwauto build 04
fwauto run 05
```

**新工作流程** (建議):
```bash
# 一次性遷移
cd C:\STM32F407\examples\實例04 UART
fwauto migrate

# 之後不需專案編號
cd C:\STM32F407\examples\實例04 UART
fwauto build
fwauto run
```

### 從 Phase 2 遷移到 Phase 3

**配置檔案更新**:

Phase 2 的 `.fwauto/toolchain.toml` 可能需要調整:
```toml
# Phase 2 (hardcoded paths in node)
[toolchain]
type = "keil"

# Phase 3 (adapter-based, need full config)
[toolchain]
type = "keil"
uv4_path = "C:\\Keil_v5\\UV4\\uv4.exe"
project_file = ".\\USER\\Project.uvprojx"
artifact_path = ".\\OBJ\\Template.hex"
```

**遷移步驟**:
1. 執行 `fwauto init` 重新產生完整配置
2. 或手動補齊 `toolchain.toml` 欄位

---

## References

### 設計文件
- `docs/makefile-design.md` - Makefile 整合設計 (v1.1)
- `docs/makefile-integration-design.md` - 詳細實作設計
- `docs/developer-adapters.md` - Adapter 開發者指南
- `.ai/rpi/research/2025-10-09-makefile-integration-research.md` - 架構分析研究

### 相關程式碼
- `src/fwauto/state.py` - FirmwareState 定義
- `src/fwauto/nodes/init.py:23` - 硬編碼基礎路徑
- `src/fwauto/nodes/build.py:83` - Keil 工具鏈路徑
- `src/fwauto/cli.py` - CLI 指令定義

### 外部文件
- [TOML Specification](https://toml.io/)
- [Python typing.Protocol](https://docs.python.org/3/library/typing.html#typing.Protocol)
- [GNU Make Manual](https://www.gnu.org/software/make/manual/)

---

## 版本歷史

- **v1.0.0** (2025-10-09) - 初始版本,涵蓋 Phase 2 和 Phase 3 完整計劃
