diff --git a/CLAUDE.md b/CLAUDE.md index 45993146..9c58d0ae 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -204,7 +204,12 @@ tests/ | `src/lib/job-executor.ts` | ジョブ実行エンジン・実行ログCRUD(Issue #479) | | `src/lib/cmate-parser.ts` | CMATE.md汎用パーサー、parseAndValidateCliToolColumn連携(Issue #588) | | `src/lib/cmate-cli-tool-parser.ts` | CLI Tool列パース・model名バリデーション共有モジュール(parseCliToolColumn, validateCopilotModelName, TOOLS_WITH_MODEL_SUPPORT)(Issue #588) | -| `src/lib/session-cleanup.ts` | セッション/ポーラー/スケジューラー停止(Facade)、killWorktreeSession共通化、syncWorktreesAndCleanup(Issue #526) | +| `src/lib/session-cleanup.ts` | セッション/ポーラー/スケジューラー停止(Facade)、killWorktreeSession共通化、syncWorktreesAndCleanup(Issue #526)、cleanupGlobalSessions追加(Issue #649) | +| `src/lib/session/global-session-constants.ts` | グローバルセッション定数(GLOBAL_SESSION_WORKTREE_ID='\_\_global\_\_', GLOBAL_POLL_INTERVAL_MS等)(Issue #649) | +| `src/lib/polling/global-session-poller.ts` | グローバルセッション専用ポーリング(pollGlobalSession, stopGlobalSessionPolling, stopAllGlobalSessionPolling, isGlobalPollerActive)(Issue #649) | +| `src/lib/assistant/context-builder.ts` | グローバルセッション用デフォルトコンテキスト生成(buildGlobalContext, getEnabledRepositories)(Issue #649) | +| `src/lib/api/assistant-api.ts` | アシスタントAPIクライアント(startSession, sendCommand, getCurrentOutput, stopSession, getInstalledTools)(Issue #649) | +| `src/types/assistant.ts` | アシスタント機能型定義(StartAssistantRequest, StartAssistantResponse, AssistantCurrentOutputResponse等)(Issue #649) | | `src/lib/session-key-sender.ts` | Claudeセッションキー送信ロジック(Issue #479) | | `src/lib/prompt-answer-input.ts` | プロンプト応答入力ロジック(getAnswerInput)(Issue #479) | | `src/lib/resource-cleanup.ts` | リソースリーク対策(孤立プロセス/Map検出) | @@ -277,6 +282,11 @@ tests/ | `src/app/api/worktrees/[id]/git/diff/route.ts` | Gitファイルdiff取得API(Issue #447) | | `src/app/api/worktrees/[id]/special-keys/route.ts` | 特殊キー送信API(Up/Down/Left/Right/Enter/Escape、6層防御)(Issue #473, #592) | | `src/app/api/templates/route.ts` | レポートテンプレートAPI(GET全件取得/POST作成、5件上限・バリデーション)(Issue #618) | +| `src/app/api/assistant/start/route.ts` | グローバルセッション開始API(POST、DB操作なし、cliToolId検証・ディレクトリバリデーション・コンテキスト送信)(Issue #649) | +| `src/app/api/assistant/terminal/route.ts` | グローバルセッションメッセージ送信API(POST、sendKeys使用)(Issue #649) | +| `src/app/api/assistant/current-output/route.ts` | グローバルセッション出力取得API(GET、capturePane使用)(Issue #649) | +| `src/app/api/assistant/session/route.ts` | グローバルセッション停止API(DELETE、stopGlobalSessionPolling+killSession)(Issue #649) | +| `src/app/api/assistant/tools/route.ts` | インストール済みCLIツール一覧API(GET、CLIToolManager.getAllToolsInfo()使用)(Issue #649) | | `src/app/api/templates/[id]/route.ts` | レポートテンプレート個別API(PUT更新/DELETE削除、UUID検証)(Issue #618) | | `src/components/worktree/NavigationButtons.tsx` | OpenCode TUI選択リストナビゲーションボタン、Left/Right対応(Issue #473, #592) | | `src/cli/utils/api-client.ts` | CLI用HTTPクライアント(認証トークン解決・エラー分類・ApiClient/ApiError)(Issue #518) | @@ -297,6 +307,8 @@ tests/ | `src/hooks/useWorktreeTabState.ts` | Worktreeタブ状態管理フック(deep link対応)(Issue #600) | | `src/components/mobile/GlobalMobileNav.tsx` | モバイルグローバルナビ(4タブ)(Issue #600) | | `src/components/home/HomeSessionSummary.tsx` | Home画面セッション集計サマリー(Issue #600) | +| `src/components/home/AssistantChatPanel.tsx` | Home画面アシスタントチャットパネル(折りたたみ可能・最大50vh・ポーリング・セッション開始/停止)(Issue #649) | +| `src/components/home/AssistantMessageInput.tsx` | アシスタント専用メッセージ入力(送信のみ・スラッシュコマンド/画像添付なし)(Issue #649) | | `src/components/review/ReviewCard.tsx` | Reviewカード(Issue #600) | | `src/components/review/SimpleMessageInput.tsx` | 軽量メッセージ入力(Review画面用)(Issue #600) | | `src/components/review/TemplateTab.tsx` | テンプレート管理UI(一覧・作成・編集・削除、最大5件制限)(Issue #618) | diff --git a/dev-reports/issue/649/pm-auto-dev/iteration-1/progress-context.json b/dev-reports/issue/649/pm-auto-dev/iteration-1/progress-context.json new file mode 100644 index 00000000..5dade92c --- /dev/null +++ b/dev-reports/issue/649/pm-auto-dev/iteration-1/progress-context.json @@ -0,0 +1,53 @@ +{ + "issue_number": 649, + "iteration": 1, + "phase_results": { + "tdd": { + "status": "success", + "implemented_files": [ + "src/lib/session/global-session-constants.ts", + "src/types/assistant.ts", + "src/lib/polling/global-session-poller.ts", + "src/lib/assistant/context-builder.ts", + "src/lib/session-cleanup.ts (変更)", + "src/lib/session/worktree-status-helper.ts (変更)", + "src/app/api/assistant/start/route.ts", + "src/app/api/assistant/terminal/route.ts", + "src/app/api/assistant/current-output/route.ts", + "src/app/api/assistant/session/route.ts", + "src/lib/api/assistant-api.ts", + "src/components/home/AssistantMessageInput.tsx", + "src/components/home/AssistantChatPanel.tsx", + "src/app/page.tsx (変更)", + "tests/unit/global-session-poller.test.ts", + "tests/unit/session-cleanup-global.test.ts", + "tests/unit/assistant-context-builder.test.ts" + ], + "test_results": { + "total": 6319, + "passed": 6319, + "failed": 0, + "skipped": 7 + }, + "lint_errors": 0, + "type_errors": 0 + }, + "acceptance": { + "status": "passed", + "criteria_passed": 17, + "criteria_failed": 0 + }, + "refactor": { + "status": "success", + "changes": [ + "GET /api/assistant/tools エンドポイント追加", + "assistantApi に getInstalledTools() 追加", + "AssistantChatPanel のCLIツール選択UIを動的インストール状態フィルタリングに改善" + ] + }, + "documentation": { + "status": "success", + "updated_files": ["CLAUDE.md"] + } + } +} diff --git a/dev-reports/issue/649/pm-auto-dev/iteration-1/progress-report.md b/dev-reports/issue/649/pm-auto-dev/iteration-1/progress-report.md new file mode 100644 index 00000000..c230e04c --- /dev/null +++ b/dev-reports/issue/649/pm-auto-dev/iteration-1/progress-report.md @@ -0,0 +1,187 @@ +# 進捗レポート: Issue #649 - アシスタントチャット機能 + +## 1. 概要 + +| 項目 | 内容 | +|------|------| +| **Issue番号** | #649 | +| **タイトル** | アシスタントチャット機能(Home画面) | +| **イテレーション** | 1 | +| **ブランチ** | `feature/649-worktree` | +| **ステータス** | SUCCESS(全フェーズ完了) | +| **レポート日時** | 2026-04-14 | + +### 概要 + +Home画面に worktree 非依存のグローバル CLI セッション(アシスタントチャット)を追加する機能を実装。CLI ツール(Claude / Codex / Copilot 等)を登録済みリポジトリ情報と共に起動し、Home 画面のチャット UI から対話できるようにする。セッションは `__global__` 仮想 worktree ID で管理し、既存 worktree セッションや DB とは独立。 + +--- + +## 2. フェーズ別結果 + +### 2-1. TDDフェーズ + +| 項目 | 結果 | +|------|------| +| ステータス | SUCCESS | +| 実装ファイル数 | 17件 | +| 新規テストファイル数 | 3件 | +| 新規テスト数 | 26件(全件パス) | +| ESLintエラー | 0件 | +| TypeScriptエラー | 0件 | +| Unit Test | 334ファイル / 6319件パス / 0件失敗 / 7スキップ | +| コミット | `38ec3c95: feat(assistant): implement assistant chat feature for Home page` | + +#### 主要実装ファイル + +**バックエンド** +- `src/lib/session/global-session-constants.ts` - グローバルセッション定数(`__global__` ID・ポーリング間隔) +- `src/types/assistant.ts` - アシスタントチャット関連型定義 +- `src/lib/polling/global-session-poller.ts` - グローバルセッション専用ポーリング(`pollGlobalSession`) +- `src/lib/assistant/context-builder.ts` - CLI起動時コンテキスト生成(`buildGlobalContext`) +- `src/lib/session-cleanup.ts` - `cleanupGlobalSessions()` 追加、`syncWorktreesAndCleanup()` から呼び出し +- `src/lib/session/worktree-status-helper.ts` - `__global__` ID早期リターン(サイドバー除外) +- `src/app/api/assistant/start/route.ts` - POST セッション開始 +- `src/app/api/assistant/terminal/route.ts` - POST メッセージ送信 +- `src/app/api/assistant/current-output/route.ts` - GET ターミナル出力取得 +- `src/app/api/assistant/session/route.ts` - DELETE セッション停止 +- `src/lib/api/assistant-api.ts` - 型安全なクライアントAPI + +**フロントエンド** +- `src/components/home/AssistantMessageInput.tsx` - 送信専用入力(IMEガード) +- `src/components/home/AssistantChatPanel.tsx` - 折りたたみ可能パネル・リポジトリ/ツール選択・ポーリング・セッション制御 +- `src/app/page.tsx` - Home画面統合 + +### 2-2. 受入テストフェーズ + +| 項目 | 結果 | +|------|------| +| ステータス | PASSED | +| 受入条件合格数 | 17 / 17件(100%) | +| ESLint | passed | +| Type Check | passed | +| Unit Tests | passed(334ファイル / 6319件) | + +#### 受入条件別サマリー(全17件PASSED) + +1. Home画面でアシスタントチャットが利用できる +2. 登録済みリポジトリから作業ディレクトリを選択できる +3. インストール済みの全CLIツールから選択してセッションを開始できる +4. セッション開始時にCLI使い方・リポジトリ情報がコンテキストとして付与される +5. メッセージの送受信が正常に動作する +6. アシスタントのターミナル出力がリアルタイムに表示される +7. キャプチャ間隔が既存capture APIと同等(2000ms) +8. セッション停止ボタンからグローバルセッションを停止できる +9. セッション実行中のリポジトリ変更時に確認ダイアログ表示 +10. サーバー再起動時に孤立した `mcbd-{cli}-__global__` セッションがクリーンアップされる +11. グローバルセッションがサイドバーworktree一覧に表示されない +12. チャットUIの折りたたみ/展開(localStorage永続化) +13. 既存worktreeセッション機能への影響なし(回帰テストパス) +14. ダークモード対応(dark:クラス適用箇所多数) +15. モバイルレイアウト対応(flex-wrap / truncate / maxHeight:50vh) +16. `/api/assistant/*` が認証ミドルウェアで保護されている +17. npm run lint / tsc --noEmit / test:unit 全パス + +#### 指摘事項(low) + +- CLIツール選択UIが全ツール表示のままインストール済みフィルタリングされていない(サーバー側で `isInstalled()` チェックはあるため機能的には問題なし)→ **リファクタリングフェーズで対応** + +### 2-3. リファクタリングフェーズ + +| 項目 | 結果 | +|------|------| +| ステータス | SUCCESS | +| ESLint/Type | 0エラー | +| Unit Test | 6319件パス維持 | + +#### 主な改善 + +1. **GET /api/assistant/tools エンドポイント追加** + - `src/app/api/assistant/tools/route.ts` 新規追加 + - `CLIToolManager.getAllToolsInfo()` で各ツールのインストール状態を取得 +2. **assistantApi.getInstalledTools() 追加** + - `AssistantToolInfo` 型追加 +3. **AssistantChatPanel UX改善** + - `availableTools` stateでマウント時にインストール状態取得 + - 未インストールツールを `(not installed)` 表示で disabled 化 + - `CLI_TOOL_IDS` ハードコードから動的取得に置換 + +### 2-4. ドキュメント更新フェーズ + +- `CLAUDE.md` の更新(モジュール一覧へのアシスタントチャット関連エントリ追加) + +--- + +## 3. 総合品質メトリクス + +| メトリクス | 値 | 判定 | +|-----------|-----|------| +| ESLintエラー | 0件 | OK | +| TypeScriptエラー | 0件 | OK | +| Unit Test パス率 | 6319 / 6319 (100%) | OK | +| Unit Test ファイル数 | 334ファイル | - | +| 新規テスト合格数 | 26 / 26 (100%) | OK | +| 受入条件合格率 | 17 / 17 (100%) | OK | +| テストカバレッジ(TDD結果) | 80% | OK | +| 実装ファイル数 | 17件(新規14 + 変更3) | - | +| コミット数 | 2件(実装1 + dev-reports 1) | - | + +### 品質特記事項 + +- **回帰なし**: 既存の334テストファイルが全件パス(ファイル基盤・tmux基盤・CLIツール基盤に回帰なし) +- **セキュリティ**: `/api/assistant/*` は認証ミドルウェアで保護(auth-config除外パス外) +- **リソースリーク対策**: `syncWorktreesAndCleanup()` 経由で孤立グローバルセッション自動クリーンアップ +- **サイドバー非影響**: `__global__` ID早期リターンでworktree一覧から除外 +- **アクセシビリティ**: ダークモード・モバイルレイアウト対応済み + +--- + +## 4. ブロッカー + +**現時点でブロッカーなし** + +受入テストで指摘された low severity 事項(CLIツールUIフィルタリング)はリファクタリングフェーズで解消済み。設計通りの実装が完了しており、機能的・品質的・セキュリティ的な阻害要因は存在しない。 + +--- + +## 5. 次のステップ + +### 推奨アクション(優先度順) + +1. **[推奨] PR作成** + - `feature/649-worktree` から `develop` へのPR作成 + - `/create-pr` コマンドの利用を推奨 + - PRタイトル案: `feat(assistant): add home-page assistant chat with global CLI session` + - 付与ラベル: `feature` + +2. **[任意] 統合テスト/E2Eテストの追加検討** + - 現状はunit testとacceptance testのみ + - 複数CLIツールでの起動・停止シナリオ、セッション孤立リカバリシナリオをE2Eで検証すると堅牢性が増す + +3. **[任意] ドキュメント追記** + - `docs/implementation-history.md` にIssue #649エントリ追加 + - `docs/user-guide/` にアシスタントチャット使用ガイド追加(ユーザー向け説明) + +4. **[任意] UAT(実機受入テスト)の実行** + - `/uat` コマンドで、実際のClaude/Codex/Copilotセッション起動・操作を確認 + - モバイル実機での折りたたみ・リポジトリ変更ダイアログの挙動確認 + +### 次イテレーションへの持ち越し + +**なし**(全受入条件PASSED・全品質ゲート通過のため、本Issueの実装タスクは完了) + +--- + +## 6. 参考情報 + +### 関連ファイル + +- コンテキスト: `dev-reports/issue/649/pm-auto-dev/iteration-1/progress-context.json` +- TDD結果: `dev-reports/issue/649/pm-auto-dev/iteration-1/tdd-result.json` +- 受入結果: `dev-reports/issue/649/pm-auto-dev/iteration-1/acceptance-result.json` +- リファクタ結果: `dev-reports/issue/649/pm-auto-dev/iteration-1/refactor-result.json` + +### 関連コミット + +- `38ec3c95` - feat(assistant): implement assistant chat feature for Home page +- `db97cbd8` - chore(issue-649): add dev-reports (issue-review, work-plan, tdd, acceptance) diff --git a/dev-reports/issue/649/pm-auto-dev/iteration-1/refactor-result.json b/dev-reports/issue/649/pm-auto-dev/iteration-1/refactor-result.json new file mode 100644 index 00000000..36d718cc --- /dev/null +++ b/dev-reports/issue/649/pm-auto-dev/iteration-1/refactor-result.json @@ -0,0 +1,26 @@ +{ + "status": "success", + "changes": [ + { + "file": "src/app/api/assistant/tools/route.ts", + "description": "新規追加: GET /api/assistant/tools エンドポイント。CLIToolManager.getAllToolsInfo() でインストール済みツール一覧を返す" + }, + { + "file": "src/lib/api/assistant-api.ts", + "description": "AssistantToolInfo 型を追加。getInstalledTools() メソッドを assistantApi に追加" + }, + { + "file": "src/components/home/AssistantChatPanel.tsx", + "description": "availableTools state 追加。fetchTools() でインストール済みツールを取得。CLIツール選択UIを未インストールツールをdisabled表示に改善。CLI_TOOL_IDS ハードコードを動的取得に置換" + } + ], + "quality_metrics": { + "lint_errors": 0, + "type_errors": 0, + "test_files": 334, + "test_passed": 6319, + "test_failed": 0, + "test_skipped": 7 + }, + "summary": "受入テストで指摘されたCLIツール選択UIの改善を実施。GET /api/assistant/tools エンドポイントを新規追加し、AssistantChatPanel がマウント時にインストール状態を取得してドロップダウンに反映するよう改善。未インストールのツールは '(not installed)' 表示でdisabled化。TypeScript/ESLint/ユニットテスト全パス。" +} diff --git a/src/app/api/assistant/tools/route.ts b/src/app/api/assistant/tools/route.ts new file mode 100644 index 00000000..9e9eda43 --- /dev/null +++ b/src/app/api/assistant/tools/route.ts @@ -0,0 +1,20 @@ +/** + * GET /api/assistant/tools + * Issue #649: Returns list of installed CLI tools for assistant chat. + */ + +import { NextResponse } from 'next/server'; +import { CLIToolManager } from '@/lib/cli-tools/manager'; + +export async function GET(): Promise { + const manager = CLIToolManager.getInstance(); + const toolsInfo = await manager.getAllToolsInfo(); + + return NextResponse.json({ + tools: toolsInfo.map((t) => ({ + id: t.id, + name: t.name, + installed: t.installed, + })), + }); +} diff --git a/src/components/home/AssistantChatPanel.tsx b/src/components/home/AssistantChatPanel.tsx index c7ecfe9f..833f6424 100644 --- a/src/components/home/AssistantChatPanel.tsx +++ b/src/components/home/AssistantChatPanel.tsx @@ -17,9 +17,10 @@ import React, { useState, useEffect, useCallback, useRef } from 'react'; import { AssistantMessageInput } from './AssistantMessageInput'; import { assistantApi } from '@/lib/api/assistant-api'; +import type { AssistantToolInfo } from '@/lib/api/assistant-api'; import { GLOBAL_POLL_INTERVAL_MS } from '@/lib/session/global-session-constants'; import type { CLIToolType } from '@/lib/cli-tools/types'; -import { CLI_TOOL_IDS, getCliToolDisplayName } from '@/lib/cli-tools/types'; +import { CLI_TOOL_IDS } from '@/lib/cli-tools/types'; /** localStorage key for panel collapsed state */ const COLLAPSED_KEY = 'commandmate-assistant-collapsed'; @@ -36,6 +37,7 @@ interface RepositoryOption { export function AssistantChatPanel() { const [collapsed, setCollapsed] = useState(true); const [repositories, setRepositories] = useState([]); + const [availableTools, setAvailableTools] = useState([]); const [selectedRepo, setSelectedRepo] = useState(''); const [selectedTool, setSelectedTool] = useState('claude'); const [sessionActive, setSessionActive] = useState(false); @@ -60,7 +62,7 @@ export function AssistantChatPanel() { } }, []); - // Fetch repositories + // Fetch repositories and installed tools useEffect(() => { async function fetchRepos() { try { @@ -83,7 +85,24 @@ export function AssistantChatPanel() { // Silently handle fetch errors } } - fetchRepos(); + + async function fetchTools() { + const tools = await assistantApi.getInstalledTools(); + if (tools.length > 0) { + setAvailableTools(tools); + // Update selectedTool to first installed tool if current is not installed + const installedTool = tools.find((t) => t.installed); + if (installedTool) { + setSelectedTool((prev) => { + const prevInstalled = tools.find((t) => t.id === prev)?.installed; + return prevInstalled ? prev : installedTool.id; + }); + } + } + } + + void fetchRepos(); + void fetchTools(); }, []); // eslint-disable-line react-hooks/exhaustive-deps // Polling for output @@ -267,9 +286,9 @@ export function AssistantChatPanel() { className="text-sm border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 rounded px-2 py-1" data-testid="assistant-tool-select" > - {CLI_TOOL_IDS.map((toolId) => ( - ))} diff --git a/src/lib/api/assistant-api.ts b/src/lib/api/assistant-api.ts index 8b16a053..62ae0ed6 100644 --- a/src/lib/api/assistant-api.ts +++ b/src/lib/api/assistant-api.ts @@ -11,6 +11,12 @@ import type { AssistantCurrentOutputResponse, } from '@/types/assistant'; +export interface AssistantToolInfo { + id: CLIToolType; + name: string; + installed: boolean; +} + /** * Assistant API client object. * All methods throw on network errors; callers should handle errors appropriately. @@ -105,4 +111,18 @@ export const assistantApi = { return res.json(); }, + + /** + * Get list of installed CLI tools. + * + * @returns Array of tool info with installation status + */ + async getInstalledTools(): Promise { + const res = await fetch('/api/assistant/tools'); + if (!res.ok) { + return []; + } + const data = await res.json(); + return (data.tools ?? []) as AssistantToolInfo[]; + }, };