# メンテナンスチェックリスト コード変更時に連動して修正が必要な箇所のリスト。 過去に実際に発生した不整合をもとに作成。 --- ## 0. ツールのカテゴリ区分(workspace tool policy) 新しいツールを追加した場合、または既存ツールのカテゴリを変更した場合は、**`src/engine/tools/tool-categories.ts`** の `MODULE_SPECS` に登録し、安全(標準)かセンシティブかの区分を決める。workspace tool policy はこの定義を参照して、ワークスペースごとにツールの利用可否を解決する。 - `SENSITIVE_CATEGORIES`(現在: `ssh` / `browser`)に含まれるカテゴリのツールはデフォルト無効 - `SENSITIVE_TOOLS`(現在: `Bash`)に列挙されたツールは、カテゴリが `core` であっても個別にセンシティブ扱い - 新カテゴリは安全側(デフォルト有効)が既定。意図的にセンシティブにする場合のみ `SENSITIVE_CATEGORIES` に追加する **対象ファイル:** - `src/engine/tools/tool-categories.ts` — `MODULE_SPECS` / `SENSITIVE_CATEGORIES` / `SENSITIVE_TOOLS` - `src/engine/tools/index.ts` — `tryLoadModule` でのカテゴリ指定 - `src/bridge/tools-api.ts` — カテゴリ情報を返す `/api/tools` エンドポイント - `ui/src/content/help/16-tools.md` — カテゴリ別の概要テーブルに行を追加 **確認方法:** ```bash # tool-categories.ts のカテゴリ定義とツール一覧が揃っているか npx vitest run src/engine/tools/tool-categories.test.ts # センシティブ区分が resolver に正しく伝わるか(regression guard) npx vitest run src/engine/tools/resolver.test.ts ``` --- ## 1. ツールモジュールを新規追加した場合 **対象ファイル:** - `src/engine/tools/.ts` — 新モジュール本体 - `src/engine/tools/index.ts` — `tryLoadModule` で動的ロード追加 - `src/bridge/tools-api.ts` — `/api/tools` のモジュール一覧に追加 - `CLAUDE.md` — 「ツールモジュール構成」テーブルに行を追加 **なぜ必要か:** `index.ts` はエージェント実行時のツール dispatch に使われ、`tools-api.ts` は Settings UI のピース編集画面でツール候補を返す API。 `tools-api.ts` への追加を忘れると、エンジンでは使えるのに UI のドロップダウンに表示されないという不整合が起きる(実際に amazon, speech, checklist, knowledge の4モジュールで発生した)。 **確認方法:** ```bash # index.ts でロードしているモジュール数と tools-api.ts のモジュール数を比較 grep -c 'tryLoadModule' src/engine/tools/index.ts grep -c "engine/tools/" src/bridge/tools-api.ts # 数が一致していなければ漏れがある ``` --- ## 2. 既存モジュールにツールを追加した場合 **対象ファイル:** - `src/engine/tools/tool-categories.ts` — 新ツールを適切なカテゴリに割り当てる(sensitive は既定 OFF)。ツールの利用可否は workspace tool policy(設定→ツール)が決めるので、**piece 側に追加する必要はない** - `CLAUDE.md` — 「ツールモジュール構成」テーブルの該当モジュール行に新ツール名を追加 - `docs/tools/{name}.md` — 新ツールの詳細ドキュメント(推奨) - ツール `description` — 1 文 + 「詳細は ReadToolDoc({ name: "XXX" })」を末尾に記述 **なぜ必要か:** ツールの提示は workspace tool policy が決める(カテゴリ単位。sensitive 以外は既定 ON)。カテゴリ未割り当てだとどのワークスペースでも出ない。 CLAUDE.md のテーブルが古いと、Claude Code 自身が既存ツールを認識せずに新規実装してしまうリスクがある。 ツール description は毎 LLM 呼び出しに乗るため 1 文に絞り、詳細は ReadToolDoc 経由で必要時のみ読み込む(agent-loop が movement 開始時に description サマリを自動カタログ化する)。 **確認方法:** `tool-categories.ts` で新ツールがカテゴリに割り当てられているか確認する。 CLAUDE.md のモジュールテーブルに新ツール名が含まれているか確認する。 **実例: TestWorkspaceApp(2026-06-24):** `src/engine/tools/app-test.ts` に追加 → `tools/index.ts` で `tryLoadModule` 追加 → `src/bridge/tools-api.ts` のモジュール一覧に追加 → `tool-categories.ts` でカテゴリ割り当て → `src/engine/tools/docs.ts` の `TOOL_DOC_ALIASES` に `testworkspaceapp: 'testworkspaceapp'` を追加 → `docs/tools/testworkspaceapp.md` を新規作成。(旧手順の「piece の allowed_tools に追加」は撤去済み) --- ## 3. ツールをリネーム・削除した場合 **対象ファイル:** - `src/engine/tools/tool-categories.ts` — カテゴリ定義・`SENSITIVE_TOOLS` から旧名を削除/リネーム - `src/engine/tools/raw-save.ts` — `RAW_SAVE_TOOLS` に旧名が残っていないか - `ui/src/components/settings/ToolsForm.tsx` — ヘルプテキスト等にツール名の言及がないか - `CLAUDE.md` — ツールモジュール構成テーブル **なぜ必要か:** BrowserAction を BrowseWeb に統合した際、5つの piece と UI ヘルプテキストに旧名の参照が残っていた。 **確認方法:** ```bash grep -r '旧ツール名' pieces/ src/ ui/src/ CLAUDE.md ``` --- ## 4. config.yaml に新しい設定キーを追加した場合 **対象ファイル:** - `src/config.ts` — `ToolsConfig` / `AppConfig` / `LlmConfig` / `StorageConfig` 等のインターフェースにフィールド追加 - `src/config-normalize.ts` — v1 → v2 normalizer に新フィールドの mirror が要るか確認 (storage / llm 配下に置く場合) - `config.yaml.example` — YAML キー(スネークケース)でサンプル・コメント追加。`config_version: 2` を必ず先頭に保つ - `ui/src/components/settings/` — 該当セクションの Form コンポーネントに UI フィールド追加 - `CLAUDE.md` — 設定セクションに説明追加(必要に応じて) **なぜ必要か:** config.ts と config.yaml.example の不一致は「設定が効かない」「ドキュメントと実動作が違う」を招く。 Settings UI への追加を忘れると、YAML を直接編集しないと設定できなくなる。 v2 layout (`llm.*` / `storage.*`) では normalizer 側の backfill / migrate path も触らないと、v1 ファイルでの読み取り互換が壊れる。 **確認方法:** ```bash # config.ts のフィールド数と config.yaml.example のキー数を目視比較 # Settings UI の各 Form ファイルで未対応フィールドがないか確認 # normalize の v1 fixture が落ちていないか npx vitest run src/config-normalize.test.ts ``` --- ## 4-B. 新しい Settings Form コンポーネントを追加した場合 **対象ファイル (両方必須):** - `ui/src/components/settings/Form.tsx` — Form 本体 - `ui/src/components/settings/ConfigForm.tsx` — `renderActiveForm()` の `switch(section)` に新 `case 'new-id': return ;` を追加 - `ui/src/components/settings/SettingsSidebar.tsx` — `CONFIG_GROUPS[].sections[]` に `{ id: 'new-id', label: '...' }` を追加。admin 限定なら親グループに `adminOnly: true` - `ui/src/lib/urlState.ts` — section id の許可リストにも追加 (URL state からの復元) - 旧 id をリネームした場合は `LEGACY_SECTION_REDIRECT` (SettingsSidebar.tsx) にエントリ追加してブックマーク互換を維持 **なぜ必要か:** ConfigForm の switch と SettingsSidebar の section リストは独立に管理されており、片方だけ追加すると「サイドバーには項目があるが何も描画されない」「描画はされるがサイドバーから飛べない」状態になる。Step 3 (sidebar 再編) / Step 7-9 (form 分割) で両方を毎回触る前提で設計されている。 **禁止事項:** - 削除した旧 Form (`ProviderForm.tsx`, `WorkersForm.tsx`, `WorkspaceForm.tsx` のような単一巨大 form) を復活させない。LLM Workers / Paths & Storage / Execution / Tools-* の分割は v2 design の前提 - カンマ区切り入力で配列フィールドを作らない。chip エディタ / textarea / multiselect のいずれかを使うこと (値に `,` を含めるケースを壊さないため) - Secret 入力で `'********'` の magic string 判定を再導入しない。`SecretInput` (4-state: `unchanged` / `literal` / `env_ref` / `cleared`) を再利用すること **確認方法:** ```bash # section id が両方のファイルで揃っているか grep -E "'[a-z-]+'" ui/src/components/settings/SettingsSidebar.tsx | grep "id:" grep "case '" ui/src/components/settings/ConfigForm.tsx # legacy redirect が漏れていないか grep -A 20 'LEGACY_SECTION_REDIRECT' ui/src/components/settings/SettingsSidebar.tsx ``` --- ## 4-C. TopBar のトップレベルタブ (page) を追加・削除した場合 `PageId` は複数ファイルに分散しているので、追加・削除のどちらでも下記をすべて触る。 **対象ファイル (すべて必須):** - `ui/src/lib/urlState.ts` — `PAGES` 配列 (PageId の唯一の定義源) - `ui/src/components/layout/TopBar.tsx` — `NAV_ITEMS` (タブ表示順 + adminOnly / requiresAuth) - `ui/src/components/layout/NavDrawer.tsx` — `NAV_ICONS` は `Record` の網羅型。**PageId を削除したら同名キーも消さないと TS が excess-property で落ちる** - `ui/src/App.tsx` — `{page === '...' && <... />}` のレンダー分岐 (+ 不要になった import の削除) - 外部からそのページへ飛ばす導線 (`url.searchParams.set('page', '...')` 等) の付け替え - i18n `layout.nav.` (`ui/src/i18n/locales/{en,ja}/layout.json`) **履歴 (2026-06-19):** 旧「ユーザーフォルダ」(`userfolder`) タブは廃止。中身は個人ワークスペースの **設定** タブ (AGENTS.md / メモリ / Pieces / スキル / MCP / SSH / ブラウザ。`SpaceSettings.tsx`) に集約し、孤立していた **Pets** はグローバル設定の `pets` セクション (`settings/PetsForm.tsx`) へ移設した。`userfolder/` 配下のパネル (AgentsMdPanel / MemoryPanel / McpPanel / SshConnectionsPanel / SkillsPanel / AddBrowserSessionDialog / MonacoFileEditor 等) は SpaceSettings / SpaceBrowserPanel / 設定フォームが再利用するため残置。 --- ## 5. SSRF 保護に関わる変更をした場合 **対象ファイル:** - `src/engine/tools/shared/ssrf.ts` — SSRF チェックロジック本体 - `src/engine/tools/browser.ts` — BrowseWeb(`setupRouteInterception` + `ssrfCheck`) - `src/engine/tools/web.ts` — WebFetch / DownloadFile - `ui/src/components/settings/ToolsForm.tsx` — SSRF Allowed Hosts のヘルプテキスト **なぜ必要か:** `webfetchAllowedHosts` は WebFetch と BrowseWeb の両方で使われている。片方だけ修正すると保護に穴が開くか、意図しないブロックが発生する。 --- ## 6. ログ出力に関わる変更をした場合 **対象ファイル:** - `src/engine/tools/raw-save.ts` — `RAW_SAVE_TOOLS` / `RAW_LOG_ONLY_TOOLS` の更新 - 各ツールモジュール内の `appendXxxHistory()` 関数 **なぜ必要か:** 新ツールを追加したとき、`RAW_SAVE_TOOLS` への追加を忘れると `logs/raw/` に出力が保存されない。 ツール側で独自に raw 保存する場合は `RAW_SAVE_TOOLS` に含めない(二重保存回避)。 **保存先の整理:** | ログ種別 | パス | 内容 | |---------|------|------| | 生データ | `logs/raw/{tool}-{timestamp}.txt/.json` | ツール実行結果の生出力 | | WebFetch 履歴 | `logs/webfetch-history.jsonl` | URL, ステータス, サイズ | | ダウンロード履歴 | `logs/downloads.jsonl` | 保存パス, サイズ | | チェックリスト | `logs/checklists/{name}.json` | チェックリスト状態 | | rawdata インデックス | `logs/rawdata-history.jsonl` | raw 保存のメタデータ | --- ## 7. ツールの詳細ドキュメントを書く場合 **対象ファイル:** - `docs/tools/{toolname-lowercase}.md` — ツールの詳細な使い方ガイド - 該当ツールの `description` — 概要 + `ReadToolDoc({ name: "XXX" })` で参照可能と明記 - `src/engine/tools/docs.ts` の `TOOL_DOC_ALIASES` — 関連ツール名を同じ doc にマップ(例: `checkitem: 'checklist'`) - 既存 piece に同じガイダンスが書かれていれば削除する(重複解消) **なぜ必要か:** Tool description は毎回 LLM のコンテキストに乗るため肥大化させたくない。詳細手順・ワークフロー例は `docs/tools/{name}.md` に置き、`ReadToolDoc` ツールで必要時に取得する設計。 ReadToolDoc は META_TOOLS として常時利用可能(workspace tool policy でも常時 ON)。 関連ツール(CheckItem / CreateChecklist / GetChecklist 等)は1つの doc にまとめてエイリアス経由で引けるようにする。 **確認方法:** - ファイル名は小文字(例: `BrowseWeb` → `browseweb.md`) - description の末尾に「詳細は ReadToolDoc(...) で取得可能」を明記 - 全 piece から対応する重複ガイダンスを削除 --- ## 8. piece の movement 構造を変更した場合 **対象ファイル:** - 対象 `pieces/*.yaml` — `rules` に遷移先を明示追加 **なぜ必要か:** `default_next` は機械的フォールバック専用。LLM が選べる遷移先は `rules[].next` に明示されたものだけ。 `rules` に追加せず `default_next` だけに頼ると、LLM が遷移先を選択できない。 --- ## 9. Reflection に新しい memory `type` を追加した場合 **対象ファイル:** - `src/engine/reflection/types.ts` — `ReflectionMemoryType` の union を拡張 - `src/engine/reflection/reflection-schema.ts` — LLM tool schema の `type` enum - `src/engine/reflection/reflection-prompt.ts` — システムプロンプトの type 説明 - `src/engine/reflection/semantic-validator.ts` — `ALLOWED_TYPES` セット - `src/bridge/memory-api.ts` — PUT のバリデーション - `ui/src/components/settings/MemoryLearningForm.tsx` — type ドロップダウン **なぜ必要か:** 4 種類 (user / feedback / project / reference) はメモリ意味論の単位。LLM は schema で型強制されるので、schema・prompt・validator のいずれかを忘れると LLM が「未知の type」を吐いて全 reflection が `rejected_unknown_type` で落ちる。UI ドロップダウンの更新を忘れるとユーザーが手動編集できなくなる。 --- ## 10. Reflection に新しい rejection code を追加した場合 **対象ファイル:** - `src/engine/reflection/types.ts` — `ReflectionRejectionCode` の union を拡張 - `src/engine/reflection/semantic-validator.ts` — 新しいケースで返す - `src/engine/reflection/applier.ts` — 必要に応じて applier 側の rejection 経路も追加 - `src/bridge/memory-api.ts` — 同じコードを返す PUT バリデーションを更新 - `ui/src/components/settings/MemoryLearningForm.tsx` — UI 側でコード → 日本語メッセージ変換 - `src/engine/reflection/applier.fuzz.test.ts` — fuzz の "known code list" に追加 **なぜ必要か:** rejection code は metrics・UI 表示・fuzz invariant の3箇所で参照される。fuzz テストは「decision の code は known list のいずれか」と assertion しているので、追加し忘れると fuzz が落ちる。UI 側で未対応コードはユーザーに「不明なエラー」と表示されてしまう。 --- ## 11. MCP サーバー追加 / authKind 仕様変更時 **対象ファイル:** - `src/mcp/registry.ts` — `auth_kind` の受け入れ・バリデーション - `src/mcp/token-manager.ts` — `hasToken` / `getValidToken` の authKind 分岐 - `src/bridge/mcp-api.ts` — admin (global server) ルート、`/api/mcp/connections` - `src/bridge/user-servers-api.ts` — user-owned server CRUD - `ui/src/components/userfolder/McpServersPanel.tsx` — authKind ラジオ + 条件付きフォーム - `ui/src/components/userfolder/McpConnectionsPanel.tsx` — authKind=api_key は Authorize ボタン非表示 - `docs/mcp.md` — 運用者向け手順 (OAuth と api_key 両方) **なぜ必要か:** authKind は server 行に保存され、token-manager・aggregator・UI のそれぞれで分岐する。Phase 8 で OAuth 専用設計から拡張したため、新しい authKind を入れる場合は schema migration + UI フォーム + docs の 3 点を必ず触る。OAuth start ルートは authKind=oauth でしか動かない (api_key は 400 を返す) ので、route 側のガードも忘れない。 **確認方法:** - `grep -rn "authKind" src/mcp/ src/bridge/mcp-api.ts src/bridge/user-servers-api.ts ui/src/components/userfolder/Mcp*.tsx` で参照漏れを確認 - 統合テスト: `npx vitest run src/mcp/integration.test.ts` (OAuth + api_key 両 path をカバー) --- ## UI フォントサイズスケール (Tailwind class 規約) UI に新しいパネルやコンポーネントを追加するときは、以下の **6 段階スケール** から選ぶ。それ以外の `text-[Npx]` を導入すると全体の字組が崩れる。 | サイズ | クラス | 用途 | |---|---|---| | 10px | `text-[10px]` | セクションラベル(uppercase)、極小バッジ | | 11px | `text-2xs` ⭐ (Tailwind 拡張、`tailwind.config.js` で定義済み) | ヘルプテキスト、キャプション、メタ情報 | | 12px | `text-xs` | 本文小、ボタンラベル、フォーム入力、テーブルセル | | 13px | `text-[13px]` (Tailwind 標準に該当なし) | パネル本文、メイン段落 | | 14px | `text-sm` | フォームラベル、ナビゲーション項目、見出し小 | | 16px+ | `text-base` / `text-lg` / `text-xl` / `text-2xl` | h2 セクション見出し、ページタイトル、ヒーロー | ### 禁止リスト 以下は **絶対に書かないこと**。書いた場合は `2026-05 commit 993ef2f` の正規化と同様の一括置換でやり直しになる。 | 書いてしまった | 置き換え先 | |---|---| | `text-[11px]` | `text-2xs` | | `text-[12px]` | `text-xs` | | `text-[14px]` | `text-sm` | | `text-[15px]` | `text-sm` | | `text-[16px]` | `text-base` | | `text-[18px]` | `text-lg` | | `text-[9px]` | `text-[10px]` (極小に丸め) | | `text-[12.5px]` | `text-[13px]` | | 任意の非標準 px | 上記スケールに最も近いものに丸める | ### なぜ必要か - 過去に新規パネル (Help, MCP, AGENTS.md 等) を追加するたびに implementer が独自に `text-[Npx]` を書き、累積で **13 種類のサイズ** が混在した - 統一したフォントサイズは UI の一貫性 + 認知負荷の低下に直結する - Tailwind utility (`text-xs/sm/base/lg/xl/2xl`) は font-size と line-height がセット定義されているので、行間も自然に揃う - 不要な arbitrary value (`text-[Npx]`) は CSS の出力サイズも増やす ### 確認方法 ```bash cd ui # 禁止サイズが残っていないか確認 — 何も出なければ OK grep -rohE 'text-\[(9|11|12|12\.5|14|15|16|17|18)px\]' src --include='*.tsx' # 全体のサイズ分布を確認 grep -rohE 'text-\[[0-9]+(\.[0-9]+)?px\]|text-(xs|sm|base|lg|xl|2xl|3xl|4xl|2xs)' src --include='*.tsx' | sort | uniq -c | sort -rn ``` 期待される分布 (Tailwind utility が主、arbitrary は 10px / 13px のみ): ``` text-xs (~250 ↑) text-2xs (~180 ↑) text-sm (~100 ↑) text-[13px] (~100) text-[10px] (~100) text-base, text-lg, text-xl, text-2xl, text-4xl ... (少量) ``` ### 触る場所 - 新規 UI コンポーネント全般 (`ui/src/**/*.tsx`) - 既存パネルへの追加要素 - 設定フォーム (`ui/src/components/settings/`) - ダイアログ・モーダル --- ## 12. SSH 関連変更時 (Phase 0-8) ### 12-A. SSH ツール (`SshExec` / `SshUpload` / `SshDownload`) を変更したとき **対象ファイル:** - `src/engine/tools/ssh.ts` — TOOL_DEFS + executeTool + 12-step orchestration - `src/engine/tools/ssh.test.ts` — unit tests (23 件) - `src/engine/tools/ssh.e2e.test.ts` — in-process ssh2 server e2e (4 件、SKIP_SSH_E2E=1 で skip) - `src/engine/tools/index.ts` — `tryLoadModule('./ssh.js')` 経由のロード + dispatch - `src/engine/tools/core.ts` — `ToolContext.pieceName` (piece grants check に必要) - `src/engine/piece-runner.ts` — `ToolContext` 構築時に `pieceName: piece.name` を thread - `src/engine/tools/docs.ts` — `TOOL_DOC_ALIASES` で 3 ツール名を `'ssh-tools'` にマップ - `docs/tools/ssh-tools.md` — LLM 向け詳細ドキュメント - `src/bridge/server.ts` — `setSshSubsystem({...})` で DI **新ツールを SSH 系で追加する場合、または既存ツールに引数を増やす場合:** - ssh.ts の `TOOL_DEFS` description は **1 文 + 「詳細は ReadToolDoc」** に絞る (毎呼び出しのトークン節約) - preflight (steps 1-7) を流用するため `runExec`/`runUpload`/`runDownload` の skeleton を踏襲 - `finally` で `clearBuffer(pem, passphrase)` を必ず実行 — ssh2 の internal retention は防げないが我々の copy は zeroize する - 監査 action 名は `ssh.` 形式 (例: `ssh.exec`, `ssh.upload`)。command 全文は **記録せず SHA-256 16-char hex** を `detail.command_hash` に - piece grants check は `subsystem.accessResolver.resolve()` を必ず通す (piece membership → access decision → state checks → policy の順) **新しい SshSessionError code を追加する場合:** - `src/ssh/session.ts` の `SshSessionErrorCode` union を拡張 - `src/engine/tools/ssh.ts` の `formatSessionError` switch に LLM-actionable message を追加 - `docs/tools/ssh-tools.md` の「共通エラーコード一覧」表に追記 - `docs/ssh.md` の「Troubleshooting → Symptom → cause table」に追記 **確認方法:** ```bash # tool が grants check を通っているか grep -n "accessResolver\.resolve\|preflight" src/engine/tools/ssh.ts # 監査 action 名が既存と整合 grep -n "action:" src/engine/tools/ssh.ts src/bridge/ssh-api.ts | sort -u # tests npx vitest run src/engine/tools/ssh.test.ts SKIP_SSH_E2E= npx vitest run src/engine/tools/ssh.e2e.test.ts # 明示的に run ``` ### 12-B. SSH HTTP API (`src/bridge/ssh-api.ts`) を変更したとき **対象ファイル:** - `src/bridge/ssh-api.ts` — User router + Admin router (25 endpoints) - `src/bridge/ssh-api.test.ts` — auth / maintenance / reason / CRUD / TOFU / grants / rotation - `src/ssh/maintenance.ts` — in-memory maintenance flag - `src/ssh/admin-rate-limit.ts` — token bucket for force-unlock (10/hr) - `src/bridge/server.ts` — `ssh.enabled` ゲートで両 router をマウント - `src/ssh/session.ts` — `sshTest()` (HTTP test endpoint で使用) - `docs/ssh.md` §"HTTP API Reference" — endpoint table **新しい admin write エンドポイントを追加するとき:** - `body.reason` 必須 (≥ 8 chars)。`validateReason()` を必ず呼ぶ - 操作直前に `maintenance503()` ガードを呼ぶ (read 系は不要) - 成功・失敗どちらも `auditRepo.beginAndComplete()` で `ssh_audit_log` に記録 - 監査 action は `src/ssh/audit-repo.ts` の `SshAuditOutcome` と Phase 5 設計書の enum を踏襲 - admin-only フラグ (`allow_remote_unrestricted`, `allow_private_addresses` per-connection) は user 経路で常に reject **新しい user write エンドポイントを追加するとき:** - `getUserId()` で取得した userId をオーナーチェックに使う - 他人の所有物に対しては `404` を返す (存在を漏らさない) **確認方法:** ```bash # admin endpoint で reason check / maintenance ガードが漏れていないか grep -n "deps.requireAdmin\|maintenance503\|validateReason" src/bridge/ssh-api.ts # audit log の action 名が既存と整合しているか grep -n "action:" src/bridge/ssh-api.ts ``` ### 12-C. SSH repo / schema を変更したとき **対象ファイル:** - `src/db/schema.sql` — `ssh_connections`, `ssh_connection_grants`, `ssh_audit_log`, `ssh_abuse_counters`, `system_deks` - `src/db/migrate.ts` — `PRAGMA table_info` → 列存在チェック → `ALTER TABLE ADD COLUMN` パターン (バージョン管理テーブル不使用) - `src/ssh/connection-repo.ts` / `grants-repo.ts` / `audit-repo.ts` / `abuse-repo.ts` / `access.ts` - 各 repo に対応する `*.test.ts` - `src/ssh/recovery.ts` — boot 時 pending → aborted sweep - `src/ssh/crypto.ts` — DEK ラップ / マスター鍵検証 - `docs/ssh.md` §"Connection Model" / §"Access Grants" / §"Audit Log" **スキーマ列を追加するとき:** - `schema.sql` の `CREATE TABLE` に列追加 (初期スキーマ) - `migrate.ts` に冪等 ALTER TABLE 追加 (既存 DB 用) - 既存テストが影響を受けないか `npm test` で検証 - 列をエンドポイントから書く場合、`POST/PATCH` ハンドラのバリデーションに追加し、admin-only フラグなら user 経路で reject **新しい監査 action を追加するとき:** - `src/ssh/audit-repo.ts` の `SshAuditOutcome` は触らない (固定 5 値: `pending|success|failed|denied|aborted`) - action 名は `ssh..` 形式 (例: `ssh.connection.host_key.replace`) - `docs/ssh.md` §"Audit Log → Actions" 表に追加 - `docs/tools/ssh-tools.md` の「監査ログ」セクションは tool 経由 action のみなので、tool 起因なら追記 **確認方法:** ```bash # 監査 action 一覧 (重複/typo 検出) grep -rn "action: 'ssh\." src/ | awk -F"'" '{print $2}' | sort -u # repo CAS / transaction が崩れていないか grep -n "BEGIN\|COMMIT\|prepare(" src/ssh/*.ts | head -30 ``` ### 12-D. SSH UI を変更したとき **対象ファイル:** - ユーザー向け: - `ui/src/components/userfolder/SshConnectionsPanel.tsx` — 一覧 + actions - `ui/src/components/userfolder/SshConnectionForm.tsx` — 作成・編集フォーム - `ui/src/components/userfolder/SshHostKeyDialog.tsx` — TOFU verify ダイアログ - 管理者向け: - `ui/src/components/settings/SshForm.tsx` — Settings → SSH のルート - `ui/src/components/settings/SshGlobalConnectionsForm.tsx` — グローバル接続 CRUD - `ui/src/components/settings/SshGrantsForm.tsx` — grants CRUD - `ui/src/components/settings/SshAuditLog.tsx` — 全テナント監査ログ - `ui/src/components/settings/SshMasterKeyRotationForm.tsx` — 鍵 rotation - 型定義: `ui/src/lib/ssh-types.ts` - `ui/src/components/settings/ConfigForm.tsx` / `SettingsSidebar.tsx` — `SshForm` の登録 (新規 Form 追加時) **確認方法:** - 新 form を追加した場合 `ConfigForm.tsx` の section リストに登録、`SettingsSidebar.tsx` のナビ項目に追加 - ssh-types.ts と API レスポンス shape (`SshConnection`, `SshGrant`, `SshAuditRow`) が一致しているか - 禁止フォントサイズ (`text-[11px]` 等) を導入していないか — 既存セクションの「UI フォントサイズスケール」参照 ### 12-E. SSH 接続の認可 (workspace connection scoping) を変更したとき SSH 接続の認可は **piece ではなくワークスペース**で決まる(設定→SSH の登録接続)。 piece の `allowed_ssh_connections` は撤去済み。接続スコープは worker が解決して `workspaceSshConnections` として piece-runner に渡す。 **対象ファイル:** - `src/worker.ts` — `parsedPolicy.enabledSensitive` に `ssh` があるとき `listBySpace(spaceId)` で接続 ID を解決(`workspaceSshConnections`) - `src/engine/piece-runner.ts` — `workspaceSshConnections ?? []` を `ctx.allowedSshConnections` / `movement.allowedSshConnections` に流す - `src/engine/tools/ssh.ts` — preflight で `ctx.allowedSshConnections` に対して接続 ID を検証 - `docs/ssh.md` §"接続の認可" **規約:** - 接続が 1 つも登録されていない(空配列)→ SSH ツールはクリーンに reject - `'*'` ワイルドカードは worker からは渡さない(常に明示 ID リスト = クロススペース分離保証) **確認方法:** ```bash # worker が ssh 接続を解決している経路 grep -n 'workspaceSshConnections' src/worker.ts src/engine/piece-runner.ts ``` ### 12-F. config.yaml の SSH セクション (`SshRuntimeConfig`) を変更したとき **対象ファイル:** - `src/ssh/config.ts` — `SshRuntimeConfig` interface + `SSH_DEFAULTS` + `mergeSshConfig` - `src/config.ts` — `transformKeys` でスネーク→キャメル変換、`AppConfig` に統合 - `config.yaml.example` — コメント付きデフォルト値 - `ui/src/components/settings/SshForm.tsx` — UI で編集可能にする場合 - `docs/ssh.md` §"`config.yaml` Reference" **確認方法:** ```bash # config.ts と config.yaml.example の SSH キー数が一致するか grep -E '^\s+[a-z_]+:' config.yaml.example | grep -A 30 '^# ssh:' | head -40 ``` ### 12-G. SSH Console (interactive PTY) を変更したとき SSH Console は SshExec/Upload/Download とは別系統の対話的 PTY 経路。Phase 8 で新規追加。 - 新ツール SshConsoleEnsure / SshConsoleSend / SshConsoleSnapshot を追加した時: - [ ] `src/engine/tools/ssh-console.ts` の `TOOL_DEFS` に追加 - [ ] `src/engine/tools/index.ts` の dispatch + aggregation に追加 - [ ] `src/engine/tools/docs.ts` の `TOOL_DOC_ALIASES` にエイリアス - [ ] `docs/tools/ssh-console-tools.md` を更新 - 新 WS endpoint を追加した時: - [ ] `src/bridge/server.ts` に upgrade handler 配線 - [ ] auth / visibility / access の 4 段階チェックを記述 - 新 `SessionCloseReason` を追加した時: - [ ] `src/ssh/console-protocol.ts` (server) と `ui/src/lib/ssh-console-types.ts` (UI) の両方に追加 - [ ] `ConsoleHeader.tsx` の disconnected reason 表示に文言を追加 ## 13. Scheduler から呼ばない手動オペレーション endpoint 以下の endpoint は **UI からの手動操作専用** で、scheduler / Routine / 自動化経路から起動できない設計になっている。Routine 側の payload schema にこれらの override を追加してはいけない (scheduled task のツール境界〈ワークスペースのツールポリシーで決まる〉が想定外に拡大するリスクのため)。 - `POST /api/local/tasks/:id/continue` — 別 piece で task を続ける (handoff) - 実装: `src/bridge/local-tasks-api.ts` の `/continue` ハンドラ - terminal 状態 (`succeeded` / `failed` / `waiting_human` / `cancelled`) のときのみ有効 - 新カテゴリを追加する場合: `scheduled-tasks-api.ts` / `scheduler.ts` 側に「実行する piece を override する」機能を持たせない。実行する piece は task 作成時に固定し、人間が UI から明示的に切り替える経路だけ残す **確認方法:** ```bash # scheduler 経路から /continue を叩いている箇所が無いか grep -rn "/continue\b\|continueTaskWithPiece" src/scheduler.ts src/bridge/scheduled-tasks-api.ts # 無いことが期待値 ``` --- ## 14. Knowledge Notes 機能 (削除済み) 共有 Markdown ノート + 購読 + `SearchNotes` / `ReadNote` / `WriteNote` ツールからなる Knowledge Notes 機能は 2026-06 に削除された (共有ワークスペースと役割が重複したため)。 `src/notes/`・`src/bridge/notes-api.ts`・`src/engine/notes-inject.ts`・`NotesForm.tsx`・ `note_index` / `note_subscriptions` / `pending_reindex` テーブルはすべて撤去済み。 既存 DB に残る孤立テーブルは無害なので DROP していない。再導入の予定はない。 --- ## Bash Unrestricted モード (`safety.bash_unrestricted`) の変更時 **対象ファイル:** - `src/engine/tools/sandbox.ts` — bwrap サンドボックスのマウント構成・実行ロジック - `src/engine/tools/core.ts` — `executeBash` の分岐 (`ctx.bashUnrestricted`)、`ToolContext` インターフェース - `src/engine/piece-runner.ts` — `safetyConfig.bashUnrestricted` → `ToolContext` への伝播 - `src/worker-bootstrap.ts` — 起動時 bwrap 可用性チェック - `src/config.ts` — `SafetyConfig.bashUnrestricted` 定義・バリデーション **なぜ必要か:** bwrap のマウント構成を変えた場合、セキュリティ境界が変わる。テスト (`sandbox.test.ts`, `core.test.ts` の `bashUnrestricted mode` セクション) を必ず更新すること。 --- ## 15. A2A 認可サーバー (`src/bridge/a2a/`) 外部エージェント連携(A2A)の OAuth2 認可サーバー関連を変更するときに連動が必要な箇所。 ### DB テーブル・スキーマを変更するとき - `src/db/schema.sql` の `CREATE TABLE`(`oidc_models` / `a2a_clients`)を更新(初期スキーマ) - `src/db/migrate.ts` に冪等 `ALTER TABLE ADD COLUMN` を追加(既存 DB 用)。**dual-path 必須**(どちらか片方だけ更新するとテストが大量に落ちる) - `src/db/repository.a2a.ts` / `src/bridge/a2a/oidc-adapter.ts` のクエリを合わせて修正 ### `config.yaml` の `a2a` セクションを変更するとき - YAML キーはスネークケース (`client_ttl_seconds`)、コード内はキャメルケース (`clientTtlSeconds`) - `src/config.ts` の `transformKeys` が自動変換するため、読み取り側では キャメルケースのみ使う - `config.yaml.example` にコメント付きで追記する - `A2aConfig` interface(`src/config.ts`)に対応フィールドを追加 ### A2A ルーターを変更するとき - `createConsentRouter` / `createA2aClientsAdminRouter` / `mountA2aOidc` はいずれも `src/bridge/server.ts` でマウントされる。新しい router を追加した場合は `server.ts` の配線を忘れずに - 管理者専用エンドポイントは `requireAdmin` ミドルウェアを必ず通すこと - ユーザー向け同意エンドポイントは `requireAuth` を通し、操作対象の `sub`(subject)をセッションの userId と照合する ### A2A 公開スキル(スペース許可リスト)を変更するとき - 公開スキルは `spaces.a2a_skills`(JSON 配列カラム)に保存される。スキーマ変更時は `schema.sql` と `migrate.ts` の**両方**(dual-path 必須)を更新すること - 委任は `a2a_delegations` テーブルで管理し、`grant_id` で OAuth grant(`oidc_models`)にリンクする。`grant_id` の整合性は外部キーではなくアプリ層で保証しているため、grant 削除時は対応する delegation も削除すること - 委任フィルタは **`buildVisibilityWhere` の OR 句に追加しない**。タスク/ジョブの公開スコープ(private/org/public)とは独立したフィルタとして AND 交差で適用すること(OR に混ぜると cross-space IDOR の原因になる) - Agent Card の公開エンドポイント(`GET /.well-known/agent.json`)とスペースオーナー向け API(公開スキル設定 PATCH)は、いずれも `a2a.enabled` ゲートを通過した場合のみ有効。ゲートを外したルートを追加しないこと ### A2A スキル実行(executor)を変更するとき - `a2a_tasks` テーブルは `schema.sql` と `migrate.ts` の**両方**(dual-path 必須)を更新すること - executor は `createLocalTask` → `createJob` の順に呼ぶ。`createLocalTask` 単体はジョブをエンキューしない(`createJob` で初めて Worker がポーリングで拾える状態になる) - ジョブ完了の検知はポーリング(`a2a_tasks` の `status` カラムを定期参照)で行い、進捗は SSE で外部エージェントに返す - 実行完了後、`output/` 以下のファイルは A2A Artifact として返却する - 委任スコープは executor でも `computeEffectiveScope` を通じて再強制する。トークンの委任スコープがスペースの公開スキル設定を上回ることは許容しない(fail-closed) - `buildVisibilityWhere` の OR 句に委任条件を追加しないこと(AND 交差で適用する) ### 非ブロッキング送信 + reconciler(`reconciler.ts` / `task-finalize.ts`)を変更するとき - **非ブロッキング契約**: `message/send` の `params.configuration.blocking: false` は SDK ネイティブ機能。executor が初期 Task(`submitted`)を publish した時点で SDK が即座にレスポンスを返す(完了を待たない)。外部エージェントは後から `tasks/get`(`params.id`)で最新状態を取得する。executor 自体を非ブロッキング化する改造は不要(SDK 側で完結)。 - **reconciler は単一起動**: `A2aTaskReconciler` は `server.ts` で `a2a.enabled` ゲート内から 1 インスタンスだけ `start()` する(多重起動は state churn の原因)。テストでは `start()` の interval を使わず `reconcileOnce()` を明示的に呼んで決定論化すること。 - **DB が source of truth**: reconciler は `a2a_tasks`(非 terminal 行)を `listNonTerminalA2aTasks` で走査し、リンク先の MAESTRO ジョブ状態(`getJob`)と突き合わせて収束させる。live executor が切断・再起動で消えても、reconciler だけでタスクを terminal 化できる(再起動耐性)。in-memory の per-task detached promise には依存しない。 - **終端化ロジックは共有**: `enumerateOutputArtifacts` / `finalizeStatusFromJob` / `TERMINAL_JOB_STATUSES`(`task-finalize.ts`)を executor と reconciler の**両方**が使う。ジョブ状態 → A2A state の写像や artifact 列挙を変えるときは、この共有ヘルパだけを直せば両経路に反映される(ロジックを片方に複製しないこと)。 - **冪等な状態一致スキップ**: reconciler は `payload.status.state` が算出後の state と一致する場合は再書き込みしない(`waiting_human → input-required` の churn 防止)。`saveA2aTask` は task id の upsert なので executor と reconciler が競合しても二重書きは安全。 - **委任カラムの保持**: reconciler が `saveA2aTask` を呼ぶ際は `payload.metadata` の `a2aDelegationId` / `a2aGrantId` / `a2aActingUserId` を `a2a_tasks` の `delegation_id` / `grant_id` / `acting_user_id` 列へ写す(`task-store.ts` の save 規約と同一)。列を増やすときは executor の初期 Task metadata・`SqliteA2aTaskStore.save`・`reconciler.persistTask` の 3 箇所を揃えること。 **確認方法:** ```bash # A2A 関連テスト一括実行 npx vitest run src/bridge/a2a/ src/db/migrate.test.ts src/db/repository.a2a.test.ts src/db/repository.a2a-deleg.test.ts # router のマウント漏れを確認 grep -n "mountA2aOidc\|createA2aClients\|createConsent" src/bridge/server.ts # buildVisibilityWhere に委任条件が混入していないか確認 grep -n "a2a_delegations\|a2a_skills" src/db/repository.ts ``` --- ## 16. OSS リリース(`scripts/oss-sync.sh --push`)を実行するとき - **スクリーンショットの目視確認(必須)**: `docs/screenshots/*.png` はバイナリのため、release gate(`oss/forbidden.txt` の正規表現スキャン)では**描画内容を検査できない**。スクリーンショットを更新した場合は、push 前に各画像を開いて以下が写り込んでいないか目視で確認する - 内部ホスト名・URL(Gitea / 社内サービスのアドレスバー等) - 実在のタスクタイトル・会話内容・ファイル名 - ユーザー名・メールアドレス・組織名 - 資格情報やトークンの断片(設定画面のマスク漏れ) - 撮り直しの手順は README スクリーンショット刷新時のレシピ(隔離 worktree + シードデータ)に従い、実データ環境のキャプチャをそのまま使わない --- ## 17. 承認パーク型 META ツール(RequestTool / RequestPackage)を追加・変更する場合 エージェントが「これが欲しい」と申告 → 人が承認 → ジョブ再開、という停車型ツールの配線点。RequestPackage(PR3)は RequestTool を全面ミラーしている。片方だけ直すと不整合になるので、両方を並べて確認すること。 **対象ファイル(RequestPackage を例に):** - `src/engine/tools/package-request.ts` — ツール本体(`TOOL_DEFS` + `executeTool`)。バリデーションは `engine/python-packages.ts` の `parseAndValidateSpec` / `assertNotShadowing` を再利用 - `src/engine/tools/core.ts` — `ToolContext` に park フラグ(`pendingPackageApproval`)+ recorder(`recordPackageRequest`)+ 補助状態(`installedPackages` / `declinedPackages`) - `src/engine/tools/index.ts` — module loader / `Object.assign(allDefs, ...)` / **`META_TOOLS` 配列(2 箇所)** / dispatch 分岐 - `src/engine/tools/tool-categories.ts` — `META_TOOLS` Set にツール名を追加 - `src/engine/agent-loop/pending-states.ts` — `buildPackageApprovalWaitResult`(park → `next:'WAITING_HUMAN_PACKAGE_REQUEST'`) - `src/engine/agent-loop.ts` — import + `buildToolApprovalWaitResult` の隣で呼ぶ - `src/engine/piece-runner.ts` — `WAITING_HUMAN_PACKAGE_REQUEST` sentinel 分岐(waiting_human park)+ **2 つの PieceRunOptions interface** に recorder/補助状態を追加 + ctx 構築で piece/movement を注入 - `src/worker.ts` — recorder を task/job/space にバインド(feature enabled かつ space 解決可のときのみ)+ waiting_human 分岐で **`wait_reason='package_request'` を永続化**(忘れると resume がマッチせず永久停止) - **DB(三重ミラー)**: `src/db/schema.sql` / `src/db/migrate.ts` / `src/db/repositories/schema.ts`(`package_requests` テーブル)+ `src/db/repositories/tool-requests.ts`(record/get/list/decide/revert/listDeclined + 型 + rowMapper)+ `src/db/repositories/jobs.ts`(`resumePackageRequestJob` + `createJobIfNoPending` の承認ポーズ判定)。実装は `repositories/*` に置き、`src/db/repository.ts` ファサードに委譲メソッドを足す(`repository.ts` は肥大分割済みの薄いファサード) - `src/bridge/local-tasks-package-requests-api.ts` — GET 一覧(view 権限)+ POST decide(**task write 権限= owner/admin/space editor のみ**、エージェント自己承認不可)。承認時のインストールは `space-python-packages-service.ts` の共有パスを通す - `src/bridge/local-tasks-api.ts` — ルート登録 + `LocalTasksApiOptions` に config アクセサ / `server.ts` で配線 - `ui/src/components/chat/PackageRequestApproval.tsx` + `ChatPane.tsx` + `ui/src/api/tasks.ts` + i18n `chat.json`(ja/en) - `docs/tools/requestpackage.md`(ReadToolDoc)+ ヘルプ + changelog **なぜ必要か:** 停車系は「ジョブ停車=ワーカー解放」で成り立つ(ツール内同期ブロックはデッドロック)。sentinel(`WAITING_HUMAN_*`)は tool → pending-states → agent-loop → piece-runner → **worker(wait_reason 永続化)** の全経路が揃って初めて機能する。承認 API は必ず write 権限ゲートを通し、インストールは共有サービス(bwrap 必須 fail-closed / wheels のみ / 固定 index / shadowing 拒否)経由に限定する。 --- ## 自動検知の可能性 - **ツールモジュール登録漏れ**: `index.ts` と `tools-api.ts` のモジュール一覧を比較するスクリプトで CI チェック可能 - **code-review-graph**: `importers_of` で各ツールモジュールの参照元を列挙できるが、「tools-api.ts にも登録すべき」というルールの自動適用は困難。変更時の `detect_changes` + `get_impact_radius` で影響範囲の見落としを防ぐ用途が現実的