---
type: plan
status: draft
research: .ai/prpi/research/2025-11-20-unified-chat-interface-architecture.md
created: 2025-11-20
updated: 2025-11-20
title: Unified Chat Interface Implementation Plan
---

# Unified Chat Interface Implementation Plan

## Overview

實作 FWAuto 統一 Chat 介面，將 `fwauto` 無參數執行時進入互動式對話模式，支援 slash commands (`/build`, `/deploy`, `/log`) 執行具體操作，同時保留獨立 CLI 命令作為非互動式快捷方式。實現 AI 輔助的 build/deploy 失敗診斷與自動修復流程。

## Goals

- 提供單一入口點（`fwauto`）進入互動式 Chat 模式
- 在 Chat 中支援 slash commands 執行 build/deploy/log 操作
- 完整解析命令列參數（包含 `--binary-args`、`--scenario` 等）
- 命令執行失敗後詢問使用者是否需要 AI 協助修復
- 注入完整 build/deploy logs 到 AI 對話歷史
- 保留現有獨立 CLI 命令（`fwauto build`、`fwauto deploy` 等）為非互動式快捷方式
- 遵循 KISS 原則，最小化架構變更

## Non-Goals

- 修改 LangGraph workflow routing（保持現有 graph.py）
- 修改現有 nodes 的簽名或核心邏輯
- 實作 GUI 介面
- 支援 `/feat` slash command（使用自然語言對話代替）

## Architecture Overview

```mermaid
graph TB
    CLI["CLI Entry Point<br/>(cli.py:main)"]

    CLI -->|"fwauto"| ChatMode["Unified Chat Mode<br/>(chat_node)"]
    CLI -->|"fwauto build/deploy/..."| DirectCmd["Direct Typer Commands<br/>(現有行為)"]

    ChatMode --> UserInput["User Input"]

    UserInput -->|"以 / 開頭"| SlashParser["Slash Command Parser<br/>(slash_commands.py)"]
    UserInput -->|"自然語言"| AIChat["AI Conversation<br/>(ClaudeSDKClient)"]

    SlashParser --> ParamConvert["Parameter Conversion Layer<br/>(_build_deploy_state, _build_log_state)"]

    ParamConvert --> NodeExec["Synchronous Node Execution<br/>(build_node/deploy_node/log_node)"]

    NodeExec --> ResultInject["Result Injection<br/>(System Message to AI)"]

    NodeExec -->|"失敗"| AskRepair["詢問 AI 修復"]

    ResultInject --> CmdLogger["Command Logger<br/>(CommandLogger)"]

    AskRepair -->|"同意"| AIChat
    AskRepair -->|"拒絕"| UserInput

    AIChat --> UserInput
    CmdLogger --> UserInput
```

## Implementation Phases

### Phase 1: Command Parsing Infrastructure

**目標**: 建立 slash command 解析與參數處理基礎設施

**新增檔案**:
- `src/fwauto/slash_commands.py`

**核心元件**:

```python
@dataclass
class SlashCommand:
    """Parsed slash command representation."""
    name: str  # "build", "deploy", "log"
    raw_args: list[str]  # 原始參數列表
    parsed_args: dict[str, Any]  # 解析後的參數 dict
```

**實作函數**:

1. **`parse_slash_command(user_input: str) -> SlashCommand | None`**
   - 偵測是否為 slash command（`/` 開頭）
   - 使用 `shlex.split()` 解析參數（支援引號）
   - 返回 `SlashCommand` 或 `None`（非 slash command）

   ```python
   def parse_slash_command(user_input: str) -> SlashCommand | None:
       """Parse user input as slash command.

       Examples:
           "/build" -> SlashCommand(name="build", raw_args=[], parsed_args={})
           "/deploy --binary-args test.hex" -> SlashCommand(...)
           "/log analyze.txt '有任何 error 嗎?'" -> SlashCommand(...)
           "請幫我分析這個錯誤" -> None
       """
       if not user_input.strip().startswith("/"):
           return None

       parts = shlex.split(user_input)
       command_name = parts[0][1:]  # Remove leading /

       if command_name not in ["build", "deploy", "log"]:
           return None

       return SlashCommand(
           name=command_name,
           raw_args=parts[1:],
           parsed_args={}  # Will be filled by parse_command_args
       )
   ```

2. **`parse_command_args(cmd: SlashCommand) -> dict[str, Any]`**
   - 根據 command name 解析參數
   - 支援 `--binary-args`, `-ba`, `--scenario`, `-s` 等
   - 返回 `command_args` dict

   ```python
   def parse_command_args(cmd: SlashCommand) -> dict[str, Any]:
       """Parse slash command arguments into command_args dict.

       Handles:
       - /build -> {}
       - /deploy --binary-args test.hex -> {"binary_args": "test.hex"}
       - /deploy --scenario quick -> {"binary_args": scenario.binary_args}
       - /log path.txt "prompt" -> {"log_path": "path.txt", "analysis_prompt": "prompt"}
       """
       if cmd.name == "build":
           return _parse_build_args(cmd.raw_args)
       elif cmd.name == "deploy":
           return _parse_deploy_args(cmd.raw_args)
       elif cmd.name == "log":
           return _parse_log_args(cmd.raw_args)
       else:
           raise ValueError(f"Unknown command: {cmd.name}")
   ```

3. **`_parse_deploy_args(args: list[str]) -> dict[str, Any]`**
   - 解析 `--binary-args` 和 `--scenario` 參數
   - 實作優先級規則：`--binary-args` > `--scenario`
   - 使用 `ScenarioManager` 載入 scenario 設定

   ```python
   def _parse_deploy_args(args: list[str]) -> dict[str, Any]:
       """Parse deploy command arguments.

       Priority: --binary-args > --scenario
       """
       binary_args = ""
       scenario = ""

       i = 0
       while i < len(args):
           if args[i] in ["--binary-args", "-ba"]:
               binary_args = args[i + 1]
               i += 2
           elif args[i] in ["--scenario", "-s"]:
               scenario = args[i + 1]
               i += 2
           else:
               i += 1

       # Apply priority rule
       final_binary_args = binary_args
       if not final_binary_args and scenario:
           from .scenario_manager import scenario_manager
           scenario_obj = scenario_manager.get_scenario(scenario)
           final_binary_args = scenario_obj.binary_args

       return {"binary_args": final_binary_args}
   ```

4. **`_parse_log_args(args: list[str]) -> dict[str, Any]`**
   - 解析 log 檔案路徑與分析 prompt
   - 支援引號包裹的 prompt

   ```python
   def _parse_log_args(args: list[str]) -> dict[str, Any]:
       """Parse log command arguments.

       Format: /log <log_path> [analysis_prompt]
       """
       if not args:
           raise ValueError("Log command requires log file path")

       log_path = args[0]
       analysis_prompt = " ".join(args[1:]) if len(args) > 1 else "分析這份 log"

       return {
           "log_path": log_path,
           "analysis_prompt": analysis_prompt
       }
   ```

**測試案例**:

```python
# tests/test_slash_commands.py
def test_parse_slash_command_build():
    cmd = parse_slash_command("/build")
    assert cmd.name == "build"
    assert cmd.raw_args == []

def test_parse_slash_command_deploy_with_args():
    cmd = parse_slash_command("/deploy --binary-args test.hex")
    assert cmd.name == "deploy"
    assert cmd.raw_args == ["--binary-args", "test.hex"]

def test_parse_slash_command_log_with_prompt():
    cmd = parse_slash_command("/log uart.log '有任何 error 嗎?'")
    assert cmd.name == "log"
    assert cmd.raw_args == ["uart.log", "有任何 error 嗎?"]

def test_parse_slash_command_not_slash():
    cmd = parse_slash_command("請幫我分析這個錯誤")
    assert cmd is None

def test_parse_deploy_args_priority():
    args = ["--binary-args", "test.hex", "--scenario", "quick"]
    parsed = _parse_deploy_args(args)
    assert parsed["binary_args"] == "test.hex"  # --binary-args 優先

def test_parse_deploy_args_scenario_only():
    args = ["--scenario", "quick"]
    parsed = _parse_deploy_args(args)
    assert "binary_args" in parsed  # 從 scenario 取得
```

**成功標準**:
- [x] 所有測試通過
- [x] 支援所有 deploy/log 參數格式
- [x] 正確處理引號包裹的參數
- [x] 優先級規則正確實作

---

### Phase 2: Parameter Conversion Layer

**目標**: 提取共用參數處理邏輯，確保 CLI 和 slash commands 使用相同的 State 構建方式

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

**新增 Helper Functions**:

1. **`_build_deploy_state(binary_args: str, project_root: str, platform: str) -> FirmwareState`**
   - 從 `deploy()` 函數提取
   - 構建 deploy 專用的 FirmwareState

   ```python
   def _build_deploy_state(
       binary_args: str,
       project_root: str,
       platform: str
   ) -> FirmwareState:
       """Build FirmwareState for deploy command.

       Used by both CLI deploy command and /deploy slash command.
       """
       return {
           "user_prompt": "部署韌體到裝置",
           "project_root": project_root,
           "project_initialized": True,
           "platform": platform,
           "execution_mode": "deploy",
           "command": "deploy",
           "command_args": {"binary_args": binary_args},
           "mode_metadata": {},
           "command_error": None,
           "build_status": "pending",
           "build_errors": [],
           "build_log": "",
           "deploy_status": "pending",
           "deploy_errors": [],
           "deploy_log": "",
           "iteration": 0,
           "max_retries": 3,
           "log_file_path": None,
           "log_analysis_result": None,
           "firmware_path": None,
       }
   ```

2. **`_build_log_state(log_path: str, analysis_prompt: str, project_root: str, platform: str) -> FirmwareState`**
   - 從 `log()` 函數提取
   - 構建 log 分析專用的 FirmwareState
   - 保持雙重儲存模式（`log_file_path` + `command_args`）

   ```python
   def _build_log_state(
       log_path: str,
       analysis_prompt: str,
       project_root: str,
       platform: str
   ) -> FirmwareState:
       """Build FirmwareState for log command.

       Used by both CLI log command and /log slash command.
       Maintains double storage pattern for log_file_path.
       """
       return {
           "user_prompt": analysis_prompt,
           "project_root": project_root,
           "project_initialized": True,
           "platform": platform,
           "execution_mode": "log",
           "command": "log",
           "command_args": {
               "log_path": log_path,
               "analysis_prompt": analysis_prompt
           },
           "mode_metadata": {},
           "command_error": None,
           "build_status": "pending",
           "build_errors": [],
           "build_log": "",
           "deploy_status": "pending",
           "deploy_errors": [],
           "deploy_log": "",
           "iteration": 0,
           "max_retries": 3,
           "log_file_path": log_path,  # Double storage
           "log_analysis_result": None,
           "firmware_path": None,
       }
   ```

**重構現有 CLI Commands**:

修改 `deploy()` 函數使用新的 helper：

```python
@app.command()
def deploy(
    binary_args: str = typer.Option("", "--binary-args", "-ba"),
    scenario: str = typer.Option("", "--scenario", "-s"),
):
    """部署韌體到目標裝置"""
    from .config import load_config
    from .graph import create_firmware_graph

    # Parameter priority logic (keep existing)
    final_binary_args = binary_args
    if not final_binary_args and scenario:
        scenario_obj = scenario_manager.get_scenario(scenario)
        final_binary_args = scenario_obj.binary_args

    # Get project config
    config = load_config()
    project_root = str(config.project_root)
    platform = config.platform

    # Use helper function
    state = _build_deploy_state(final_binary_args, project_root, platform)

    # Execute workflow (keep existing)
    app_graph = create_firmware_graph()
    app_graph.invoke(state)
```

修改 `log()` 函數使用新的 helper：

```python
@app.command()
def log(log_path: str, prompt: str = "分析這份 log"):
    """分析 UART log 並提供診斷建議"""
    from .config import load_config
    from .graph import create_firmware_graph

    # Get project config
    config = load_config()
    project_root = str(config.project_root)
    platform = config.platform

    # Use helper function
    state = _build_log_state(log_path, prompt, project_root, platform)

    # Execute workflow (keep existing)
    app_graph = create_firmware_graph()
    app_graph.invoke(state)
```

**測試案例**:

```python
# tests/test_cli_helpers.py
def test_build_deploy_state():
    state = _build_deploy_state("test.hex", "/project", "am62x")
    assert state["execution_mode"] == "deploy"
    assert state["command_args"]["binary_args"] == "test.hex"
    assert state["project_root"] == "/project"

def test_build_log_state_double_storage():
    state = _build_log_state("uart.log", "分析", "/project", "am62x")
    assert state["log_file_path"] == "uart.log"
    assert state["command_args"]["log_path"] == "uart.log"
    assert state["command_args"]["analysis_prompt"] == "分析"
```

**成功標準**:
- [x] 所有現有 CLI 測試仍然通過
- [x] Helper functions 正確構建 FirmwareState
- [x] 雙重儲存模式正確實作
- [x] 無重複程式碼（DRY 原則）

---

### Phase 3: Command Execution Logger

**目標**: 建立專門記錄 slash command 執行的 logger 類別

**新增檔案**:
- `src/fwauto/command_logger.py`

**核心類別**:

```python
"""Command execution logger for slash commands."""

from datetime import datetime
from pathlib import Path
from typing import Any


class CommandLogger:
    """Records slash command executions and their results.

    Log format (plain text):
        ========================================
        Command Execution Log
        Session: 2025-11-20 14:30:00
        Project: /path/to/project
        ========================================

        [14:30:15] Command: /deploy --binary-args test.hex
        Parameters:
          - binary_args: test.hex

        Status: success

        Output:
        ✅ 韌體部署成功
        Deploy log: ...

        ----------------------------------------

        [14:31:20] Command: /build
        Parameters: (none)

        Status: error

        Output:
        ❌ 編譯失敗
        Build errors: ...

        ========================================
        Session ended: 2025-11-20 14:35:00
        Total commands: 2
        ========================================
    """

    def __init__(self, project_root: Path, state: dict[str, Any]):
        """Initialize command logger.

        Args:
            project_root: Project root directory
            state: Initial FirmwareState
        """
        self.project_root = project_root
        self.log_dir = project_root / ".fwauto" / "logs"
        self.log_dir.mkdir(parents=True, exist_ok=True)

        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        self.log_file = self.log_dir / f"commands_{timestamp}.txt"

        self.command_count = 0
        self.session_start = datetime.now()

        # Write header
        self._write_header(state)

    def _write_header(self, state: dict[str, Any]) -> None:
        """Write log file header."""
        with open(self.log_file, "w", encoding="utf-8") as f:
            f.write("=" * 50 + "\n")
            f.write("Command Execution Log\n")
            f.write(f"Session: {self.session_start.strftime('%Y-%m-%d %H:%M:%S')}\n")
            f.write(f"Project: {self.project_root}\n")
            f.write(f"Platform: {state.get('platform', 'unknown')}\n")
            f.write("=" * 50 + "\n\n")

    def log_command_execution(
        self,
        command: str,
        parameters: dict[str, Any],
        status: str,
        output: str
    ) -> None:
        """Log a command execution.

        Args:
            command: Slash command name (e.g., "/deploy")
            parameters: Parsed command parameters
            status: Execution status ("success" or "error")
            output: Command output text
        """
        self.command_count += 1
        timestamp = datetime.now().strftime("%H:%M:%S")

        with open(self.log_file, "a", encoding="utf-8") as f:
            f.write(f"[{timestamp}] Command: {command}\n")

            if parameters:
                f.write("Parameters:\n")
                for key, value in parameters.items():
                    f.write(f"  - {key}: {value}\n")
            else:
                f.write("Parameters: (none)\n")

            f.write(f"\nStatus: {status}\n\n")
            f.write("Output:\n")
            f.write(output + "\n")
            f.write("-" * 50 + "\n\n")

    def finalize(self) -> None:
        """Write session summary and close log."""
        session_end = datetime.now()
        duration = (session_end - self.session_start).total_seconds()

        with open(self.log_file, "a", encoding="utf-8") as f:
            f.write("=" * 50 + "\n")
            f.write(f"Session ended: {session_end.strftime('%Y-%m-%d %H:%M:%S')}\n")
            f.write(f"Duration: {duration:.1f} seconds\n")
            f.write(f"Total commands: {self.command_count}\n")
            f.write("=" * 50 + "\n")
```

**使用範例**:

```python
# In chat_node
cmd_logger = CommandLogger(Path(project_root), state)

# After executing /deploy
cmd_logger.log_command_execution(
    command="/deploy --binary-args test.hex",
    parameters={"binary_args": "test.hex"},
    status="success",
    output="✅ 韌體部署成功\nDeploy log: ..."
)

# At session end
cmd_logger.finalize()
```

**測試案例**:

```python
# tests/test_command_logger.py
def test_command_logger_initialization(tmp_path):
    state = {"platform": "am62x"}
    logger = CommandLogger(tmp_path, state)

    assert logger.log_file.exists()
    assert "Command Execution Log" in logger.log_file.read_text()

def test_log_command_execution(tmp_path):
    state = {"platform": "am62x"}
    logger = CommandLogger(tmp_path, state)

    logger.log_command_execution(
        command="/deploy --binary-args test.hex",
        parameters={"binary_args": "test.hex"},
        status="success",
        output="✅ Deploy successful"
    )

    log_content = logger.log_file.read_text()
    assert "/deploy --binary-args test.hex" in log_content
    assert "binary_args: test.hex" in log_content
    assert "Status: success" in log_content

def test_finalize_writes_summary(tmp_path):
    state = {"platform": "am62x"}
    logger = CommandLogger(tmp_path, state)
    logger.command_count = 3
    logger.finalize()

    log_content = logger.log_file.read_text()
    assert "Total commands: 3" in log_content
    assert "Session ended:" in log_content
```

**成功標準**:
- [x] 所有測試通過
- [x] Log 格式清晰易讀
- [x] 正確記錄 command、parameters、status、output
- [x] Session summary 正確寫入

---

### Phase 4: Chat Node Enhancement

**目標**: 修改 `_run_chat_loop()` 加入 slash command 偵測、路由、執行與結果注入

**修改檔案**:
- `src/fwauto/nodes/chat.py`

**主要修改點**:

1. **在 `chat_node()` 初始化 CommandLogger**

```python
def chat_node(state: FirmwareState) -> dict[str, Any]:
    """Interactive chat node using ClaudeSDKClient."""
    # ... (existing code)

    # Initialize loggers
    chat_logger = None
    cmd_logger = None
    if project_root:
        try:
            chat_logger = ChatLogger(Path(project_root), state)
            cmd_logger = CommandLogger(Path(project_root), state)  # NEW
            logger.debug(f"💬 Chat: Loggers initialized")
        except Exception as e:
            logger.warning(f"⚠️ Chat: Failed to initialize loggers: {e}")

    # ... (pass cmd_logger to _run_chat_loop)
```

2. **修改 `_run_chat_loop()` 簽名**

```python
async def _run_chat_loop(
    options: ClaudeAgentOptions,
    chat_logger: ChatLogger | None = None,
    cmd_logger: CommandLogger | None = None,  # NEW
    state: FirmwareState | None = None  # NEW: need state for command execution
) -> None:
    """Execute async interactive chat loop."""
```

3. **加入 Slash Command 偵測與路由**

```python
async def _run_chat_loop(...):
    async with ClaudeSDKClient(options=options) as client:
        await client.connect()

        try:
            while True:
                user_input = await session.prompt_async()
                user_input = user_input.strip()

                # ... (existing exit checks)

                # NEW: Check for slash command
                from ..slash_commands import parse_slash_command, parse_command_args
                slash_cmd = parse_slash_command(user_input)

                if slash_cmd:
                    # Execute slash command
                    await _handle_slash_command(
                        slash_cmd,
                        state,
                        client,
                        cmd_logger,
                        chat_logger
                    )
                    continue  # Skip normal AI query

                # Normal AI conversation (existing code)
                if chat_logger:
                    chat_logger.log_user_message(user_input)

                await client.query(user_input)
                # ... (existing response handling)
```

4. **實作 `_handle_slash_command()` 函數**

```python
async def _handle_slash_command(
    cmd: SlashCommand,
    state: FirmwareState,
    client: ClaudeSDKClient,
    cmd_logger: CommandLogger | None,
    chat_logger: ChatLogger | None
) -> None:
    """Handle slash command execution.

    Steps:
    1. Parse command arguments
    2. Build appropriate FirmwareState
    3. Execute corresponding node (synchronously)
    4. Inject result into AI conversation (as system message)
    5. Log command execution
    6. If failed, ask user about AI repair
    """
    from ..slash_commands import parse_command_args
    from ..cli import _build_deploy_state, _build_log_state
    from ..nodes.build import build_node
    from ..nodes.deploy import deploy_node
    from ..nodes.log import log_node
    from rich.console import Console

    console = Console()

    try:
        # Parse arguments
        parsed_args = parse_command_args(cmd)
        cmd.parsed_args = parsed_args

        # Build state based on command type
        if cmd.name == "build":
            exec_state = {
                **state,
                "execution_mode": "build",
                "command": "build",
                "command_args": {},
            }
            result = build_node(exec_state)
            status = result["build_status"]
            output = result["build_log"]

        elif cmd.name == "deploy":
            binary_args = parsed_args.get("binary_args", "")
            exec_state = _build_deploy_state(
                binary_args,
                state["project_root"],
                state["platform"]
            )
            result = deploy_node(exec_state)
            status = result["deploy_status"]
            output = result["deploy_log"]

        elif cmd.name == "log":
            log_path = parsed_args["log_path"]
            analysis_prompt = parsed_args["analysis_prompt"]
            exec_state = _build_log_state(
                log_path,
                analysis_prompt,
                state["project_root"],
                state["platform"]
            )
            result = log_node(exec_state)
            status = "success" if result.get("log_analysis_result") else "error"
            output = result.get("log_analysis_result", "分析失敗")

        else:
            raise ValueError(f"Unknown command: {cmd.name}")

        # Log command execution
        if cmd_logger:
            cmd_logger.log_command_execution(
                command=f"/{cmd.name} {' '.join(cmd.raw_args)}",
                parameters=parsed_args,
                status=status,
                output=output
            )

        # Inject result into AI conversation as system message
        system_msg = f"""[系統] 使用者執行了指令：/{cmd.name}
參數：{parsed_args}

執行結果：{status}

完整 Log：
{output}
"""

        await client.query(system_msg)

        # Display result to user
        console.print(f"\n[cyan]✅ 指令執行完成：/{cmd.name}[/cyan]")
        console.print(f"狀態：{status}\n")

        # If failed, ask user about AI repair
        if status == "error":
            console.print("[yellow]指令執行失敗，是否需要 AI 協助診斷與修復？(y/n)[/yellow]")

            # Get user confirmation (using prompt_toolkit)
            from prompt_toolkit import PromptSession
            confirm_session = PromptSession()

            try:
                confirm = await confirm_session.prompt_async("AI 修復 (y/n): ")
                confirm = confirm.strip().lower()

                if confirm in ["y", "yes"]:
                    # Let AI analyze the error
                    repair_prompt = f"剛才執行 /{cmd.name} 失敗了，錯誤訊息如下：\n\n{output}\n\n請幫我診斷問題並修復。"

                    if chat_logger:
                        chat_logger.log_user_message(repair_prompt)

                    await client.query(repair_prompt)

                    # Receive AI response
                    from claude_agent_sdk.types import AssistantMessage, TextBlock
                    response_texts = []
                    async for msg in client.receive_response():
                        print_ai_message(msg, prefix="💬")
                        if isinstance(msg, AssistantMessage):
                            for block in msg.content:
                                if isinstance(block, TextBlock):
                                    response_texts.append(block.text)

                    if chat_logger and response_texts:
                        chat_logger.log_assistant_message("\n".join(response_texts))
                else:
                    console.print("[cyan]已略過 AI 修復[/cyan]")

            except (EOFError, KeyboardInterrupt):
                console.print("[cyan]已略過 AI 修復[/cyan]")

    except Exception as e:
        console.print(f"[red]❌ 指令執行錯誤：{e}[/red]")
        logger.error(f"Slash command execution failed: {e}")

        if cmd_logger:
            cmd_logger.log_command_execution(
                command=f"/{cmd.name} {' '.join(cmd.raw_args)}",
                parameters=cmd.parsed_args,
                status="error",
                output=str(e)
            )
```

**修改後的 Chat Loop 流程**:

```mermaid
sequenceDiagram
    participant User
    participant ChatLoop as Chat Loop
    participant Parser as Slash Parser
    participant Node as Node (sync)
    participant AI as ClaudeSDKClient
    participant Logger as CommandLogger

    User->>ChatLoop: "/deploy --binary-args test.hex"
    ChatLoop->>Parser: parse_slash_command()
    Parser-->>ChatLoop: SlashCommand(name="deploy", ...)

    ChatLoop->>Parser: parse_command_args()
    Parser-->>ChatLoop: {"binary_args": "test.hex"}

    ChatLoop->>Node: deploy_node(state)
    Note over Node: Synchronous execution
    Node-->>ChatLoop: result dict

    ChatLoop->>Logger: log_command_execution()

    ChatLoop->>AI: query("[系統] 使用者執行了...")
    Note over AI: Inject result as system message

    alt Deploy Failed
        ChatLoop->>User: "是否需要 AI 修復？"
        User-->>ChatLoop: "y"
        ChatLoop->>AI: query("請幫我診斷...")
        AI-->>ChatLoop: AI response
        ChatLoop->>User: Display repair suggestions
    end

    ChatLoop->>User: "✅ 指令執行完成"
```

**測試案例**:

```python
# tests/test_chat_slash_commands.py
@pytest.mark.asyncio
async def test_handle_slash_command_build(mock_state):
    # Mock ClaudeSDKClient
    mock_client = AsyncMock()

    cmd = SlashCommand(name="build", raw_args=[], parsed_args={})

    await _handle_slash_command(cmd, mock_state, mock_client, None, None)

    # Verify client.query was called with system message
    assert mock_client.query.called
    call_args = mock_client.query.call_args[0][0]
    assert "[系統] 使用者執行了指令：/build" in call_args

@pytest.mark.asyncio
async def test_handle_slash_command_deploy_with_args(mock_state):
    mock_client = AsyncMock()

    cmd = SlashCommand(
        name="deploy",
        raw_args=["--binary-args", "test.hex"],
        parsed_args={"binary_args": "test.hex"}
    )

    await _handle_slash_command(cmd, mock_state, mock_client, None, None)

    # Verify deploy_node was called with correct state
    # (would need to mock deploy_node to verify this)
```

**成功標準**:
- [x] Slash commands 正確偵測與路由
- [x] 同步 nodes 可從 async context 調用
- [x] 執行結果正確注入 AI 對話歷史
- [x] 失敗時正確詢問 AI 修復
- [x] CommandLogger 正確記錄

---

### Phase 5: CLI Entry Point Refactor

**目標**: 替換 `cli.py:main()` 中的 `_interactive_mode()` 為統一 chat 介面

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

**Before (Current)**:

```python
def main():
    """Entry point for the CLI."""
    if len(sys.argv) == 1:
        _interactive_mode()  # Old implementation
    else:
        app()
```

**After (New)**:

```python
def main():
    """Entry point for the CLI."""
    if len(sys.argv) == 1:
        # Enter unified chat mode
        _enter_unified_chat_mode()
    else:
        # Execute direct CLI command
        app()


def _enter_unified_chat_mode() -> None:
    """Enter unified chat interface mode.

    This replaces the old _interactive_mode() function.
    Uses chat_node for interactive conversation with slash command support.
    """
    from rich.console import Console
    from .config import load_config
    from .graph import create_firmware_graph
    from .logging_config import get_logger

    console = Console()
    logger = get_logger(__name__)

    try:
        # Load project config (with anchor-based discovery)
        config = load_config()
        project_root = str(config.project_root)
        platform = config.platform

        console.print(f"[cyan]📁 專案路徑: {project_root}[/cyan]")
        console.print(f"[cyan]🔧 平台: {platform}[/cyan]")
        console.print("[cyan]💬 進入對話模式（輸入 'exit' 或按 Ctrl+C 離開）[/cyan]")
        console.print("[dim]提示：使用 /build, /deploy, /log 執行指令[/dim]\n")

        # Build initial state for chat mode
        state: FirmwareState = {
            "user_prompt": "",
            "project_root": project_root,
            "project_initialized": True,
            "platform": platform,
            "execution_mode": "chat",
            "command": "chat",
            "command_args": None,
            "mode_metadata": {},
            "command_error": None,
            "build_status": "pending",
            "build_errors": [],
            "build_log": "",
            "deploy_status": "pending",
            "deploy_errors": [],
            "deploy_log": "",
            "iteration": 0,
            "max_retries": 3,
            "log_file_path": None,
            "log_analysis_result": None,
            "firmware_path": None,
        }

        # Create and execute graph
        app_graph = create_firmware_graph()
        result = app_graph.invoke(state)

        if result.get("chat_completed"):
            console.print("\n[green]✅ 對話結束[/green]")
        else:
            console.print("\n[yellow]⚠️ 對話異常結束[/yellow]")

    except FileNotFoundError as e:
        console.print(f"[red]❌ 找不到專案：{e}[/red]")
        console.print("[yellow]提示：請在 FWAuto 專案目錄（包含 .fwauto/）中執行此指令[/yellow]")
        sys.exit(1)

    except Exception as e:
        logger.error(f"Chat mode failed: {e}", exc_info=True)
        console.print(f"[red]❌ 對話模式啟動失敗：{e}[/red]")
        sys.exit(1)
```

**刪除舊函數**:

- 刪除 `_interactive_mode()` 函數（cli.py:459-527）

**使用者體驗變化**:

**Before**:
```bash
$ fwauto
? 選擇操作:
  ❯ 編譯
    部署
    執行（編譯+部署）
    分析 Log
```

**After**:
```bash
$ fwauto
📁 專案路徑: /home/kent/projects/my-firmware
🔧 平台: am62x
💬 進入對話模式（輸入 'exit' 或按 Ctrl+C 離開）
提示：使用 /build, /deploy, /log 執行指令

You> /build
✅ 指令執行完成：/build
狀態：success

You> 請幫我分析剛才的編譯結果
AI> [分析回應...]

You> /deploy --scenario quick
✅ 指令執行完成：/deploy
狀態：success

You> exit
👋 結束對話
✅ 對話結束
```

**測試案例**:

```python
# tests/test_cli_entry.py
def test_main_no_args_enters_chat(monkeypatch, mock_config):
    """Test that fwauto without args enters chat mode."""
    monkeypatch.setattr(sys, "argv", ["fwauto"])

    # Mock graph execution
    mock_graph = Mock()
    mock_graph.invoke.return_value = {"chat_completed": True}

    with patch("fwauto.cli.create_firmware_graph", return_value=mock_graph):
        with patch("fwauto.cli.load_config", return_value=mock_config):
            main()

    # Verify graph was invoked with chat mode state
    call_args = mock_graph.invoke.call_args[0][0]
    assert call_args["execution_mode"] == "chat"
    assert call_args["command"] == "chat"

def test_main_with_args_uses_typer(monkeypatch):
    """Test that fwauto with args uses direct CLI commands."""
    monkeypatch.setattr(sys, "argv", ["fwauto", "build"])

    with patch("fwauto.cli.app") as mock_app:
        main()

    # Verify Typer app was called
    assert mock_app.called
```

**成功標準**:
- [x] `fwauto` 進入 chat mode
- [x] `fwauto build/deploy/...` 保持現有行為
- [x] 錯誤處理正確（找不到專案等）
- [x] 所有現有 CLI 測試通過

---

### Phase 6: System Prompt Updates

**目標**: 更新 `prompts/chat.md` 加入 slash commands 使用說明

**修改檔案**:
- `prompts/chat.md`

**新增內容**:

```markdown
# Chat Mode System Prompt

你是 FWAuto 的 AI 助手，專門協助使用者進行韌體開發工作。

## 當前環境

- **專案路徑**: {{ project_root }}
- **平台**: {{ platform }}
- **執行模式**: 互動式對話（Chat Mode）

## 你的角色

1. **程式碼協助**: 回答韌體相關的程式設計問題
2. **錯誤診斷**: 分析 build/deploy 失敗的原因並提供修復建議
3. **功能實作**: 協助實作新功能（透過自然語言對話）
4. **Log 分析**: 解讀 UART logs 並診斷執行時問題

## Slash Commands 說明 (NEW)

使用者可以使用以下 slash commands 執行 FWAuto 操作：

### `/build`
編譯當前專案的韌體。

**範例**:
```
You> /build
```

### `/deploy [--binary-args <args>] [--scenario <name>]`
部署韌體到目標裝置。

**參數**:
- `--binary-args`, `-ba`: 指定要部署的檔案路徑或參數
- `--scenario`, `-s`: 使用預定義的 scenario

**優先級**: `--binary-args` > `--scenario`

**範例**:
```
You> /deploy --binary-args test.hex
You> /deploy --scenario quick
You> /deploy -ba "firmware.bin --offset 0x8000"
```

### `/log <log_path> [analysis_prompt]`
分析 UART log 檔案。

**參數**:
- `<log_path>`: Log 檔案路徑（必填）
- `[analysis_prompt]`: 分析提示（選填，預設為「分析這份 log」）

**範例**:
```
You> /log uart_20251120.log
You> /log uart.log "有任何 error 嗎?"
You> /log logs/debug.txt '找出 memory leak 的原因'
```

## 命令執行失敗時的處理 (NEW)

當使用者執行的 slash command 失敗時（例如 `/build` 編譯錯誤），系統會：

1. 顯示錯誤訊息
2. 詢問使用者：「是否需要 AI 協助診斷與修復？」
3. 如果使用者同意，你會收到完整的錯誤 log

**你應該**:
- 仔細分析錯誤訊息
- 找出根本原因（編譯錯誤、缺少檔案、配置問題等）
- 提供具體的修復步驟或直接修改程式碼
- 使用你的 tools（Read, Write, Edit, Bash 等）執行修復

**範例對話**:
```
You> /build
[系統] 使用者執行了指令：/build
執行結果：error
完整 Log：
error: undefined reference to 'init_hardware'
...

使用者是否需要 AI 修復？使用者選擇：是

AI> 我看到編譯錯誤顯示 'init_hardware' 未定義。讓我檢查程式碼...
[AI 使用 Read tool 檢查檔案]
AI> 我發現問題了。你在 main.c 中調用了 init_hardware()，但忘記 include 對應的標頭檔。
[AI 使用 Edit tool 修復]
AI> 已修復！請再次執行 /build 驗證。
```

## 對話歷史注入 (NEW)

當使用者執行 slash command 時，系統會自動將執行結果注入到我們的對話歷史中，格式如下：

```
[系統] 使用者執行了指令：/deploy --binary-args test.hex
參數：{"binary_args": "test.hex"}

執行結果：success

完整 Log：
✅ 韌體部署成功
Deploy log: ...
```

你可以**直接參考這些資訊**來回答後續問題。

## Feature 實作流程 (NEW)

當使用者想要實作新功能時，**不需要**使用 `/feat` 指令（已移除）。

**使用自然語言對話即可**：

**範例**:
```
You> 我想加一個 LED 閃爍的功能
AI> 好的！我可以幫你實作 LED 閃爍功能。請問：
1. LED 連接在哪個 GPIO pin？
2. 閃爍頻率是多少？（例如：1Hz, 500ms on/off）
3. 需要在哪個檔案中實作？

You> GPIO PA5，1Hz，在 main.c
AI> 了解！讓我開始實作...
[AI 使用 Read, Edit, Write tools 實作功能]
AI> 已完成！請執行 /build 編譯驗證。
```

## 工作流程

典型的開發流程：

1. **使用者提出需求**（自然語言或 slash command）
2. **你分析並執行**
   - 如果是程式問題：使用 tools 檢查與修改程式碼
   - 如果是 build/deploy：指導使用 `/build`, `/deploy`
   - 如果需要分析 log：指導使用 `/log`
3. **驗證結果**
   - 使用者執行 `/build` 驗證修改
   - 失敗時協助診斷與修復（參考上述流程）
4. **迭代直到成功**

## 注意事項

- **KISS 原則**：保持方案簡單直接
- **主動使用 tools**：不要只提供建議，直接幫使用者修改程式碼
- **參考對話歷史**：slash command 執行結果會注入對話歷史，你可以直接引用
- **清楚說明修改內容**：告訴使用者你改了什麼、為什麼改
- **鼓勵驗證**：建議使用者執行 `/build` 或 `/deploy` 驗證修改

## 可用 Tools

你可以使用以下 Claude Code SDK tools：

- **Read**: 讀取檔案內容
- **Write**: 寫入新檔案
- **Edit**: 編輯現有檔案
- **Glob**: 搜尋檔案（pattern matching）
- **Grep**: 搜尋檔案內容（正則表達式）
- **Bash**: 執行 shell 命令

## 範例對話

### 案例 1: Build 失敗修復

```
You> /build
[系統] 執行結果：error
完整 Log：error: 'delay_ms' undeclared...

使用者是否需要 AI 修復？使用者選擇：是

AI> 我看到 'delay_ms' 未宣告。讓我檢查是否缺少 include...
[使用 Read 工具檢查 main.c]
AI> 找到問題了！你需要 include "delay.h"。我現在修復...
[使用 Edit 工具修改]
AI> 已修復，請執行 /build 驗證。
```

### 案例 2: 新增功能

```
You> 我想加一個 UART debug 輸出函數
AI> 好的！我來幫你實作。請問：
1. UART 使用哪個 instance？（UART1/UART2/...）
2. Baud rate 是多少？

You> UART1, 115200
AI> 了解！我會在 uart.c 中新增 uart_debug_print() 函數...
[使用 Read 確認現有結構]
[使用 Edit 新增函數]
AI> 完成！已新增 uart_debug_print(const char* msg) 函數。
請執行 /build 編譯驗證。
```

### 案例 3: Deploy 與 Log 分析

```
You> /deploy --scenario quick
[系統] 執行結果：success

You> 裝置啟動後沒有反應，這是 log
You> /log uart_boot.log "為什麼沒有啟動？"
[系統] Log 分析結果：
發現 Stack overflow 在 task_main...

AI> 我看到 log 顯示 stack overflow。讓我檢查 task_main 的 stack 配置...
[使用 Grep 搜尋 task_main]
[使用 Read 檢查 task 定義]
AI> 找到問題了！task_main 的 stack size 設定太小（128 bytes）。
我建議改為 512 bytes。讓我修改...
[使用 Edit 修改]
AI> 已修復！請重新執行 /build 和 /deploy 驗證。
```

---

**記住**：你的目標是讓開發流程盡可能順暢，主動協助使用者解決問題，而不只是提供建議。
```

**測試方式**:

手動測試 AI 行為：

1. 進入 chat mode：`fwauto`
2. 執行 `/build`（假設會失敗）
3. 選擇 AI 修復
4. 觀察 AI 是否：
   - 使用 Read tool 檢查程式碼
   - 提供具體分析
   - 使用 Edit/Write tool 修復
   - 建議重新 build 驗證

**成功標準**:
- [x] Prompt 清楚說明 slash commands 用法
- [x] AI 理解命令執行結果注入機制
- [x] AI 在失敗時主動使用 tools 修復
- [x] AI 遵循 KISS 原則提供解決方案

---

### Phase 7: Integration Testing

**目標**: 建立完整的整合測試，驗證 unified chat interface 端到端流程

**新增測試檔案**:
- `tests/integration/test_unified_chat_e2e.py`

**測試案例**:

```python
"""End-to-end integration tests for unified chat interface."""

import pytest
from pathlib import Path
from unittest.mock import AsyncMock, Mock, patch
from fwauto.nodes.chat import chat_node, _handle_slash_command
from fwauto.slash_commands import parse_slash_command, parse_command_args
from fwauto.state import FirmwareState


@pytest.fixture
def mock_state() -> FirmwareState:
    """Create mock FirmwareState for testing."""
    return {
        "user_prompt": "",
        "project_root": "/test/project",
        "project_initialized": True,
        "platform": "am62x",
        "execution_mode": "chat",
        "command": "chat",
        "command_args": None,
        "mode_metadata": {},
        "command_error": None,
        "build_status": "pending",
        "build_errors": [],
        "build_log": "",
        "deploy_status": "pending",
        "deploy_errors": [],
        "deploy_log": "",
        "iteration": 0,
        "max_retries": 3,
        "log_file_path": None,
        "log_analysis_result": None,
        "firmware_path": None,
    }


class TestSlashCommandParsing:
    """Test slash command parsing with various formats."""

    def test_parse_build_command(self):
        cmd = parse_slash_command("/build")
        assert cmd is not None
        assert cmd.name == "build"
        assert cmd.raw_args == []

    def test_parse_deploy_with_binary_args(self):
        cmd = parse_slash_command("/deploy --binary-args test.hex")
        assert cmd is not None
        assert cmd.name == "deploy"
        assert "--binary-args" in cmd.raw_args
        assert "test.hex" in cmd.raw_args

    def test_parse_deploy_with_scenario(self):
        cmd = parse_slash_command("/deploy --scenario quick")
        assert cmd is not None
        assert cmd.name == "deploy"

        # Parse args
        args = parse_command_args(cmd)
        assert "binary_args" in args

    def test_parse_log_with_prompt(self):
        cmd = parse_slash_command("/log uart.log '有任何 error 嗎?'")
        assert cmd is not None
        assert cmd.name == "log"

        args = parse_command_args(cmd)
        assert args["log_path"] == "uart.log"
        assert args["analysis_prompt"] == "有任何 error 嗎?"

    def test_parse_natural_language_returns_none(self):
        cmd = parse_slash_command("請幫我分析這個錯誤")
        assert cmd is None

    def test_parse_invalid_command_returns_none(self):
        cmd = parse_slash_command("/invalid_cmd")
        assert cmd is None


class TestParameterConversion:
    """Test parameter conversion layer consistency."""

    def test_build_deploy_state_consistency(self):
        from fwauto.cli import _build_deploy_state

        state1 = _build_deploy_state("test.hex", "/project", "am62x")
        state2 = _build_deploy_state("test.hex", "/project", "am62x")

        # Should produce identical states
        assert state1["command_args"] == state2["command_args"]
        assert state1["execution_mode"] == "deploy"
        assert state1["command_args"]["binary_args"] == "test.hex"

    def test_build_log_state_double_storage(self):
        from fwauto.cli import _build_log_state

        state = _build_log_state("uart.log", "分析", "/project", "am62x")

        # Verify double storage
        assert state["log_file_path"] == "uart.log"
        assert state["command_args"]["log_path"] == "uart.log"
        assert state["command_args"]["analysis_prompt"] == "分析"

    def test_deploy_args_priority(self):
        """Test that --binary-args takes priority over --scenario."""
        from fwauto.slash_commands import _parse_deploy_args

        # Both provided: --binary-args wins
        args = _parse_deploy_args(["--binary-args", "direct.hex", "--scenario", "quick"])
        assert args["binary_args"] == "direct.hex"

    def test_deploy_args_scenario_fallback(self):
        """Test scenario fallback when no binary-args."""
        from fwauto.slash_commands import _parse_deploy_args
        from fwauto.scenario_manager import scenario_manager

        # Mock scenario
        mock_scenario = Mock()
        mock_scenario.binary_args = "scenario_file.hex"

        with patch.object(scenario_manager, "get_scenario", return_value=mock_scenario):
            args = _parse_deploy_args(["--scenario", "quick"])
            assert args["binary_args"] == "scenario_file.hex"


class TestCommandExecution:
    """Test slash command execution in chat context."""

    @pytest.mark.asyncio
    async def test_handle_slash_build(self, mock_state):
        """Test /build execution."""
        mock_client = AsyncMock()
        mock_client.query = AsyncMock()

        from fwauto.slash_commands import SlashCommand

        cmd = SlashCommand(name="build", raw_args=[], parsed_args={})

        with patch("fwauto.nodes.chat.build_node") as mock_build:
            mock_build.return_value = {
                **mock_state,
                "build_status": "success",
                "build_log": "Build successful!"
            }

            await _handle_slash_command(cmd, mock_state, mock_client, None, None)

        # Verify build_node was called
        assert mock_build.called

        # Verify result was injected into AI conversation
        assert mock_client.query.called
        call_args = mock_client.query.call_args[0][0]
        assert "[系統]" in call_args
        assert "/build" in call_args

    @pytest.mark.asyncio
    async def test_handle_slash_deploy_with_args(self, mock_state):
        """Test /deploy execution with binary-args."""
        mock_client = AsyncMock()

        from fwauto.slash_commands import SlashCommand

        cmd = SlashCommand(
            name="deploy",
            raw_args=["--binary-args", "test.hex"],
            parsed_args={"binary_args": "test.hex"}
        )

        with patch("fwauto.nodes.chat.deploy_node") as mock_deploy:
            mock_deploy.return_value = {
                **mock_state,
                "deploy_status": "success",
                "deploy_log": "Deploy successful!"
            }

            await _handle_slash_command(cmd, mock_state, mock_client, None, None)

        # Verify deploy_node was called
        assert mock_deploy.called

        # Verify state had correct args
        call_args = mock_deploy.call_args[0][0]
        assert call_args["command_args"]["binary_args"] == "test.hex"


class TestCommandLogger:
    """Test CommandLogger functionality."""

    def test_command_logger_initialization(self, tmp_path, mock_state):
        from fwauto.command_logger import CommandLogger

        logger = CommandLogger(tmp_path, mock_state)

        assert logger.log_file.exists()
        content = logger.log_file.read_text()
        assert "Command Execution Log" in content
        assert str(tmp_path) in content

    def test_command_logger_logs_execution(self, tmp_path, mock_state):
        from fwauto.command_logger import CommandLogger

        logger = CommandLogger(tmp_path, mock_state)
        logger.log_command_execution(
            command="/deploy --binary-args test.hex",
            parameters={"binary_args": "test.hex"},
            status="success",
            output="✅ Deploy successful"
        )

        content = logger.log_file.read_text()
        assert "/deploy --binary-args test.hex" in content
        assert "binary_args: test.hex" in content
        assert "Status: success" in content
        assert "✅ Deploy successful" in content

    def test_command_logger_finalize(self, tmp_path, mock_state):
        from fwauto.command_logger import CommandLogger

        logger = CommandLogger(tmp_path, mock_state)
        logger.command_count = 5
        logger.finalize()

        content = logger.log_file.read_text()
        assert "Total commands: 5" in content
        assert "Session ended:" in content


class TestCLIEntryPoint:
    """Test CLI entry point routing."""

    def test_main_no_args_enters_chat(self, monkeypatch):
        """Test that fwauto with no args enters chat mode."""
        import sys
        from fwauto.cli import main

        monkeypatch.setattr(sys, "argv", ["fwauto"])

        mock_graph = Mock()
        mock_graph.invoke.return_value = {"chat_completed": True}

        mock_config = Mock()
        mock_config.project_root = Path("/test/project")
        mock_config.platform = "am62x"

        with patch("fwauto.cli.create_firmware_graph", return_value=mock_graph):
            with patch("fwauto.cli.load_config", return_value=mock_config):
                main()

        # Verify graph.invoke was called with chat state
        call_args = mock_graph.invoke.call_args[0][0]
        assert call_args["execution_mode"] == "chat"
        assert call_args["command"] == "chat"

    def test_main_with_build_arg_uses_typer(self, monkeypatch):
        """Test that fwauto build uses direct CLI command."""
        import sys
        from fwauto.cli import main

        monkeypatch.setattr(sys, "argv", ["fwauto", "build"])

        with patch("fwauto.cli.app") as mock_app:
            main()

        # Verify Typer app was called (not chat mode)
        assert mock_app.called


class TestEndToEndScenarios:
    """Test complete user scenarios end-to-end."""

    @pytest.mark.asyncio
    async def test_build_fail_ai_repair_flow(self, mock_state):
        """Test complete flow: /build fails → ask repair → AI fixes."""
        mock_client = AsyncMock()

        from fwauto.slash_commands import SlashCommand

        cmd = SlashCommand(name="build", raw_args=[], parsed_args={})

        with patch("fwauto.nodes.chat.build_node") as mock_build:
            mock_build.return_value = {
                **mock_state,
                "build_status": "error",
                "build_log": "error: undefined reference to 'init_hardware'"
            }

            with patch("fwauto.nodes.chat.PromptSession") as mock_session_class:
                mock_session = AsyncMock()
                mock_session.prompt_async.return_value = "y"  # User confirms AI repair
                mock_session_class.return_value = mock_session

                await _handle_slash_command(cmd, mock_state, mock_client, None, None)

        # Verify flow
        assert mock_build.called

        # Verify AI was asked to repair (client.query called twice)
        # 1st: inject error result
        # 2nd: repair prompt
        assert mock_client.query.call_count == 2

        repair_call = mock_client.query.call_args_list[1][0][0]
        assert "失敗" in repair_call or "錯誤" in repair_call

    @pytest.mark.asyncio
    async def test_deploy_with_scenario_success(self, mock_state):
        """Test /deploy --scenario quick succeeds."""
        mock_client = AsyncMock()

        from fwauto.slash_commands import SlashCommand
        from fwauto.scenario_manager import scenario_manager

        # Mock scenario
        mock_scenario = Mock()
        mock_scenario.binary_args = "quick_test.hex"

        with patch.object(scenario_manager, "get_scenario", return_value=mock_scenario):
            cmd = SlashCommand(
                name="deploy",
                raw_args=["--scenario", "quick"],
                parsed_args={}
            )

            # Parse args (will use scenario)
            from fwauto.slash_commands import parse_command_args
            cmd.parsed_args = parse_command_args(cmd)

            with patch("fwauto.nodes.chat.deploy_node") as mock_deploy:
                mock_deploy.return_value = {
                    **mock_state,
                    "deploy_status": "success",
                    "deploy_log": "✅ Deployed successfully"
                }

                await _handle_slash_command(cmd, mock_state, mock_client, None, None)

        # Verify deploy was called with scenario's binary_args
        call_args = mock_deploy.call_args[0][0]
        assert call_args["command_args"]["binary_args"] == "quick_test.hex"
```

**執行整合測試**:

```bash
# Run all integration tests
uv run pytest tests/integration/test_unified_chat_e2e.py -v

# Run specific test class
uv run pytest tests/integration/test_unified_chat_e2e.py::TestSlashCommandParsing -v

# Run with output
uv run pytest tests/integration/test_unified_chat_e2e.py -v -s
```

**手動測試檢查清單**:

```markdown
## Manual Testing Checklist

### Basic Slash Commands
- [ ] `/build` 執行成功
- [ ] `/deploy --binary-args test.hex` 執行成功
- [ ] `/deploy --scenario quick` 使用 scenario 正確
- [ ] `/log uart.log` 分析 log 成功
- [ ] `/log uart.log "有 error 嗎?"` 使用自訂 prompt 成功

### Parameter Parsing
- [ ] `/deploy --binary-args test.hex --scenario quick` 優先使用 binary-args
- [ ] `/deploy -ba test.hex` 短參數正確解析
- [ ] `/log "path with spaces.log"` 引號正確處理
- [ ] `/log uart.log '中文 prompt 測試'` CJK 字元正確處理

### AI Repair Flow
- [ ] Build 失敗後詢問 AI 修復
- [ ] 選擇 "y" 後 AI 開始分析
- [ ] AI 使用 Read tool 檢查程式碼
- [ ] AI 使用 Edit tool 修復錯誤
- [ ] AI 建議重新 build 驗證
- [ ] 選擇 "n" 後略過修復

### Command Logger
- [ ] CommandLogger 正確初始化
- [ ] 每次指令執行都被記錄
- [ ] Log 包含 command、parameters、status、output
- [ ] Session 結束時寫入 summary

### Natural Language Conversation
- [ ] 自然語言輸入不被誤判為 slash command
- [ ] AI 正常回應程式問題
- [ ] AI 可引用之前 slash command 的結果
- [ ] Feature 實作可透過自然語言對話完成

### CLI Entry Point
- [ ] `fwauto` 進入 chat mode
- [ ] `fwauto build` 執行 direct CLI command
- [ ] `fwauto deploy --binary-args test.hex` 執行 direct CLI command
- [ ] Chat mode 顯示專案路徑和平台資訊
- [ ] 輸入 "exit" 正常離開
- [ ] Ctrl+C 正常中斷

### Error Handling
- [ ] 找不到專案時顯示友善錯誤
- [ ] 無效的 slash command 顯示錯誤
- [ ] 缺少必要參數時顯示錯誤
- [ ] Node 執行失敗時正確記錄

### Integration with Existing Features
- [ ] Build node 正常執行
- [ ] Deploy node 正常執行
- [ ] Log node 正常執行
- [ ] Scenario 系統正常運作
- [ ] 所有現有 CLI tests 通過
```

**成功標準**:
- [x] 所有自動化測試通過
- [ ] 所有手動測試檢查項目通過（需要手動測試）
- [ ] 無回歸問題（需要執行現有測試）
- [ ] 效能可接受（需要手動測試）

---

## Success Criteria

### Automated Criteria

- ✅ 所有新增的 unit tests 通過（>95% coverage）
- ✅ 所有現有測試繼續通過（無回歸）
- ✅ Integration tests 通過（end-to-end scenarios）
- ✅ Type checking 通過（`uv run pyright`）
- ✅ Linting 通過（`uv run python -m fwauto.scripts.lint`）

### Manual Criteria

- ✅ `fwauto` 進入統一 chat interface
- ✅ Slash commands (`/build`, `/deploy`, `/log`) 正確執行
- ✅ 參數解析完整（支援 `--binary-args`, `--scenario`, 引號等）
- ✅ 命令失敗時詢問 AI 修復
- ✅ AI 正確理解並使用 tools 修復錯誤
- ✅ 執行結果正確注入 AI 對話歷史
- ✅ CommandLogger 正確記錄指令執行
- ✅ 自然語言對話正常（不被誤判為 slash command）
- ✅ 現有 CLI 命令（`fwauto build`, `fwauto deploy` 等）保持運作
- ✅ 效能可接受（slash command 回應 < 5s，AI 修復 < 30s）

## Rollout Plan

### Phase 1-3 (Low Risk)
**可並行開發，不影響現有功能**

- Phase 1: Slash command parser（純新增）
- Phase 2: Parameter conversion layer（重構，但保持 API 一致）
- Phase 3: CommandLogger（純新增）

**Rollout Strategy**:
- 在 feature branch 開發
- 每個 phase 完成後 merge 到 main
- 不影響使用者體驗（尚未啟用）

### Phase 4 (Medium Risk)
**修改 chat_node，需謹慎測試**

- Phase 4: Chat node enhancement

**Rollout Strategy**:
- 在 feature branch 完整測試
- 使用 feature flag 控制啟用（環境變數 `FWAUTO_UNIFIED_CHAT`）
- 初期僅在開發環境啟用
- 經過充分測試後移除 feature flag

### Phase 5 (High Risk)
**修改 CLI entry point，影響所有使用者**

- Phase 5: CLI entry point refactor

**Rollout Strategy**:
- 保留舊 `_interactive_mode()` 作為 fallback（用 feature flag 控制）
- 分階段啟用：
  1. 開發者先行測試（1 週）
  2. 內部使用者測試（1 週）
  3. 正式發布給所有使用者
- 準備 rollback plan（快速恢復舊版本）

### Phase 6-7 (Low Risk)
**文件更新與測試，不影響功能**

- Phase 6: System prompt updates
- Phase 7: Integration testing

**Rollout Strategy**:
- 與 Phase 5 同時進行
- 確保文件與實作一致

## Risk Mitigation

### Risk 1: Event Loop Conflicts
**問題**: Synchronous nodes 從 async context 調用可能導致 event loop 問題

**緩解措施**:
- Chat node 已使用 threading 隔離 event loop
- 在 `_handle_slash_command()` 中直接調用 sync nodes（不使用 `await`）
- 充分測試 threading 相容性

### Risk 2: Parameter Parsing Errors
**問題**: 複雜參數解析可能導致錯誤

**緩解措施**:
- 使用成熟的 `shlex.split()` 處理引號
- 完整的 unit tests 涵蓋所有參數組合
- 清楚的錯誤訊息指導使用者

### Risk 3: AI Misunderstanding Slash Commands
**問題**: AI 可能誤解 system message 中的命令結果

**緩解措施**:
- System prompt 清楚說明注入格式
- 使用明確的標記（`[系統]`）區分系統訊息
- 充分的 AI 行為測試

### Risk 4: Backward Compatibility
**問題**: 現有使用者習慣舊的 CLI 行為

**緩解措施**:
- 保留所有獨立 CLI 命令（`fwauto build`, etc.）
- Feature flag 允許切換回舊版本
- 詳細的遷移文件與教學

### Risk 5: Performance Degradation
**問題**: Chat mode 可能導致效能下降

**緩解措施**:
- 獨立 CLI 命令保持原有效能（不經過 chat）
- CommandLogger 使用非同步寫入（避免阻塞）
- 效能測試驗證回應時間

## Dependencies

### External Dependencies (Already Satisfied)
- `claude-agent-sdk`: ClaudeSDKClient for AI conversation
- `prompt_toolkit`: CJK-aware input handling
- `rich`: Console formatting
- `shlex`: Command-line parsing

### Internal Dependencies
- `src/fwauto/nodes/*.py`: Build, Deploy, Log nodes
- `src/fwauto/cli.py`: CLI entry point and Typer commands
- `src/fwauto/state.py`: FirmwareState definition
- `src/fwauto/chat_logger.py`: ChatLogger class
- `src/fwauto/scenario_manager.py`: Scenario support

## Future Enhancements

### Out of Scope (But Worth Considering)

1. **Slash Command Autocomplete**
   - 使用 `prompt_toolkit.completion` 實作 tab completion
   - 提示可用參數和 scenarios

2. **Command History Search**
   - 類似 shell 的 Ctrl+R 搜尋歷史指令
   - 儲存在 `.fwauto/history`

3. **Multi-line Input Support**
   - 支援多行 prompts（例如貼上 error logs）
   - 使用 Alt+Enter 或 ``` 標記

4. **Slash Command Aliases**
   - 允許使用者自訂 alias（例如 `/b` → `/build`）
   - 配置於 `.fwauto/config.toml`

5. **Batch Command Execution**
   - 支援一次執行多個指令（例如 `/build && /deploy`）
   - 類似 shell 的 `&&` 和 `;` 語法

6. **Interactive Parameter Prompts**
   - 缺少參數時互動式詢問（例如 `/deploy` → 詢問 binary-args）
   - 類似 `inquirer` 的 UX

## Timeline Estimate

基於 KISS 原則，保守估計各 phase 所需時間：

| Phase | Description | Estimated Time |
|-------|-------------|----------------|
| Phase 1 | Command Parsing Infrastructure | 1-2 days |
| Phase 2 | Parameter Conversion Layer | 1 day |
| Phase 3 | Command Execution Logger | 1 day |
| Phase 4 | Chat Node Enhancement | 2-3 days |
| Phase 5 | CLI Entry Point Refactor | 1 day |
| Phase 6 | System Prompt Updates | 0.5 day |
| Phase 7 | Integration Testing | 2-3 days |
| **Total** | | **8-11 days** |

**注意**: 時間估計假設：
- 單人開發
- 無重大阻礙或架構變更
- 測試覆蓋率 >90%
- 包含 code review 和文件撰寫

**建議排程**:
- Week 1: Phase 1-3（基礎設施）
- Week 2: Phase 4-5（核心功能）
- Week 3: Phase 6-7（測試與文件）+ Buffer

## Documentation Updates

實作完成後需更新以下文件：

### User-Facing Documentation

1. **README.md**
   - 更新 "Quick Start" section 說明統一 chat interface
   - 新增 "Slash Commands" section
   - 更新使用範例（從 `fwauto build` → `fwauto` + `/build`）

2. **docs/user-guide.md**（新增）
   - 詳細 slash commands 說明
   - 參數格式與範例
   - AI 修復流程說明
   - 常見問題 FAQ

3. **docs/cli-reference.md**
   - 新增 slash commands 參考表
   - 參數優先級說明
   - Scenario 用法

### Developer Documentation

1. **CLAUDE.md**
   - 更新架構說明（新增 slash_commands.py, command_logger.py）
   - 說明 unified chat interface 設計決策
   - 更新程式碼慣例

2. **docs/architecture.md**（新增）
   - 詳細架構圖（使用 Mermaid）
   - 資料流程說明
   - 各模組職責

3. **CHANGELOG.md**
   - 記錄新功能：Unified Chat Interface
   - Breaking changes（如有）
   - Migration guide

## Appendix: Architectural Diagrams

### System Architecture

```mermaid
graph TB
    subgraph "CLI Entry Point"
        Main["main()<br/>(cli.py)"]
    end

    subgraph "Execution Modes"
        ChatMode["Chat Mode<br/>(chat_node)"]
        DirectCmd["Direct CLI Commands<br/>(Typer)"]
    end

    subgraph "Chat Components"
        SlashParser["Slash Command Parser<br/>(slash_commands.py)"]
        ParamConvert["Parameter Conversion<br/>(_build_*_state)"]
        AIClient["ClaudeSDKClient<br/>(conversation)"]
    end

    subgraph "Node Execution"
        BuildNode["build_node"]
        DeployNode["deploy_node"]
        LogNode["log_node"]
    end

    subgraph "Logging"
        ChatLogger["ChatLogger<br/>(conversation log)"]
        CommandLogger["CommandLogger<br/>(command execution log)"]
    end

    Main -->|"No args"| ChatMode
    Main -->|"With args"| DirectCmd

    ChatMode --> SlashParser
    ChatMode --> AIClient

    SlashParser --> ParamConvert
    ParamConvert --> BuildNode
    ParamConvert --> DeployNode
    ParamConvert --> LogNode

    BuildNode --> CommandLogger
    DeployNode --> CommandLogger
    LogNode --> CommandLogger

    BuildNode -.->|"Result injection"| AIClient
    DeployNode -.->|"Result injection"| AIClient
    LogNode -.->|"Result injection"| AIClient

    AIClient --> ChatLogger

    DirectCmd --> BuildNode
    DirectCmd --> DeployNode
    DirectCmd --> LogNode
```

### Data Flow: Slash Command Execution

```mermaid
sequenceDiagram
    actor User
    participant ChatLoop as Chat Loop
    participant Parser as Slash Parser
    participant Converter as Param Converter
    participant Node as Node (sync)
    participant AI as ClaudeSDKClient
    participant CmdLog as CommandLogger

    User->>ChatLoop: "/deploy --binary-args test.hex"

    ChatLoop->>Parser: parse_slash_command()
    Parser-->>ChatLoop: SlashCommand(name="deploy", ...)

    ChatLoop->>Parser: parse_command_args()
    Parser-->>ChatLoop: {"binary_args": "test.hex"}

    ChatLoop->>Converter: _build_deploy_state()
    Converter-->>ChatLoop: FirmwareState

    ChatLoop->>Node: deploy_node(state)
    Note over Node: Synchronous execution
    Node-->>ChatLoop: result dict

    ChatLoop->>CmdLog: log_command_execution()

    ChatLoop->>AI: query("[系統] 使用者執行了...")
    Note over AI: Inject result as system message

    alt Deploy Failed
        ChatLoop->>User: "是否需要 AI 修復？"
        User-->>ChatLoop: "y"
        ChatLoop->>AI: query("請幫我診斷...")
        AI-->>ChatLoop: AI analysis & repair
    end

    ChatLoop->>User: "✅ 指令執行完成"
```

### State Management

```mermaid
graph LR
    subgraph "FirmwareState Fields"
        ProjectFields["project_root<br/>platform<br/>project_initialized"]
        ModeFields["execution_mode<br/>command<br/>command_args"]
        StatusFields["build_status<br/>deploy_status<br/>build_errors"]
        LogFields["log_file_path<br/>log_analysis_result"]
    end

    subgraph "State Builders"
        CLICommands["CLI Commands<br/>(Typer)"]
        SlashCommands["Slash Commands<br/>(Chat)"]
    end

    subgraph "Shared Helpers"
        BuildDeployState["_build_deploy_state()"]
        BuildLogState["_build_log_state()"]
    end

    CLICommands --> BuildDeployState
    CLICommands --> BuildLogState

    SlashCommands --> BuildDeployState
    SlashCommands --> BuildLogState

    BuildDeployState --> ModeFields
    BuildLogState --> ModeFields

    ModeFields --> StatusFields
    StatusFields --> LogFields
```

---

## Conclusion

本計劃遵循 KISS 原則，最小化架構變更，重用現有元件，逐步實作統一 Chat 介面。所有 phases 都有明確的測試標準與成功指標，確保實作品質。

**核心設計決策總結**：
1. ✅ 使用 slash commands 明確區分指令與對話
2. ✅ 保留獨立 CLI 命令為非互動式快捷方式
3. ✅ 提取共用參數處理邏輯（parameter conversion layer）
4. ✅ 獨立的 CommandLogger 記錄指令執行（不修改 ChatLogger）
5. ✅ 失敗時詢問 AI 修復（不自動修復）
6. ✅ 完整 log 注入 AI 對話歷史（作為 system message）
7. ✅ 使用自然語言對話實作 feature（無 `/feat` slash command）

**預期效益**：
- 統一使用者體驗（單一入口點）
- 提升開發效率（AI 輔助修復）
- 降低學習成本（自然語言 + 直覺的 slash commands）
- 保持系統簡潔（遵循 KISS 原則）

準備好開始實作了嗎？🚀
