計画v2.1: B-119 ディレクトリ構造リファクタリング実施計画(レビューv2全指摘反映版)
B-119 ディレクトリ構造リファクタリング実施計画 v2.1
本計画は、計画v2(19c976d8fd7)にレビューv2(19c9772c0a3)の全指摘(Major 3件、Minor 5件)を反映した修正版である。変更箇所には【v2.1修正】タグを付記している。
1. ゴール
1-1. 誰のために
プロジェクトのコードを読み書きするAIエージェント、および将来の人間の開発者のため。
1-2. 提供する価値
- フィーチャー単位の凝集度向上:機能の追加・修正・削除が1ディレクトリ内で完結する
- 新規フィーチャー追加のテンプレートが明確になる(「新しいコードをどこに置くか」の判断基準ドキュメント作成)
- コードベースの見通し改善によりAIエージェントの作業精度と速度が向上する
- 設計アンチパターン5件を修正し、コードの健全性を回復する
- 結果としてサイトの品質改善サイクルが加速し、PV向上に寄与する
1-3. 完成の定義
- 全フィーチャーのコード(ロジック、コンポーネント、テスト)がフィーチャー単位でコロケーションされていること
- src/components/ には common/ と search/ のみが残ること
- src/lib/ にはフィーチャー固有でない共有ユーティリティのみが残ること
- src/data/ が「共有データ」と「フィーチャー固有データ」に適切に整理されていること
- 調査で発見された設計アンチパターン5件がすべて修正されていること
- npm run typecheck, npm run test, npm run build がすべてパスすること
- npm run lint および npm run format:check がパスすること(各フェーズでも確認)
- pre-commit hookがすべてパスすること
- 既存のURL構造に変更がないこと
- path alias @/* -> ./src/* の設定が変更されていないこと
- scripts/generate-puzzle-schedule.ts が正しいパスを参照し動作すること
- 「新しいフィーチャーを追加するときのガイド」ドキュメントが作成されていること
- アーキテクチャ決定記録(ADR)が作成されていること
2. アーキテクチャ決定
2-1. 採用パターン: パターンC「ハイブリッド型」
深層調査(19c976845ac)で6パターンを7観点で評価した結果、パターンCが総合スコア26/35で最高評価を獲得した。
2-2. 選定根拠(定量的データに基づく)
パターンC(ハイブリッド型)を選定する理由:
現在の成功パターンの自然な拡張: src/tools/ は既に32ツールが Component, logic, meta, CSS, tests のコロケーションに成功しており(コロケーション度: 最良)、src/cheatsheets/ も同様。この成功パターンを他のフィーチャーに展開するのが最も一貫性が高い。
段階的移行が可能(リスク分散): パターンBは500+ファイルの一括移行が必要だが、パターンCはフィーチャー単位で段階的に移行可能。1回あたり15〜80ファイルの作業量に収まり、各ステップで検証できる。
最大のペインポイントを解決: games(154ファイル/4箇所散在)が最大の問題であり、パターンCでこれを完全に解決できる。
Next.js親和性が高い: Next.js公式戦略1(appの外にプロジェクトファイルを配置)の自然な発展形。app/ はルーティング専用のままで、既存の構造と整合性がある。
将来のスケーラビリティ: フィーチャー数が15-20に増えてsrc/直下が煩雑になった場合、パターンCからパターンB(features/配下に集約)への移行は比較的容易。各フィーチャーディレクトリをfeatures/直下に移動するだけでよい。
パターンBを不採用とする理由:
- 移行コスト最大(500+ファイル)で、理論上の優位性(コロケーション5/5)がパターンC(4/5)と実質的に僅差
- features/ という追加のネスト層がNext.jsのルーティング構造(app/)との間に不要な乖離を生む
- 現在のtools/は既にsrc/直下で成功しており、これをfeatures/tools/に移動するのは既存の成功を不必要に壊す
パターンAを不採用とする理由:
- gamesの4箇所散在(154ファイル)問題が解決されず、長期的にメンテナンス効率が低下し続ける
2-3. 最終ディレクトリ構造
前回計画からの変更点:
- features/ ネームスペースを使用しない(パターンCはsrc/直下にフィーチャーを配置)
- src/data/ は廃止せず、共有データ専用として維持(shared-data/ へのリネームは行わない)
- src/components/search/ は共有層に残す
- src/content/blog/ はそのまま
- ツール数は正確に32ディレクトリ
src/
app/ # ルーティング層(変更なし)
blog/
cheatsheets/
colors/
dictionary/
games/
memos/
quiz/
tools/
...
tools/ # 変更なし(既にコロケーション済み: 32ツール)
[各ツール32個]/ # Component.tsx, Component.module.css, logic.ts, meta.ts, __tests__/
_components/ # components/tools/ から移動(ToolCard, ToolLayout, ToolsGrid等)
registry.ts
types.ts
cheatsheets/ # 変更なし + _components統合
[各チートシート]/ # Component.tsx, meta.ts
_components/ # components/cheatsheets/ から移動
registry.ts
types.ts
games/ # 新設: lib/games/ + components/games/ を統合
kanji-kanaru/
_components/ # GameContainer, GameBoard, GuessInput等 + CSS
_lib/ # engine, daily, storage, share, types + __tests__
data/ # puzzle-schedule.json(ゲーム固有データ)
yoji-kimeru/
_components/
_lib/
data/ # yoji-schedule.json
nakamawake/
_components/
_lib/
data/ # nakamawake-data.json, nakamawake-schedule.json
irodori/
_components/
_lib/
data/ # irodori-schedule.json
shared/
_components/ # CountdownTimer, GameDialog等 + __tests__
_lib/ # crossGameProgress, share等 + __tests__
# webShare.ts は lib/ に移動(アンチパターン修正)
registry.ts
types.ts
dictionary/ # 新設: lib/dictionary/ + components/dictionary/ を統合
_components/ # DictionaryCard, SearchBox, CategoryNav等 + __tests__
_lib/ # kanji.ts, yoji.ts, colors.ts, types.ts + __tests__
# index.ts は削除(アンチパターン修正: 未使用)
quiz/ # 新設: lib/quiz/ + components/quiz/ を統合
_components/ # QuizContainer, QuestionCard, ShareButtons等
_lib/ # scoring.ts + __tests__
data/ # クイズデータファイル
registry.ts
types.ts
blog/ # 新設: lib/blog.ts + components/blog/ を統合
_components/ # BlogCard, BlogListView, BlogLayout等
# BlogListView.tsx のCSS依存をアンチパターン修正
_lib/ # blog.ts(lib.ts ではなく元のファイル名を維持)
memos/ # 新設: lib/memos*.ts + components/memos/ を統合
_components/ # MemoCard, MemoFilter等
_lib/ # memos.ts, memos-shared.ts
content/ # コンテンツデータ(変更なし)
blog/ # ブログMarkdownファイル
data/ # 共有データ(data/ のまま維持)
kanji-data.json # dictionary + games が共用
yoji-data.json # dictionary + games が共用
traditional-colors.json # dictionary + games が共用
components/ # 共有コンポーネント
common/ # Header, Footer, Breadcrumb, Pagination等
search/ # SearchTrigger, SearchModal等
lib/ # 共有ユーティリティ
constants.ts
cross-links.ts
date.ts
feed.ts
feed-memos.ts
markdown.ts
ogp-image.tsx
pagination.ts
search/ # build-index.ts等
seo.ts
webShare.ts # games/shared/ から移動(アンチパターン修正)
__tests__/ # constants, date, markdown, pagination, ogp-image, seo等のテスト
# + webShare.test.ts(games/shared/__tests__/ から移動)
types/ # サードパーティ型定義(変更なし)
test/ # テストセットアップ(変更なし)
2-4. ディレクトリの責任と配置ルール(「新しいコードをどこに置くか」の判断基準)
| 分類 | ディレクトリ | 責任 | 配置すべきもの |
|---|---|---|---|
| フィーチャー | src/{feature-name}/ | 1つのフィーチャーの全コード | そのフィーチャー固有のコンポーネント、ロジック、型、テスト、データ |
| ルーティング | src/app/{route}/ | Next.jsルーティングとページエントリ | page.tsx, layout.tsx, opengraph-image.tsx のみ。ビジネスロジックは置かない |
| 共有コンポーネント | src/components/common/ | 2つ以上のフィーチャーから使われるUIコンポーネント | Breadcrumb, Pagination, ShareButtons等 |
| 基盤コンポーネント | src/components/search/ | アプリ全体の基盤UI機能 | 検索モーダル、検索入力等 |
| 共有ユーティリティ | src/lib/ | フィーチャー横断の汎用ロジック | constants, date, seo, pagination, markdown等 |
| 共有データ | src/data/ | 複数フィーチャーから参照されるデータファイル | 辞典+ゲーム共用JSON |
| コンテンツ | src/content/ | 非コード資産(Markdownファイル等) | ブログ記事MD |
新しいフィーチャーを追加する場合の判断基準:
- src/{feature-name}/ にディレクトリを作成する
- _components/, _lib/, data/ 等のサブディレクトリは必要に応じて作成
- registry.ts と types.ts を作成し、既存のregistryパターンに従う
- app/{route}/ にルーティングファイル(page.tsx, layout.tsx)を作成
- 他フィーチャーのコードを直接importしない。共有が必要なら lib/ に昇格させる
- 2つ以上のフィーチャーから使われるUIコンポーネントは components/common/ に配置する
「このコードは共有層に置くべきか?」の判断基準:
- 現在1フィーチャーのみ利用 -> フィーチャーディレクトリ内に配置
- 2つ以上のフィーチャーから利用 -> lib/ または components/common/ に昇格
- アプリ全体の基盤機能(検索、SEO、フィード等) -> lib/ に配置
2-5. 将来のスケーラビリティ対応
現時点でsrc/直下のフィーチャーディレクトリは8個(tools, cheatsheets, games, dictionary, quiz, blog, memos + content)。将来フィーチャー数が15-20に増えた場合の対応方針:
- src/features/ ディレクトリを新設し、全フィーチャーディレクトリをfeatures/配下に移動(パターンB相当)
- この移行はフィーチャーディレクトリの「移動」のみで、内部構造の変更は不要
- 現時点では過剰な抽象化を避け、必要になった時点で判断する
共有データが増えた場合の方針:
- 現在のdata/内の共有データは3ファイル(kanji-data, yoji-data, traditional-colors)のみ
- 将来共有データが増えた場合もdata/に配置する。10ファイルを超える場合はdata/内にサブディレクトリで分類を検討
3. 設計アンチパターンの修正計画
調査で発見された5件のアンチパターンに対する修正方針を以下に定める。
AP-1: BlogListView.tsx -> app/blog/page.module.css(レイヤー逆転)
問題: コンポーネント層がルーティング層のCSSに依存している 修正方針: page.module.css のうち BlogListView が参照しているスタイルを blog/_components/BlogListView.module.css として抽出する。app/blog/page.module.css からは BlogListView 固有のスタイルを除去し、必要なスタイルがあれば BlogListView.module.css に移動する 実施タイミング: フェーズ6(blog移行時)
AP-2: Footer.tsx -> lib/games/registry(共有層->フィーチャー依存)
問題: 共有コンポーネント(Footer)が特定フィーチャー(games)に直接依存している
【v2.1修正: M-3反映 -- 設計詳細を追記し、選択肢Aの採用とその根拠を明記】
検討した選択肢:
- 選択肢A: layout.tsx 経由でpropsとして渡す -- layout.tsx が games/registry から allGameMetas をインポートし、{ href: string; label: string }[] の形に変換してからFooterにpropsで渡す。Footer.tsx は games/registry への直接依存を持たない純粋な共有コンポーネントになる。ただし layout.tsx にフィーチャー依存が移動する。
- 選択肢B: Footer内のゲーム一覧を静的ハードコード -- SECTION_LINKS のゲームセクションに /games/kanji-kanaru 等のリンクを直接記述する。games/registry への依存が完全に消えるが、新ゲーム追加時にFooterの手動更新が必要になる。
採用: 選択肢A(layout.tsx 経由のprops)
根拠:
- layout.tsx はアプリのルーティングルート定義であり、全フィーチャーの存在を把握している場所として、フィーチャーへの依存を持つのは構造上自然で許容できる。Header/Footer にナビゲーションデータを渡すのは layout.tsx の責務として適切。
- 選択肢Bは新ゲーム追加時にFooterの更新を忘れるリスクがある。ゲーム一覧がregistryから自動生成される現在のメリットを維持すべきである。
- Footer.tsx のインターフェースは現在の引数なし関数から、gameLinks?: { href: string; label: string }[] をオプショナルpropsとして受け取る形に変更する。
具体的な実装方針:
- Footer.tsx の型: interface FooterProps { gameLinks?: { href: string; label: string }[] }
- gameLinks が渡されない場合のフォールバックとして、「ゲーム一覧」リンクのみ表示する(後方互換性のため)
- layout.tsx で allGameMetas.map(g => ({ href: getGamePath(g.slug), label: g.title })) を生成し、Footer に渡す
- Footer.tsx から @/lib/games/registry のimport文を完全に除去する
実施タイミング: フェーズ1(games移行時、パスが変わるタイミングで同時修正)
AP-3: quiz/ShareButtons.tsx -> games/shared/webShare(フィーチャー間依存)
問題: quiz フィーチャーが games フィーチャーのロジックに依存している。webShare.ts の内容(Web Share API判定、共有実行)は完全に汎用的であり、games名前空間にある理由がない 修正方針: webShare.ts を src/lib/webShare.ts に移動し、共有ユーティリティとして扱う。games/shared/ と quiz/ の両方から lib/webShare を参照するように変更する 実施タイミング: フェーズ0(前準備として共有ユーティリティの整理)
AP-4: lib/dictionary/index.ts が未使用
問題: re-exportモジュールが存在するが、誰もインポートしていない(全消費者が直接 kanji.ts, yoji.ts 等をインポート) 修正方針: index.ts を削除する 実施タイミング: フェーズ5(dictionary移行時)
AP-5: lib/seo.ts -> 3フィーチャーのtypesに依存
問題: 共有ユーティリティが tools/types, cheatsheets/types, quiz/types に依存している 修正方針: 現時点では型のみの依存(import type)でランタイムへの影響はなく、seo.ts がフィーチャーごとのメタデータ生成関数を持つのは合理的な設計である。各フィーチャーにseo関数を分散させると、サイト全体のSEO一貫性が損なわれるリスクがある。修正は行わず、型のみの依存として許容する。 ただし、seo.ts 内にコメントで「この依存は型のみであり意図的」と明記し、対象はToolMeta, CheatsheetMeta, QuizMetaの3つのみであることを記載する 実施タイミング: フェーズ8(最終クリーンアップ時にコメント追加)
4. 移行計画
移行の原則
- 1フェーズ = 1フィーチャーの移行(または1つの論理的作業単位)
- 各フェーズは独立したコミットとする
- 各フェーズ完了後に必ず以下の全項目を検証:
- npm run typecheck
- npm run test
- npm run build
- npm run lint
- npm run format:check
- 旧パス残存チェック(grep)
- git mv を使ってファイル移動し、リネームとしてトレーサビリティを保つ
- フェーズ間の依存関係を最小化し、各フェーズ内で完結させる
フェーズ0: 前準備(アンチパターン修正 + 共有ユーティリティ整理)
作業内容:
- AP-3修正: src/lib/games/shared/webShare.ts を src/lib/webShare.ts に移動
- src/components/quiz/ShareButtons.tsx のインポートを @/lib/webShare に更新
- src/components/games/shared/GameShareButtons.tsx のインポートを @/lib/webShare に更新
- src/components/games/shared/tests/GameShareButtons.test.tsx のモック対象パスを更新
- 【v2.1修正: N-5反映】src/lib/games/shared/tests/webShare.test.ts を src/lib/tests/webShare.test.ts に移動し、モックパスとインポートパスを @/lib/webShare に更新する
- AP-4修正: src/lib/dictionary/index.ts を削除(未使用のため影響なし)
- ゲーム固有データの分離準備(フェーズ1で各ゲームディレクトリに移動するため、ここでは分離しない)
影響ファイル: 5ファイル【v2.1修正: N-5反映により4->5ファイルに変更】 検証: typecheck + test + build + lint + format:check
フェーズ1: games の移行(最優先・最大規模)
gamesは154ファイルが4箇所に散在しており、最も改善効果が大きい。ゲームごとにサブタスクに分割して実行することを推奨する。
作業内容:
- src/games/ ディレクトリを新規作成
- 各ゲームごとに以下を移動:
- src/lib/games/{game}/ -> src/games/{game}/_lib/
- src/components/games/{game}/ -> src/games/{game}/_components/
- src/data/{game}-schedule.json -> src/games/{game}/data/ (ゲーム固有データ)
- src/data/nakamawake-data.json -> src/games/nakamawake/data/
- 共有コードの移動:
- src/lib/games/shared/ -> src/games/shared/_lib/ (webShare.ts は除外: フェーズ0で移動済み)
- src/components/games/shared/ -> src/games/shared/_components/
- registry/types の移動:
- src/lib/games/registry.ts -> src/games/registry.ts
- src/lib/games/types.ts -> src/games/types.ts
- src/lib/games/tests/registry.test.ts -> src/games/tests/registry.test.ts
- 全インポートパスの更新:
- @/lib/games/* -> @/games/*
- @/components/games/* -> @/games//_components/
- @/data/{game}-schedule.json -> @/games/{game}/data/*
- @/data/nakamawake-data.json -> @/games/nakamawake/data/nakamawake-data.json
- 【v2.1修正: M-1反映】src/app/games/ 内のインポートを更新(page.tsx, layout.tsx, opengraph-image.tsx に加え、tests/ 内のテストファイルも含む)。具体的には以下の5ファイルのインポートパスを更新:
- src/app/games/tests/page.test.tsx
- src/app/games/kanji-kanaru/tests/GameBoard.test.tsx(@/components/games/kanji-kanaru/GameBoard -> @/games/kanji-kanaru/_components/GameBoard、@/lib/games/kanji-kanaru/types -> @/games/kanji-kanaru/_lib/types)
- src/app/games/kanji-kanaru/tests/GuessInput.test.tsx(@/components/games/kanji-kanaru/GuessInput -> @/games/kanji-kanaru/_components/GuessInput)
- src/app/games/kanji-kanaru/tests/page.test.tsx
- src/app/games/yoji-kimeru/tests/page.test.tsx
- search/build-index.ts の games registry インポートを更新
- AP-2修正: components/common/Footer.tsx を修正(選択肢A: layout.tsx 経由のprops方式を採用。詳細はセクション3 AP-2を参照)
- Footer.tsx に FooterProps インターフェースを追加: { gameLinks?: { href: string; label: string }[] }
- allGameMetas, getGamePath の直接importを除去
- SECTION_LINKS のゲームセクションで props.gameLinks を使用するように変更
- app/layout.tsx で games/registry から allGameMetas, getGamePath をインポートし、gameLinks を生成してFooterに渡す
- scripts/generate-puzzle-schedule.ts のパス更新:
- 入力パス: ../src/data/kanji-data.json(変更なし: 共有データはdata/に残る)
- 出力パス: ../src/data/puzzle-schedule.json -> ../src/games/kanji-kanaru/data/puzzle-schedule.json
- 空になった src/lib/games/, src/components/games/ を削除
影響ファイル: 約80ファイルのインポートパス変更 + AP-2修正 + scripts更新 注意: src/data/ から移動するのはゲーム固有データ(5ファイル: puzzle-schedule.json, yoji-schedule.json, nakamawake-data.json, nakamawake-schedule.json, irodori-schedule.json)のみ。共有データ(kanji-data.json, yoji-data.json, traditional-colors.json)はsrc/data/に残る 検証: typecheck + test + build + lint + format:check + 旧パス残存チェック
フェーズ2: tools の移行
toolsは既にsrc/tools/でコロケーション済み。主な作業はcomponents/tools/の統合。
作業内容:
- src/components/tools/ を src/tools/_components/ に移動
- src/components/tools/tests/ を src/tools/_components/tests/ に移動
- 全インポートパスを更新:
- @/components/tools/* -> @/tools/_components/*
- src/app/tools/ 内のインポートを更新
- 空になった src/components/tools/ を削除
注意: src/tools/ 自体は移動しない。registry.ts, types.ts, 各ツールディレクトリの位置は変わらない。@/tools/ パスは変更なし。 影響ファイル: 約20ファイルのインポートパス変更 検証: typecheck + test + build + lint + format:check
フェーズ3: cheatsheets の移行
作業内容:
- 【v2.1修正: N-2反映】src/components/cheatsheets/ を src/cheatsheets/_components/ に移動(tests/ の4ファイル(CheatsheetCard.test.tsx, CheatsheetLayout.test.tsx, CodeBlock.test.tsx, TableOfContents.test.tsx)を含む)
- 【v2.1修正: N-3反映】全インポートパスを更新(テストファイル内のインポートパスも含む):
- @/components/cheatsheets/* -> @/cheatsheets/_components/*
- tests/ 内の各テストファイルが @/components/cheatsheets/ パスでインポートしている箇所も @/cheatsheets/_components/ に更新する
- src/app/cheatsheets/ 内のインポートを更新
- 空になった src/components/cheatsheets/ を削除
注意: src/cheatsheets/ 自体は移動しない。_components/CheatsheetLayout.tsx 内の allToolMetas インポートパスは @/tools/registry のままで変更不要(tools/は移動していないため) 影響ファイル: 約15ファイルのインポートパス変更 検証: typecheck + test + build + lint + format:check
フェーズ4: quiz の移行
作業内容:
- src/quiz/ ディレクトリを新規作成
- src/lib/quiz/ の内容を移動:
- registry.ts, types.ts, scoring.ts -> src/quiz/
- data/ -> src/quiz/data/
- 【v2.1修正: M-2反映】tests/ の2ファイル(registry.test.ts, scoring.test.ts)を src/quiz/tests/ に移動
- src/components/quiz/ を src/quiz/_components/ に移動
- 全インポートパスを更新:
- @/lib/quiz/* -> @/quiz/*
- @/components/quiz/* -> @/quiz/_components/*
- src/app/quiz/ 内のインポートを更新
- src/lib/seo.ts のインポートを更新: @/lib/quiz/types -> @/quiz/types
- search/build-index.ts の quiz registry インポートを更新
- 空になった src/lib/quiz/, src/components/quiz/ を削除
影響ファイル: 約20ファイルのインポートパス変更 検証: typecheck + test + build + lint + format:check
フェーズ5: dictionary の移行
作業内容:
- src/dictionary/ ディレクトリを新規作成
- src/lib/dictionary/ を src/dictionary/_lib/ に移動(index.ts はフェーズ0で削除済み)
- src/lib/dictionary/tests/ の4ファイル(colors.test.ts, kanji.test.ts, staticParams.test.ts, yoji.test.ts)も含めて移動
- src/components/dictionary/ を src/dictionary/_components/ に移動
- 全インポートパスを更新:
- @/lib/dictionary/* -> @/dictionary/_lib/*
- @/components/dictionary/* -> @/dictionary/_components/*
- src/app/dictionary/ と src/app/colors/ 内のインポートを更新
- search/build-index.ts のインポートを更新
- 空になった src/lib/dictionary/, src/components/dictionary/ を削除
注意: src/data/ のJSONファイル(kanji-data.json等)はそのまま。dictionary/_lib/kanji.ts は @/data/kanji-data.json を参照し続ける 影響ファイル: 約30ファイルのインポートパス変更(7ファイルがdata/を参照: dictionary 3 + games GameContainer 4) 検証: typecheck + test + build + lint + format:check
フェーズ6: blog の移行
作業内容:
- src/blog/ ディレクトリを新規作成
- src/lib/blog.ts を src/blog/_lib/blog.ts に移動
- src/lib/tests/blog-series.test.ts を src/blog/tests/blog-series.test.ts に移動
- src/components/blog/ を src/blog/_components/ に移動
- AP-1修正: BlogListView.tsx の CSS依存を修正
- app/blog/page.module.css からBlogListView固有のスタイルを抽出
- src/blog/_components/BlogListView.module.css として新規作成
- BlogListView.tsx のインポートを @/blog/_components/BlogListView.module.css に変更
- blog/_lib/blog.ts 内の BLOG_DIR パスは変更なし(process.cwd() + 'src/content/blog' のまま)
- src/content/blog/ はそのまま(Markdownファイルは移動しない)
- 全インポートパスを更新:
- @/lib/blog -> @/blog/_lib/blog
- @/components/blog/* -> @/blog/_components/*
- src/lib/cross-links.ts のインポートを更新(@/lib/blog -> @/blog/_lib/blog)(blog部分のみ更新)
- src/lib/feed.ts のインポートを更新
- search/build-index.ts のインポートを更新
- 空になった src/components/blog/ を削除
影響ファイル: 約15ファイルのインポートパス変更 + AP-1修正 検証: typecheck + test + build + lint + format:check
フェーズ7: memos の移行
作業内容:
- src/memos/ ディレクトリを新規作成
- src/lib/memos.ts を src/memos/_lib/memos.ts に移動
- src/lib/memos-shared.ts を src/memos/_lib/memos-shared.ts に移動
- src/lib/tests/memos.test.ts を src/memos/tests/memos.test.ts に移動
- src/lib/tests/memos-shared.test.ts を src/memos/tests/memos-shared.test.ts に移動
- src/components/memos/ を src/memos/_components/ に移動
- 全インポートパスを更新:
- @/lib/memos -> @/memos/_lib/memos
- @/lib/memos-shared -> @/memos/_lib/memos-shared
- @/components/memos/* -> @/memos/_components/*
- src/lib/cross-links.ts のインポートを更新(@/lib/memos -> @/memos/_lib/memos)(memos部分を更新)
- src/lib/feed-memos.ts のインポートを更新
- src/blog/_components/RelatedMemos.tsx のインポートを更新(@/lib/memos-shared -> @/memos/_lib/memos-shared)
- search/build-index.ts のインポートを更新
- src/app/memos/ 内のインポートを更新
- 空になった src/components/memos/ を削除
影響ファイル: 約20ファイルのインポートパス変更 検証: typecheck + test + build + lint + format:check
フェーズ8: 最終クリーンアップ・ドキュメント・検証
作業内容:
- src/lib/tests/ の整理:
- blog-series.test.ts, memos.test.ts, memos-shared.test.ts はフェーズ6,7で移動済み
- webShare.test.ts はフェーズ0で移動済み【v2.1修正: N-5反映で追記】
- seo.test.ts, seo-cheatsheet.test.ts, constants.test.ts, date.test.ts, markdown.test.ts, pagination.test.ts, ogp-image.test.tsx はそのまま残す(lib/に属するテスト)
- AP-5対応: src/lib/seo.ts に型依存の意図を説明するコメントを追加(対象はToolMeta, CheatsheetMeta, QuizMetaの3つの import type のみ)
- src/components/ 配下に common/ と search/ 以外のディレクトリが残っていないことを確認
- src/lib/ 配下にフィーチャー固有のディレクトリが残っていないことを確認
- src/data/ に共有データ(3ファイル)のみが残っていることを確認
ドキュメント作成: 6. docs/architecture-decision.md(ADR)を作成:
- 採用パターン(ハイブリッド型)とその根拠
- 不採用パターンとその理由
- ディレクトリの責任と配置ルール
- フィーチャー間依存のルール
- 【v2.1修正: N-4反映】searchを共有層(components/search/ + lib/search/)に残した理由を記載する。具体的には: searchは特定のフィーチャーではなくアプリ全体の基盤機能であること、全フィーチャーのregistryを横断的に参照する性質上フィーチャー単位のコロケーションが適さないこと、を根拠として記載する
- docs/new-feature-guide.md を作成:
- 新しいフィーチャー追加時の手順
- 新しいゲーム追加時の手順(テンプレート)
- 新しいツール追加時の手順(テンプレート)
- 「このコードはどこに置くべきか」判断フロー
最終検証: 8. npm run typecheck 9. npm run test(全テスト) 10. npm run build 11. npm run lint 12. npm run format:check 13. 全フィーチャーの旧パスが残存していないことをgrepで確認 14. scripts/generate-puzzle-schedule.ts を実行して正常動作を確認
5. リスクと対策
リスク1: インポートパスの変更漏れ
- 対策: 各フェーズ完了後に tsc --noEmit を実行(TypeScriptが未解決インポートを全て検出)
- 対策: grep で旧パスが残存していないことを確認するステップを全フェーズに含める
- 対策: eslintも毎フェーズ実行して検出を強化
リスク2: テスト破損
- 対策: 各フェーズで npm run test を実行
- 対策: vitest.config.mts は vite-tsconfig-paths を使用しているため、@/* エイリアスは自動解決。ただしインポートパス自体は更新が必要
- 対策: テストファイルも含めて移動対象に含める(各フェーズに明記済み)
リスク3: ビルド失敗
- 対策: 各フェーズで npm run build を実行
- 対策: 動的インポート(tools/registry.ts の componentImport)は相対パスを使用しているため影響なし
リスク4: scripts/generate-puzzle-schedule.ts のパス参照
- 対策: フェーズ1でゲーム固有データを移動する際に、出力パスを ../src/games/kanji-kanaru/data/puzzle-schedule.json に更新する。入力パス(kanji-data.json)は data/ に残るため変更不要
リスク5: git diff の大きさ
- 対策: フェーズごとにコミットし、git mv を使ってリネームとして認識させる
リスク6: next.config.ts のリダイレクト
- 影響なし: URLベースであり、ファイル構造には依存しない。確認のみ実施
リスク7: cross-links.ts の2段階更新
- 対策: cross-links.ts は @/lib/blog と @/lib/memos の両方をインポートしている。フェーズ6でblog部分(@/lib/blog -> @/blog/_lib/blog)を更新し、フェーズ7でmemos部分(@/lib/memos -> @/memos/_lib/memos)を更新する。各フェーズでcross-links.tsの該当行のみを更新し、他の行は触らない
【v2.1修正: N-1反映】リスク8: build-index.ts の多段階更新
- 説明: src/lib/search/build-index.ts は全7フィーチャーのregistryをインポートしており、フェーズ1, 4, 5, 6, 7の5フェーズにわたって段階的にインポートパスを更新する必要がある。1つのファイルが複数フェーズで繰り返し修正されるため、修正漏れのリスクがある。
- 対策: 各フェーズでは該当フィーチャーの1行(または数行)のみを更新し、他のインポート行は次のフェーズまで触らないこと。ビルダーはフェーズ開始時にbuild-index.tsの現在のインポート状態を確認し、当該フェーズの対象行のみを更新する。各フェーズ後の npm run build でインポート整合性が検証される。
6. 作業分割と実行方針
各フェーズをそれぞれ独立したビルダータスクとして実行する。
| フェーズ | 対象 | 規模 | タスク数 |
|---|---|---|---|
| 0 | 前準備(AP-3, AP-4修正) | 小(5ファイル) | 1タスク |
| 1 | games | 大(約80ファイル) | ゲームごとに4サブタスク推奨 |
| 2 | tools | 小(約20ファイル) | 1タスク |
| 3 | cheatsheets | 小(約15ファイル) | 1タスク |
| 4 | quiz | 小(約20ファイル) | 1タスク |
| 5 | dictionary | 中(約30ファイル) | 1タスク |
| 6 | blog(+AP-1修正) | 小(約15ファイル) | 1タスク |
| 7 | memos | 小(約20ファイル) | 1タスク |
| 8 | クリーンアップ+ドキュメント | 小 | 1タスク |
フェーズは順番に直列で実行する。前のフェーズが完了し検証をパスしてから次のフェーズに進む。
各フェーズ完了後の機械的リファクタリング部分にはフェーズごとのレビューは不要。ただし以下の2点でレビューを実施:
- フェーズ1完了後(AP-2のFooter修正を含むため、設計変更のレビューが必要)
- フェーズ8完了後(全体の最終レビュー + ドキュメントレビュー)
7. レビューv2指摘への対応サマリー
v1レビュー指摘(v2で反映済み)
| 指摘ID | 内容 | 対応状況 |
|---|---|---|
| C-1 | scripts/generate-puzzle-schedule.ts のパス参照 | v2で反映済み |
| M-1 | フェーズ0の影響ファイル見積もり漏れ | v2で反映済み |
| M-2 | テスト移動対象の不足 | v2で反映済み |
| M-3 | ツール数の不一致 | v2で反映済み |
| M-4 | 各フェーズのESLint検証 | v2で反映済み |
| N-1 | shared-data/ の命名 | v2で反映済み |
| N-2 | search共有層維持 | v2で反映済み |
| N-3 | blog Markdown維持 | v2で反映済み |
| N-4 | cross-links.ts 2段階更新 | v2で反映済み |
v2レビュー指摘(v2.1で反映)
| 指摘ID | 内容 | 対応箇所 |
|---|---|---|
| M-1 | app/games/tests/ のテストファイルのインポートパス更新明記 | フェーズ1手順6に5ファイルを具体的に列挙 |
| M-2 | quiz/tests/ のテストファイル具体化 | フェーズ4手順2に「2ファイル(registry.test.ts, scoring.test.ts)」と明記 |
| M-3 | AP-2修正(Footer props化)の設計詳細追記 | セクション3 AP-2に選択肢A/Bの比較と選択肢A採用の根拠を記載。フェーズ1手順8に具体的な実装方針を追記 |
| N-1 | リスク8: build-index.ts の多段階更新 | セクション5にリスク8として追加 |
| N-2 | フェーズ3 cheatsheets 移行で tests/ 4ファイルの明記 | フェーズ3手順1に4ファイル名を明記 |
| N-3 | フェーズ3のテストファイル内インポートパス更新の明示 | フェーズ3手順2に「テストファイル内のインポートパスも含む」と追記 |
| N-4 | ADRに「searchを共有層に残した理由」を記載 | フェーズ8手順6のADR作成項目に追記 |
| N-5 | フェーズ0にwebShare.test.tsの移動先を追記 | フェーズ0手順1に移動先(lib/tests/webShare.test.ts)とパス更新を追記 |