diff --git a/.vscodeignore b/.vscodeignore index 7222f74..868e9f1 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -38,6 +38,7 @@ docs/ *.docx PUBLIC_REPO_PREP.md AGENTS.md +portfolio/ # 公開・CI運用ファイル(拡張機能実行には不要) .github/ diff --git a/README.md b/README.md index e00b708..833e5c3 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,16 @@ Task checkbox/title edits in `status.md` are also parsed and synced back to `ari --- +## Core Build Docs + +Public portfolio docs for core feature construction are available in: + +- `portfolio/ai_shared_core/` + +This slice excludes legal/FTO and medium-or-higher risk records. + +--- + ## Extension Commands | Command | Description | diff --git a/portfolio/ai_shared_core/README.md b/portfolio/ai_shared_core/README.md new file mode 100644 index 0000000..eebcfae --- /dev/null +++ b/portfolio/ai_shared_core/README.md @@ -0,0 +1,28 @@ +# ai_shared_core (Public Portfolio Slice) + +This folder is a public-safe slice of `ai_shared` focused on core feature construction. + +Policy: +- Include only core implementation/spec/test evidence documents. +- Exclude governance/compliance and non-core risk-track records. +- Generated from `scripts/ai-shared-core-allowlist.txt`. + +Source Mapping: +- `conventions/coding_standards.md` <= `ai_shared/conventions/coding_standards.md` +- `specs/M0_environment_setup.md` <= `ai_shared/specs/M0_environment_setup.md` +- `specs/M1_extension_host.md` <= `ai_shared/specs/M1_extension_host.md` +- `specs/M2_webview_reactflow.md` <= `ai_shared/specs/M2_webview_reactflow.md` +- `specs/M3_zustand_store.md` <= `ai_shared/specs/M3_zustand_store.md` +- `specs/M4_file_output.md` <= `ai_shared/specs/M4_file_output.md` +- `specs/M5_reverse_sync.md` <= `ai_shared/specs/M5_reverse_sync.md` +- `specs/M7_marketplace.md` <= `ai_shared/specs/M7_marketplace.md` +- `specs/M9_c4_drilldown.md` <= `ai_shared/specs/M9_c4_drilldown.md` +- `specs/M10_adr_container.md` <= `ai_shared/specs/M10_adr_container.md` +- `specs/M11_semantic_network.md` <= `ai_shared/specs/M11_semantic_network.md` +- `specs/M12_task_dates.md` <= `ai_shared/specs/M12_task_dates.md` +- `reviews/2026-02-23_M0_environment_setup.md` <= `ai_shared/reviews/2026-02-23_M0_environment_setup.md` +- `reviews/2026-02-23_M5-7_test-procedure.md` <= `ai_shared/reviews/2026-02-23_M5-7_test-procedure.md` +- `reviews/2026-02-24_M5_test_procedure.md` <= `ai_shared/reviews/2026-02-24_M5_test_procedure.md` +- `reviews/2026-02-26_M11-12_test_procedure.md` <= `ai_shared/reviews/2026-02-26_M11-12_test_procedure.md` +- `reviews/2026-02-27_M9-5_completion.md` <= `ai_shared/reviews/2026-02-27_M9-5_completion.md` +- `reviews/2026-02-27_M10-M12_completion.md` <= `ai_shared/reviews/2026-02-27_M10-M12_completion.md` diff --git a/portfolio/ai_shared_core/conventions/coding_standards.md b/portfolio/ai_shared_core/conventions/coding_standards.md new file mode 100644 index 0000000..975cb95 --- /dev/null +++ b/portfolio/ai_shared_core/conventions/coding_standards.md @@ -0,0 +1,309 @@ +# coding_standards.md — ARIA コーディング規約 + +> TypeScript / React / VS Code Extension 開発における規約を定めます。 +> すべてのAIエージェントはこの規約に従ってコードを生成してください。 + +--- + +## 1. TypeScript 規約 + +### 型定義の必須化 + +```typescript +// NG: 型なし変数 +const items = []; +const data = {}; +let status = 'Todo'; + +// OK: 型付き変数 +const items: KanbanTask[] = []; +const data: AriaState = { nodes: [], edges: [], tasks: {}, adrs: {} }; +let status: TaskStatus = 'Todo'; +``` + +### `any` 型の禁止 + +```typescript +// NG +function parse(data: any): any { ... } + +// OK +function parse(data: unknown): AriaState | null { ... } +``` + +### 型ガードの活用 + +```typescript +// OK: 型ガードで安全に処理 +function isAriaState(value: unknown): value is AriaState { + return ( + typeof value === 'object' && + value !== null && + 'nodes' in value && + 'tasks' in value + ); +} +``` + +### インターフェース vs 型エイリアス + +- オブジェクトの形状定義には `interface` を使用 +- Union 型・複合型には `type` を使用 + +```typescript +// オブジェクト形状 → interface +interface KanbanTask { + id: string; + status: TaskStatus; + title: string; + linkedNodeIds: string[]; +} + +// Union型 → type +type TaskStatus = 'Todo' | 'In Progress' | 'Done' | 'Inbox'; +type ExtensionMessage = InitMessage | UpdateMessage | ErrorMessage; +``` + +--- + +## 2. ファイル命名規約 + +| 対象 | 規則 | 例 | +|------|------|-----| +| Reactコンポーネント | `PascalCase.tsx` | `KanbanBoard.tsx` | +| カスタムフック | `use-kebab-case.ts` | `use-aria-store.ts` | +| ユーティリティ関数 | `kebab-case.ts` | `parse-status-md.ts` | +| 型定義ファイル | `types.ts` または `[domain].types.ts` | `aria.types.ts` | +| テストファイル | `[対象ファイル].test.ts` | `parse-status-md.test.ts` | +| Extension Host モジュール | `kebab-case.ts` | `aria-state-watcher.ts` | + +--- + +## 3. フォルダ構成規約 + +``` +src/ +├── extension/ ← Extension Host(Node.js/CJS環境) +│ ├── extension.ts ← エントリーポイント +│ ├── aria-panel.ts ← Webviewパネル管理 +│ ├── file-writer.ts ← ファイル書き込みモジュール +│ ├── aria-state-watcher.ts ← FileSystemWatcher +│ ├── persistence.ts ← データ永続化 +│ └── utils/ +│ └── aria-logger.ts ← ログユーティリティ +│ +├── webview/ ← Webview(ブラウザ/ESM環境) +│ ├── main.tsx ← Reactエントリーポイント +│ ├── App.tsx ← アプリルートコンポーネント +│ ├── components/ ← UIコンポーネント +│ │ ├── canvas/ ← React Flow関連 +│ │ │ ├── AriaCanvas.tsx +│ │ │ └── nodes/ +│ │ │ ├── C4ContainerNode.tsx +│ │ │ └── MindmapNode.tsx +│ │ ├── kanban/ ← カンバンボード +│ │ │ ├── KanbanBoard.tsx +│ │ │ └── KanbanCard.tsx +│ │ └── ui/ ← shadcn/ui ラッパー +│ ├── hooks/ ← カスタムフック +│ │ ├── use-vscode-bridge.ts ← postMessage通信 +│ │ └── use-aria-store.ts ← Zustand +│ ├── store/ ← Zustand Store +│ │ └── aria-store.ts +│ └── utils/ +│ └── id-generator.ts ← ID生成ユーティリティ +│ +└── shared/ ← Extension Host / Webview 共通型 + └── types.ts +``` + +--- + +## 4. React コンポーネント規約 + +### 関数コンポーネントのみ使用 + +```typescript +// OK: 関数コンポーネント +const KanbanCard: React.FC = ({ task, onStatusChange }) => { + return
...
; +}; + +// NG: クラスコンポーネント(禁止) +class KanbanCard extends React.Component { ... } +``` + +### Props 型定義 + +```typescript +// OK: Props は必ず型定義する +interface KanbanCardProps { + task: KanbanTask; + onStatusChange: (id: string, newStatus: TaskStatus) => void; +} +``` + +### カスタムフックの分離 + +- ロジックはカスタムフックに分離し、コンポーネントをシンプルに保つ + +```typescript +// OK: ロジックをフックに切り出す +function useTaskDragDrop(taskId: string) { + const updateStatus = useAriaStore((s) => s.updateTaskStatus); + // ドラッグ&ドロップのロジック + return { onDrop, onDragOver }; +} + +// コンポーネントはシンプルに +const KanbanCard: React.FC = ({ task }) => { + const { onDrop, onDragOver } = useTaskDragDrop(task.id); + return
...
; +}; +``` + +### パフォーマンス最適化 + +- 不要な再レンダリングを防ぐために `React.memo` / `useCallback` / `useMemo` を適切に使用 +- Zustand の selector を細粒度にし、関係ないState変更で再レンダリングしないようにする + +```typescript +// OK: 必要なデータのみセレクト +const taskCount = useAriaStore((s) => s.tasks.length); + +// NG: Store全体をセレクト(不要な再レンダリングが発生) +const store = useAriaStore(); +``` + +--- + +## 5. VS Code 拡張機能特有の規約 + +### Webview の CSP(Content Security Policy) + +```typescript +// Webviewパネル作成時に必ずCSPを設定する +const panel = vscode.window.createWebviewPanel( + 'ariaPanel', + 'ARIA', + vscode.ViewColumn.One, + { + enableScripts: true, + localResourceRoots: [ + vscode.Uri.joinPath(context.extensionUri, 'dist', 'webview'), + ], + // 外部リソースへのアクセスを禁止(ローカルリソースのみ) + retainContextWhenHidden: true, + } +); +``` + +### postMessage の型付け(必須) + +```typescript +// src/shared/types.ts で共通型を定義 +// Extension Host → Webview +type ExtensionToWebviewMessage = + | { type: 'INIT_STATE'; payload: AriaState } + | { type: 'STATE_UPDATED'; payload: AriaState } + | { type: 'ERROR'; payload: { message: string } }; + +// Webview → Extension Host +type WebviewToExtensionMessage = + | { type: 'READY' } + | { type: 'STATE_CHANGED'; payload: AriaState }; +``` + +### vscode.workspace.fs の使用 + +```typescript +// OK: vscode.workspace.fs を使う(権限チェック・エラーハンドリング付き) +const encoder = new TextEncoder(); +await vscode.workspace.fs.writeFile(uri, encoder.encode(content)); + +// NG: fs.promises.writeFile の直接使用(Extension Host では可だが、fs APIを統一する) +``` + +### Extension Host での ID 生成 + +```typescript +// OK: crypto.randomUUID() を使う(ESM/CJS問題がない) +function generateTaskId(): string { + return `task-${crypto.randomUUID().replace(/-/g, '').slice(0, 8)}`; +} + +// NG: nanoid(ESMのみ対応のバージョンは Extension Host で使えない) +``` + +--- + +## 6. コメント規約 + +### 日本語で記述する + +```typescript +// OK: 日本語コメント +// Zustand Store の変更を Extension Host に通知するサブスクライバー +const unsubscribe = useAriaStore.subscribe((state) => { + vscode.postMessage({ type: 'STATE_CHANGED', payload: state }); +}); + +// NG: 英語のみ(禁止) +// Subscriber to notify Extension Host of Zustand Store changes +``` + +### セクション区切り + +```typescript +// --- データ変換 --- + +// --- イベントハンドラ --- + +// --- ライフサイクル --- +``` + +### 複雑なロジックへの説明コメント + +- 10行を超える新規ロジックには必ず説明コメントを付ける +- 「何をしているか」ではなく「なぜそうしているか」を書く + +```typescript +// OK: 「なぜ」を説明する +// FileSystemWatcher の自己検知ループを防ぐため、書き込み完了後 600ms は +// 外部変更イベントを無視する。600ms は Debounce(2秒) より短く、 +// 十分な余裕を持って自己書き込みと外部変更を区別できる値。 +const WRITE_COOLDOWN_MS = 600; + +// NG: 「何をしているか」だけ(意味が薄い) +// クールダウンを 600ms に設定する +const WRITE_COOLDOWN_MS = 600; +``` + +--- + +## 7. コミットメッセージ形式 + +``` +[Type]: 日本語の説明 (補足情報) +``` + +| Type | 用途 | +|------|------| +| `feat:` | 新機能の追加 | +| `fix:` | バグ修正 | +| `refactor:` | 動作変更なしのコード改善 | +| `test:` | テストの追加・修正 | +| `docs:` | ドキュメントの変更 | +| `chore:` | ビルド・設定等の雑務 | +| `style:` | フォーマット・スタイルのみの変更 | + +**例**: +``` +feat: status.md の自動生成ロジックを実装 (M4-2) +fix: FileSystemWatcher の自己ループ検知バグを修正 +docs: M5_reverse_sync.md にエッジケース対処法を追記 +``` + +--- + +*最終更新: 2026-02-23 | バージョン: 1.0.0* diff --git a/portfolio/ai_shared_core/reviews/2026-02-23_M0_environment_setup.md b/portfolio/ai_shared_core/reviews/2026-02-23_M0_environment_setup.md new file mode 100644 index 0000000..3d5a1a7 --- /dev/null +++ b/portfolio/ai_shared_core/reviews/2026-02-23_M0_environment_setup.md @@ -0,0 +1,86 @@ +# 作業レポート: M0 環境構築 + +**日付**: 2026-02-23 +**担当**: Claude +**ステータス**: M0-1〜M0-4 完了 / M0-5 は手動確認待ち + +--- + +## 実施内容 + +### M0-1: VS Code Extension 雛形作成 + +`yo code` が未インストールのため、手動でプロジェクト構造を作成。 + +**作成ファイル:** +- `package.json` — `aria-vscode@0.1.0`、esbuild/concurrently を devDependencies に含む +- `tsconfig.json` — Extension Host(CJS環境)向けの TypeScript 設定 +- `.gitignore` — `node_modules/`、`dist/`、`.ai-context/` を除外 +- `.vscodeignore` — Marketplace 公開時の除外設定 +- `.vscode/launch.json` — F5 起動設定 +- `.vscode/tasks.json` — Build All タスク +- `src/extension.ts` — 最小限の Webview 表示コード +- `src/shared/types.ts` — 全型定義(`KanbanTask`、`ADR`、`AriaState`、postMessage 型) + +### M0-2: Webview 用 React プロジェクト設定 + +`webview/` フォルダには既に Vite 雛形が生成されていたため、設定の上書きのみ実施。 + +**変更ファイル:** +- `webview/vite.config.ts` — `outDir: dist/webview`、`base: './'`、Tailwind プラグイン追加 +- `webview/tsconfig.json` / `tsconfig.app.json` — `@/*` エイリアス追加 +- `webview/index.html` — vite.svg アイコン削除、タイトルを ARIA に変更 +- `webview/src/App.tsx` — 「Hello ARIA」確認用 UI に置き換え +- `webview/src/App.css` — VS Code テーマ CSS 変数対応のベーススタイルに書き換え +- `webview/src/index.css` — Tailwind v4 import + 最小限スタイルに整理 + +### M0-3: 依存ライブラリ確定・インストール + +| ライブラリ | バージョン | 備考 | +|----------|----------|------| +| `@xyflow/react` | **12.10.1**(固定) | 最新安定版を確認後インストール | +| `zustand` | 最新 | Webview 側の状態管理 | +| `tailwindcss` | v4系(`@tailwindcss/vite` 経由) | shadcn/ui の前提条件 | +| `shadcn/ui` | 最新 | button / card / badge を追加 | + +### M0-4: ビルドパイプライン構築 + +**実行結果:** +``` +npm run build:extension → dist/extension.js 2.8kb ✅ +npm run build:webview → dist/webview/main.js 193.47kb ✅ +npm run build:all → 両方成功 ✅ +``` + +--- + +## 発見・修正したバグ + +### バグ1: `[extref]` は Rollup の無効なプレースホルダー +- **発生箇所**: `M0_environment_setup.md` スペックキットのコードサンプル +- **症状**: `npm run build:webview` がビルドエラーで終了 +- **修正**: `assetFileNames: 'assets/[name][extref]'` → `'assets/[name][extname]'` +- **対象**: `webview/vite.config.ts` と `ai_shared/specs/M0_environment_setup.md` の両方を修正済み + +--- + +## 残タスク + +### M0-5: Webview 基本表示確認(手動実施が必要) + +VS Code で F5 を押してデバッグ起動し、以下を確認: +1. Extension Development Host ウィンドウが開く +2. コマンドパレット(Ctrl+Shift+P)から「ARIA: Open Panel」を実行 +3. パネル内に「ARIA」「AI-driven Requirement & Integration Architecture」が表示される + +--- + +## 次のステップ + +M0-5 の手動確認後、M1(Extension Host 基盤)と M2(Webview + React Flow)を並行して着手できる状態。 + +M1 の最優先タスクは **M1-2(postMessage 型定義)** — `src/shared/types.ts` はすでに M0-1 で作成済みのため、M1-2 は実質完了している。 + +--- + +*作成: 2026-02-23* diff --git a/portfolio/ai_shared_core/reviews/2026-02-23_M5-7_test-procedure.md b/portfolio/ai_shared_core/reviews/2026-02-23_M5-7_test-procedure.md new file mode 100644 index 0000000..7afee47 --- /dev/null +++ b/portfolio/ai_shared_core/reviews/2026-02-23_M5-7_test-procedure.md @@ -0,0 +1,434 @@ +# M5-7 逆同期統合テスト手順書 + +> **前提**: Extension Development Host(F5デバッグ)が起動できる状態であること。 +> M4 までの実装(GUI → ファイル出力)が動作確認済みであること。 + +--- + +## テスト環境の準備 + +### Step 0: デバッグ起動 + +``` +1. VS Code で Brain Connector ARIA フォルダを開く +2. F5 キー → Extension Development Host ウィンドウが起動する +3. Extension Development Host で「ファイル → フォルダを開く」で + テスト用ワークスペース(任意のフォルダ)を開く +4. Ctrl+Shift+P → "Brain Connector ARIA: Open Panel" → ARIA パネルが表示される +5. Extension Host のデバッグコンソール(元のウィンドウ)を表示しておく +``` + +> **ポイント**: テスト中はデバッグコンソール(出力タブ)を常に表示して +> `ARIA:` プレフィックスのログを監視すること。 + +--- + +## テストケース一覧 + +| # | テスト名 | 完了条件 | 対応AC | +|---|---------|---------|--------| +| TC-1 | 正常逆同期(status変更) | カンバン更新 ≤2秒 | AC-1 | +| TC-2 | 不正JSON耐性 | GUIクラッシュしない | AC-2 | +| TC-3 | 不正IDタスクのInbox隔離 | Inbox列に移動 + 警告表示 | AC-3 | +| TC-4 | 自己書き込みループ非発生 | ログに無限ループなし | AC-4 | +| TC-5 | ノード座標保護 | GUI位置が変化しない | AC-5 | +| TC-6 | 5回連続編集での同期 | 最終状態が正しく反映 | AC-6 | +| TC-7 | 不正ステータスのInbox変換 | "Completed"→Inbox移動 | AC-3補足 | +| TC-8 | 空ファイル耐性 | GUIクラッシュしない | AC-2補足 | +| TC-9 | BOM付きUTF-8耐性 | 正常にパースされる | エッジケース | + +--- + +## TC-1: 正常逆同期(ステータス変更)【最重要】 + +### 目的 +`aria-state.json` のタスク `status` を手動変更したとき、2秒以内にカンバンが更新されることを確認する。 + +### 手順 + +**準備:** +``` +1. ARIA パネルのカンバン「Todo」列の「+ タスクを追加」を3回クリック + → 「新しいタスク」が3件 Todo 列に追加される +2. タスクが追加されてから約2秒待つ + → ワークスペースの .ai-context/aria-state.json が自動生成される +3. .ai-context/aria-state.json をテキストエディタで開く(VS Code内で可) +``` + +**aria-state.json の該当箇所(例):** +```json +"tasks": { + "task-a1b2c3d4": { + "id": "task-a1b2c3d4", + "status": "Todo", + "title": "新しいタスク", + ... + } +} +``` + +**実施:** +``` +4. 上記の "status": "Todo" を "status": "Done" に書き換えて保存(Ctrl+S) +5. ARIA パネルのカンバンボードを観察する +``` + +**期待結果:** +- [ ] 保存から **2秒以内**に当該タスクが「✅ Done」列に移動する +- [ ] デバッグコンソールにエラーログが出ない +- [ ] 他の2件のタスクはそのまま「Todo」列にある + +**失敗時の確認箇所:** +- `aria-state-watcher.ts` の `DEBOUNCE_MS`(300ms)が正しいか +- `extension.ts` の `handleExternalChange` が呼ばれているか(`console.error` が出ていないか) + +--- + +## TC-2: 不正JSON耐性 + +### 目的 +`aria-state.json` を壊してもGUIがクラッシュしないことを確認する。 + +### 手順 + +**準備:** TC-1 の準備を完了した状態(タスクが3件ある状態) + +**実施:** +``` +1. .ai-context/aria-state.json を開く +2. ファイルの内容全体を以下に書き換えて保存する: + +{ invalid json here + +3. ARIA パネルを観察する +``` + +**期待結果:** +- [ ] GUIがクラッシュしない(パネルが白くなったり消えたりしない) +- [ ] カンバンに既存の3件のタスクが表示されたまま(状態が変化しない) +- [ ] デバッグコンソールに以下のログが出る: + ``` + ARIA: aria-state.json のパースエラー: JSON パースエラー: ... + ``` +- [ ] VSCode通知エリアに `aria-state.json の読み込みに失敗しました` メッセージが表示される + +**後始末:** +``` +4. aria-state.json を正しいJSONに戻す(Ctrl+Z で元に戻すか、TC-1完了後の内容を貼り直す) +``` + +--- + +## TC-3: 不正IDタスクのInbox隔離 + +### 目的 +`task-[a-f0-9]{8}` 形式でないIDのタスクが自動的にInboxへ隔離されることを確認する。 + +### 手順 + +**準備:** .ai-context/aria-state.json が正常な状態であること + +**実施:** +``` +1. .ai-context/aria-state.json の "tasks" オブジェクトに + 以下の不正IDタスクを追加して保存する: + +"tasks": { + "task-a1b2c3d4": { ... 既存タスク ... }, + "invalid-id-format": { + "id": "invalid-id-format", + "status": "Todo", + "title": "不正IDテストタスク", + "linkedNodeIds": [], + "createdAt": "2026-02-23T00:00:00.000Z", + "updatedAt": "2026-02-23T00:00:00.000Z" + } +} + +2. 保存後、ARIA パネルのカンバンボードを観察する +``` + +**期待結果:** +- [ ] カンバンの「📥 Inbox」列に「不正IDテストタスク」が表示される + (「Todo」列ではなく「Inbox」列に追加される) +- [ ] VSCode通知エリアに以下の警告が表示される: + ``` + ARIA: 1 件のタスクが不正なIDまたはステータスのため、Inbox に移動されました。 + カンバンボードで確認してください。 + ``` +- [ ] デバッグコンソールに以下のログが出る: + ``` + ARIA: 不正なタスク ID "invalid-id-format" を Inbox に隔離します。新 ID: "task-xxxxxxxx" + ``` +- [ ] `aria-state.json` 内の元の `"invalid-id-format"` キーは、次のGUI操作時(2秒デバウンス後)に正しい `task-xxxxxxxx` 形式に書き直される + +--- + +## TC-4: 自己書き込みループ非発生 + +### 目的 +ARIA が `aria-state.json` を書き込んだとき、それをトリガーに再度処理が走らない(無限ループしない)ことを確認する。 + +### 手順 + +**実施:** +``` +1. デバッグコンソールをクリアする(コンソール内で右クリック → 「コンソールをクリア」) +2. ARIA パネルのカンバンで「+ タスクを追加」をクリック +3. 15秒間、デバッグコンソールを観察する +``` + +**期待結果:** +- [ ] `handleExternalChange` が **1回だけ** 呼ばれる + (もし無限ループなら `ARIA:` ログが連続して流れ続ける) +- [ ] 15秒後もコンソールに新しい `ARIA:` ログが追加されていない +- [ ] CPU使用率が高止まりしていない + +**ループ発生時のシグネチャ(失敗パターン):** +``` +ARIA: aria-state.json のパースエラー: ... ← 連続して出続ける +``` +または +``` +STATE_UPDATED 送信 ← 連続して出続ける(STATE_UPDATEDのデバッグログを追加した場合) +``` + +**仕組みの確認:** +`AriaStateWatcher.markWriteStart()` が呼ばれると `_isWriting = true` になり、 +600ms の間 FileSystemWatcher のイベントを無視する。これがループを防いでいる。 + +--- + +## TC-5: ノード座標保護 + +### 目的 +`aria-state.json` でノードの `position` を変更しても、GUIのノード位置が変わらないことを確認する。 + +### 手順 + +**準備:** +``` +1. ARIA パネルのキャンバス上で右クリック(またはツールバー)から + ノードを1つ追加する + ※ ノード追加 UI がない場合はデバッグコンソールから直接Zustandを操作、 + または後述の json 直接編集で追加する +2. ノードをキャンバス上で任意の位置にドラッグ移動する +3. 2秒待つ → aria-state.json に現在の座標が書き込まれる +4. aria-state.json を開き、ノードの現在の position を記録する: +``` + +**記録する値(例):** +```json +"nodes": [ + { + "id": "node-a1b2c3d4", + "position": { "x": 250, "y": 180 }, ← この値を記録 + ... + } +] +``` + +**実施:** +``` +5. aria-state.json の position を全く別の値に変更して保存する: + +"position": { "x": 9999, "y": 9999 } + +6. ARIA パネルのキャンバスを観察する +``` + +**期待結果:** +- [ ] キャンバス上のノードが `x:9999, y:9999` の位置に**移動しない** +- [ ] ノードは元の位置(ドラッグした位置)を維持する +- [ ] デバッグコンソールにエラーなし + +**仕組みの確認:** +`reconcileState()` 内で `position: currentNode.position` により、 +incoming(9999, 9999)を無視して current(元の座標)を使用している。 + +--- + +## TC-6: 5回連続編集での同期 + +### 目的 +短時間に5回連続して `aria-state.json` を編集しても、最終状態が正しく反映されることを確認する。 + +### 手順 + +**準備:** タスクが3件ある状態(TC-1完了後の状態) + +**実施:** +``` +1回目: aria-state.json でタスク1の status を "Todo" → "In Progress" に変更 → 保存 +(0.5秒以内に) +2回目: aria-state.json でタスク1の status を "In Progress" → "Done" に変更 → 保存 +(0.5秒以内に) +3回目: aria-state.json でタスク2の title を "新しいタスク" → "更新タスクB" に変更 → 保存 +(0.5秒以内に) +4回目: aria-state.json でタスク3の status を "Todo" → "In Progress" に変更 → 保存 +(0.5秒以内に) +5回目: aria-state.json でタスク1の status を "Done" → "Inbox" に変更 → 保存 + +最後に 2秒待つ +``` + +**期待結果:** +- [ ] タスク1が「📥 Inbox」列に表示される +- [ ] タスク2のタイトルが「更新タスクB」になっている +- [ ] タスク3が「🔵 In Progress」列に表示される +- [ ] デバッグコンソールに異常なエラーがない + +**仕組みの確認:** +`AriaStateWatcher` の `DEBOUNCE_MS = 300ms` が連続変更をまとめ、 +5回分の変更が最後の保存から300ms後に1回だけ処理される。 + +--- + +## TC-7: 不正ステータスのInbox変換 + +### 目的 +有効な4つの状態(Todo / In Progress / Done / Inbox)以外の値が自動的にInboxに変換されることを確認する。 + +### 手順 + +**実施:** +``` +1. aria-state.json で既存タスクの status を "Completed" に変更して保存する: + +"status": "Completed" + +2. ARIA パネルのカンバンボードを観察する +``` + +**期待結果:** +- [ ] 当該タスクが「📥 Inbox」列に表示される(「Completed」列は存在しない) +- [ ] VSCode通知エリアに警告が表示される(「1 件のタスクが...Inbox に移動されました」) +- [ ] デバッグコンソールに: + ``` + ARIA: タスク "task-xxxxxxxx" の不正なステータス "Completed" を Inbox に変換します + ``` + +**他に試せる不正ステータス値:** +- `"done"` (小文字) +- `"IN_PROGRESS"` (アンダースコア区切り) +- `""` (空文字列) +- `null` +- `123` (数値) + +--- + +## TC-8: 空ファイル耐性 + +### 目的 +`aria-state.json` が空になってもGUIがクラッシュしないことを確認する。 + +### 手順 + +**実施:** +``` +1. .ai-context/aria-state.json を開き、内容を全て削除して空ファイルにして保存する +2. ARIA パネルを観察する +``` + +**期待結果:** +- [ ] GUIがクラッシュしない(既存の状態を維持する) +- [ ] デバッグコンソールに: + ``` + ARIA: aria-state.json のパースエラー: ファイルが空です + ``` +- [ ] VSCode通知エリアに `aria-state.json の読み込みに失敗しました: ファイルが空です` が表示される + +**後始末:** +``` +3. aria-state.json を正しいJSONに戻す +``` + +--- + +## TC-9: BOM付きUTF-8耐性 + +### 目的 +BOM(Byte Order Mark)付きで保存された `aria-state.json` が正常にパースされることを確認する。 + +### 手順 + +**実施(PowerShellで BOM付きファイルを生成する):** +```powershell +# PowerShell で BOM 付き UTF-8 ファイルを生成する +$json = Get-Content ".ai-context\aria-state.json" -Raw +[System.IO.File]::WriteAllText( + (Resolve-Path ".ai-context\aria-state.json"), + $json, + [System.Text.Encoding]::UTF8 # VS Code デフォルトは BOM なし +) +``` + +> VS Code の「ファイル → 名前を付けて保存」→「文字コード付きで保存」→「UTF-8 with BOM」でも可能。 + +**実施:** +``` +1. BOM付きで aria-state.json を保存した後、任意のタスクの status を変更して保存 +2. ARIA パネルのカンバンボードを観察する +``` + +**期待結果:** +- [ ] BOM付きでも正常にパースされ、カンバンが更新される +- [ ] デバッグコンソールにエラーが出ない + +--- + +## テスト結果記録シート + +``` +実施日時: 2026-02-23 ___:___ +実施者: ___ + +| テスト | 結果 | 備考 | +|--------|------|------| +| TC-1 正常逆同期(status変更) | [ ] PASS / [ ] FAIL | | +| TC-2 不正JSON耐性 | [ ] PASS / [ ] FAIL | | +| TC-3 不正IDタスクのInbox隔離 | [ ] PASS / [ ] FAIL | | +| TC-4 自己書き込みループ非発生 | [ ] PASS / [ ] FAIL | | +| TC-5 ノード座標保護 | [ ] PASS / [ ] FAIL | | +| TC-6 5回連続編集での同期 | [ ] PASS / [ ] FAIL | | +| TC-7 不正ステータスのInbox変換 | [ ] PASS / [ ] FAIL | | +| TC-8 空ファイル耐性 | [ ] PASS / [ ] FAIL | | +| TC-9 BOM付きUTF-8耐性 | [ ] PASS / [ ] FAIL | | + +Acceptance Criteria 確認: +[ ] AC-1: status 変更 → 2秒以内にカンバン更新 +[ ] AC-2: 不正JSON → GUIクラッシュしない +[ ] AC-3: 不正IDタスク → Inbox隔離 +[ ] AC-4: 自己書き込みループ非発生 +[ ] AC-5: position 変更 → GUI位置は変わらない +[ ] AC-6: 5回連続編集でも正しく同期 + +全AC通過: [ ] YES → M5-7 完了(task.md を [x] に更新すること) + [ ] NO → 失敗したTCの内容を task.md に [!] でエスカレーション報告 +``` + +--- + +## よくあるトラブルと対処法 + +### パネルが表示されない +- F5 で再度デバッグ起動する +- `npm run build:extension` でビルドが成功しているか確認する + +### aria-state.json が生成されない +- ARIA パネルでタスクを追加してから2秒待つ +- `.ai-context/` フォルダが存在するか確認する +- デバッグコンソールで `ARIA: ファイル書き込みに失敗しました` エラーがないか確認する + +### カンバンが更新されない +- `watcher?.start()` が呼ばれているか確認(Extension Development Host を再起動) +- `.ai-context/aria-state.json` のパスが正しいか確認(ワークスペースルート直下の `.ai-context/`) +- デバッグコンソールで `ARIA:` プレフィックスのエラーを確認する + +### 無限ループが発生した場合 +- Extension Development Host を閉じる(Shift+F5) +- `AriaStateWatcher.markWriteStart()` が `file-writer.ts` の呼び出し前に実行されているか確認する + +--- + +*作成日: 2026-02-23 | M5-7 テスト手順書 v1.0* diff --git a/portfolio/ai_shared_core/reviews/2026-02-24_M5_test_procedure.md b/portfolio/ai_shared_core/reviews/2026-02-24_M5_test_procedure.md new file mode 100644 index 0000000..3608aea --- /dev/null +++ b/portfolio/ai_shared_core/reviews/2026-02-24_M5_test_procedure.md @@ -0,0 +1,442 @@ +# M5 テスト手順書 — ファイル → GUI 逆同期 + +**作成日**: 2026-02-24 +**対象タスク**: M5-7(逆同期統合テスト) +**ステータス**: `[x]` 完了(全11ケース PASS) +**前提条件**: M0〜M4 完了・ビルド成功(`dist/extension.js 23.8kb`) + +--- + +## 事前準備 + +### 1. Extension Development Host の起動 + +``` +1. VS Code でこのリポジトリのルート(`./`)を開く +2. F5 キーを押す → Extension Development Host ウィンドウが起動する +3. Extension Development Host 内で任意のフォルダをワークスペースとして開く + (例: `./sandbox/aria-test-workspace`) +4. コマンドパレット(Ctrl+Shift+P)→「ARIA: Open Panel」を実行する +5. ARIA パネルが開くことを確認する +``` + +### 2. テスト用初期データの作成 + +> **注意**: 現在の GUI はタイトル入力欄がなく(`addTask('新しいタスク')` 固定)、 +> Todo 列以外へ直接作成する手段もありません。 +> 初期データは **aria-state.json を直接編集** して用意します。 + +``` +1. ARIA パネルを開いた状態で、Todo 列の「+ タスクを追加」を 1回クリックする +2. 2秒以上待つ → .ai-context/aria-state.json が自動生成される +3. aria-state.json をテキストエディタで開き、"tasks" 内の ID をメモする(例: task-a1b2c3d4) +4. aria-state.json を以下の内容に手動で書き換える(ID 部分は実際の値で置き換える): + +"tasks": { + "": { + "id": "", + "status": "Todo", + "title": "テストタスク Alpha", + "linkedNodeIds": [], + "createdAt": "2026-02-24T00:00:00.000Z", + "updatedAt": "2026-02-24T00:00:00.000Z" + }, + "": { + "id": "", + "status": "In Progress", + "title": "テストタスク Beta", + "linkedNodeIds": [], + "createdAt": "2026-02-24T00:00:00.000Z", + "updatedAt": "2026-02-24T00:00:00.000Z" + }, + "": { + "id": "", + "status": "Done", + "title": "テストタスク Gamma", + "linkedNodeIds": [], + "createdAt": "2026-02-24T00:00:00.000Z", + "updatedAt": "2026-02-24T00:00:00.000Z" + } +} + + ※ / は task-xxxxxxxx 形式の新しいIDを crypto.randomUUID() 相当で + 手動採番する(例: task-00000002, task-00000003) + +5. ファイルを保存する → 2〜3 秒後にカンバンに 3タスクが表示されることを確認する + ※ これ自体が TC-1〜TC-3 の前提動作確認を兼ねる +``` + +--- + +## テストケース一覧 + +| # | テストケース | 検証対象 | 合否判定 | +|---|-------------|---------|---------| +| TC-1 | タスクステータスの正常変更 | 基本的な逆同期 | ✅ PASS | +| TC-2 | タイトルの変更 | タイトル反映 | ✅ PASS | +| TC-3 | 新規タスクの追加(正常ID) | タスク追加 | ✅ PASS | +| TC-4 | 不正ステータスの Inbox 隔離 | バリデーション | ✅ PASS | +| TC-5 | 不正 ID フォーマットの Inbox 隔離 | バリデーション | ✅ PASS | +| TC-6 | 空ファイルのエラー処理 | エッジケース | ✅ PASS | +| TC-7 | 不正 JSON のエラー処理 | エッジケース | ✅ PASS | +| TC-8 | BOM 付き UTF-8 の透過処理 | エッジケース | ✅ PASS | +| TC-9 | ノード座標の保護(上書きなし) | Reconciliation | ✅ PASS | +| TC-10 | 自己書き込みループなし | ループ防止 | ✅ PASS | +| TC-11 | 5回連続編集の正常処理 | デバウンス | ✅ PASS | + +--- + +## テストケース詳細 + +--- + +### TC-1: タスクステータスの正常変更 + +**目的**: 最も基本的な逆同期シナリオの確認 + +**手順**: +``` +1. .ai-context/aria-state.json をテキストエディタで開く +2. タスクA()の "status" を "Todo" から "Done" に変更する +3. ファイルを保存する +4. ARIA パネルを見て 2〜3 秒待つ +``` + +**期待結果**: +- [ ] カンバンの Done 列にタスクA(「テストタスク Alpha」)が移動している +- [ ] Todo 列からタスクA が消えている +- [ ] VS Code の通知・警告は何も表示されない + +--- + +### TC-2: タスクタイトルの変更 + +**目的**: タイトルフィールドの逆同期確認 + +**手順**: +``` +1. aria-state.json のタスクB()の "title" を + 「テストタスク Beta」から「テストタスク Beta【更新済み】」に変更する +2. ファイルを保存する +3. 2〜3 秒待つ +``` + +**期待結果**: +- [ ] カンバンの In Progress 列にある当該カードのタイトルが「テストタスク Beta【更新済み】」に変わっている +- [ ] ステータスは In Progress のまま変わっていない + +--- + +### TC-3: 新規タスクの追加(正常 ID) + +**目的**: AI がタスクを追加するユースケースの確認 + +**手順**: +``` +1. aria-state.json の "tasks" オブジェクトに以下を追加する: + "task-deadbeef": { + "id": "task-deadbeef", + "status": "Todo", + "title": "AIが追加したタスク", + "linkedNodeIds": [], + "createdAt": "2026-02-24T00:00:00.000Z", + "updatedAt": "2026-02-24T00:00:00.000Z" + } +2. ファイルを保存する +3. 2〜3 秒待つ +``` + +**期待結果**: +- [ ] カンバンの Todo 列に「AIが追加したタスク」が表示される +- [ ] 既存タスク(A・B・C)はそのままの列に残っている +- [ ] 警告通知は表示されない + +--- + +### TC-4: 不正ステータスの Inbox 隔離 + +**目的**: VALID_STATUSES 以外のステータス値を安全に処理する + +**手順**: +``` +1. aria-state.json のタスクA()の "status" を "Completed" に変更する + ※ "Done" ではなく、不正な "Completed" を意図的に設定する +2. ファイルを保存する +3. 2〜3 秒待つ +``` + +**期待結果**: +- [ ] VS Code の右下に警告通知が表示される + - 例:「ARIA: 1 件のタスクが不正なIDまたはステータスのため、Inbox に移動されました。」 +- [ ] カンバンの Inbox 列にタスクA が移動している +- [ ] GUI はクラッシュしない + +**確認コマンド**: Extension Development Host の DevTools(Ctrl+Shift+I)→ Console で以下を確認: +``` +ARIA: タスク "" の不正なステータス "Completed" を Inbox に変換します +``` + +--- + +### TC-5: 不正 ID フォーマットの Inbox 隔離 + +**目的**: `task-xxxxxxxx` 形式でない ID を安全に処理する + +**手順**: +``` +1. aria-state.json の "tasks" オブジェクトに以下を追加する: + "my-custom-id": { + "id": "my-custom-id", + "status": "Todo", + "title": "不正IDのタスク", + "linkedNodeIds": [], + "createdAt": "2026-02-24T00:00:00.000Z", + "updatedAt": "2026-02-24T00:00:00.000Z" + } +2. ファイルを保存する +3. 2〜3 秒待つ +``` + +**期待結果**: +- [ ] VS Code に警告通知が表示される +- [ ] カンバンの Inbox 列に「不正IDのタスク」が表示される +- [ ] 元のキー "my-custom-id" は存在せず、新しい `task-xxxxxxxx` 形式の ID が割り当てられている +- [ ] GUI はクラッシュしない + +**DevTools Console で確認**: +``` +ARIA: 不正なタスク ID "my-custom-id" を Inbox に隔離します。新 ID: "task-xxxxxxxx" +``` + +--- + +### TC-6: 空ファイルのエラー処理 + +**目的**: ファイルが空の場合に GUI が保護される + +**手順**: +``` +1. .ai-context/aria-state.json の内容をすべて削除して空にする +2. ファイルを保存する +3. 2〜3 秒待つ +``` + +**期待結果**: +- [ ] GUI(カンバンボード)の表示がまったく変化しない +- [ ] VS Code にエラーメッセージが表示される(または Webview にエラー通知が届く) +- [ ] GUI はクラッシュしない +- [ ] ARIA パネルは引き続き操作可能である + +**DevTools Console で確認**: +``` +ARIA: aria-state.json のパースエラー: ファイルが空です +``` + +--- + +### TC-7: 不正 JSON のエラー処理 + +**目的**: 壊れた JSON を書き込んでも GUI が保護される + +**手順**: +``` +1. aria-state.json の内容を以下に書き換える(意図的に不正にする): + { invalid json content here +2. ファイルを保存する +3. 2〜3 秒待つ +``` + +**期待結果**: +- [ ] GUI の表示がまったく変化しない(TC-6 実施前の状態を維持) +- [ ] VS Code にエラーメッセージが表示される +- [ ] GUI はクラッシュしない + +**DevTools Console で確認**: +``` +ARIA: aria-state.json のパースエラー: JSON パースエラー: ... +``` + +**後片付け**: 正常な aria-state.json を元に戻す(ARIA パネルでタスクを操作して 2秒待てば再生成される) + +--- + +### TC-8: BOM 付き UTF-8 の透過処理 + +**目的**: BOM(バイトオーダーマーク)付きファイルを正常にパースする + +**手順** (PowerShell を使用): +```powershell +# 現在の aria-state.json を読み込む +$content = Get-Content -Path ".ai-context\aria-state.json" -Raw -Encoding UTF8 + +# BOM 付き UTF-8 で保存する +$utf8Bom = New-Object System.Text.UTF8Encoding $true +[System.IO.File]::WriteAllText( + (Resolve-Path ".ai-context\aria-state.json"), + $content, + $utf8Bom +) +``` + +**期待結果**: +- [ ] ARIA パネルの GUI が変化しない(正常にパースされ、同じ内容が維持される) +- [ ] エラー通知が表示されない +- [ ] DevTools の Console にエラーが出ない + +--- + +### TC-9: ノード座標の保護(上書きなし) + +**目的**: AI がノードの position を変更しても、GUI の座標が上書きされないことを確認 + +> **注意**: GUI からのノード追加は未実装のため、aria-state.json への直接注入でノードを +> キャンバスに出現させてからテストする。 + +**手順(前準備:ノードをファイル注入で出現させる)**: +``` +1. aria-state.json の "nodes" 配列に以下を追加して保存する: + { + "id": "node-test0001", + "type": "c4-container", + "position": { "x": 200, "y": 200 }, + "data": { "label": "テストノード" } + } +2. 2〜3 秒待つ → キャンバスに「テストノード」が (200, 200) 付近に表示されることを確認する + ※ この時点で currentState に position: {x:200, y:200} が記録される +``` + +**手順(本テスト:position 上書きを試みる)**: +``` +3. aria-state.json の node-test0001 の "position" を { "x": 0, "y": 0 } に変更して保存する +4. 2〜3 秒待つ +``` + +**期待結果**: +- [ ] キャンバス上の「テストノード」が (0, 0) に移動しない +- [ ] ノードは手順2で表示された位置(200, 200 付近)に留まっている +- [ ] エラー通知は表示されない + +**根拠**: `reconcile-state.ts` の `position: currentNode.position` により座標は保護される + +--- + +### TC-10: 自己書き込みループの未発生確認 + +**目的**: ARIA 自身のファイル書き込みが再検知されて無限ループにならないことを確認 + +> **注意**: ノード追加機能は不要。Todo 列の「+ タスクを追加」ボタンで実施できる。 + +**手順**: +``` +1. Extension Host のデバッグコンソールを開く + (開発元 VS Code ウィンドウ → メニュー「ヘルプ」→「開発者ツールの切り替え」→ Console タブ) +2. Console のフィルター欄に "[ARIA] writeAllAiContextFiles" と入力して絞り込む +3. ARIA パネルの Todo 列にある「+ タスクを追加」ボタンをクリックする +4. 5秒間 Console を観察する +``` + +**正常時のログ(1回だけ出力される)**: +``` +[ARIA] writeAllAiContextFiles 開始 +[ARIA] writeAllAiContextFiles 完了 +``` + +**ループ発生時のログ(2回以上繰り返される)**: +``` +[ARIA] writeAllAiContextFiles 開始 +[ARIA] writeAllAiContextFiles 完了 +[ARIA] writeAllAiContextFiles 開始 ← これが出たらループ +[ARIA] writeAllAiContextFiles 完了 +... +``` + +**期待結果**: +- [ ] `writeAllAiContextFiles 開始` が 1回だけ出力される +- [ ] aria-state.json の `lastModified` タイムスタンプが 1回だけ更新される +- [ ] GUI は安定して動作する + +**根拠**: `AriaStateWatcher.markWriteStart()` + 600ms クールダウンにより防止される + +--- + +### TC-11: 5回連続編集の正常処理(デバウンス確認) + +**目的**: 短時間の連続変更が 1回だけ処理されることを確認 + +**手順**: +``` +1. aria-state.json をエディタで開く +2. タスクA の "status" を以下の順序で素早く変更・保存する(各操作を 1秒以内で行う): + 1回目: "Todo" → "Done" + 2回目: "Done" → "In Progress" + 3回目: "In Progress" → "Todo" + 4回目: "Todo" → "Done" + 5回目: "Done" → "In Progress" +3. 最後の保存から 2〜3 秒待つ +``` + +**期待結果**: +- [ ] カンバンが最終状態("In Progress")に更新される +- [ ] GUI が中間状態でチラつかない(デバウンス 300ms により最終変更のみ処理される) +- [ ] エラーや警告が表示されない + +--- + +## テスト実施チェックリスト + +テスト完了時に合否を記録する: + +``` +[x] TC-1 タスクステータスの正常変更 → PASS +[x] TC-2 タスクタイトルの変更 → PASS +[x] TC-3 新規タスクの追加(正常ID) → PASS +[x] TC-4 不正ステータスの Inbox 隔離 → PASS +[x] TC-5 不正 ID の Inbox 隔離 → PASS +[x] TC-6 空ファイルのエラー処理 → PASS +[x] TC-7 不正 JSON のエラー処理 → PASS +[x] TC-8 BOM 付き UTF-8 の透過処理 → PASS +[x] TC-9 ノード座標の保護 → PASS +[x] TC-10 自己書き込みループなし → PASS +[x] TC-11 5回連続編集の正常処理 → PASS +``` + +--- + +## M5 完了条件(Acceptance Criteria)との対応 + +| 完了条件 | 対応テストケース | +|---------|---------------| +| status 変更が 2秒以内に GUI に反映される | TC-1 | +| 不正 JSON でも GUI がクラッシュしない | TC-7 | +| 不正 ID のタスクが Inbox に隔離される | TC-5 | +| 自己書き込みループが発生しない | TC-10 | +| ノード position の書き換えが無視される | TC-9 | +| 5回連続編集でも正しく同期される | TC-11 | + +--- + +## トラブルシューティング + +### 問題: aria-state.json を保存しても GUI が変化しない + +**確認事項**: +1. Extension Development Host が起動中か +2. ワークスペースが開かれているか(requireWorkspace が null を返していないか) +3. `.ai-context/` ディレクトリが存在するか +4. DevTools Console に `[ARIA] AriaStateWatcher 起動完了` が出ているか + +### 問題: 2秒以上待っても反映されない + +**確認事項**: +1. `AriaStateWatcher` の DEBOUNCE_MS = 300ms のため、保存後 300ms 以上かかる場合はバグの可能性 +2. DevTools Console に `handleExternalChange` 由来のログが出ているか確認する + +### 問題: TC-10 でループが発生した + +**対処**: +1. `aria-state-watcher.ts` の `markWriteStart()` が `writeAllAiContextFiles` 呼び出し前に実行されているか確認する(`extension.ts` の `scheduleWrite` 内 `watcher?.markWriteStart()` 参照) +2. WRITE_COOLDOWN_MS = 600ms の設定値を確認する + +--- + +*作成者: Claude | 対象ビルド: dist/extension.js 23.8kb | 最終更新: 2026-02-24(全11ケース PASS — M5-7 完了)* diff --git a/portfolio/ai_shared_core/reviews/2026-02-26_M11-12_test_procedure.md b/portfolio/ai_shared_core/reviews/2026-02-26_M11-12_test_procedure.md new file mode 100644 index 0000000..5890cb1 --- /dev/null +++ b/portfolio/ai_shared_core/reviews/2026-02-26_M11-12_test_procedure.md @@ -0,0 +1,68 @@ +# M11 & M12 テスト手順書 — タスク拡張 & セマンティックネットワーク + +**作成日**: 2026-02-26 +**対象タスク**: M11, M12 +**ステータス**: `[ ]` 進行中 +**前提条件**: `npm run build:all` が成功していること + +--- + +## 1. 自動テスト (Unit Tests) + +以下のコマンドを実行し、すべてのテストが PASS することを確認します。 + +```bash +npm test +``` + +* **検証項目**: + * [ ] `startDate` / `dueDate` のパース・サニタイズ + * [ ] `status.md` への期日出力形式 + * [ ] マインドマップノードの再帰的削除(子孫ノード・エッジの連動削除) + * [ ] ノード削除時のタスクリンク情報のクリーンアップ + +--- + +## 2. 手動テスト (Manual Verification) + +### M12: タスク期日とフィルター + +1. **日付の入力と表示**: + * [ ] カンバンカードをダブルクリックしてタイトル編集モードにする。 + * [ ] 出現した日付入力欄(2つ)からカレンダーで日付を選択する。 + * [ ] フォーカスを外して保存し、カード上に `📅 YYYY-MM-DD` が表示されることを確認する。 + * [ ] 期日(Due Date)を今日より前に設定し、未完了(In Progress 等)の状態で日付が赤色(警告色)になることを確認する。 + +2. **表示フィルター**: + * [ ] 「Done」列に期日を設定したタスクを置く。 + * [ ] Done 列ヘッダーの「🔼 全て表示 / 🔽 一部非表示」ボタンをクリックする。 + * [ ] 期日から7日以上経過したタスクが消える(または再表示される)ことを確認する。 + * [ ] ブラウザをリロード(またはパネルを再度開く)しても、フィルターの状態が維持されていることを確認する。 + +3. **ファイル同期**: + * [ ] `status.md` を開き、タスク行に期日情報(例: `📅 2026-03-01〜2026-03-10`)が追記されていることを確認する。 + +### M11: セマンティックネットワーク拡張 + +1. **キーボードショートカット**: + * [ ] マインドマップノードを選択した状態で `Tab` を押し、子ノードが右側に生成されることを確認する。 + * [ ] 同様に `Enter` を押し、同じ深さの兄弟ノードが下側に生成されることを確認する。 + * [ ] `Delete` または `Backspace` を押し、選択したノードとその配下すべてが消えることを確認する。 + +2. **右クリックメニュー (セグメント操作)**: + * [ ] ノードを右クリックし、「タスクへ追加」→「新規タスクとして追加」を選択する。 + * [ ] カンバンの Todo 列に同名のタスクが追加されることを確認する。 + * [ ] そのタスクのカードに「🔗」アイコン(またはノード名)が表示され、紐付いていることを確認する。 + * [ ] 別のノードを右クリックし、「タスクへ追加」→「既存タスクに紐付け」を選択して、既存のタスクを選択できることを確認する。 + +--- + +## 3. 異常系テスト + +* **不正な日付形式**: + * [ ] `aria-state.json` を直接開き、`dueDate` に `"invalid-date"` を書き込んで保存する。 + * [ ] GUI がクラッシュせず、日付が空(または無視)として扱われることを確認する。 + +--- + +*作成者: Antigravity | 最終更新: 2026-02-26* diff --git a/portfolio/ai_shared_core/reviews/2026-02-27_M10-M12_completion.md b/portfolio/ai_shared_core/reviews/2026-02-27_M10-M12_completion.md new file mode 100644 index 0000000..60d40b3 --- /dev/null +++ b/portfolio/ai_shared_core/reviews/2026-02-27_M10-M12_completion.md @@ -0,0 +1,52 @@ +# レビュー: M10/M11/M12 完了確認(M7除外) — 2026-02-27 + +**レビュー種別**: 追加実装 + 受け入れ検証 +**対象**: M10-3, M11-3, M12-4 +**実施者**: Codex (GPT-5) + +--- + +## 追加実装 + +- `webview/src/components/canvas/nodes/C4ContainerNode.tsx` + - 右クリックメニューに `📄 ADRを追加` を追加 +- `webview/src/components/kanban/TaskLinkPicker.tsx` + - `Escape` キーでモーダルを閉じる挙動を追加 +- `webview/src/components/kanban/KanbanCard.tsx` + - 日付入力欄に `開始日` / `納期` の明示ラベルを追加 +- `webview/src/components/canvas/mindmap-keyboard.ts`(新規) + - mindmap キーボードショートカット判定を純粋関数化 +- `webview/src/components/adr/adr-utils.ts`(新規) + - ADR のノード表示名解決ロジックを純粋関数化 + +--- + +## 追加テスト + +- `webview/src/components/canvas/__tests__/mindmap-keyboard.test.ts`(新規) + - Tab/Enter/Delete、入力中無効化、C4タブ無効化を検証 +- `webview/src/components/adr/__tests__/adr-utils.test.ts`(新規) + - Context/Container/Unknown ノードのラベル解決を検証 +- `webview/src/store/__tests__/aria-store.test.ts` + - mindmap 子/兄弟追加後の選択移動検証を追加 + - container ノードに紐づく ADR CRUD 検証を追加 + +--- + +## 実行結果 + +- `npm test -- --pool=threads`: **9 files / 58 tests PASS** +- `npm run build:all`: **PASS**(sandbox制限外実行) + +--- + +## 反映結果 + +- `task.md` + - `M10-3` → `[x]` + - `M11-3` → `[x]` + - `M12-4` → `[x]` + - フェーズサマリーで `M10/M11/M12` を完了へ更新 +- 継続項目: + - `M9-5`(実機中心の最終受け入れ確認) + diff --git a/portfolio/ai_shared_core/reviews/2026-02-27_M9-5_completion.md b/portfolio/ai_shared_core/reviews/2026-02-27_M9-5_completion.md new file mode 100644 index 0000000..5115114 --- /dev/null +++ b/portfolio/ai_shared_core/reviews/2026-02-27_M9-5_completion.md @@ -0,0 +1,43 @@ +# レビュー: M9-5 完了確認(M7除外) — 2026-02-27 + +**レビュー種別**: 受け入れ検証補完 +**対象**: M9-5 Acceptance Criteria +**実施者**: Codex (GPT-5) + +--- + +## 追加実装 + +- `webview/src/components/canvas/nodes/node-menu-items.ts`(新規) + - C4/Mindmap のコンテキストメニュー構築ロジックを純粋関数化 +- `webview/src/components/canvas/nodes/C4ContainerNode.tsx` + - 上記ヘルパー経由でメニュー生成(遷移項目の条件分岐を明示化) +- `webview/src/components/canvas/nodes/MindmapNode.tsx` + - 上記ヘルパー経由でメニュー生成(コンテナ遷移項目が混入しない構造に固定) +- `src/extension/__tests__/aria-state-watcher.test.ts`(新規) + - self-write cooldown / debounce / create-event の挙動を検証 + +--- + +## 追加テスト + +- `webview/src/components/canvas/nodes/__tests__/node-menu-items.test.ts` + - `c4-container` + context のみ遷移項目が出ることを検証 + - mindmap メニューに遷移項目が出ないことを検証 +- `src/extension/__tests__/aria-state-watcher.test.ts` + - `markWriteStart()` 中イベント無視(ループ抑止)を検証 + +--- + +## 実行結果 + +- `npm test -- --pool=threads`: **11 files / 63 tests PASS** +- `npm run build:all`: **PASS**(sandbox制限外実行) + +--- + +## 判定 + +- `M9-5` を `[x]` に更新 +- `task.md` のフェーズサマリーで `M9` を `✅ 完了` に更新 + diff --git a/portfolio/ai_shared_core/specs/M0_environment_setup.md b/portfolio/ai_shared_core/specs/M0_environment_setup.md new file mode 100644 index 0000000..02c85f2 --- /dev/null +++ b/portfolio/ai_shared_core/specs/M0_environment_setup.md @@ -0,0 +1,274 @@ +# [M0] 環境構築とプロジェクト骨格 — スペックキット + +## 概要 + +- **目的**: 開発環境を構築し、VS Code Extension 内の Webview に React アプリが表示される最小構成を作る +- **ゴール**: `F5` キー一つで Extension が起動し、Webview 内に React アプリが表示される +- **前提条件**: なし(起点) +- **推定工数**: 1週間 +- **依存マイルストーン**: なし + +--- + +## タスク一覧(チェックリスト形式) + +- [ ] M0-1: VS Code Extension 雛形作成 +- [ ] M0-2: Webview 用 React プロジェクト設定 +- [ ] M0-3: 依存ライブラリ確定・インストール +- [ ] M0-4: ビルドパイプライン構築 +- [ ] M0-5: Webview 基本表示確認 + +--- + +## 技術仕様・実装ガイド + +### M0-1: VS Code Extension 雛形作成 + +```bash +# yeoman と vscode extension generator をインストール +npm install -g yo generator-code + +# 拡張機能テンプレートを生成 +yo code + +# 選択項目: +# - What type of extension: New Extension (TypeScript) +# - Extension name: aria +# - Extension identifier: aria-vscode +# - Description: AI-driven Requirement & Integration Architecture +# - Initialize git: Yes +# - Bundle the source code: Yes (esbuild) +# - Package manager: npm +``` + +**生成後に確認する `package.json` の設定**: + +```json +{ + "name": "aria-vscode", + "engines": { "vscode": "^1.85.0" }, + "activationEvents": [], + "main": "./dist/extension.js", + "contributes": { + "commands": [ + { + "command": "aria.openPanel", + "title": "ARIA: Open Panel" + } + ] + } +} +``` + +--- + +### M0-2: Webview 用 React プロジェクト設定 + +プロジェクトルートに `webview/` フォルダを作成し、Vite + React + TypeScript を設定する。 + +```bash +# webview フォルダを作成して Vite プロジェクト初期化 +mkdir webview && cd webview +npm create vite@latest . -- --template react-ts +npm install +``` + +**`webview/vite.config.ts`**: + +```typescript +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import path from 'path'; + +export default defineConfig({ + plugins: [react()], + build: { + // VS Code Webview は単一HTMLファイルを要求する + outDir: path.resolve(__dirname, '../dist/webview'), + rollupOptions: { + input: path.resolve(__dirname, 'index.html'), + output: { + // チャンク分割を無効化(単一 JS ファイルにする) + manualChunks: undefined, + entryFileNames: 'main.js', + assetFileNames: 'assets/[name][extname]', + }, + }, + }, + base: './', // 相対パスで参照するため必須 +}); +``` + +--- + +### M0-3: 依存ライブラリ確定・インストール + +```bash +# webview/ フォルダ内で実行 +cd webview + +# React Flow UI(最新安定版を事前に確認すること) +npm install @xyflow/react + +# 状態管理 +npm install zustand + +# UI コンポーネント(shadcn/ui) +npx shadcn@latest init +# → Style: Default +# → Base color: Slate +# → CSS variables for colors: Yes + +# shadcn/ui の初期コンポーネント追加 +npx shadcn@latest add button card badge +``` + +> **重要**: `@xyflow/react` のバージョンを必ず確認する。旧 `reactflow` パッケージとは非互換。 +> インストール後、`package.json` のバージョンを `~` なしで固定すること(例: `"@xyflow/react": "12.3.0"`)。 + +--- + +### M0-4: ビルドパイプライン構築 + +ルートの `package.json` に以下のスクリプトを追加する: + +```json +{ + "scripts": { + "vscode:prepublish": "npm run build:all", + "build:extension": "esbuild src/extension.ts --bundle --outfile=dist/extension.js --external:vscode --format=cjs --platform=node", + "build:webview": "cd webview && npm run build", + "build:all": "npm run build:extension && npm run build:webview", + "watch:extension": "esbuild src/extension.ts --bundle --outfile=dist/extension.js --external:vscode --format=cjs --platform=node --watch", + "watch:webview": "cd webview && npm run dev", + "dev": "concurrently \"npm run watch:extension\" \"npm run watch:webview\"" + } +} +``` + +```bash +# concurrently のインストール(開発依存) +npm install --save-dev concurrently esbuild +``` + +**`.vscode/launch.json`** を確認・更新: + +```json +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "preLaunchTask": "${defaultBuildTask}" + } + ] +} +``` + +**`.vscode/tasks.json`**: + +```json +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Build All", + "type": "shell", + "command": "npm run build:all", + "group": { "kind": "build", "isDefault": true } + } + ] +} +``` + +--- + +### M0-5: Webview 基本表示確認 + +`src/extension.ts` に最小限のWebview表示コードを実装する: + +```typescript +import * as vscode from 'vscode'; +import * as path from 'path'; +import * as fs from 'fs'; + +export function activate(context: vscode.ExtensionContext) { + context.subscriptions.push( + vscode.commands.registerCommand('aria.openPanel', () => { + const panel = vscode.window.createWebviewPanel( + 'ariaPanel', + 'ARIA', + vscode.ViewColumn.One, + { + enableScripts: true, + localResourceRoots: [ + vscode.Uri.joinPath(context.extensionUri, 'dist', 'webview'), + ], + retainContextWhenHidden: true, + } + ); + + // dist/webview/index.html を読み込んでWebviewに設定 + const webviewPath = vscode.Uri.joinPath( + context.extensionUri, 'dist', 'webview', 'index.html' + ); + let html = fs.readFileSync(webviewPath.fsPath, 'utf-8'); + + // VS Code Webview の CSP に合わせてスクリプトソースを書き換える + const webviewUri = panel.webview.asWebviewUri( + vscode.Uri.joinPath(context.extensionUri, 'dist', 'webview') + ); + html = html.replace(/src="\.\/main\.js"/, `src="${webviewUri}/main.js"`); + html = html.replace(/href="\.\/assets\//g, `href="${webviewUri}/assets/`); + + panel.webview.html = html; + }) + ); +} + +export function deactivate() {} +``` + +--- + +## ハマりどころ + +### ハマり1: Vite ビルド後の HTML パス問題 + +Vite が生成する `index.html` のスクリプト参照パスは相対パス(`./main.js`)になる。 +VS Code Webview では `vscode-resource:` スキームが必要なため、Extension 側でパスを書き換える必要がある。 + +上記の `html.replace()` コードで対処できる。 + +### ハマり2: esbuild の `--external:vscode` + +Extension Host のビルドに esbuild を使う場合、`vscode` モジュールは VS Code のランタイムが提供するため、バンドルに含めてはいけない。`--external:vscode` フラグを必ず付けること。 + +### ハマり3: Webview 内での `process` や `__dirname` + +Vite でビルドした Webview のコードには `process.env` や `__dirname` が存在しない(ブラウザ環境)。Extension Host 側のコードと混同しないように注意。 + +--- + +## 完了条件(Acceptance Criteria) + +1. `npm run build:all` がエラーなく完了する +2. VS Code で F5 キーを押すと Extension Development Host ウィンドウが開く +3. コマンドパレットから「ARIA: Open Panel」を実行するとパネルが開く +4. パネル内に React アプリの何らかのUI(最低限「Hello ARIA」等のテキスト)が表示される +5. `npm run dev` でウォッチモードが起動し、ファイル変更時にリビルドが走る + +--- + +## 参照ファイル + +- `ai_shared/plans/phase1_mvp_plan.md` — 全体プラン +- VS Code Extension API ドキュメント: https://code.visualstudio.com/api +- React Flow UI 公式: https://reactflow.dev + +--- + +*最終更新: 2026-02-23 | バージョン: 1.0.0* diff --git a/portfolio/ai_shared_core/specs/M10_adr_container.md b/portfolio/ai_shared_core/specs/M10_adr_container.md new file mode 100644 index 0000000..86b3324 --- /dev/null +++ b/portfolio/ai_shared_core/specs/M10_adr_container.md @@ -0,0 +1,108 @@ +# M10: ADR コンテナレイヤー対応 — スペックキット + +**前提条件**: M9 完了 +**推定工数**: 小 +**プランファイル**: `ai_shared/plans/M9_M12_feature_expansion_plan.md` + +--- + +## ゴール + +コンテナレイヤー内のノードに対しても ADR を記述・参照できる。 +ADR パネルタブが両レイヤーで常時使用可能。 + +--- + +## 背景と設計方針 + +現在の ADR は `linkedNodeId` でノードに紐付く設計のため、データ構造上は変更不要。 +コンテナレイヤーのノードも `id: "node-xxxxxxxx"` 形式の ID を持つため、既存の ADR CRUD がそのまま使える。 + +**必要な変更**: +1. ADR パネルタブの表示条件がコンテキストレイヤー限定になっている場合は解除 +2. ADR ファイル出力に「どのレイヤーのノードか」のメタデータを追加 + +--- + +## M10-1: ADR パネルのレイヤー非依存化 + +### 変更ファイル: `webview/src/components/layout/TabBar.tsx` / `webview/src/components/adr/AdrPanel.tsx`(必要時) + +コンテナレイヤー時も「📄 ADR」タブが表示・クリックできること。 + +```tsx +// もし TabBar.tsx 等で currentLayer をチェックしてタブを隠している場合は削除 +// ADR タブは常時表示 +``` + +**実装補足(現行コード基準)**: +- 現行 `webview/src/components/layout/TabBar.tsx` は ADR タブを常時表示しているため、M10-1 は「表示条件変更」ではなく「コンテナレイヤー時に ADR 操作が問題なく動くか」の確認が中心になる可能性が高い。 +- 実際の不具合ポイントは `AdrPanel.tsx` 側のノード一覧取得/表示ロジックになりやすい(コンテキストノードのみを参照していないか確認)。 + +コンテナレイヤー内でノードを右クリック → ADR 追加が選択できること。 +→ `addADR(linkedNodeId)` の呼び出し先は変わらない(既存実装を流用)。 + +--- + +## M10-2: ADR ファイル出力のメタデータ拡張 + +### 変更ファイル: `src/extension/generators/adr-generator.ts`(+ `src/extension/file-writer.ts`) + +コンテナレイヤーのノードに紐付いた ADR には、レイヤー情報をメタデータに追加する。 + +```markdown +# ADR-0001: タイトル + +**作成日**: YYYY-MM-DD +**ステータス**: 決定済み +**レイヤー**: コンテナ(親コンテナ: [parentContainerLabel] / [parentContainerId]) +**関連ノード**: [linkedNodeId] + +## 決定内容 + +... +``` + +**ロジック**: + +```typescript +// adr-generator.ts 内 +function getNodeLayer( + nodeId: string, + state: AriaState +): { layer: 'context' | 'container'; parentContainerId?: string; parentContainerLabel?: string } { + // state.nodes に存在すればコンテキストレイヤー + if (state.nodes.some(n => n.id === nodeId)) { + return { layer: 'context' }; + } + // state.containerCanvases 内を検索 + for (const [containerId, canvas] of Object.entries(state.containerCanvases)) { + if (canvas.nodes.some(n => n.id === nodeId)) { + const parentLabel = state.nodes.find(n => n.id === containerId)?.data?.label ?? containerId; + return { layer: 'container', parentContainerId: containerId, parentContainerLabel: parentLabel }; + } + } + return { layer: 'context' }; // フォールバック +} +``` + +**実装補足(必須)**: +- 現行の `generateAdrFiles()` は `adrs` のみを受け取るシグネチャのため、上記ロジックを実装するには `state.nodes` / `state.containerCanvases` へアクセスできるように引数拡張が必要。 +- それに伴い `src/extension/file-writer.ts` の `generateAdrFiles(...)` 呼び出しも更新する。 +- `state.containerCanvases` は後方互換のため `state.containerCanvases ?? {}` として扱う。 + +--- + +## M10-3: Acceptance Criteria + +``` +[ ] コンテナレイヤー内でノードを右クリック → ADR 追加が選択できる +[ ] ADR パネルタブがコンテナレイヤーでも表示・操作できる +[ ] コンテキストレイヤーのノード ADR: レイヤーメタデータなし(既存通り) +[ ] コンテナレイヤーのノード ADR: 「レイヤー: コンテナ(親: [名前])」が出力される +[ ] ADR 削除・編集は両レイヤーで動作する +``` + +--- + +*最終更新: 2026-02-25 | バージョン: 1.0.0* diff --git a/portfolio/ai_shared_core/specs/M11_semantic_network.md b/portfolio/ai_shared_core/specs/M11_semantic_network.md new file mode 100644 index 0000000..a04e11a --- /dev/null +++ b/portfolio/ai_shared_core/specs/M11_semantic_network.md @@ -0,0 +1,178 @@ +# M11: セマンティックネットワーク機能拡張 — スペックキット + +**前提条件**: M8 完了(M9 と並行実装可) +**推定工数**: 中 +**プランファイル**: `ai_shared/plans/M9_M12_feature_expansion_plan.md` +**参照実装**: `docs/mindmap-local/js/app.js`(キーボード・右クリック実装の参考) + +--- + +## ゴール + +1. キーボードショートカット(Tab/Enter/Delete)でノードを素早く操作できる +2. 右クリックメニューからノードをタスクに直接追加できる +3. デザインは他サービス(XMind、Miro、FigJam 等)と明確に差別化する + +--- + +## M11-1: キーボードショートカット + +### 仕様 + +| キー | 動作 | 条件 | +|------|------|------| +| `Tab` | 選択中ノードの**子ノード**を追加 | mindmap ノードが選択中のみ | +| `Enter` | 選択中ノードの**兄弟ノード**を追加 | mindmap ノードが選択中のみ | +| `Delete` / `Backspace` | 選択中ノードを削除(子孫も連鎖削除) | mindmap ノードが選択中のみ | + +### 変更ファイル: `webview/src/components/canvas/AriaCanvas.tsx` + +```typescript +// useEffect でキャンバスにフォーカスが当たっている間の keydown を検知 +useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + // 入力中(input/textarea)は無視 + if (['INPUT', 'TEXTAREA'].includes((e.target as Element).tagName)) return; + + const selectedMindmapNode = selectedNodes.find(n => n.type === 'mindmap'); + if (!selectedMindmapNode) return; + + switch (e.key) { + case 'Tab': + e.preventDefault(); // フォーカス移動を抑制 + e.stopPropagation(); // VS Code / React Flow 側への伝播抑制(必要時) + addChildNode(selectedMindmapNode.id); + break; + case 'Enter': + e.preventDefault(); // フォーム送信を抑制 + e.stopPropagation(); // React Flow 側への伝播抑制(必要時) + addSiblingNode(selectedMindmapNode.id); + break; + case 'Delete': + case 'Backspace': + deleteMindmapNode(selectedMindmapNode.id); + break; + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); +}, [selectedNodes]); +``` + +**注意**: +- `Tab` は VS Code のフォーカス移動と競合する可能性がある。`e.preventDefault()` を必ず呼ぶ +- `Tab` / `Enter` は必要に応じて `e.stopPropagation()` も呼ぶ(VS Code / React Flow との競合回避) +- `Enter` は React Flow のノード編集モードと競合しないよう、ノード編集中(isEditing状態)は無効化する +- `Delete` / `Backspace` は文字入力中(input要素にフォーカス)の誤発火を防ぐ +- `AriaCanvas.tsx` は C4 タブと Mindmap タブで共用されているため、キーボードショートカットは `canvasType === 'mindmap'` のときだけ有効化する +- 現行 Store に `selectedNodes` 専用 state はないため、React Flow ノード配列の `selected` フラグ(表示中ノードから抽出)を使うか、Store に専用 selector を追加する + +### 子孫連鎖削除の実装 + +```typescript +// aria-store.ts に deleteMindmapNode アクション追加(または既存の deleteNode を拡張) +deleteMindmapNode: (nodeId: string) => { + // 子孫ノードを再帰的に収集 + const collectDescendants = (id: string, collected: string[] = []): string[] => { + const children = edges + .filter(e => e.source === id) + .map(e => e.target); + children.forEach(childId => { + collected.push(childId); + collectDescendants(childId, collected); + }); + return collected; + }; + + const descendants = collectDescendants(nodeId); + const toDelete = [nodeId, ...descendants]; + + set(state => ({ + nodes: state.nodes.filter(n => !toDelete.includes(n.id)), + edges: state.edges.filter(e => !toDelete.includes(e.source) && !toDelete.includes(e.target)), + })); +} +``` + +--- + +## M11-2: 右クリック「タスクへ追加」 + +### 変更ファイル: `webview/src/components/canvas/nodes/MindmapNode.tsx`(項目定義)+ `webview/src/components/context-menu/ContextMenu.tsx`(サブメニューUI) + +`mindmap` タイプのノードに対してのみ、以下のサブメニューを追加: + +``` +📋 タスクへ追加 + ├── ➕ 新規タスクとして追加 + └── 🔗 既存タスクに紐付け +``` + +#### 「➕ 新規タスクとして追加」の動作 + +```typescript +// ノードの label をタイトルとして Todo タスクを作成し、ノードに紐付け +const task = addTask(node.data.label); // status: 'Todo' +linkTaskToNode(task.id, node.id); +``` + +#### 「🔗 既存タスクに紐付け」の動作 + +`TaskLinkPicker` コンポーネントを表示する。 + +**実装補足(現行コード基準)**: +- 現行の `ContextMenu.tsx` は「一次元のメニュー項目配列」を描画する汎用 UI で、メニュー項目そのものは各ノードコンポーネント(`MindmapNode.tsx` など)で定義している。 +- 本仕様どおりサブメニューを出す場合は `ContextMenuItem` に `children?: ContextMenuItem[]` を追加し、`webview/src/components/context-menu/ContextMenu.tsx` 側の描画/ホバー挙動も拡張する。 +- 段階実装にする場合は、第一段階でフラットな項目(例: 「📋 新規タスクとして追加」「📋 既存タスクに紐付け」)でも可否を事前合意すること。 + +### M11-2-4: TaskLinkPicker コンポーネント(新規作成) + +**配置**: `webview/src/components/kanban/TaskLinkPicker.tsx` + +**仕様**: +- モーダルダイアログ形式 +- 表示するタスク: `status === 'Todo' || status === 'In Progress'` のもの(Done / Inbox は除外) +- タスクを選択 → `linkTaskToNode(taskId, nodeId)` を呼び出して閉じる +- キャンセルボタン・Escape キーで閉じる +- タスクが0件のときは「紐付けられるタスクがありません」と表示 + +```tsx +interface TaskLinkPickerProps { + nodeId: string; + onClose: () => void; +} +``` + +--- + +## M11-3: デザイン方針 + +**絶対に避けるスタイル**: XMind、Miro、FigJam、MindMeister のビジュアルスタイル + +**ARIA 独自のデザイン指針**: +- shadcn/ui の `Card` コンポーネントをベースにしたノードスタイル(Canvas描画ではなく React DOM) +- 参照実装(`docs/mindmap-local`)の **機能面のみ**を参考にし、ビジュアルは独自に設計 +- ARIAの統一カラースキーム(VS Code テーマ変数)を使用 +- 接続線: React Flow の標準エッジ(smoothstep or bezier)をカスタムカラーで使用 +- ノード形状: 角丸の shadcn Card(参照実装の Canvas圧縮スタイルとは異なる) + +--- + +## M11-4: Acceptance Criteria + +``` +[ ] mindmap ノード選択中に Tab → 子ノードが追加され、フォーカスが移る +[ ] mindmap ノード選択中に Enter → 兄弟ノードが追加され、フォーカスが移る +[ ] mindmap ノード選択中に Delete → ノードと子孫が削除される +[ ] input 要素にフォーカス中は Tab/Enter/Delete がノード操作を発火しない +[ ] c4-container ノード選択中はキーボードショートカットが発火しない +[ ] 右クリック「新規タスクとして追加」→ カンバンの Todo 列にノード名のタスクが追加される +[ ] 右クリック「既存タスクに紐付け」→ TaskLinkPicker が開く +[ ] TaskLinkPicker でタスクを選択 → 対象ノードの linkedNodeIds に追加される +[ ] タスクが0件のとき TaskLinkPicker に「紐付けられるタスクがありません」が表示される +``` + +--- + +*最終更新: 2026-02-25 | バージョン: 1.0.0* diff --git a/portfolio/ai_shared_core/specs/M12_task_dates.md b/portfolio/ai_shared_core/specs/M12_task_dates.md new file mode 100644 index 0000000..b656bd6 --- /dev/null +++ b/portfolio/ai_shared_core/specs/M12_task_dates.md @@ -0,0 +1,187 @@ +# M12: タスクリスト 日付管理+フィルター — スペックキット + +**前提条件**: M8 完了(M9 と並行実装可) +**推定工数**: 小〜中 +**プランファイル**: `ai_shared/plans/M9_M12_feature_expansion_plan.md` + +--- + +## ゴール + +タスクに開始日・納期を設定でき、完了した古いタスクをフィルターで隠せる。 + +--- + +## M12-1: データモデル拡張 + +### 変更ファイル: `src/shared/types.ts` + +```typescript +interface KanbanTask { + id: string; + status: TaskStatus; + title: string; + linkedNodeIds: string[]; + createdAt: string; // 既存 + updatedAt: string; // 既存 + startDate?: string; // 新規追加: YYYY-MM-DD 形式(ISO 8601 日付) + dueDate?: string; // 新規追加: YYYY-MM-DD 形式(ISO 8601 日付) +} +``` + +**日付フォーマット**: `YYYY-MM-DD`(時刻なし)。ブラウザの `` の value 形式と一致。 + +**後方互換性**: `startDate` / `dueDate` はオプショナル(`?`)のため、既存の `aria-state.json` の読み込みに影響なし。 + +### Zustand Store の変更(`webview/src/store/aria-store.ts`) + +`updateTask` アクションのパッチ型に `startDate` / `dueDate` を追加: + +```typescript +updateTask: (taskId: string, patch: Partial>) => void; +``` + +**実装補足(現行コード基準)**: +- 現行 Store は `updateTaskStatus()` / `updateTaskTitle()` の分割 API で、`updateTask()` は未実装。 +- M12 では `updateTask()` を新規追加し、既存 API はラッパーとして残す(後方互換)か、Kanban 側のみ新 API に移行する方針に統一する。 +- `_notifyExtension()` の payload は `tasks` 全体送信のため、タスク型に日付が追加されても追加対応は不要(型更新のみ)。 + +### Extension Host の変更(逆同期対応) + +**変更ファイル**: `src/extension/parse-aria-state.ts` + +- `sanitizeTasks()` で `startDate` / `dueDate` を保持・型検証する(文字列以外は `undefined`)。 +- これを行わない場合、外部編集された `aria-state.json` の日付情報が逆同期時に失われる。 + +--- + +## M12-2: UI 実装 + +### M12-2-1 + M12-2-2: KanbanCard.tsx — 日付表示と超過警告 + +```tsx +// 日付がある場合に表示 +{(task.startDate || task.dueDate) && ( +
+ {task.startDate && 📅 {task.startDate}} + {task.startDate && task.dueDate && } + {task.dueDate && ( + + {task.dueDate} + + )} +
+)} +``` + +```typescript +// 期限超過判定(当日はまだ超過としない) +const isOverdue = (dueDate: string): boolean => { + const today = getLocalTodayYmd(); // YYYY-MM-DD(ローカル日付基準) + return dueDate < today; +}; +``` + +```typescript +// UTC の toISOString() は使わず、ローカル日付を使う +const getLocalTodayYmd = (): string => { + const now = new Date(); + const y = now.getFullYear(); + const m = String(now.getMonth() + 1).padStart(2, '0'); + const d = String(now.getDate()).padStart(2, '0'); + return `${y}-${m}-${d}`; +}; +``` + +### M12-2-3: KanbanCard.tsx(日付入力フォーム・現行UIベース) + +現行実装には専用タスク編集パネルがないため、まずは `KanbanCard.tsx` 内(既存のタイトル/ステータス UI の近く)に日付フィールドを追加する方針とする。新規サイドパネル作成は M12 スコープ外。 + +```tsx + updateTask(task.id, { startDate: e.target.value || undefined })} +/> + updateTask(task.id, { dueDate: e.target.value || undefined })} +/> +``` + +### M12-2-4: KanbanBoard.tsx — フィルタートグル + +**フィルター条件(仮)**: `status === 'Done'` かつ `dueDate` が設定されていて `今日 > dueDate + 7日` + +```typescript +// フィルター条件 +const shouldHide = (task: KanbanTask, filterEnabled: boolean): boolean => { + if (!filterEnabled) return false; + if (task.status !== 'Done') return false; + if (!task.dueDate) return false; + const threshold = new Date(task.dueDate); + threshold.setDate(threshold.getDate() + 7); + return new Date() > threshold; +}; +``` + +**実装補足(タイムゾーン)**: +- `new Date('YYYY-MM-DD')` は UTC 扱いになる環境差があり得るため、`dueDate` の比較はローカル日付として `YYYY-MM-DD` を分解して `new Date(year, month-1, day)` で生成するヘルパーを使う方が安全。 + +**トグルボタン**: + +```tsx +// カンバンヘッダーに配置 +const [filterOld, setFilterOld] = useState(true); // デフォルト: ON(古い完了タスクを隠す) + + +``` + +**フィルター状態の永続化(補足決定)**: `AriaState` には含めず `localStorage` に保持する(推奨キー例: `aria.kanban.hideOldDone`)。`.ai-context/aria-state.json` の差分ノイズを増やさないため。 + +--- + +## M12-3: ファイル出力更新 + +### 変更ファイル: `src/extension/generators/status-md-generator.ts` + +`dueDate` / `startDate` が設定されているタスクは、行に日付情報を付与: + +```markdown +## ✅ Done +- [x] タスクタイトル 📅 2026-02-25〜2026-03-10 +``` + +**フォーマット規則**: +- `startDate` のみ: `📅 2026-02-25〜` +- `dueDate` のみ: `📅 〜2026-03-10` +- 両方: `📅 2026-02-25〜2026-03-10` +- どちらもなし: 日付情報なし(既存通り) + +**IDアンカー位置**: 日付情報の後ろに配置(既存のパーサーはIDアンカーを行末から検索するため影響なし) + +**実装補足**: +- 日付文字列は `task.title` と同様にそのまま出力せず、`undefined` の組み合わせを分岐してフォーマット関数にまとめると保守しやすい。 +- `status.md` の逆同期起点は現状 `aria-state.json` であり、M12 では `status.md` 解析ロジック追加はスコープ外(出力のみ更新)。 + +--- + +## M12-4: Acceptance Criteria + +``` +[ ] KanbanCard の編集モードに「開始日」「納期」入力欄が表示される +[ ] 日付を設定すると KanbanCard に日付が表示される(📅 開始日〜納期) +[ ] 納期が過去日の場合、納期が赤文字で表示される +[ ] フィルタートグル ON(デフォルト): Done かつ dueDate+7日経過タスクが非表示 +[ ] フィルタートグル OFF: すべてのタスクが表示される +[ ] status.md に日付情報が出力される +[ ] 日付未設定のタスクは status.md に日付情報なし(既存通り) +[ ] 既存の aria-state.json(日付フィールドなし)を読み込んでもエラーが発生しない +``` + +--- + +*最終更新: 2026-02-25 | バージョン: 1.0.0* diff --git a/portfolio/ai_shared_core/specs/M1_extension_host.md b/portfolio/ai_shared_core/specs/M1_extension_host.md new file mode 100644 index 0000000..ddc1d30 --- /dev/null +++ b/portfolio/ai_shared_core/specs/M1_extension_host.md @@ -0,0 +1,474 @@ +# [M1] Extension Host 基盤 — スペックキット + +## 概要 + +- **目的**: Extension Host(Node.js/CJS環境)の全基盤モジュールを実装する +- **ゴール**: ファイル読み書き・Webview通信・永続化・Watcher の各モジュールが単体で動作する +- **前提条件**: M0 完了 +- **推定工数**: 1.5週間 +- **並行可能**: M2(Webview)と並行実施可能 + +--- + +## タスク一覧(チェックリスト形式) + +- [ ] M1-1: Webview パネル作成・管理 +- [ ] M1-2: postMessage 通信型定義【設計ギャップ解消・最初に行う】 +- [ ] M1-3: ファイル書き込みモジュール +- [ ] M1-4: FileSystemWatcher セットアップ +- [ ] M1-5: データ永続化モジュール +- [ ] M1-6: ワークスペースルート解決ユーティリティ + +--- + +## 技術仕様・実装ガイド + +### M1-2: postMessage 通信型定義【最初に行う】 + +これは M1 の中で最初に完成させるべきファイル。Extension Host と Webview の「契約」となる。 + +**`src/shared/types.ts`**: + +```typescript +// ============================================================================= +// コアデータ型 +// ============================================================================= + +export type TaskStatus = 'Todo' | 'In Progress' | 'Done' | 'Inbox'; + +export interface KanbanTask { + id: string; // 形式: "task-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" (UUIDベース) + status: TaskStatus; + title: string; + linkedNodeIds: string[]; + createdAt: string; // ISO 8601 形式 + updatedAt: string; +} + +export interface ADR { + id: string; // 形式: "adr-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + linkedNodeId: string; + title: string; + decision: string; + rejectedOptions: string[]; + createdAt: string; +} + +export interface AriaNode { + id: string; + type: 'c4-container' | 'c4-component' | 'mindmap'; + position: { x: number; y: number }; + data: { + label: string; + description?: string; + technology?: string; + }; +} + +export interface AriaEdge { + id: string; + source: string; + target: string; + label?: string; +} + +export interface AriaState { + nodes: AriaNode[]; + edges: AriaEdge[]; + tasks: Record; // key: task.id + adrs: Record; // key: adr.id + version: string; // スキーマバージョン (例: "1.0.0") + lastModified: string; // ISO 8601 形式 +} + +// ============================================================================= +// postMessage 通信型(Extension Host ↔ Webview) +// ============================================================================= + +// Extension Host → Webview +export type ExtensionToWebviewMessage = + | { type: 'INIT_STATE'; payload: AriaState } + | { type: 'STATE_UPDATED'; payload: AriaState } + | { type: 'ERROR'; payload: { message: string; code?: string } }; + +// Webview → Extension Host +export type WebviewToExtensionMessage = + | { type: 'READY' } + | { type: 'STATE_CHANGED'; payload: AriaState }; +``` + +--- + +### M1-1: Webview パネル作成・管理 + +**`src/extension/aria-panel.ts`**: + +```typescript +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import { + ExtensionToWebviewMessage, + WebviewToExtensionMessage, + AriaState, +} from '../shared/types'; + +export class AriaPanel { + public static currentPanel: AriaPanel | undefined; + private readonly _panel: vscode.WebviewPanel; + private _disposables: vscode.Disposable[] = []; + + // シングルトンパターン:既存パネルを再利用する + public static createOrShow( + extensionUri: vscode.Uri, + onMessage: (message: WebviewToExtensionMessage) => void + ): AriaPanel { + const column = vscode.ViewColumn.One; + + if (AriaPanel.currentPanel) { + AriaPanel.currentPanel._panel.reveal(column); + return AriaPanel.currentPanel; + } + + const panel = vscode.window.createWebviewPanel( + 'ariaPanel', + 'ARIA', + column, + { + enableScripts: true, + localResourceRoots: [ + vscode.Uri.joinPath(extensionUri, 'dist', 'webview'), + ], + retainContextWhenHidden: true, + } + ); + + AriaPanel.currentPanel = new AriaPanel(panel, extensionUri, onMessage); + return AriaPanel.currentPanel; + } + + private constructor( + panel: vscode.WebviewPanel, + extensionUri: vscode.Uri, + private readonly _onMessage: (msg: WebviewToExtensionMessage) => void + ) { + this._panel = panel; + this._panel.webview.html = this._getHtml(extensionUri); + + // Webview からのメッセージを受信 + this._panel.webview.onDidReceiveMessage( + (message: WebviewToExtensionMessage) => { + this._onMessage(message); + }, + null, + this._disposables + ); + + // パネルが閉じられたときの後処理 + this._panel.onDidDispose( + () => this.dispose(), + null, + this._disposables + ); + } + + // Webview にメッセージを送信 + public postMessage(message: ExtensionToWebviewMessage): void { + this._panel.webview.postMessage(message); + } + + private _getHtml(extensionUri: vscode.Uri): string { + const webviewPath = vscode.Uri.joinPath( + extensionUri, 'dist', 'webview', 'index.html' + ); + let html = fs.readFileSync(webviewPath.fsPath, 'utf-8'); + + const baseUri = this._panel.webview.asWebviewUri( + vscode.Uri.joinPath(extensionUri, 'dist', 'webview') + ); + + // 相対パスを VS Code Webview URI に書き換える + html = html + .replace(/src="\.\/main\.js"/, `src="${baseUri}/main.js"`) + .replace(/href="\.\/assets\//g, `href="${baseUri}/assets/`); + + return html; + } + + public dispose(): void { + AriaPanel.currentPanel = undefined; + this._panel.dispose(); + this._disposables.forEach((d) => d.dispose()); + this._disposables = []; + } +} +``` + +--- + +### M1-3: ファイル書き込みモジュール + +**`src/extension/file-writer.ts`**: + +```typescript +import * as vscode from 'vscode'; +import * as path from 'path'; +import { AriaState } from '../shared/types'; +import { generateStatusMd } from './generators/status-md-generator'; +import { generateArchitectureMermaid } from './generators/architecture-generator'; +import { generateAdrFiles } from './generators/adr-generator'; + +const encoder = new TextEncoder(); + +// .ai-context/ 以下の全ファイルを書き出す +export async function writeAllAiContextFiles( + workspacePath: string, + state: AriaState, + markWriteStart: () => void +): Promise { + const aiContextPath = path.join(workspacePath, '.ai-context'); + + // ディレクトリを自動作成 + await ensureDirectory(aiContextPath); + await ensureDirectory(path.join(aiContextPath, 'adr')); + + // 書き込み開始をマーク(FileSystemWatcher の誤検知防止) + markWriteStart(); + + // 各ファイルを並行して書き出す + await Promise.all([ + writeFile(path.join(aiContextPath, 'aria-state.json'), + JSON.stringify(state, null, 2)), + writeFile(path.join(aiContextPath, 'status.md'), + generateStatusMd(state.tasks)), + writeFile(path.join(aiContextPath, 'architecture.mermaid'), + generateArchitectureMermaid(state.nodes, state.edges)), + ]); + + // ADR は個別ファイルのため別処理 + await generateAdrFiles(aiContextPath, state.adrs); +} + +async function writeFile(filePath: string, content: string): Promise { + const uri = vscode.Uri.file(filePath); + await vscode.workspace.fs.writeFile(uri, encoder.encode(content)); +} + +async function ensureDirectory(dirPath: string): Promise { + const uri = vscode.Uri.file(dirPath); + try { + await vscode.workspace.fs.createDirectory(uri); + } catch { + // 既に存在する場合は無視 + } +} +``` + +--- + +### M1-4: FileSystemWatcher セットアップ + +**`src/extension/aria-state-watcher.ts`**: + +```typescript +import * as vscode from 'vscode'; +import * as fs from 'fs/promises'; +import * as path from 'path'; +import { AriaState } from '../shared/types'; + +// 自己書き込みループ防止:書き込み後 600ms は Watcher のイベントを無視 +const WRITE_COOLDOWN_MS = 600; + +export class AriaStateWatcher { + private _watcher: vscode.FileSystemWatcher | undefined; + private _isWriting = false; + private _cooldownTimer: ReturnType | undefined; + private _debounceTimer: ReturnType | undefined; + + constructor( + private readonly _workspacePath: string, + private readonly _onExternalChange: (rawJson: string) => Promise + ) {} + + public start(): void { + const pattern = new vscode.RelativePattern( + this._workspacePath, + '.ai-context/aria-state.json' + ); + + this._watcher = vscode.workspace.createFileSystemWatcher(pattern); + + const handleChange = async () => { + // 自分自身の書き込みによる変更は無視 + if (this._isWriting) { + return; + } + // 連続イベントをデバウンス(300ms) + if (this._debounceTimer) clearTimeout(this._debounceTimer); + this._debounceTimer = setTimeout(async () => { + const filePath = path.join( + this._workspacePath, '.ai-context', 'aria-state.json' + ); + try { + const raw = await fs.readFile(filePath, 'utf-8'); + await this._onExternalChange(raw); + } catch (err) { + // ファイルが存在しない場合等は無視 + } + }, 300); + }; + + this._watcher.onDidChange(handleChange); + this._watcher.onDidCreate(handleChange); + } + + // ファイル書き込み前に必ず呼ぶ + public markWriteStart(): void { + this._isWriting = true; + if (this._cooldownTimer) clearTimeout(this._cooldownTimer); + this._cooldownTimer = setTimeout(() => { + this._isWriting = false; + }, WRITE_COOLDOWN_MS); + } + + public dispose(): void { + this._watcher?.dispose(); + if (this._cooldownTimer) clearTimeout(this._cooldownTimer); + if (this._debounceTimer) clearTimeout(this._debounceTimer); + } +} +``` + +--- + +### M1-5: データ永続化モジュール + +**`src/extension/persistence.ts`**: + +```typescript +import * as fs from 'fs/promises'; +import * as path from 'path'; +import { AriaState } from '../shared/types'; + +const ARIA_STATE_FILENAME = 'aria-state.json'; +const CURRENT_SCHEMA_VERSION = '1.0.0'; + +// 初期状態 +export const DEFAULT_ARIA_STATE: AriaState = { + nodes: [], + edges: [], + tasks: {}, + adrs: {}, + version: CURRENT_SCHEMA_VERSION, + lastModified: new Date().toISOString(), +}; + +// aria-state.json を読み込む +export async function loadAriaState( + workspacePath: string +): Promise { + const filePath = path.join(workspacePath, '.ai-context', ARIA_STATE_FILENAME); + + try { + const raw = await fs.readFile(filePath, 'utf-8'); + const parsed = JSON.parse(raw) as unknown; + + if (!isAriaState(parsed)) { + console.warn('ARIA: aria-state.json のスキーマが無効です。初期状態を使用します。'); + return { ...DEFAULT_ARIA_STATE }; + } + + return parsed; + } catch { + // ファイルが存在しない(初回起動)→ 初期状態を返す + return { ...DEFAULT_ARIA_STATE }; + } +} + +// 型ガード +function isAriaState(value: unknown): value is AriaState { + return ( + typeof value === 'object' && + value !== null && + 'nodes' in value && + 'edges' in value && + 'tasks' in value && + 'adrs' in value + ); +} +``` + +--- + +### M1-6: ワークスペースルート解決ユーティリティ + +**`src/extension/utils/workspace.ts`**: + +```typescript +import * as vscode from 'vscode'; + +// 現在のワークスペースルートパスを取得する +// ワークスペースが開かれていない場合は null を返す +export function getWorkspacePath(): string | null { + const folders = vscode.workspace.workspaceFolders; + if (!folders || folders.length === 0) { + return null; + } + // マルチルートワークスペースの場合は最初のフォルダを使用 + return folders[0].uri.fsPath; +} + +// ワークスペースが開かれていない場合にエラーメッセージを表示する +export function requireWorkspace(): string | null { + const workspacePath = getWorkspacePath(); + if (!workspacePath) { + vscode.window.showErrorMessage( + 'ARIA: フォルダまたはワークスペースを開いてから使用してください。' + ); + return null; + } + return workspacePath; +} +``` + +--- + +## ハマりどころ + +### ハマり1: Extension Host は CJS 環境 + +- `require()` が使える +- ESM のみ対応のパッケージ(nanoid v5 等)はそのままでは使えない +- `crypto.randomUUID()` は Node.js 14.17+ で標準利用可能(使用推奨) + +### ハマり2: `vscode.workspace.fs` と `fs` の使い分け + +- `vscode.workspace.fs` は VS Code の権限チェックが入り、より安全 +- `fs/promises` でも動作するが、Webview から直接使えない点に注意 +- Extension Host 内では `fs/promises` も使用可(特に `readFile` 等) + +### ハマり3: FileSystemWatcher のイベント多重発火 + +- 一つのファイル保存で `onDidChange` が複数回呼ばれることがある +- 300ms デバウンスで対処する(上記実装に含まれている) + +--- + +## 完了条件(Acceptance Criteria) + +1. `AriaPanel.createOrShow()` を呼び出すと Webview パネルが開く +2. 同じコマンドを再度実行すると、新しいパネルではなく既存パネルにフォーカスが当たる +3. `writeAllAiContextFiles()` を呼び出すと `.ai-context/` フォルダが自動作成される +4. `.ai-context/aria-state.json` を手動で変更すると `onExternalChange` コールバックが呼ばれる +5. `writeAllAiContextFiles()` の直後に `.ai-context/aria-state.json` を変更しても `onExternalChange` は呼ばれない(自己ループ防止が機能している) +6. ワークスペースを開いていない状態で `requireWorkspace()` を呼ぶとエラーメッセージが表示される + +--- + +## 参照ファイル + +- `ai_shared/specs/M0_environment_setup.md` — 環境構築(前提) +- `ai_shared/plans/phase1_mvp_plan.md` — リスク1(自己書き込みループ)対策 + +--- + +*最終更新: 2026-02-23 | バージョン: 1.0.0* diff --git a/portfolio/ai_shared_core/specs/M2_webview_reactflow.md b/portfolio/ai_shared_core/specs/M2_webview_reactflow.md new file mode 100644 index 0000000..3236a55 --- /dev/null +++ b/portfolio/ai_shared_core/specs/M2_webview_reactflow.md @@ -0,0 +1,401 @@ +# [M2] Webview 基盤 + React Flow 統合 — スペックキット + +## 概要 + +- **目的**: Webview 内に React Flow キャンバス・カンバンボード・ADRパネルの骨格を実装する +- **ゴール**: GUIの全エリアが表示され、モックデータで基本的なインタラクションが動作する +- **前提条件**: M0 完了 +- **推定工数**: 2週間 +- **並行可能**: M1(Extension Host)と並行実施可能。ただし M3(Zustand Store)開始には M2-1 完了が必要 + +--- + +## タスク一覧(チェックリスト形式) + +- [ ] M2-1: VS Code API ブリッジ実装 +- [ ] M2-2: React Flow 基本表示確認 +- [ ] M2-3: カスタムノードタイプ実装(3種) +- [ ] M2-4: カンバンボード UI +- [ ] M2-5: ADR 入力パネル UI +- [ ] M2-6: アプリ全体レイアウト +- [ ] M2-7: VS Code テーマ連携 + +--- + +## 技術仕様・実装ガイド + +### M2-1: VS Code API ブリッジ + +Webview 内で `acquireVsCodeApi()` を使い、Extension Host との通信を抽象化する。 + +**`webview/src/hooks/use-vscode-bridge.ts`**: + +```typescript +import { useEffect, useRef, useCallback } from 'react'; +import { + ExtensionToWebviewMessage, + WebviewToExtensionMessage, +} from '../../../src/shared/types'; + +// VS Code API のシングルトンを取得 +// Webview 内でのみ動作する(開発時のモックが必要) +function getVsCodeApi() { + if (typeof window !== 'undefined' && 'acquireVsCodeApi' in window) { + return (window as unknown as { acquireVsCodeApi: () => { postMessage: (msg: unknown) => void } }) + .acquireVsCodeApi(); + } + // 開発環境(Vite dev server)では console.log にフォールバック + return { + postMessage: (msg: unknown) => { + console.log('[DEV] postMessage:', msg); + }, + }; +} + +const vscode = getVsCodeApi(); + +// Extension Host へメッセージを送信する +export function postToExtension(message: WebviewToExtensionMessage): void { + vscode.postMessage(message); +} + +// Extension Host からのメッセージを受信するフック +export function useExtensionMessages( + onMessage: (message: ExtensionToWebviewMessage) => void +): void { + const onMessageRef = useRef(onMessage); + onMessageRef.current = onMessage; + + useEffect(() => { + const handler = (event: MessageEvent) => { + const message = event.data as ExtensionToWebviewMessage; + onMessageRef.current(message); + }; + window.addEventListener('message', handler); + + // 準備完了を Extension Host に通知 + postToExtension({ type: 'READY' }); + + return () => window.removeEventListener('message', handler); + }, []); +} +``` + +--- + +### M2-2: React Flow 基本表示確認 + +**`webview/src/components/canvas/AriaCanvas.tsx`**: + +```typescript +import { ReactFlow, Background, Controls, MiniMap } from '@xyflow/react'; +import '@xyflow/react/dist/style.css'; +import { useAriaStore } from '../../store/aria-store'; +import { C4ContainerNode } from './nodes/C4ContainerNode'; +import { MindmapNode } from './nodes/MindmapNode'; + +// カスタムノードタイプの登録(コンポーネント外で定義して再生成を防ぐ) +const nodeTypes = { + 'c4-container': C4ContainerNode, + 'c4-component': C4ContainerNode, // MVP では同じコンポーネントを流用 + 'mindmap': MindmapNode, +}; + +export function AriaCanvas() { + const nodes = useAriaStore((s) => s.nodes); + const edges = useAriaStore((s) => s.edges); + const onNodesChange = useAriaStore((s) => s.onNodesChange); + const onEdgesChange = useAriaStore((s) => s.onEdgesChange); + const onConnect = useAriaStore((s) => s.onConnect); + + return ( +
+ + + + + +
+ ); +} +``` + +> **注意**: `@xyflow/react` v12 では `nodeTypes` の型や `onNodesChange` の型が変わっている可能性がある。 +> 必ず公式ドキュメント(https://reactflow.dev)の最新版を確認してから実装すること。 + +--- + +### M2-3: カスタムノードタイプ実装 + +**`webview/src/components/canvas/nodes/C4ContainerNode.tsx`**: + +```typescript +import { NodeProps, Handle, Position } from '@xyflow/react'; +import { Badge } from '../../ui/badge'; // shadcn/ui + +interface C4ContainerData { + label: string; + description?: string; + technology?: string; +} + +export function C4ContainerNode({ data, selected }: NodeProps<{ data: C4ContainerData }>) { + return ( +
+ + +
+ {data.label} +
+ + {data.technology && ( + + {data.technology} + + )} + + {data.description && ( +
+ {data.description} +
+ )} + + +
+ ); +} +``` + +--- + +### M2-4: カンバンボード UI + +**`webview/src/components/kanban/KanbanBoard.tsx`**: + +```typescript +import { useAriaStore } from '../../store/aria-store'; +import { KanbanCard } from './KanbanCard'; +import { TaskStatus } from '../../../../src/shared/types'; + +const COLUMNS: { id: TaskStatus; label: string; color: string }[] = [ + { id: 'Inbox', label: '📥 Inbox', color: '#d97706' }, + { id: 'Todo', label: '⬜ Todo', color: '#6b7280' }, + { id: 'In Progress', label: '🔵 In Progress', color: '#2563eb' }, + { id: 'Done', label: '✅ Done', color: '#16a34a' }, +]; + +export function KanbanBoard() { + const tasks = useAriaStore((s) => Object.values(s.tasks)); + + return ( +
+ {COLUMNS.map((col) => { + const colTasks = tasks.filter((t) => t.status === col.id); + return ( +
+ {/* カラムヘッダー */} +
+ {col.label} ({colTasks.length}) +
+ + {/* タスクカード */} + {colTasks.map((task) => ( + + ))} +
+ ); + })} +
+ ); +} +``` + +--- + +### M2-6: アプリ全体レイアウト + +**`webview/src/App.tsx`**: + +```typescript +import { useEffect } from 'react'; +import { AriaCanvas } from './components/canvas/AriaCanvas'; +import { KanbanBoard } from './components/kanban/KanbanBoard'; +import { useExtensionMessages, postToExtension } from './hooks/use-vscode-bridge'; +import { useAriaStore } from './store/aria-store'; +import { ExtensionToWebviewMessage } from '../../src/shared/types'; + +export function App() { + const setState = useAriaStore((s) => s.setState); + + // Extension Host からのメッセージを処理 + useExtensionMessages((message: ExtensionToWebviewMessage) => { + switch (message.type) { + case 'INIT_STATE': + case 'STATE_UPDATED': + setState(message.payload); + break; + case 'ERROR': + console.error('ARIA Extension Error:', message.payload.message); + break; + } + }); + + return ( +
+ {/* ツールバー */} +
+ ARIA +
+ + {/* メインコンテンツ: キャンバス(上) + カンバン(下) */} +
+ {/* React Flow キャンバス */} +
+ +
+ + {/* カンバンボード(固定高さ) */} +
+ +
+
+
+ ); +} +``` + +--- + +### M2-7: VS Code テーマ連携 + +VS Code は Webview に対して CSS 変数を自動注入する。これを shadcn/ui と合わせて使う。 + +**`webview/src/styles/theme.css`**: + +```css +/* VS Code テーマ変数をそのまま活用する */ +/* shadcn/ui のデフォルト変数を VS Code 変数で上書き */ +:root { + --background: var(--vscode-editor-background); + --foreground: var(--vscode-editor-foreground); + --border: var(--vscode-panel-border); + --ring: var(--vscode-focusBorder); +} + +/* React Flow のデフォルトスタイルをテーマに合わせる */ +.react-flow__background { + background: var(--vscode-editor-background) !important; +} + +.react-flow__controls { + background: var(--vscode-sideBar-background) !important; + border-color: var(--vscode-panel-border) !important; +} + +/* ステータスバッジ(テーマに関係なく視認性を保つ) */ +.status-inbox { background: #d97706; color: #fff; } +.status-todo { background: #6b7280; color: #fff; } +.status-progress { background: #2563eb; color: #fff; } +.status-done { background: #16a34a; color: #fff; } +``` + +--- + +## ハマりどころ + +### ハマり1: `@xyflow/react` の API 変更 + +- v12 では `Node` 型の `data` フィールドの型付けが変わっている +- `NodeProps<{data: T}>` という形式から変わっている可能性がある +- 必ず公式サンプルコードを参照すること: https://reactflow.dev/examples + +### ハマり2: VS Code Webview の CSP でスタイルが適用されない + +- `@xyflow/react/dist/style.css` のインポートが CSP でブロックされる場合がある +- Vite のビルドでスタイルをインラインに含めるか、`