# FWAuto VM SSH Integration Plan

**計劃建立時間**: 2025-11-03
**研究文件**: `.ai/prpi/research/2025-11-03-fwauto-ssh-remote-execution-feasibility.md`
**問題文件**: `.ai/prpi/problems/2025-11-03-fwauto-vm-dev-machine-control-dilemma.md`

---

## 📊 Executive Summary

### 目標

實現跨三環境（開發機 Arch + VM Ubuntu + 開發板 AM62X）的 AI 輔助韌體開發閉環，支援遠端編譯和執行。

### 核心洞察

**FWAuto 的閉環流程**：`build → flash → log → ai → build → ...`

對於不同平台，**flash 的實作方式不同**：
- **STM32**: flash = 燒錄韌體到 MCU
- **AM62X**: flash = SCP 部署 + SSH 執行

**兩者效果相同**：讓最新編譯的程式在硬體上執行。

### 核心決策

- **Phase 1 技術方案**: 透過 Makefile 實現 SSH 部署+執行（不修改 Python code）
- **Phase 2 技術方案**: subprocess + ssh 實現 remote build（需修改 Python code）
- **架構原則**: 遵循 Maketool abstraction，Python 只負責 orchestration
- **實施策略**: 兩階段漸進式（Phase 1 驗證 → Phase 2 生產）

### 預期成果

- Phase 1 (1天): VM 環境內透過 flash Makefile 實現 SSH 部署+執行
- Phase 2 (1-2週): 開發機完全控制 VM + 開發板的完整閉環

### 成功標準

**Phase 1**:
```
VM 內執行 fwauto run
  ↓
build (VM 本地編譯)
  ↓
flash (Makefile: scp + ssh execute 到開發板)
  ↓
閉環驗證成功
```

**Phase 2**:
```
開發機 (Arch) 執行 fwauto run --remote-build vm
  ↓ rsync code
VM (Ubuntu, 192.168.50.163) 編譯
  ↓ scp binary
開發板 (AM62X, 192.168.50.170) 執行
  ↓ stdout log
開發機收集分析 → AI fix
  ↓
循環
```

---

## 🎯 Phase 1: VM-Centric Flash Makefile (極簡版)

### 目標

在 VM 內執行 FWAuto，透過 **flash Makefile** 實現 SSH 部署+執行，驗證閉環可行性。

**核心原則**:
- ❌ **不修改任何 Python code**
- ✅ **只撰寫 AM62X 專案的 flash Makefile**
- ✅ **遵循 Maketool abstraction 架構**

### 環境設定

**執行環境**: VM Ubuntu (192.168.50.163)
**目標裝置**: 開發板 (192.168.50.170, root, no password)

**SSH 連線驗證**:

```bash
# 在 VM 內測試
ssh root@192.168.50.170 "echo 'SSH OK'"
```

應該能直接連線，不需要密碼和 SSH key。

### 實作內容

#### 1.1 建立 AM62X 專案的 Flash Makefile（極簡版）

**檔案**: `<am62x-project>/.fwauto/flash/Makefile`

```makefile
# AM62X Flash Makefile
# Purpose: Entry point for flash operation, delegates to bash script

.PHONY: flash
flash:
	@bash flash.sh
```

**Makefile 說明**:
- **極簡設計**：只負責調用 bash script
- **架構原則**：Makefile 作為 interface，實際邏輯在 script 中
- **好處**：邏輯易測試、易除錯、易維護

#### 1.2 建立 AM62X 專案的 Flash Script

**檔案**: `<am62x-project>/.fwauto/flash/flash.sh`

```bash
#!/usr/bin/env bash
# AM62X Flash Script
# Purpose: Deploy and execute binary on remote AM62X board via SSH

set -euo pipefail  # Exit on error, undefined variable, pipe failure

# ============================================
# Configuration
# ============================================
BOARD_IP="192.168.50.170"
BOARD_USER="root"
DEPLOY_PATH="/root"
BUILD_DIR="$(cd "$(dirname "$0")/../build" && pwd)"
SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5"

# ============================================
# Functions
# ============================================
log_info() {
    echo "ℹ️  $*"
}

log_success() {
    echo "✅ $*"
}

log_error() {
    echo "❌ Error: $*" >&2
}

log_section() {
    echo ""
    echo "========================================="
    echo "$*"
    echo "========================================="
}

find_binary() {
    local binary
    binary=$(find "$BUILD_DIR" -maxdepth 1 -type f -executable 2>/dev/null | head -n1)

    if [ -z "$binary" ]; then
        log_error "No binary found in $BUILD_DIR"
        exit 1
    fi

    echo "$binary"
}

deploy_binary() {
    local binary="$1"
    local binary_name
    binary_name=$(basename "$binary")

    log_info "Deploying $binary_name to $BOARD_USER@$BOARD_IP:$DEPLOY_PATH"

    if ! scp $SSH_OPTS "$binary" "$BOARD_USER@$BOARD_IP:$DEPLOY_PATH/"; then
        log_error "Deploy failed: Cannot connect to board"
        exit 1
    fi

    log_success "Deploy successful"
    echo "$binary_name"
}

execute_on_board() {
    local binary_name="$1"

    log_info "Executing $binary_name on board..."
    echo ""

    if ! ssh $SSH_OPTS "$BOARD_USER@$BOARD_IP" \
        "cd $DEPLOY_PATH && chmod +x ./$binary_name && ./$binary_name"; then
        log_error "Execution failed"
        exit 1
    fi
}

# ============================================
# Main
# ============================================
main() {
    log_section "AM62X Flash: Deploy and Execute"

    local binary
    binary=$(find_binary)

    local binary_name
    binary_name=$(deploy_binary "$binary")

    execute_on_board "$binary_name"

    echo ""
    log_section "✅ Flash completed successfully"
}

main "$@"
```

**Script 說明**:

1. **錯誤處理**: `set -euo pipefail` 確保任何錯誤都會終止執行
2. **模組化函數**:
   - `find_binary()`: 自動偵測 build 目錄內的執行檔
   - `deploy_binary()`: SCP 部署到開發板
   - `execute_on_board()`: SSH 執行程式
3. **清晰的日誌**: 使用統一的 log 函數，輸出易讀
4. **易於測試**: 可獨立測試每個函數
5. **可配置**: 所有配置集中在頂部

#### 1.3 建立 AM62X 專案的 Build Makefile（極簡版）

**檔案**: `<am62x-project>/.fwauto/build/Makefile`

```makefile
# AM62X Build Makefile
# Purpose: Entry point for build operation, delegates to bash script

.PHONY: build clean

build:
	@bash build.sh

clean:
	@bash build.sh clean
```

**Makefile 說明**:
- **極簡設計**：只負責調用 bash script
- **支援多個 target**：build 和 clean 都委託給 script
- **統一架構**：與 flash Makefile 保持一致

#### 1.4 建立 AM62X 專案的 Build Script

**檔案**: `<am62x-project>/.fwauto/build/build.sh`

```bash
#!/usr/bin/env bash
# AM62X Build Script
# Purpose: Compile application for AM62X board

set -euo pipefail

# ============================================
# Configuration
# ============================================
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
SRC_DIR="$PROJECT_ROOT/src"
BUILD_DIR="$PROJECT_ROOT/build"
TARGET="led_app"

# Compiler (根據你的 AM62X SDK 調整)
CC="arm-linux-gnueabihf-gcc"
CFLAGS="-Wall -O2"

# ============================================
# Functions
# ============================================
log_info() {
    echo "ℹ️  $*"
}

log_success() {
    echo "✅ $*"
}

log_error() {
    echo "❌ Error: $*" >&2
}

log_section() {
    echo ""
    echo "========================================="
    echo "$*"
    echo "========================================="
}

do_clean() {
    log_section "Cleaning build artifacts"
    if [ -d "$BUILD_DIR" ]; then
        rm -rf "$BUILD_DIR"
        log_success "Clean completed"
    else
        log_info "Build directory already clean"
    fi
}

do_build() {
    log_section "AM62X Build"

    # Create build directory
    mkdir -p "$BUILD_DIR"

    # Find all C source files
    local sources
    sources=$(find "$SRC_DIR" -name "*.c" 2>/dev/null || true)

    if [ -z "$sources" ]; then
        log_error "No source files found in $SRC_DIR"
        exit 1
    fi

    log_info "Compiling sources..."
    local obj_files=()

    # Compile each source file
    while IFS= read -r src_file; do
        local basename
        basename=$(basename "$src_file" .c)
        local obj_file="$BUILD_DIR/$basename.o"

        echo "  🔨 Compiling $(basename "$src_file")..."
        if ! $CC $CFLAGS -c "$src_file" -o "$obj_file"; then
            log_error "Compilation failed: $src_file"
            exit 1
        fi

        obj_files+=("$obj_file")
    done <<< "$sources"

    # Link
    log_info "Linking $TARGET..."
    if ! $CC $CFLAGS -o "$BUILD_DIR/$TARGET" "${obj_files[@]}"; then
        log_error "Linking failed"
        exit 1
    fi

    # Make executable
    chmod +x "$BUILD_DIR/$TARGET"

    echo ""
    log_success "Build successful: $BUILD_DIR/$TARGET"
}

# ============================================
# Main
# ============================================
main() {
    local command="${1:-build}"

    case "$command" in
        build)
            do_build
            ;;
        clean)
            do_clean
            ;;
        *)
            log_error "Unknown command: $command"
            echo "Usage: $0 [build|clean]"
            exit 1
            ;;
    esac
}

main "$@"
```

**Script 說明**:

1. **支援多種操作**: build 和 clean
2. **自動偵測原始檔**: 掃描 src/ 目錄內所有 .c 檔案
3. **模組化編譯**: 先編譯 .o，再連結成執行檔
4. **易於客製化**: 修改 compiler、flags、target 名稱都很簡單
5. **錯誤處理完善**: 任何步驟失敗都會立即終止

**注意**:
- 這只是範例，實際 compiler 和 flags 需根據你的 AM62X SDK 調整
- 如果你已經有現成的 build 系統（如 CMake），可以在 build.sh 中調用它
- 確保輸出到 `build/` 目錄即可

#### 1.5 專案結構

```
am62x-led-project/
├── .fwauto/
│   ├── config.toml          # 專案配置（可選）
│   ├── build/
│   │   ├── Makefile         # Build Makefile（極簡，調用 script）
│   │   └── build.sh         # Build Script（實際編譯邏輯）
│   └── flash/
│       ├── Makefile         # Flash Makefile（極簡，調用 script）
│       └── flash.sh         # Flash Script（實際部署+執行邏輯）
├── src/
│   └── main.c               # 應用程式原始碼
└── build/                   # 編譯輸出（自動產生）
    └── led_app              # 編譯後的執行檔
```

**架構說明**:
- **Makefile**: 極簡 interface，只負責調用對應的 bash script
- **Bash Script**: 包含所有實際邏輯（編譯、部署、執行）
- **優點**:
  - 邏輯分離清晰
  - Script 可獨立測試和除錯
  - 易於維護和擴展

### Phase 1 驗證流程

#### 測試方式 1: 直接測試 Bash Script

```bash
# 在 VM 內測試（繞過 Makefile，直接測試 script）
cd /path/to/am62x-led-project/.fwauto/flash
bash flash.sh
```

**優點**: 可以快速除錯 script 邏輯

#### 測試方式 2: 透過 Makefile 測試

```bash
# 在 VM 內測試（完整流程）
cd /path/to/am62x-led-project/.fwauto/flash
make flash
```

**優點**: 驗證 Makefile → Script 整合

**預期輸出**:
```
=========================================
AM62X Flash: Deploy and Execute
=========================================
📦 Binary: led_app
🎯 Target: root@192.168.50.170:/root

⬆️  Deploying...
✅ Deploy successful

▶️  Executing on board...
[程式的 stdout 輸出]

=========================================
✅ Flash completed successfully
=========================================
```

#### 整合測試：FWAuto 完整流程

```bash
# 在 VM 內執行
cd /path/to/am62x-led-project
fwauto run
```

**預期行為**:

1. **build_node** 調用 `.fwauto/build/Makefile build`
   - 編譯成功 → `build/led_app` 產生

2. **flash_node** 調用 `.fwauto/flash/Makefile flash`
   - SCP 部署 `led_app` 到開發板
   - SSH 執行程式
   - 程式 stdout/stderr 顯示在終端

3. **閉環驗證**:
   - 如果程式執行成功（exit code 0）→ 流程結束
   - 如果程式執行失敗（exit code 非 0）→ FWAuto 記錄錯誤

**成功標準**:

- [x] Flash Makefile 能成功部署到開發板
- [x] 程式能在開發板上執行
- [ ] `fwauto run` 完整流程無錯誤（需要 build Makefile）
- [ ] 修改 code 後重新執行，變更能正確部署

---

## 🚀 Phase 2: Dev-Machine Control (Production)

### 目標

開發機 (Arch) 完全控制 VM 編譯 + 開發板執行，實現真正的閉環。

### 架構調整

**執行環境**: 開發機 Arch (Hyprland)
**編譯環境**: VM Ubuntu (192.168.50.163, alientek, password: 1234)
**執行環境**: 開發板 (192.168.50.170, root)

#### 2.1 雙層 SSH 架構

```
開發機 (Arch)
  ├─ SSH → VM (192.168.50.163) 執行 make build
  │   └─ 回傳 build output
  ├─ SCP ← VM 下載編譯產物
  └─ SSH → 開發板 (192.168.50.170) 部署 + 執行
      └─ 回傳 execution log
```

#### 2.2 Code Sync 策略

**方案選擇**: rsync over SSH（不是 scp）

**理由**:

1. 增量同步，避免每次全專案傳輸
2. 保留檔案權限和時間戳
3. 支援排除規則（.gitignore 整合）

**實作**:

**新檔案**: `src/fwauto/code_sync.py`

```python
"""Code synchronization with remote environments."""
import subprocess
from pathlib import Path

from fwauto.logging_config import get_logger

logger = get_logger("fwauto.code_sync")


def sync_project_to_remote(
    local_project_root: Path,
    remote_host: str,
    remote_user: str,
    remote_project_path: str,
    ssh_key: str | None = None,
    exclude_patterns: list[str] | None = None,
) -> bool:
    """Sync local project to remote host using rsync.

    Args:
        local_project_root: Local project root directory
        remote_host: Remote hostname or IP
        remote_user: SSH username
        remote_project_path: Remote project path
        ssh_key: Path to SSH private key
        exclude_patterns: List of patterns to exclude (e.g., ['.git', 'build/'])

    Returns:
        True if sync successful, False otherwise
    """
    if exclude_patterns is None:
        exclude_patterns = ['.git', 'build/', '.fwauto/']

    rsync_cmd = ["rsync", "-avz", "--delete"]

    # Add exclude patterns
    for pattern in exclude_patterns:
        rsync_cmd.extend(["--exclude", pattern])

    # Configure SSH options
    ssh_opts = "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
    if ssh_key:
        ssh_opts = f"-i {ssh_key} " + ssh_opts
    rsync_cmd.extend(["-e", f"ssh {ssh_opts}"])

    # Source and destination
    rsync_cmd.append(f"{local_project_root}/")
    rsync_cmd.append(f"{remote_user}@{remote_host}:{remote_project_path}/")

    logger.info(f"Syncing project to {remote_host}:{remote_project_path}")
    logger.debug(f"Command: {' '.join(rsync_cmd)}")

    try:
        result = subprocess.run(
            rsync_cmd,
            capture_output=True,
            text=True,
            timeout=60,
        )

        if result.returncode == 0:
            logger.info("Project synced successfully")
            return True
        else:
            logger.error(f"Sync failed: {result.stderr}")
            return False

    except Exception as e:
        logger.error(f"Sync failed: {e}")
        return False
```

#### 2.3 Remote Build Execution

**修改檔案**: `src/fwauto/utils.py`

**新增函數**（在 line 156 之後）:

```python
def execute_remote_make_command(
    makefile_path: Path,
    target: str,
    project_root: Path,
    remote_host: str,
    remote_user: str,
    remote_project_root: str,
    ssh_key: str | None = None,
    timeout: int = 300,
) -> tuple[int, str]:
    """Execute make command on remote host.

    Args:
        makefile_path: Relative path to Makefile (e.g., .fwauto/build/Makefile)
        target: Make target (e.g., "build", "flash")
        project_root: Local project root
        remote_host: Remote hostname or IP
        remote_user: SSH username
        remote_project_root: Remote project root path
        ssh_key: Path to SSH private key
        timeout: Execution timeout in seconds

    Returns:
        Tuple of (return_code, output)
    """
    from fwauto.code_sync import sync_project_to_remote

    logger.info("Syncing project to remote build environment")
    sync_success = sync_project_to_remote(
        local_project_root=project_root,
        remote_host=remote_host,
        remote_user=remote_user,
        remote_project_path=remote_project_root,
        ssh_key=ssh_key,
    )

    if not sync_success:
        return 1, "Failed to sync project to remote host"

    # Build remote make command
    remote_makefile = f"{remote_project_root}/{makefile_path.relative_to(project_root)}"
    remote_cmd = f"cd {remote_project_root} && make -f {remote_makefile} {target}"

    logger.info(f"Executing remote build: {remote_cmd}")

    ssh_cmd = ["ssh"]
    if ssh_key:
        ssh_cmd.extend(["-i", ssh_key])
    ssh_cmd.extend([
        "-o", "StrictHostKeyChecking=no",
        "-o", "UserKnownHostsFile=/dev/null",
        f"{remote_user}@{remote_host}",
        remote_cmd
    ])

    try:
        result = subprocess.run(
            ssh_cmd,
            capture_output=True,
            text=True,
            timeout=timeout,
        )

        return result.returncode, result.stdout + result.stderr

    except subprocess.TimeoutExpired:
        return 1, f"Remote build timed out after {timeout}s"
    except Exception as e:
        return 1, f"Remote build failed: {e}"
```

**修改 build_node**（`src/fwauto/nodes/build_node.py`）:

在 line 35 之前新增 remote build 檢測:

```python
def build_node(state: FirmwareState) -> dict[str, Any]:
    """Build firmware with potential remote execution."""
    log_node_start(logger, "build")

    # ... existing code ...

    # Check if remote build is configured
    remote_config = state.get("mode_metadata", {}).get("remote_build")

    if remote_config and remote_config.get("enabled"):
        logger.info("Using remote build environment")
        returncode, output = execute_remote_make_command(
            makefile_path=makefile_path,
            target="build",
            project_root=project_root,
            remote_host=remote_config["ssh_host"],
            remote_user=remote_config["ssh_user"],
            remote_project_root=remote_config["project_path"],
            ssh_key=remote_config.get("ssh_key"),
            timeout=timeout,
        )
    else:
        # Original local build
        returncode, output = execute_make_command(
            makefile_path=makefile_path,
            target="build",
            project_root=project_root,
            timeout=timeout,
        )

    # ... rest of existing code ...
```

#### 2.4 User Config 完整範例

**檔案**: `~/.config/fwauto/user-config.toml` (開發機)

```toml
# Remote build environment (VM)
[remote.vm-am62x]
enabled = true
ssh_host = "192.168.50.163"
ssh_user = "alientek"
ssh_key = "~/.ssh/fwauto_vm"
project_path = "/home/alientek/fwauto-projects"
build_timeout = 300

# Remote execution environment (Dev board)
[remote.am62x]
enabled = true
ssh_host = "192.168.50.170"
ssh_user = "root"
ssh_key = "~/.ssh/fwauto_am62x"
deploy_path = "/root"
execution_timeout = 30
```

**SSH 金鑰設定**:

```bash
# 開發機設定
ssh-keygen -t ed25519 -f ~/.ssh/fwauto_vm -N ""
ssh-copy-id -i ~/.ssh/fwauto_vm.pub alientek@192.168.50.163

ssh-keygen -t ed25519 -f ~/.ssh/fwauto_am62x -N ""

# 然後在 VM 內執行
ssh-copy-id -i ~/.ssh/fwauto_am62x.pub root@192.168.50.170
```

#### 2.5 CLI 整合

**修改檔案**: `src/fwauto/cli.py`

新增 `--remote-build` 和 `--remote-exec` 參數:

```python
@app.command()
def run(
    remote_build: str = typer.Option(None, help="Remote build config name (e.g., vm-am62x)"),
    remote_exec: str = typer.Option(None, help="Remote execution config name (e.g., am62x)"),
    exec_args: str = typer.Option("", help="Arguments to pass to executable"),
):
    """Build, flash, and run firmware with optional remote execution."""
    initial_state = {
        "execution_mode": "run",
        "command": "run",
        "mode_metadata": {},
    }

    # Configure remote build
    if remote_build:
        from fwauto.user_config import get_remote_config
        build_config = get_remote_config(remote_build)
        if build_config:
            initial_state["mode_metadata"]["remote_build"] = build_config
        else:
            console.print(f"[red]Remote build config '{remote_build}' not found[/red]")
            raise typer.Exit(1)

    # Configure remote execution
    if remote_exec:
        from fwauto.user_config import get_remote_config
        exec_config = get_remote_config(remote_exec)
        if exec_config:
            initial_state["mode_metadata"]["remote_enabled"] = True
            initial_state["mode_metadata"]["remote_type"] = remote_exec
            initial_state["mode_metadata"]["exec_args"] = exec_args
        else:
            console.print(f"[red]Remote exec config '{remote_exec}' not found[/red]")
            raise typer.Exit(1)

    graph = create_graph()
    final_state = graph.invoke(initial_state)

    # ... existing output handling ...
```

### Phase 2 驗證流程

**完整閉環測試**:

```bash
# 在開發機 (Arch) 執行
cd /home/kent/dev/am62x-led-project
fwauto run --remote-build vm-am62x --remote-exec am62x --exec-args "on"
```

**預期完整流程**:

1. **Code Sync**: rsync 同步專案到 VM
2. **Remote Build**: SSH 到 VM 執行 make build
3. **Build Result**: 收集編譯輸出回開發機
4. **Deploy**: SCP 編譯產物到開發板
5. **Execute**: SSH 到開發板執行程式
6. **Log Collection**: 收集執行日誌
7. **AI Analysis**: 分析日誌決定是否需要修復
8. **Loop**: 如果需要修復，回到步驟 1

**成功標準**:

- [ ] 開發機可完全控制 VM + 開發板
- [ ] Code 變更在開發機，自動同步到 VM
- [ ] 編譯、部署、執行、分析全自動
- [ ] 發生 error 時 AI 自動修復並重試
- [ ] 完整閉環至少成功執行 3 次

---

## 📋 Implementation Checklist

### Phase 1 (VM-Centric Flash Makefile + Script)

- [x] 1.1 驗證 VM → 開發板 SSH 連線（無需密碼）
- [x] 1.2 建立 AM62X 專案的 `.fwauto/flash/Makefile`（極簡版）
- [x] 1.3 建立 AM62X 專案的 `.fwauto/flash/flash.sh`（實際邏輯）
- [x] 1.4 將 flash Makefile 和 script 加入 FWAuto templates
- [x] 1.5 更新 sdk_am62x_linux/metadata.toml
- [x] 1.6 手動測試 flash script（`bash flash.sh`）
- [x] 1.7 測試 Makefile 整合（`make flash`）
- [x] 1.8 測試 FWAuto 整合（`fwauto flash`）
- [ ] 1.9 建立 AM62X 專案的 `.fwauto/build/Makefile`（極簡版）
- [ ] 1.10 建立 AM62X 專案的 `.fwauto/build/build.sh`（實際邏輯）
- [ ] 1.11 整合測試：`fwauto run` 完整流程
- [ ] 1.12 驗證 code 變更能正確部署

### Phase 2 (Dev-Machine Control)

- [ ] 2.1 設定開發機 → VM SSH 免密登入
- [ ] 2.2 建立 code_sync.py，實作 rsync 同步
- [ ] 2.3 修改 utils.py，新增 execute_remote_make_command()
- [ ] 2.4 修改 build_node.py，支援 remote build
- [ ] 2.5 修改 cli.py，新增 --remote-build 參數
- [ ] 2.6 User config 配置與測試
- [ ] 2.7 整合測試：完整閉環驗證
- [ ] 2.8 錯誤處理與重試機制驗證

---

## 🧪 Testing Strategy

### Phase 1 測試（手動測試為主）

**Phase 1 不需要 Python unit tests**，因為沒有修改 Python code。

#### 手動測試 Checklist

**1. SSH 連線測試** (在 VM 內):

```bash
# 測試基本連線
ssh root@192.168.50.170 "echo 'SSH OK'"

# 測試檔案傳輸
echo "test" > /tmp/test.txt
scp /tmp/test.txt root@192.168.50.170:/root/
ssh root@192.168.50.170 "cat /root/test.txt"
```

**2a. Flash Script 獨立測試** (在 VM 內):

```bash
cd /path/to/am62x-project/.fwauto/flash
bash flash.sh
```

預期：
- [ ] 顯示部署進度（log_info, log_success）
- [ ] 檔案成功傳輸到開發板
- [ ] 程式在開發板執行
- [ ] stdout/stderr 顯示在終端
- [ ] Script 錯誤時正確終止（exit code 非 0）

**2b. Flash Makefile 整合測試** (在 VM 內):

```bash
cd /path/to/am62x-project/.fwauto/flash
make flash
```

預期：
- [ ] Makefile 正確調用 flash.sh
- [ ] 輸出與直接執行 script 一致

**3. FWAuto 整合測試** (在 VM 內):

```bash
cd /path/to/am62x-project
fwauto run
```

預期：
- [ ] Build 成功
- [ ] Flash（部署+執行）成功
- [ ] 流程無錯誤結束

**4. 閉環驗證**:

```bash
# 修改 source code
echo '// test change' >> src/main.c

# 重新執行
fwauto run
```

預期：
- [ ] 變更被正確編譯
- [ ] 新版本被部署到開發板
- [ ] 開發板執行新版本程式

### Phase 2 測試

**Phase 2 需要 Python unit tests**，因為會修改 Python code。

#### Unit Tests

**新增測試檔案**:

- `tests/test_code_sync.py`: 測試 rsync 邏輯（需 mock）
- `tests/test_remote_build.py`: 測試 remote build 邏輯（需 mock）

**Mock 策略範例**:

```python
@patch("fwauto.code_sync.subprocess.run")
def test_sync_project_to_remote(mock_run):
    mock_run.return_value = Mock(returncode=0, stdout="", stderr="")
    result = sync_project_to_remote(
        local_project_root=Path("/local/project"),
        remote_host="192.168.50.163",
        remote_user="alientek",
        remote_project_path="/remote/project"
    )
    assert result is True
```

#### Integration Tests

**測試環境需求**:

- 開發機必須能訪問 VM（192.168.50.163）
- VM 必須能訪問開發板（192.168.50.170）
- SSH 金鑰已正確配置

**手動測試 Checklist** (在開發機):

1. [ ] 執行 `fwauto run --remote-build vm-am62x`
2. [ ] 確認專案正確同步到 VM
3. [ ] 確認編譯在 VM 內執行
4. [ ] 確認編譯產物部署到開發板
5. [ ] 確認程式在開發板執行
6. [ ] 修改 code，驗證增量同步
7. [ ] 完整閉環至少 3 次

---

## ⚠️ Risk Mitigation

### Phase 1 風險

| 風險             | 影響 | 緩解策略                             |
| ---------------- | ---- | ------------------------------------ |
| VM 環境變動      | 高   | 明確標註 Phase 1 為過渡方案          |
| SSH 連線不穩     | 中   | 增加 retry 機制和 timeout 配置       |
| Log 格式解析失敗 | 中   | 使用 AI 自然語言分析，不依賴固定格式 |

### Phase 2 風險

| 風險           | 影響 | 緩解策略                           |
| -------------- | ---- | ---------------------------------- |
| Code sync 衝突 | 中   | 使用 rsync --delete 保持一致性     |
| 網路延遲高     | 低   | 可調整 timeout，增加進度顯示       |
| SSH 金鑰管理   | 低   | 文件清楚說明配置步驟               |
| 開發機權限不足 | 低   | 使用 user-level SSH key（~/.ssh/） |

---

## 📊 Success Metrics

### Phase 1 成功標準

- ✅ Flash Makefile 能成功部署到開發板
- ✅ 程式能在開發板上執行
- ✅ `fwauto run` 完整流程（build → flash）無錯誤
- ✅ Code 變更能正確部署（修改後重新執行生效）
- ✅ 錯誤處理正確（連線失敗、執行失敗時有明確訊息）
- ✅ **無需修改任何 Python code**

### Phase 2 成功標準

- ✅ 開發機完全控制 VM + 開發板
- ✅ Code 變更自動同步（< 5 秒）
- ✅ 完整閉環成功率 > 90%（10 次測試中至少 9 次成功）
- ✅ 使用者可在任意目錄執行（anchor mode 正常運作）
- ✅ 向後相容：現有本地專案不受影響

---

## 📝 Documentation Plan

### Phase 1 文件

**新增文件**:

1. **`examples/am62x-led/`**: AM62X 範例專案
   - 完整可運作的 LED 專案
   - `.fwauto/build/Makefile` - 編譯範例
   - `.fwauto/flash/Makefile` - SSH 部署+執行範例
   - `README.md` - 說明如何使用

**更新文件**:

1. **`docs/fwauto-commands-overview.md`**:
   - 補充說明 flash 對不同平台的語意
   - STM32: 燒錄韌體
   - AM62X: SCP 部署 + SSH 執行

2. **`CLAUDE.md`**:
   - 新增 AM62X 專案範例說明
   - 強調 Maketool abstraction 的彈性

### Phase 2 文件

**新增文件**:

1. **`docs/remote-build.md`**: 遠端編譯完整指南
   - 架構說明
   - SSH 配置步驟
   - User config 範例
   - Troubleshooting

**更新文件**:

1. **`CLAUDE.md`**:
   - 新增 remote build 架構說明
   - 更新 code_sync 和相關工具說明

2. **`README.md`**:
   - 新增 remote build 功能介紹
   - 更新 CLI 命令範例（--remote-build）

---

## 🔄 Migration Path

### From Phase 1 to Phase 2

**使用者行動**:

1. 在開發機安裝 FWAuto: `uv tool install fwauto`
2. 複製 VM 內的專案到開發機（或使用 git clone）
3. 設定開發機的 user-config.toml（加入 remote-build 配置）
4. 配置 SSH 金鑰（開發機 → VM）
5. 測試: `fwauto run --remote-build vm-am62x`

**向後相容性**:

- Phase 1 方式仍可使用（在 VM 內執行 FWAuto，專案內 Makefile 不變）
- 所有現有本地專案不受影響（不使用 --remote-build 參數）
- User config 向後相容（新欄位可選）
- **專案的 Makefile 完全不變**（Phase 1 已經完成 flash Makefile）

---

## 📅 Timeline Estimate

### Phase 1: 半天到 1 天

- **撰寫 Makefile**（1-2 小時）
  - 建立 flash Makefile（SCP + SSH 執行）
  - 建立 build Makefile（如果沒有）
- **測試驗證**（1-2 小時）
  - SSH 連線測試
  - 手動測試 flash Makefile
  - FWAuto 整合測試
- **除錯與調整**（1-2 小時）
  - 處理連線問題
  - 調整 Makefile 錯誤處理

**Phase 1 極簡**：不修改任何 Python code，只撰寫 Makefile。

### Phase 2: 1-2 週

- Week 1:
  - code_sync.py 實作（1 天）
  - Remote build 整合（2 天）
  - CLI 參數與 config（1 天）
  - 初步測試（1 天）
- Week 2:
  - 整合測試與除錯（3 天）
  - 文件撰寫（1 天）
  - 使用者驗收測試（1 天）

**總計**: ~1.5 週全職開發時間

---

## ❓ Open Questions & Decisions

### 已決策項目

| 問題                  | 決策                       | 理由                                   |
| --------------------- | -------------------------- | -------------------------------------- |
| Phase 1 實作方式      | 透過 Makefile（不改 Python） | 遵循 Maketool abstraction，KISS 原則   |
| Flash 語意統一        | STM32=燒錄, AM62X=部署+執行 | 兩者效果相同：讓新程式在硬體執行       |
| SSH 認證（開發板）    | 無密碼登入                 | 開發板環境已配置，無需額外設定         |
| Phase 2 SSH library   | subprocess + ssh           | KISS 原則，零依賴                      |
| Phase 2 Code sync 方法 | rsync                      | 增量同步，效能佳                       |
| 實施策略              | 兩階段                     | 快速驗證（Phase 1）+ 漸進式遷移（Phase 2） |

### 待驗證項目

**Phase 1**:

| 項目                | 驗證方式                      | 優先級 |
| ------------------- | ----------------------------- | ------ |
| SSH 連線穩定性      | 多次執行測試                  | P1     |
| Binary 偵測準確性   | 測試不同檔案結構              | P1     |
| 錯誤訊息清晰度      | 故意製造錯誤檢查 Makefile 輸出 | P2     |

**Phase 2**:

| 項目               | 驗證方式                    | 優先級 |
| ------------------ | --------------------------- | ------ |
| rsync 效能         | 實際測試大專案同步時間      | P1     |
| SSH timeout 合理值 | 不同網路環境測試            | P1     |
| 多使用者環境       | 測試多人同時使用 VM 的情況  | P3     |

### 未來擴展考慮

- **多 VM 支援**: 使用者可能有多個編譯環境
- **Docker 整合**: 取代 VM，使用 Docker container 編譯
- **Local cache**: 避免每次都 sync 全部檔案
- **Parallel execution**: 多開發板同時測試

---

## 🎓 Learning Resources

### 使用者需學習內容

- SSH 基本概念（金鑰認證、config 檔案）
- rsync 基本用法
- FWAuto config 結構

### 提供的學習材料

- `docs/remote-execution.md`: 從零開始的配置教學
- 範例專案: 可直接執行的 LED 專案
- Troubleshooting guide: 常見問題解決方案

---

## 📎 References

- **Problem Document**: `.ai/prpi/problems/2025-11-03-fwauto-vm-dev-machine-control-dilemma.md`
- **Research Document**: `.ai/prpi/research/2025-11-03-fwauto-ssh-remote-execution-feasibility.md`
- **FWAuto Architecture**: `docs/high-level-design.md`
- **LangGraph Workflow**: `docs/flow-spec.md`
- **Closed Loop Concept**: `docs/integration-flow.md`

---

## ✅ Next Steps

### 立即可執行（Phase 1）

1. **驗證 SSH 連線**:
   ```bash
   ssh root@192.168.50.170 "echo 'SSH OK'"
   ```

2. **建立 AM62X 範例專案**:
   - 建立專案結構
   - 撰寫 `.fwauto/flash/Makefile`（極簡版）
   - 撰寫 `.fwauto/flash/flash.sh`（實際邏輯）
   - 撰寫 `.fwauto/build/Makefile`（極簡版）
   - 撰寫 `.fwauto/build/build.sh`（實際邏輯）

3. **測試驗證**:
   - 獨立測試 bash scripts（`bash flash.sh`, `bash build.sh`）
   - 測試 Makefile 整合（`make flash`, `make build`）
   - FWAuto 整合測試（`fwauto run`）
   - 修改 code 驗證閉環

4. **建立範例文件**:
   - 建立 `examples/am62x-led/` 目錄
   - 撰寫 README 說明

**預計時間**: 半天到 1 天

**架構優勢**:
- Makefile 極簡，只負責調用 script
- Bash script 包含所有邏輯，易於除錯和測試
- 符合 KISS 原則

### Phase 2 準備（未來工作）

1. Review Phase 2 詳細設計
2. 開發機環境準備
3. SSH 金鑰配置
4. Python code 實作（code_sync.py, 修改 build_node 等）

---

**Plan Status**: ✅ Ready for Implementation
**Last Updated**: 2025-11-04 (Revised)
**Author**: Claude (via /prpi:plan)
**重大修正**:
- 2025-11-03: Phase 1 大幅簡化，不修改 Python code，只撰寫 Makefile
- 2025-11-04: 架構調整 - Makefile 極簡化，實際邏輯委託給 bash script
