エンジン: Claude (Anthropic Claude Research 経由) 実行日: 2026-05-22 調査トリガー: RQ-053 — マルチ-package リポジトリの monorepo 化トポロジー設計のベストプラクティス ペア結果: 未取得 (Gemini / GPT-4o の Deep Research は別途実行予定)

本ファイルの位置付け: Claude Research の回答を 改変なしで 保存。整理・解釈は突合フェーズで行う。PDF 由来の rendering 残骸 (citation marker 等) は除去済。


TL;DR

  • 推奨は「pnpm workspaces + Turborepo + ルート同梱型モノレポ」への段階移行で、ルート (GAS) は包含する。理由は、docs/spec/sidebar_api.d.ts / sync-engines.mjs という既存の type/code 共有結線が「ルートを workspace の 1 メンバとして扱う」前提でしか自然に表現できないため。
  • 依存管理は pnpm 単一化 (package-lock.json を削除し pnpm import → ルート単一 pnpm-lock.yaml し、pnpm-workspace.yamlcatalog:@anthropic-ai/sdk などの共有依存をピン留めする)。Bun / npm workspaces / Nx / Bazel は今回の要件下では過剰または不適合。
  • GAS への漏洩防止は .clasp.jsonrootDirdist/ 専用に切り換え、.claspignorenode_modules/** を排除することで構造的に解決する。Turbo の --affected と Vercel Remote Cache 無料化 (2024-12-22 アナウンス、hobby 含む全プラン対象) により、ソロ開発 + AI エージェント運用でも明確な投資対効果が出る。

Q1. モノレポ vs ポリレポの判断基準

業界の判断フレーム

Google の Potvin & Levenberg (2016, Communications of the ACM 59(7), pp.78-87, DOI 10.1145/2854146)「Why Google Stores Billions of Lines of Code in a Single Repository」は、モノレポ採用の主動機を (1) 統一バージョニング、(2) 大規模リファクタの原子性、(3) 横断的なコード共有・可視性、(4) 共通ツーリングと統一エンジニア体験、と整理している。Google は同論文時点で約 20 億行・1 日 4 万コミット・1 万人超のエンジニアという規模だが、論文が強調しているのは「規模そのものより、cross-cutting change のコストが高すぎることをツーリングで解決し続けた」という点である。

業界の現代的な集計値としては、The Software House の State of Frontend 2024 (6,000+ developers) が「npm 56.6%、Yarn Classic 21.5%、pnpm 19.9%」と報告しており、pnpm の伸長が顕著。モノレポ採用率については、daily.dev の Nimrod Kramer 著「Monorepo in 2026: Turborepo vs Nx vs Bazel for Modern Development Teams」(2026-04-03 公開) が 2025 年データとして「50 名以上の開発者を抱える企業の 63% がモノレポを採用、37% がポリレポ継続」と報告している。

ポリレポが向く条件 (pkgpulse / daily.dev による整理):

  • チームが独立して動き、安定 API 経由でしか連携しない
  • リポジトリに巨大バイナリ・ML モデルが含まれ Git 操作が遅くなる
  • アクセス制御を repo 単位で厳格に分けたい

モノレポが向く条件:

  • 横断リファクタ・型共有・コード共有が頻繁
  • 統一 lint / test / format が運用上の利得を生む
  • atomic commit で「複数 service にまたがる変更を 1 PR」にしたい

bizlp-gas-accounting に対する評価

判断軸状況モノレポ寄り / ポリレポ寄り
プロジェクト数4 (GAS / webapp_client / drp / docs-search-worker)中立
言語の同質性全て TypeScript/JavaScriptモノレポ強く有利
デプロイ先GAS, CF Workers×2, GAS HtmlService (Pages 相当)中立 (Turbo/Nx が吸収可能)
横断変更頻度sidebar_api.d.ts 更新 → webapp_client + GAS、400_domain/*.jswebapp_client/src/engines/ の sync が 既に発生モノレポ強く有利
チームサイズソロ (+ 2026 末ジュニア 1 名)中立
ビルド時間 / CI コスト個人スケールで非ボトルネック中立
AI エージェント駆動 (Claude Code)全 repo を find grep Read 横断モノレポ強く有利

AI コーディングファースト下での特殊論点

Anthropic 公式 Best Practices (code.claude.com/docs/en/best-practices) は明示的に「Parent directories: useful for monorepos where both root/CLAUDE.md and root/foo/CLAUDE.md are pulled in automatically」と述べており、Claude Code はモノレポを階層 CLAUDE.md で扱う設計を前提化している。Anthropic 自身の Claude Code リポジトリも DeepWiki の解析によれば「The Claude Code codebase is organized as a Bun monorepo, utilizing Bun workspaces to manage internal packages, native modules (NAPI), and the core application logic」と公開されている (ツール選択は Bun だが、構造としては monorepo)。Nx 公式の AI agent skills (nx configure-ai-agents) は Claude Code・Cursor・GitHub Copilot・Gemini・Codex・OpenCode を統一的にサポートし、monorepo の project graph を agent に MCP/skill 経由で提示するアプローチを取っている。

逆向きの注意として、anthropics/claude-code の Issue #23627 では「Claude Code の remote/web セッションが one-session=one-repo を前提にしており、これは結果として monorepo 採用へのエコシステム圧力になっている」という議論が提起されており、ローカル /add-dir で multi-repo に対応できる点も合わせて把握しておくべきである。

結論: ソロ + AI エージェント駆動 + 全 JS/TS + 既に code/type 共有を実施中、という 4 条件が揃う本プロジェクトは「正式モノレポ昇格の境界条件を超えている」と判定できる。「準モノレポを正式モノレポへ昇格」が合理的選択であり、「ポリレポへ分割」は AI エージェント探索効率と既存 sync 結線を破壊するため不推奨。


Q2. ツーリング選定

依存管理レイヤ

観点npm workspacesYarn (Berry) workspacespnpm workspacesBun workspaces
ディスク効率平凡 (フラット hoisting)中 (PnP は別格だが互換性課題)content-addressable store で大幅削減global cache あり
インストール速度最遅 (Syncfusion 計測でコールド ~33.4s クラス)速い (コールド系で上位)最速 (PkgPulse 計測で pnpm の 4〜5 倍級)
phantom dep 防止✗ フラット hoist で発生○ isolated linker が既定
Monorepo 機能の枯れ具合7+ で対応、機能は最小成熟最も成熟 (catalog / filter / overrides)機能あり、ただし Turbo/Nx 連携は pnpm 中心の文献が圧倒的
GAS など特殊 deploy target との相性◎ (互換最強)○ (clasp は package manager に非依存)△ (docs/tooling 中で second-class 扱いの報告あり)
Cloudflare Builds 互換△ (issue #10941: 「all 26 workspace projects を初期化時に install してしまう」報告)

pnpm を推奨。理由: (1) phantom dependency 防止が isolated linker で構造的、(2) catalog: プロトコルにより @anthropic-ai/sdk 等の version 統一が 1 行で済む、(3) Turborepo/Nx の公式ドキュメント・ベンチマーク (vsavkin/large-monorepo, Nx 公式比較ページ) が全て pnpm 前提、(4) すでに pnpm-lock.yaml が 3 サブプロジェクトで生成済みで馴染みコストが低い。Bun は install 速度の優位がソロ規模では年に数分しか効かず、CFW Builds との互換性に若干の不安が残るため見送り。

ビルドオーケストレータ層

ツール学習コスト強み弱み本プロジェクト適合度
Turborepo (Vercel)Rust 実装、tasks 設定が薄い (~20 行で動く)、Vercel Remote Cache 無料、CFW 連携の公式ガイドありdependency graph 可視化・generator 弱、enforcer 弱
Nx中〜高project graph、generators、affected の精度、AI agent skills 公式提供設定が ~200 行級になりやすい、Vercel 公式ドキュメントに「Nx users as of version 19.7+ may need to pay a license fee to Nx to continue using Vercel Remote Cache」との注記あり○ (個人スケールではオーバーキル寄り)
Bazelhermetic build, polyglot, 大規模 (1000+人)個人開発で完全に過剰
Rush (Microsoft)publishing policy 強、phantom dep 完全防止 (Steve Kinney)npm パッケージ公開が主目的、本プロジェクトは publish しない
Lernaレガシーversioning/publishingtask runner は Nx に依存 (2022 以降) (Steve Kinney)
Moon (moonrepo)toolchain pinning、polyglot (Moonrepo)エコシステム小、CFW/clasp の公式知見が乏しい

Turborepo 推奨。「Turborepo gives you an incredibly fast car, but it doesn't give you a map or a seatbelt」(dev.to/thedavestack) という性格付けは、ソロ + AI エージェントが「map と seatbelt の代替」を CLAUDE.md と ADR で持っている本プロジェクトと相性が良い。Nx の AI skill 機能は魅力的だが、ソロ規模での維持コスト負担と Vercel Remote Cache の Nx 課金体系から優先度を下げる。

deploy target 別の適合性

  • GAS (clasp): clasp は package manager 非依存で、.clasp.jsonrootDir.claspignore で push 対象を制御するため、pnpm 構成・Turbo 構成のいずれとも干渉しない。重要なのは「rootDirdist/ 等の build 出力専用ディレクトリに固定」して node_modules を構造的に隔離すること。Google clasp Issue #67 で Google 担当 erickoledadev が「For the node_modules case I found success by adding the following three lines to .claspignore: node_modules/**, node_modules/**/.*/**, node_modules/**/.*」と確認している組合せを採用する。(Lightrun)
  • Cloudflare Workers (wrangler): Cloudflare 公式の Workers Builds ドキュメントは「Simplified dependency management: Manage dependencies across all your workers and shared packages from a single place using tools like pnpm workspaces and syncpack」と明記、pnpm + Turborepo の組合せをファーストクラスでサポートする (developers.cloudflare.com/workers/ci-cd/builds/advanced-setups/)。ただし issue #10941 が「Installing project dependencies: pnpm install --frozen-lockfile / Scope: all 26 workspace projects」というログを引用し、Cloudflare Builds の自動 install が workspace 全体を初期化してしまう挙動を報告しているので、不要 workspace の install を skip したい場合は GitHub Actions 経由の手動 deploy を併用する選択肢を残す。
  • Vite SPA (multi-target): VITE_BUILD_TARGET で 4 ビルドする現行構成は、Turbo タスクを build:sidebar / build:cockpit / build:multiyear / build:ocr_bench に分解し、env: ["VITE_BUILD_TARGET"] を指定することで並列実行 + 個別キャッシュが効く。

Q3. 依存統一戦略

現状の問題点 (再掲)

  • ルートに package-lock.json (npm) と pnpm-lock.yaml (pnpm) が共存。
  • pnpm-workspace.yaml がプレースホルダ (allowBuilds: fsevents: set this to true or false) で packages: フィールド未定義。pnpm 公式ドキュメント (pnpm.io/pnpm-workspace_yaml) は「If the packages field is omitted, only the root package is included in the workspace」と明記しており、現状は 実質的に workspace が無効。(pnpm)
  • 4 サブプロジェクトで lock file が独立 → @anthropic-ai/sdk 等の version drift リスクが構造的に残る。

統一パターン

  1. pnpm import を各サブプロジェクトで実行 → 各 package-lock.json から pnpm lockfile を生成 (公式 CLI: pnpm.io/cli/import が「pnpm import generates a pnpm-lock.yaml from another package manager's lockfile」と明記、package-lock.json / npm-shrinkwrap.json / yarn.lock をサポート)。
  2. ルートに pnpm-workspace.yaml を再構成:
packages:
  - '.'                              # ルート GAS をメンバーとして含める
  - 'webapp_client'
  - 'drp'
  - 'docs-search-worker'
  - 'packages/*'                     # 新設する shared packages の置き場
catalog:
  "@anthropic-ai/sdk": ^0.30.0
  typescript: ^5.5.0
  vitest: ^2.0.0
  1. 共有依存を catalog:: pnpm 9.5 で導入された Catalogs (pnpm.io/catalogs) により、各 package.json では "@anthropic-ai/sdk": "catalog:" と書けば pnpm-workspace.yaml の 1 行で全 package を一斉更新でき、merge conflict も激減する。Socket.dev の解説記事は「pnpm version 9.5 introduces the Catalogs feature, enabling multiple package.json files to share the same version specifier of a dependency via the new catalog: protocol」と整理している。Nx 自身も catalog で技術スタックを束ねている (DeepWiki: nrwl/nx 2.2 "Version Management with pnpm Catalogs", 「Catalogs enable atomic version updates for technology stacks by grouping related dependencies … under named catalogs」)。(Socket, DeepWiki)
  2. 既存 package-lock.json を削除 → ルート単一 pnpm-lock.yaml に集約。

hoisting / 隔離のバランス

pnpm 既定の isolated linker は phantom dependency を構造的に防ぐ。shamefully-hoist=true や広範な publicHoistPattern 設定は legacy package 互換のための最終手段であり、設定しないことを推奨 (Steve Kinney "Enterprise UI" の "Dependency Management in Monorepos" は明示的に「The option exists for compatibility with legacy code, not as a permanent operating mode」と述べる)。

「ルートだけモノレポから除外」案の評価

技術的には可能 (packages: ['webapp_client', 'drp', 'docs-search-worker'] と書いてルート package を除外)。ただし以下の理由で 不推奨:

  • webapp_client/scripts/sync-engines.mjs がルート 400_domain/*.jsSSoT として参照しているため、ルートを workspace から外すと workspace dep として 400_domain を扱えなくなり、結局 ad-hoc な「ルートを参照する」という抜け道が残る。
  • docs/spec/sidebar_api.d.ts も同様。
  • 「ルートをモノレポに含める」設定 (pnpm 公式の '.' を packages に列挙する標準パターン、pnpm.io/pnpm-workspace_yaml の例「specify a package in a direct subdir of the root」) の方が 副次的コストが小さい。(pnpm)

GAS 固有の対策 (node_modules を GAS ランタイムに漏らさない)

  • .clasp.json を以下のように設定し、push 対象を build 出力に絞る:
{ "scriptId": "…", "rootDir": "dist/gas" }

.claspignore の glob は rootDir 起点で評価される (npm @google/clasp 公式 README が「The .claspignore patterns are applied relative from the rootDir」と明記)。(npm)

  • .claspignore の防御線 (Google clasp issue #67 で公式に確認済みの組合せ):
**/**
!appsscript.json
!**/*.gs
!**/*.js
!**/*.html
node_modules/**
node_modules/**/.*/**
node_modules/**/.*
  • ビルドステップ (例: rollup / esbuild) でルートの 400_domain/*.js + 共有 @bizlp/shared package を bundle → dist/gas/ に出力 → clasp pushdist/gas/ 配下のみ送信、という流れに統一する (cristobalgvera/ez-clasp テンプレートが「If you need to push some other files that will not be included in transpilation process, you can put them into app folder (or whatever location you want if you change .claspignore configuration)」と同型の運用を提示)。

Q4. Build/CI 統合と既存カスタム結線の扱い

Turbo の affected 検知の精度と限界

Turborepo 2.1 以降の --affected フラグは「You can now use the --affected flag with turbo run to automatically target packages with changes between the latest commit of your current branch, and the default branch of your repository」と turborepo.dev/blog/turbo-2-1-0 が説明している。ただし以下の限界がある:

  • Issue #11144: ルート package.json が変更されたと誤検出され DefaultGlobalFileChanged が発火 → 全 package が affected と判定される現象が報告されている (「Despite git showing only 2 files changed (yarn.lock and apps/web/package.json), Turborepo's --affected flag marks all packages as changed due to detecting the root package.json as a DefaultGlobalFileChanged」)。
  • Discussion #11533: packages/ 配下の package を変更しても、internal deps を持たない leaf package は root に edge を持つため cascade で全部 affected 化される (「packages with no internal deps get connected to root」「when anything triggers a root-level change, all those packages cascade as affected」)。
  • 対策: Turbo 2.0 の turbo query affected (「More precise detection: turbo-ignore operates at the package level. turbo query affected operates at the task input level」) を CI で使う。futureFlags.affectedUsingTaskInputs 有効化も検討。

既存カスタム結線の表現

  1. webapp_client/scripts/sync-engines.mjs (ルート 400_domain/*.jswebapp_client/src/engines/ コピー prebuild hook) 2 案あり、推奨は (B) 共有 workspace package 化:
  • (A) prebuild script を Turbo task として明示:
// webapp_client/turbo.json (extends root)
{
  "extends": ["//"],
  "tasks": {
    "prebuild": {
      "inputs": ["../400_domain/**", "scripts/sync-engines.mjs"],
      "outputs": ["src/engines/**"]
    },
    "build": { "dependsOn": ["prebuild", "^build"], "outputs": ["dist/**"] }
  }
}

inputs がクロスパッケージ参照する場合は Turbo 2.0 の $TURBO_ROOT$ トークンを用いる。

  • (B) 400_domainpackages/engines として workspace package 化 し、webapp_client は "@bizlp/engines": "workspace:*" で依存。sync-engines.mjs は不要になり、pnpm の symlink で済む。GAS 側は dist/gas に bundle されるため node_modules 漏洩なし。

(B) は移行コストが (A) より大きいが、AI エージェント (Claude Code) の探索効率を最大化し、TypeScript Project References との相性も最良。

  1. ルートの npm run build:spa orchestrator → Turbo 化: turbo run build:sidebar build:cockpit build:multiyear build:ocr_bench で並列実行 + cache。env: ["VITE_BUILD_TARGET"] を各タスクに付けて cache key を分離。これにより「1 ターゲットだけ source 変更 → そのターゲットだけ rebuild」が実現する。

  2. docs/spec/sidebar_api.d.ts を共有パッケージ化するか否か:

  • 推奨は「packages/types という workspace パッケージに昇格」。Nx 公式記事 "Managing TS packages in monorepos" (nx.dev/blog) は『With widespread support in modern package managers (NPM, PNPM, Yarn, Bun), the workspaces feature has become the preferred method for managing package resolution... When combined with TypeScript project references, this method becomes even more powerful... This combination is the recommended way to structure and manage TypeScript packages in a monorepo』と整理している。monorepo.tools の TypeScript ページは更に「the TypeScript team even stated that developers should avoid using path aliases all together」と path aliases 非推奨を強調している。

  • 具体構造:

packages/
  types/
    package.json     # name: "@bizlp/types"
    src/sidebar_api.d.ts
    tsconfig.json    # composite: true, declaration: true

GAS / webapp_client / drp / docs-search-worker いずれも "@bizlp/types": "workspace:*" で参照、import type { … } from '@bizlp/types'

  • docs/spec/sidebar_api.d.ts は廃止し、packages/types/src/sidebar_api.d.ts に移動 + re-export。ADR で根拠を記録。

CI 統合 (GitHub Actions / Cloudflare Builds / clasp deploy)

  • GitHub Actions: pnpm/action-setup@v4 + actions/setup-node@v4 (cache: 'pnpm') + pnpm install --frozen-lockfile + pnpm turbo run build --affected が標準。Vercel Remote Cache を TURBO_TOKEN / TURBO_TEAM env で有効化 (Vercel が 2024-12-22 に Remote Cache を hobby 含む全プラン無料化、Vercel changelog 「Vercel Remote Cache is now free for all plans. Never do the same work twice in your Turborepo or Nx repository… Your use of Remote Cache remains subject to our Fair use Guidelines」、URL: vercel.com/changelog/free-vercel-remote-cache)。
  • Cloudflare Builds: pnpm monorepo は issue #10941 の通り「scope: all workspace projects」で全 install してしまうため、各 Worker サブプロジェクトのデプロイは GitHub Actions 経由で wrangler deploy --config drp/wrangler.toml を発火する構成を推奨。
  • clasp deploy: clasp push は CI でも実行可能 (--adc フラグで service account 認証)。GitHub Actions の job で pnpm turbo run build --filter=gaspnpm --filter gas clasp push を直列実行。(GitHub)

AI エージェント (Claude Code) 向けの可読性

  • turbo.json は jsonc で // コメントを許容 → 各 task の意図を明記。
  • ルート CLAUDE.md で「workspace の topology」「各 package の責務」「turbo run の典型コマンド」を列挙。Anthropic 公式は「root/CLAUDE.md and root/foo/CLAUDE.md are pulled in automatically」と述べており、各 package 直下にも CLAUDE.md を置き、scoped command を例示する のが best practice (MuhammadUsmanGM/claude-code-best-practices の monorepo example が参考になる:「Claude Code supports nested CLAUDE.md files — the root file provides global conventions while package-level files add specific instructions for each package」)。(GitHub)
  • AGENTS.md を nested に配置し、Claude Code 以外の agent (Cursor / Codex 等) との互換性も確保 (Datadog frontend team の "Steering AI Agents in Monorepos with AGENTS.md" が参考実装)。

Q5. 段階的移行戦略と推奨アーキテクチャ

Phase 0: 凍結 & インベントリ (Claude Code 1 セッション)

  • 現状の package-lock.json / pnpm-lock.yaml / pnpm-workspace.yaml の状態を ADR-XXXX で記録。
  • 4 サブプロジェクトそれぞれで package.jsondependencies / devDependencies を抽出 → drift 行列を作成。@anthropic-ai/sdk をはじめとする共有依存を特定。

Phase 1: workspace 宣言 (ロールバック容易)

  1. pnpm-workspace.yaml を以下に書き換え:
packages:
  - '.'
  - 'webapp_client'
  - 'drp'
  - 'docs-search-worker'
  1. ルート package.json"packageManager": "[email protected]" を明記。
  2. lock file 統一はまだ行わない。各サブプロジェクトの既存 lock を残したまま pnpm install を回し、symlink が作られることを確認。
  3. ロールバック: pnpm-workspace.yaml を元のプレースホルダに戻すだけ。

Phase 2: lock 統一 + Turborepo 導入

  1. ルート以外の package-lock.json / pnpm-lock.yaml を削除。
  2. ルートで pnpm import を実行 (既存の package-lock.json から pnpm-lock.yaml を生成) → そのまま pnpm install
  3. pnpm add -D -w turbo でルートに Turbo インストール。
  4. ルート turbo.json を作成:
{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build":          { "dependsOn": ["^build"], "outputs": ["dist/**"] },
    "build:sidebar":  { "env": ["VITE_BUILD_TARGET"], "outputs": ["dist/sidebar/**"] },
    "build:cockpit":  { "env": ["VITE_BUILD_TARGET"], "outputs": ["dist/cockpit/**"] },
    "build:multiyear":{ "env": ["VITE_BUILD_TARGET"], "outputs": ["dist/multiyear/**"] },
    "build:ocr_bench":{ "env": ["VITE_BUILD_TARGET"], "outputs": ["dist/ocr_bench/**"] },
    "lint":      {},
    "typecheck": { "dependsOn": ["^build"] },
    "test":      { "dependsOn": ["^build"] },
    "deploy":    { "dependsOn": ["build"], "cache": false }
  }
}
  1. ルートの npm run build:spaturbo run build:sidebar build:cockpit build:multiyear build:ocr_bench --filter=webapp_client に置換。
  2. ロールバック: turbo.json 削除、npm scripts を git revert、pnpm-lock.yaml を Phase 1 時点へ戻し、各サブプロジェクトで pnpm import を逆向きに (または最初に保存しておいた lock を復元)。

Phase 3: 共有 packaging (破壊的変更を含む)

  1. packages/types を新設し docs/spec/sidebar_api.d.ts を移動。各サブプロジェクトの import パスを更新。
  2. (任意) 400_domainpackages/engines 化し sync-engines.mjs を廃止。
  3. pnpm-workspace.yamlcatalog: を追加し、共有依存 (@anthropic-ai/sdk, typescript, vitest 等) を catalog: 参照に置換。catalogMode: strict を有効化してドリフトを CI で禁止 (pnpm 公式:「strict - only allows dependency versions from the catalog. Adding a dependency outside the catalog's version range will cause an error」)。
  4. .clasp.jsonrootDirdist/gas に固定し、.claspignore を強化 (前述)。
  5. ロールバック: phase 3 は破壊的なので、Phase 2 完了時点で git tag (monorepo-phase-2-stable) を打って戻れるようにする。

「broken pnpm-workspace.yaml」の扱い

現状の allowBuilds: fsevents: set this to true or false という記述は、pnpm のテンプレート由来のプレースホルダで意味のある設定ではない。pnpm 10 系では設定全般を pnpm-workspace.yaml に集約する方向で議論されている (pnpm Discussion #9037「We need to stop storing settings in package.json as it breaks the fetch command … The best approach seems to be to move all the settings to pnpm-workspace.yaml」) が、set this to true or false は値ではなく注釈文の残骸であり、現状は YAML パースは通っていても workspace 機能を実質無効化している。Phase 1 で全面書き換え (packages フィールドを定義 + 不要な placeholder を削除) が正しい対応。

可逆性 (モノレポ → ポリレポへの戻し)

  • git 履歴の保持: 現状は単一 git repo なので、もし「ポリレポへ戻す」となった場合は git filter-repo --subdirectory-filter <subproject> で各サブプロジェクトを独立 repo として抽出可能 (zenn の cloud-config tech blog "破綻したサブモジュール構成を捨てて、pnpm + Turborepo でモノレポ化した話" が逆方向で git-filter-repo を使う手順を例示)。
  • コスト: Claude Code を使えば 1〜2 セッション程度。pnpm → npm への lock 戻しはツール未整備 (pnpm/pnpm Discussion #3367 で正式機能リクエスト中、現在は npm i --package-lock-only で代替) だが、各サブプロジェクトで再 install で実害なし。
  • 結論: 可逆性は十分高い。「戻す可能性」を理由に移行をためらう必要はない。

6 ヶ月評価 KPI

KPI計測方法成功閾値
pnpm install 全 workspace 所要時間CI ログ90 秒以内 (cold)
Turbo cache hit 率 (ローカル + Remote 合算)turbo run build --summarize の JSON60% 以上
共有依存の version drift 数 (catalog 外の差異)syncpack list-mismatches を週次 CI 実行0 件
Cross-package PR の比率GitHub label / git diff stats月 30% 以上 (モノレポの価値が実現している指標)
Claude Code セッションでの誤探索率主観評価 + grep 失敗ログ移行前比 50% 減
GAS ランタイムでの「想定外 module」起因エラーclasp push 後の Stackdriver ログ0 件

ルート包含の決定

結論: ルート (GAS) は monorepo に包含する。

根拠:

  1. docs/spec/sidebar_api.d.ts および 400_domain/*.js という GAS 側ソースが、既に webapp_client から参照されている SSoT。ルートを workspace 外に置くと、これら共有資産を workspace:* プロトコルで扱えず、現行の ad-hoc sync スクリプト相当の独自パイプラインを維持し続けることになる。
  2. ルートを workspace の 1 メンバとして扱う pattern は pnpm 公式 (packages: ['.', ...]) でサポートされており、過去事例 (Cloudflare workers-sdk monorepo 等) でも標準的。
  3. GAS は package manager に非依存 (clasp は package manager を見ない) なので、ルートを pnpm workspace に取り込むこと自体には機能的副作用がない。node_modules の GAS ランタイム漏洩は .clasp.jsonrootDir.claspignore で構造的に防げる (Q3 / Q4 参照)。
  4. AI エージェント (Claude Code) の探索効率上、「ルート CLAUDE.md → 各 package CLAUDE.md」の階層が機能するためにはルートが workspace の論理的中心であることが望ましい。

推奨ツーリングスタック

レイヤ採用
Package managerpnpm 10.x
Workspacepnpm workspaces + Catalogs (catalogMode: strict)
Build orchestratorTurborepo 2.x
共有型管理TypeScript Project References + workspace:*@bizlp/types パッケージ
Remote cacheVercel Remote Cache (Turborepo, 2024-12-22 以降無料)
Dep drift 監視syncpack + pnpm catalog: 強制 (catalogMode: strict)
CIGitHub Actions (pnpm/action-setup@v4, cache: 'pnpm', turbo run … --affected)

推奨ディレクトリ (目標形)

/
├── pnpm-workspace.yaml       # packages: ['.', 'webapp_client', 'drp', 'docs-search-worker', 'packages/*']
├── package.json              # ルート(GAS) workspace、name: "@bizlp/gas"
├── turbo.json
├── CLAUDE.md                 # 全体 topology + agent ガイド
├── AGENTS.md                 # symlink/参照
├── .clasp.json               # rootDir: "dist/gas"
├── .claspignore              # node_modules/** 三重排除
├── 100_*, 200_*, …           # GAS 既存ディレクトリ群
├── 400_domain/               # 当面は現位置維持、将来 packages/engines に移行
├── scripts/                  # build orchestrator scripts
├── dist/gas/                 # clasp push 対象 (bundled 出力)
├── packages/
│   └── types/                # @bizlp/types (sidebar_api.d.ts ほか)
│       ├── package.json
│       ├── tsconfig.json     # composite: true
│       └── src/
├── webapp_client/            # @bizlp/webapp-client
│   ├── CLAUDE.md
│   └── turbo.json (extends //)
├── drp/ # @bizlp/decision-pipeline
└── docs-search-worker/       # @bizlp/docs-search-worker

Phase 別作業時間目安 (Claude Code 駆動前提)

  • Phase 1 (workspace 宣言のみ): Claude Code 1 セッション (~1h)。リスク最小、即ロールバック可。
  • Phase 2 (lock 統一 + Turbo): Claude Code 2〜3 セッション (~3〜4h)。CI 含む。
  • Phase 3 (共有 packaging): Claude Code 4〜6 セッション (~6〜8h)。sidebar_api.d.ts の移動と全 import 書き換えが主作業。

即時アクション

  1. 現状ルートの package-lock.json をコミット凍結 (タグ pre-monorepo-2026-05-22)。
  2. pnpm-workspace.yaml を Q5 Phase 1 の内容に書き換え (プレースホルダ駆逐)。
  3. ADR-00XX「Monorepo 構成と pnpm workspaces 採用」を起票し、本ドキュメントを根拠資料として添付。
  4. 6 ヶ月後 (2026-11 頃) に Q5 の KPI を採取し再評価 ADR を起票。

他モデルが見落としやすい観点 (Claude-specific insights)

  1. pnpm-workspace.yaml の現状が「壊れている」のではなく「実質無効化されている」: packages: フィールド未定義は pnpm 公式仕様で「only the root package is included in the workspace」であり、エラーは出ないが workspace の便益はゼロ。これを「壊れている設定」として削除/修正どちらかに振るのが Phase 1 の本質的価値。多くの monorepo 移行記事は「ゼロから書く」前提で、この「実質無効化された placeholder の解体」フェーズを論じない。(pnpm)
  2. Vercel Remote Cache が無料になった事実 (2024-12-22): ソロ開発でも Remote Cache の機能的恩恵を受けられる (複数 PC 間 / CI ↔ ローカルでキャッシュ共有)。これを知らないと「Remote Cache は有料だからソロには不要」という古い判断を下しがち。
  3. Cloudflare Builds と pnpm workspace の摩擦: workers-sdk issue #10941 が示すように、Cloudflare 側の自動 install が workspace 全体を初期化する仕様。これを知っていれば「CFW deploy は GitHub Actions 経由が安全」という Phase 2 の選択が説明しやすい。多くの guide はこの罠を書かない。
  4. TypeScript チームが path aliases を非推奨化: 2025-2026 の規範的アドバイスは「path aliases ではなく workspace package + project references」(monorepo.tools:「the TypeScript team even stated that developers should avoid using path aliases all together」)。docs/spec/sidebar_api.d.ts を「ルートに置きっぱなしで paths で参照する」案は短期的には楽だが、TypeScript 公式の方向性と逆。
  5. Claude Code が階層 CLAUDE.md を自動ロード: monorepo 化の隠れた便益は「各 package の CLAUDE.md を Claude Code が自動収集する」ことで、これはポリレポでは複製しづらい。AI エージェント駆動開発における monorepo の真の価値はビルド速度ではなく コンテキスト window 効率 にある。
  6. clasp + monorepo の組合せの稀少さ: Google clasp issue #740 で Google 担当 contributorpw は「I try to avoid monorepos for transparent and lightweight projection」と述べており、clasp の作者陣はそもそも monorepo を念頭に置いていない。これは公式ベストプラクティスが存在しないことを意味し、本 ADR が独自 best practice を確立する余地がある (逆に言うと、独自ベストプラクティスを ADR-RQ-053 として文書化する価値が高い)。
  7. Nx vs Turborepo の決定的差は「AI agent skills」になりつつある: Nx は nx configure-ai-agents で Claude Code 含む 6 agent を統一サポート。Turbo は agent skill を 1 つ提供する。ただしソロ規模では Nx の generators・enforcers は過剰投資で、turbo.json + CLAUDE.md の手書きで十分機能する。「規模が拡大したら Nx 移行」のエスケープルートも Nx 公式の Turborepo-to-Nx migration guide が用意されているため容易。

Caveats / 留意点

  • 本ドキュメント中の Cloudflare Builds 挙動 (issue #10941) は 2025-10-10 時点の報告であり、修正済みの可能性がある。Phase 2 着手前に最新の Cloudflare 公式ドキュメントを再確認すること。
  • pnpm catalogMode: strict は pnpm 10 系で利用可能だが、Phase 3 着手時の正確な pnpm 最新 minor バージョンに合わせて挙動を再検証すること。
  • TypeScript project references は composite: true を要求し incremental build 用の .tsbuildinfo を生成するため、.gitignore の整備が必要。
  • 本ドキュメントは「ソロ + AI エージェント駆動 + 全 JS/TS」という前提に最適化している。仮に将来「他言語サブプロジェクト (Python/Go) を追加」する場合は、Moon もしくは Nx への乗り換えを再評価すべき (Turborepo は polyglot を主目的としていない)。
  • daily.dev の「2025 年に 63% のモノレポ採用率」データは出典が原典まで遡れていない (Nimrod Kramer の 2026 年記事内の引用) ため、絶対値より傾向として参照すること。