0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【コンテキストエンジニアリングシリーズ】3)チーム開発のためのコンテキスト共有戦略 完全ガイド

Last updated at Posted at 2025-10-12

AIエージェントをチーム全体で効果的に活用するための実践的アプローチ


📚 この記事の位置づけ

本記事は、コンテキストエンジニアリングシリーズの続編です。

前提知識として、以下の記事を先にお読みいただくことをおすすめします:

本記事では、個人からチームへスケールアップする際の課題と解決策を扱います。


目次

  1. なぜチームでのコンテキスト共有が必要なのか
  2. チーム開発で起きる「あるある」問題
  3. コンテキスト共有の3つのレイヤー
  4. 実践: チーム共通の.aiディレクトリ設計
  5. コンテキストのバージョン管理
  6. レビュー可能なコンテキスト設計
  7. オンボーディングへの活用
  8. チーム規模別の戦略
  9. ケーススタディ
  10. ツールとワークフロー

なぜチームでのコンテキスト共有が必要なのか

AIエージェントが1人の開発者の生産性を10倍にしても、チーム全体では機能しない——これが多くの組織が直面する現実です。

個人で使う分には問題なかったClaude CodeやCursorも、チームで使い始めた途端に混乱が生じます。メンバーAのエージェントは「JWTを使え」と言い、メンバーBのエージェントは「セッションベースにしろ」と提案する。同じプロジェクトなのに、エージェントごとに異なる回答が返ってくる——これは個々のエージェントが「チーム全体の知識」にアクセスできていないためです。

AIエージェントの個人利用は比較的簡単ですが、チーム全体で活用する際には新たな課題が発生します。知識の分断、重複作業、オンボーディングの困難さといった問題を解決し、チーム全員が一貫した前提で開発できる環境を構築する必要性について、具体的なシナリオとともに見ていきましょう。

個人利用からチーム利用への壁

1人で使う場合と、チームで使う場合では、コンテキスト管理の複雑さが指数関数的に増加します。 個人利用では、あなた自身の頭の中にある前提知識とエージェントのコンテキストをすり合わせるだけでした。しかしチームでは、メンバー全員の暗黙知を明示化し、誰もがアクセスできる形で共有する必要があります。個人の知識ベースをどうチーム全体で共有するか——これがスケールアップの最大の壁です。

個人利用:
あなた → Claude Code → あなただけの知識ベース ✅

チーム利用:
メンバーA → Claude Code → Aの知識ベース
メンバーB → Cursor → Bの知識ベース
メンバーC → Copilot → Cの知識ベース
↓
バラバラ... 😱

起きる問題

コンテキストが共有されていないチームでは、4つの典型的な問題が発生します。 一見すると小さな不便に思えるかもしれませんが、これらは積み重なることでプロジェクトの品質と効率に深刻なダメージを与えます。多くのチームが「エージェントの問題」だと思い込んでいますが、実は「コンテキスト共有の欠如」という組織的な問題なのです。以下の表は、実際のプロジェクトで頻繁に観察される問題を整理したものです。

問題 具体例 影響
知識の分断 Aさんが決めた設計をBさんのエージェントが知らない 一貫性の欠如
重複作業 同じコンテキストを各メンバーが個別に整備 時間の無駄
オンボーディング困難 新メンバーが「暗黙知」を引き継げない 生産性低下
デバッグ不能 エージェントの出力根拠が追跡できない 品質リスク

チーム共有による効果

コンテキストを適切に共有することで、チーム全体のAIエージェント活用が次のレベルに到達します。 個人の生産性向上だけでなく、チーム全体としての一貫性、効率性、品質のすべてが向上し、真の意味での「組織的な生産性」が実現します。以下の図は、共通コンテキストがチーム全体にどのように波及効果をもたらすかを示しています。

効果:

  • ✅ 全員が同じ「前提」で開発
  • ✅ エージェントの出力品質が安定
  • ✅ オンボーディング時間が80%削減
  • ✅ コードレビューが効率化
  • ✅ 技術的負債が可視化

チーム開発で起きる「あるある」問題

「うちのチームだけの問題じゃなかったんだ」——AIエージェントをチームで使い始めた多くの組織が、同じ壁にぶつかります。

エージェントが人によって違うことを言う。コンテキストの更新が追いつかない。新しいメンバーが何を信じていいかわからない。コードレビューでエージェントの出力を検証できない——これらは個別の問題ではなく、「コンテキスト共有の欠如」という根本原因から派生した症状です。

チームでAIエージェントを活用すると、メンバーごとに異なる回答が返ってきたり、コンテキストの同期が取れていなかったり、決定事項が伝わらないといった典型的な問題が発生します。これらの問題を具体例とともに理解し、なぜ起きるのか、どう対策すればいいのかを見ていきましょう。

問題1: 「エージェントが人によって違うことを言う」

最も頻繁に遭遇し、そして最も混乱を招く問題です。 月曜日にメンバーAが「JWT認証で進めよう」とエージェントに言われて実装を始め、水曜日にメンバーBが「セッションベースが良い」とエージェントに提案され、金曜日のレビューで大混乱——こんな状況、経験ありませんか?同じプロジェクトなのに、メンバーごとにエージェントが異なる提案をすることで、コードベースの一貫性が失われ、技術的負債が急速に蓄積します。

// メンバーAのエージェント
エージェント: "認証はJWTでトークンを使いましょう"

// メンバーBのエージェント
エージェント: "認証はセッションベースが良いでしょう"

// 実際のコードベース
// → 混在してカオス 😱

原因: 各メンバーのコンテキストが異なる

問題2: 「新メンバーのエージェントが的外れな提案をする」

新しいメンバーがジョインした初日から、この問題は顕在化します。 せっかくの優秀な新人が、エージェントの助けを借りて「完璧な実装」を作り上げたと思ったら、実は3ヶ月前にチームが議論して却下したアプローチだった——オンボーディング時に頻発する悲劇です。既存のコードベースやチームの決定事項を知らないエージェントが、車輪の再発明や、過去に失敗したアプローチを何度も提案してしまいます。結果として、新メンバーの生産性向上どころか、むしろ足を引っ張ることになりかねません。

新メンバー: "ユーザー管理機能を作って"

エージェント: "はい、フルスクラッチで作ります"

先輩: "いや、すでにUserServiceあるから..."

新メンバー: "エージェント、なんで知らないの?"

原因: 既存のコードベースや設計判断がコンテキストに含まれていない

問題3: 「レビューで『なんでこの実装?』が説明できない」

「エージェントがそう言ったから」——これほどレビュアーを困惑させる言葉はありません。 AIエージェントがコードを書いてくれるのは素晴らしいことですが、その実装の背景にある判断基準や、なぜその選択をしたのかを説明できなければ、コードレビューは機能しません。エージェントが生成したコードの意図を説明できず、レビュープロセスが「承認するか却下するか」の二択になってしまい、建設的なフィードバックができなくなります。これは品質管理上の重大なリスクです。

レビュアー: "なんでここでtry-catchが3重ネストなの?"

作成者: "エージェントがそう書いたんで..."

レビュアー: "...なんでエージェントはそう判断したの?"

作成者: "わかりません..." 😓

原因: エージェントの判断根拠がトレーサブルでない

問題4: 「過去の失敗を繰り返す」

「歴史は繰り返す」——コンテキスト共有がないチームでは、この言葉が悲しいほど現実になります。 3ヶ月前にチーム全員で頭を悩ませて解決した問題に、新しいメンバーが同じようにハマる。半年前に「これはパフォーマンス問題を引き起こす」と判明したパターンを、エージェントが何も知らずに再び提案する。チームの貴重な学習(失敗から得た教訓、苦労して見つけた解決策)が蓄積されず、組織としての成長が止まってしまいます。

3ヶ月前:
チーム: "async/awaitのエラーハンドリングでハマった"
     → 解決策を見つけた

今日:
新メンバーのエージェント: 同じパターンでコード生成
新メンバー: 同じ問題にハマる 🔄

原因: 過去の学習(失敗・解決策)がチーム知識として蓄積されていない


コンテキスト共有の3つのレイヤー

「全員が同じコンテキストを使うべき」——これは正しいようで、実は間違いです。

効果的なコンテキスト共有は、「全員が知るべきこと」「チーム内で共有すべきこと」「個人で管理すべきこと」を明確に分離することから始まります。すべてを共有すると情報過多で混乱し、何も共有しないと一貫性が失われます。

効果的なコンテキスト共有には、適切なレイヤー分けが不可欠です。プロジェクト全体、ドメイン別、個人レベルの3層構造により、共通の知識基盤を維持しながら、各レベルでの柔軟性も確保できます。各レイヤーの役割と責任範囲を明確にしていきましょう。

Layer 1: チーム共通コンテキスト(全員必須)

これは、チームの「憲法」です。 すべてのメンバーが必ず読み、理解し、従うべき基盤となるコンテキストで、プロジェクトの根幹を成す原則や決定事項が含まれます。このレイヤーが揃っていないと、チームとしての一貫性が保てず、各メンバーが異なる方向を向いて開発してしまいます。逆に、ここがしっかりしていれば、チーム全体が同じビジョンに向かって効率的に進めます。

目的: プロジェクトの「共通言語」を確立

含めるべき内容:

  • プロジェクトの目的・ゴール
  • 技術スタック・アーキテクチャ
  • コーディング規約
  • 重要な技術的決定(ADR: Architecture Decision Record)
  • 制約事項・禁止パターン

管理方法: Gitでバージョン管理、全員が読み取り専用

Layer 2: ドメイン別コンテキスト(担当者向け)

これは、専門家のための「技術書」です。 担当チームやドメインエキスパートが管理する、特定領域に特化した深い知識を集約します。認証チームは認証の詳細を、決済チームは決済処理の細かい要件を、それぞれのコンテキストとして管理することで、全員が全てを知る必要なく、必要な人が必要な情報にアクセスできます。全員が知る必要はないが、関係者には必須の知識を集約します。

目的: 特定領域の深い知識を共有

含めるべき内容:

  • ドメイン固有の設計判断
  • 外部API仕様・制約
  • パフォーマンス要件
  • セキュリティ要件
  • テストケース設計

管理方法: 担当チームがメンテナンス、関係者が参照

Layer 3: 個人コンテキスト(各自カスタマイズ)

これは、各自の「作業ノート」です。 共有する必要のない個人的なメモ、進行中のタスク、試行錯誤の記録など、各メンバーが自分の作業スタイルに合わせて自由にカスタマイズする領域です。重要なのは、このレイヤーを明示的に分離することで、「何を共有すべきか」「何は個人で管理すべきか」の境界が明確になることです。共有の必要はなく、Gitにもコミットしません。

目的: 個人の作業効率化

含めるべき内容:

  • 作業中のタスクメモ
  • 個人的なショートカット
  • 試行錯誤の記録

管理方法: .gitignoreで除外、個人管理


実践: チーム共通の.aiディレクトリ設計

理論はわかった。では、実際にどうファイルを配置するのか?——ここからが本番です。

.aiディレクトリの構造は、チームの開発効率を左右します。適切に設計されていれば、新しいメンバーは迷わず必要な情報にたどり着け、既存メンバーは一貫した方法で情報を更新できます。逆に、構造が曖昧だと、どこに何を書けばいいかわからず、結局誰も更新しなくなります。

チーム全体で一貫したコンテキストを共有するためには、明確なディレクトリ構造とファイル命名規則が不可欠です。推奨される.aiディレクトリ設計、必須ファイルの内容、そして実際の運用例を通じて、すぐに使える実践的なファイル構成を見ていきましょう。

推奨ディレクトリ構造

良いディレクトリ構造は、チームの生産性を決定します。 「どこに何を置くか」が明確であれば、新しいメンバーは迷わず情報にアクセスでき、既存メンバーは一貫した方法で情報を更新できます。以下は、数十のプロジェクトで検証され、実戦で磨かれた構造です。実際のプロジェクトですぐに使える、3層構造を反映したディレクトリレイアウトで、各ディレクトリの役割が明確で、プロジェクトの成長に合わせた拡張性も確保されています。

project-root/
├── .ai/
│   ├── README.md                 # このディレクトリの使い方
│   │
│   ├── team/                     # Layer 1: チーム共通(Git管理)
│   │   ├── context.md            # プロジェクト全体のコンテキスト
│   │   ├── architecture.md       # アーキテクチャ設計
│   │   ├── conventions.md        # コーディング規約
│   │   ├── tech-stack.md         # 技術スタック詳細
│   │   ├── glossary.md           # 用語集
│   │   └── adr/                  # Architecture Decision Records
│   │       ├── 001-jwt-auth.md
│   │       ├── 002-database-choice.md
│   │       └── ...
│   │
│   ├── domains/                  # Layer 2: ドメイン別(Git管理)
│   │   ├── auth/
│   │   │   ├── context.md
│   │   │   ├── api-specs.md
│   │   │   └── security-requirements.md
│   │   ├── payment/
│   │   │   ├── context.md
│   │   │   ├── providers.md
│   │   │   └── error-handling.md
│   │   └── notification/
│   │       └── ...
│   │
│   ├── templates/                # 再利用可能なテンプレート
│   │   ├── feature-template.md
│   │   ├── bug-fix-template.md
│   │   └── refactor-template.md
│   │
│   ├── scripts/                  # コンテキスト生成・検証スクリプト
│   │   ├── generate-context.ts
│   │   ├── validate-context.ts
│   │   └── sync-context.ts
│   │
│   └── personal/                 # Layer 3: 個人用(.gitignore)
│       └── .gitkeep
│
├── .gitignore                    # .ai/personal/* を除外
└── ...

.ai/team/context.md テンプレート

# プロジェクトコンテキスト

> 最終更新: 2024-10-12 by @username
> バージョン: 2.3.0

## プロジェクト概要

**プロジェクト名**: SaaS Platform XYZ

**目的**: 
中小企業向けの統合業務管理プラットフォーム

**ゴール**:
- 2024年Q4にβ版リリース
- 初年度100社の導入
- アップタイム99.9%以上

## 技術スタック

### Backend
- Runtime: Node.js 20.x
- Framework: Nest.js 10.x
- Language: TypeScript 5.x (strict mode)
- Database: PostgreSQL 16.x
- ORM: Prisma 5.x
- Cache: Redis 7.x

### Frontend
- Framework: Next.js 14.x (App Router)
- Language: TypeScript 5.x
- State: Zustand + React Query
- UI: shadcn/ui + Tailwind CSS

### Infrastructure
- Cloud: AWS
- Container: Docker + ECS Fargate
- CI/CD: GitHub Actions
- Monitoring: DataDog

## アーキテクチャ原則

1. **Clean Architecture**
   - レイヤー分離: Controller → UseCase → Repository
   - ドメイン層は外部依存を持たない

2. **API設計**
   - RESTful API(一部GraphQL検討中)
   - OpenAPI 3.0で仕様管理
   - バージョニング: URL-based (/api/v1/...)

3. **セキュリティ**
   - 認証: JWT(アクセストークン15分、リフレッシュトークン7日)
   - 認可: RBAC(Role-Based Access Control)
   - 暗号化: すべての機密データはAES-256

## コーディング規約

### TypeScript

```typescript
// ✅ Good
export class UserService {
  async findById(userId: string): Promise<User> {
    // 実装...
  }
}

// ❌ Bad
export class UserService {
  findById(userId, callback) { // 型がない
    // コールバック使用
  }
}

命名規則

  • ファイル名: kebab-case (user-service.ts)
  • クラス名: PascalCase (UserService)
  • 関数名: camelCase (findById)
  • 定数: UPPER_SNAKE_CASE (MAX_RETRY_COUNT)

エラーハンドリング

  • カスタムエラークラスを使用
  • エラーメッセージは英語
  • ログにはcontextIDを含める
// ✅ Good
throw new UserNotFoundError(`User ${userId} not found`, {
  contextId,
  userId
});

// ❌ Bad
throw new Error('ユーザーが見つかりません');

重要な制約

パフォーマンス

  • APIレスポンス: 95%ile < 200ms
  • DB クエリ: N+1問題を避ける
  • 重い処理は非同期ジョブ化(BullMQ)

スケーラビリティ

  • ステートレス設計(セッション情報はRedis)
  • 水平スケール可能に設計
  • ファイルアップロードはS3直接

セキュリティ

  • SQL injection対策: ORMのパラメータ化クエリ
  • XSS対策: 出力時エスケープ
  • CSRF対策: トークン検証

外部サービス連携

サービス 用途 認証方式
Stripe 決済処理 API Key + Webhook
SendGrid メール送信 API Key
AWS S3 ファイルストレージ IAM Role
Twilio SMS通知 API Key + Secret

ディレクトリ構造

src/
├── modules/          # 機能モジュール
│   ├── auth/
│   ├── users/
│   └── ...
├── shared/           # 共通ユーティリティ
├── config/           # 設定ファイル
└── database/         # マイグレーション

チーム規約

コード作成時

  1. 必ず既存パターンを確認
  2. ADRを参照して設計判断
  3. テストを先に書く(TDD推奨)

エージェント利用時

  1. このcontext.mdを必ず読み込ませる
  2. ドメイン別コンテキストも参照
  3. 生成コードは必ずレビュー

コミット前

  1. lintとformatを実行
  2. テストが通ることを確認
  3. コンテキストの更新が必要か検討

関連ドキュメント

更新履歴

  • 2024-10-12: JWT有効期限を24時間→15分に変更(ADR-015)
  • 2024-10-10: Redis導入(ADR-014)
  • 2024-10-05: Clean Architecture採用(ADR-012)

### .ai/team/adr/001-jwt-auth.md テンプレート

```markdown
# ADR-001: JWT認証の採用

## ステータス
承認済み

## 決定日
2024-09-15

## コンテキスト
ユーザー認証方式を決定する必要があった。

### 検討した選択肢
1. セッションベース認証(Cookie)
2. JWT認証
3. OAuth 2.0のみ

## 決定内容
**JWT認証を採用**

- アクセストークン: 15分
- リフレッシュトークン: 7日
- トークンのローテーション実装

## 理由

### JWT採用の理由
- ✅ ステートレス(スケールしやすい)
- ✅ マイクロサービス間で共有可能
- ✅ モバイルアプリとの親和性

### セッション不採用の理由
- ❌ Redisへの依存が強くなる
- ❌ 水平スケール時の複雑性
- ❌ マイクロサービス化が困難

## 影響範囲

### 実装が必要
- [ ] JWT生成・検証ライブラリ実装
- [ ] リフレッシュトークンエンドポイント
- [ ] トークンローテーション機構
- [ ] ブラックリスト機構(Redis)

### 注意事項
- トークンサイズが大きい(Cookieに注意)
- リフレッシュトークンの安全な保管が必須
- ログアウト時の処理が必要

## 代替案検討時の参考
将来的にOAuth 2.0の導入も検討する

## 参考資料
- [JWT Best Practices](https://tools.ietf.org/html/rfc8725)
- [OWASP JWT Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.html)

## レビュー
- [@tech-lead] 承認
- [@security-team] 承認(条件: リフレッシュトークンローテーション必須)

コンテキストのバージョン管理

「コンテキストファイルもコードです」——この認識が、チームの成功を左右します。

多くのチームが「コンテキストはMarkdownだから、適当にコミットしていい」と考えがちですが、それは大きな間違いです。コンテキストファイルは、全メンバーのAIエージェントが読み込む「プログラムの入力」です。したがって、コードと同じようにバージョン管理し、レビューし、テストする必要があります。不適切なコンテキスト変更は、全メンバーのエージェント出力に影響を与え、プロジェクト全体の品質を左右する重大なインシデントとなりえます。

コンテキストファイルは、コードと同様にGitで適切にバージョン管理することで、変更履歴の追跡、競合の解決、レビュープロセスへの統合が可能になります。コミットメッセージの規約、ブランチ戦略、Pull Requestでの管理方法を具体的に見ていきましょう。

Git管理の基本戦略

何を共有し、何を個人で管理するか——この境界を.gitignoreで明確にします。 チーム共通の知識はGitで管理し、個人的なメモや作業中の試行錯誤は除外することで、リポジトリが「本当に価値のある情報」だけで構成されます。

# .gitignore
# 個人用コンテキストは除外
.ai/personal/*
!.ai/personal/.gitkeep

# IDEの個人設定も除外
.cursor/
.vscode/settings.json

ブランチ戦略

コンテキストの変更も、コードと同じブランチで管理します。 機能開発とともにコンテキストも進化させ、PRで一緒にレビューすることで、実装とドキュメントの乖離を防ぎます。「コードは更新したけど、コンテキストは古いまま」という状況を構造的に防止できます。

ルール:

  • コンテキストの変更も通常のコードと同じブランチで管理
  • PRレビューでコンテキストもレビュー対象
  • mainマージ時にバージョン番号を更新

コミットメッセージ規約

一貫したコミットメッセージ規約により、コンテキストの変更履歴が追跡可能になります。 docs(context):というプレフィックスを使うことで、通常のコード変更とコンテキスト変更を明確に区別でき、後から「いつ、なぜこの決定をしたか」を簡単に振り返ることができます。

# コンテキスト更新のプレフィックス
docs(context): JWT有効期限を15分に変更

# ADR追加
docs(adr): ADR-015 JWT有効期限変更を追加

# ドメインコンテキスト更新
docs(context/auth): 2FA実装の詳細を追加

セマンティックバージョニング

コンテキストにもバージョン番号を付けることで、互換性を管理します。 ソフトウェアと同様に、破壊的変更、機能追加、マイナー修正を明確に区別することで、「このバージョンのコンテキストから、エージェントの動作が変わる」という重要な情報をチーム全体で共有できます。

[Major].[Minor].[Patch]

Major: アーキテクチャ変更、破壊的変更
Minor: 新機能追加、ドメイン追加
Patch: 誤字修正、説明追加

:

  • 1.0.02.0.0: Clean Architecture導入(破壊的変更)
  • 1.5.01.6.0: 決済ドメイン追加
  • 1.5.21.5.3: typo修正

レビュー可能なコンテキスト設計

「コンテキストは誰がレビューするの?」——この質問に答えられないチームは、早晩コンテキストの質が劣化します。

コンテキストの品質を維持するには、コードレビューと同様のプロセスが必要です。しかし、コードと異なり、コンテキストのレビュー観点は明確ではありません。「何をチェックすればいいのか」「どの基準で承認するのか」が曖昧だと、レビューは形骸化します。

レビューしやすい文書構造、具体的なチェックリスト、Pull Requestテンプレートを活用することで、チーム全体でコンテキストの質を組織的に高められます。実践的なレビュープロセスを見ていきましょう。

コンテキストもコードレビューの対象

レビュープロセスに組み込むことで、コンテキストの品質を組織的に保証します。 コードだけでなく、そのコードを生成する「元」となるコンテキストもレビューすることで、品質管理の輪が完成します。エージェントの出力がおかしいと感じたら、コードではなくコンテキストに問題がある可能性を疑うべきです。

レビュー観点チェックリスト

「何をチェックすればいいのか」が明確でないと、レビューは形骸化します。 以下のチェックリストは、コンテキストレビューで確認すべき5つの観点を体系化したものです。これをPRテンプレートに組み込むことで、誰でも一定水準のレビューができるようになります。

## コンテキストレビューチェックリスト

### 📋 基本項目
- [ ] 最終更新日が記載されている
- [ ] 更新者が明記されている
- [ ] バージョンが適切に更新されている

### 🎯 内容の妥当性
- [ ] 追加された情報が正確
- [ ] 既存の情報と矛盾していない
- [ ] チーム合意が取れている内容

### 📝 表現の明確性
- [ ] 曖昧な表現がない("基本的に"、"なるべく"等)
- [ ] コード例が動作する
- [ ] 用語が統一されている

### 🔗 関連性
- [ ] 関連ADRが適切にリンクされている
- [ ] 影響範囲が明記されている
- [ ] 他のコンテキストとの整合性が取れている

### 🚀 実用性
- [ ] エージェントが理解できる形式
- [ ] 新メンバーが読んで理解できる
- [ ] 具体例が十分に含まれている

PRテンプレートへの組み込み

PRテンプレートにコンテキスト更新のセクションを追加することで、更新忘れを防ぎます。 機能を実装したら、関連するコンテキストの更新も必須——この習慣がチームに根付けば、ドキュメントとコードの乖離は起きません。

## Pull Request Template

### 変更内容
<!-- 何を変更したか -->

### コンテキストの更新
- [ ] コンテキストの更新が必要 → 更新済み
- [ ] コンテキストの更新は不要
- [ ] ADRの追加が必要 → 追加済み

#### 更新したコンテキスト
<!-- 更新した場合、ファイルパスを列挙 -->
- `.ai/team/context.md`
- `.ai/domains/auth/context.md`

#### 更新理由
<!-- なぜコンテキストを更新したか -->

### エージェント利用状況
- [ ] AIエージェントを利用した
- [ ] 利用したエージェント: [Claude Code / Cursor / Copilot / その他]
- [ ] エージェントへの指示内容:

### レビュアーへの依頼事項
<!-- コンテキストレビューで特に見てほしい点 -->

自動検証スクリプト

人間のレビューだけでなく、自動化されたチェックも重要です。 コンテキストファイルの構文エラー、リンク切れ、必須項目の欠落などは、CIで自動的に検出できます。これにより、レビュアーは「形式」ではなく「内容」に集中できるようになります。

コンテキストファイルの品質を保つには、人間の目によるレビューだけでは限界があります。特に、Markdown構文の間違い、リンク切れ、必須項目の欠落といった機械的にチェック可能な問題は、自動化すべきです。以下の検証スクリプトは、PRが作成されるたびに実行され、問題があれば即座にフィードバックします。

// .ai/scripts/validate-context.ts

import fs from 'fs/promises';
import path from 'path';
import { glob } from 'glob';
import chalk from 'chalk';

interface ValidationResult {
  file: string;
  errors: ValidationError[];
  warnings: ValidationWarning[];
}

interface ValidationError {
  line?: number;
  message: string;
  severity: 'error' | 'warning';
}

interface ValidationWarning {
  message: string;
}

class ContextValidator {
  private results: ValidationResult[] = [];

  async validateAll(): Promise<boolean> {
    console.log(chalk.blue('🔍 Validating context files...\n'));

    // すべての.mdファイルを検証
    const files = await glob('.ai/**/*.md');

    for (const file of files) {
      await this.validateFile(file);
    }

    return this.printResults();
  }

  private async validateFile(filePath: string): Promise<void> {
    const content = await fs.readFile(filePath, 'utf-8');
    const lines = content.split('\n');

    const result: ValidationResult = {
      file: filePath,
      errors: [],
      warnings: [],
    };

    // 1. 必須項目のチェック
    if (filePath.includes('/team/context.md')) {
      this.validateRequiredSections(content, result);
    }

    // 2. Markdown構文のチェック
    this.validateMarkdownSyntax(lines, result);

    // 3. リンク切れのチェック
    await this.validateLinks(content, filePath, result);

    // 4. コードブロックの検証
    this.validateCodeBlocks(lines, result);

    // 5. 更新日の確認
    this.validateUpdateDate(content, result);

    if (result.errors.length > 0 || result.warnings.length > 0) {
      this.results.push(result);
    }
  }

  private validateRequiredSections(content: string, result: ValidationResult): void {
    const requiredSections = [
      '## プロジェクト概要',
      '## 技術スタック',
      '## アーキテクチャ原則',
      '## コーディング規約',
    ];

    for (const section of requiredSections) {
      if (!content.includes(section)) {
        result.errors.push({
          message: `必須セクション「${section}」が見つかりません`,
          severity: 'error',
        });
      }
    }

    // バージョン情報の確認
    if (!content.match(/バージョン:\s*\d+\.\d+\.\d+/)) {
      result.errors.push({
        message: 'バージョン情報が見つかりません(形式: バージョン: X.Y.Z)',
        severity: 'error',
      });
    }

    // 最終更新日の確認
    if (!content.match(/最終更新:\s*\d{4}-\d{2}-\d{2}/)) {
      result.errors.push({
        message: '最終更新日が見つかりません(形式: 最終更新: YYYY-MM-DD)',
        severity: 'error',
      });
    }
  }

  private validateMarkdownSyntax(lines: string[], result: ValidationResult): void {
    let inCodeBlock = false;

    lines.forEach((line, index) => {
      const lineNum = index + 1;

      // コードブロックの開始/終了を追跡
      if (line.trim().startsWith('```')) {
        inCodeBlock = !inCodeBlock;
      }

      if (inCodeBlock) return; // コードブロック内はスキップ

      // 見出しの後に空行があるかチェック
      if (line.match(/^#{1,6}\s+.+/) && lines[index + 1] && lines[index + 1].trim() !== '') {
        result.warnings.push({
          message: `行${lineNum}: 見出しの後に空行がありません`,
        });
      }

      // リストのインデントチェック
      if (line.match(/^\s*[-*+]\s+/) && line.match(/^\s{1,2}[-*+]/)) {
        const spaces = line.match(/^(\s*)/)?.[1].length || 0;
        if (spaces % 2 !== 0) {
          result.warnings.push({
            message: `行${lineNum}: リストのインデントが不正です(2スペース単位にしてください)`,
          });
        }
      }
    });

    // コードブロックが閉じていない
    if (inCodeBlock) {
      result.errors.push({
        message: 'コードブロックが閉じられていません(```の数が合いません)',
        severity: 'error',
      });
    }
  }

  private async validateLinks(
    content: string,
    filePath: string,
    result: ValidationResult
  ): Promise<void> {
    // Markdownリンクを抽出 [text](url)
    const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
    let match;

    while ((match = linkRegex.exec(content)) !== null) {
      const linkText = match[1];
      const linkUrl = match[2];

      // 外部リンクはスキップ
      if (linkUrl.startsWith('http://') || linkUrl.startsWith('https://')) {
        continue;
      }

      // 相対パスのリンクを検証
      const baseDir = path.dirname(filePath);
      const targetPath = path.resolve(baseDir, linkUrl);

      try {
        await fs.access(targetPath);
      } catch (error) {
        result.errors.push({
          message: `リンク切れ: [${linkText}](${linkUrl}) → ${targetPath} が見つかりません`,
          severity: 'error',
        });
      }
    }
  }

  private validateCodeBlocks(lines: string[], result: ValidationResult): void {
    let inCodeBlock = false;
    let codeBlockLang = '';
    let codeBlockStartLine = 0;

    lines.forEach((line, index) => {
      const lineNum = index + 1;

      if (line.trim().startsWith('```')) {
        if (!inCodeBlock) {
          // コードブロック開始
          inCodeBlock = true;
          codeBlockStartLine = lineNum;
          codeBlockLang = line.trim().substring(3);

          // 言語指定がない場合は警告
          if (!codeBlockLang) {
            result.warnings.push({
              message: `行${lineNum}: コードブロックに言語指定がありません`,
            });
          }
        } else {
          // コードブロック終了
          inCodeBlock = false;
        }
      }
    });
  }

  private validateUpdateDate(content: string, result: ValidationResult): void {
    const dateMatch = content.match(/最終更新:\s*(\d{4}-\d{2}-\d{2})/);

    if (dateMatch) {
      const updateDate = new Date(dateMatch[1]);
      const today = new Date();
      const daysDiff = Math.floor((today.getTime() - updateDate.getTime()) / (1000 * 60 * 60 * 24));

      if (daysDiff > 90) {
        result.warnings.push({
          message: `最終更新から${daysDiff}日経過しています。内容が古い可能性があります。`,
        });
      }
    }
  }

  private printResults(): boolean {
    let hasErrors = false;

    for (const result of this.results) {
      console.log(chalk.bold(`\n📄 ${result.file}`));

      // エラー表示
      if (result.errors.length > 0) {
        hasErrors = true;
        result.errors.forEach(error => {
          const icon = error.severity === 'error' ? chalk.red('') : chalk.yellow('⚠️');
          console.log(`  ${icon} ${error.message}`);
        });
      }

      // 警告表示
      if (result.warnings.length > 0) {
        result.warnings.forEach(warning => {
          console.log(`  ${chalk.yellow('⚠️')} ${warning.message}`);
        });
      }
    }

    console.log('\n' + chalk.blue(''.repeat(60)));

    if (hasErrors) {
      console.log(chalk.red('\n❌ 検証に失敗しました\n'));
      return false;
    } else if (this.results.length > 0) {
      console.log(chalk.yellow('\n⚠️  警告がありますが、続行可能です\n'));
      return true;
    } else {
      console.log(chalk.green('\n✅ すべての検証に合格しました\n'));
      return true;
    }
  }
}

// 実行
if (require.main === module) {
  const validator = new ContextValidator();

  validator
    .validateAll()
    .then(success => {
      process.exit(success ? 0 : 1);
    })
    .catch(error => {
      console.error(chalk.red('検証中にエラーが発生しました:'), error);
      process.exit(1);
    });
}

export { ContextValidator };

検証項目の詳細:

検証項目 重要度 説明
必須セクション エラー context.mdに必須のセクションが存在するか
バージョン情報 エラー セマンティックバージョン形式で記載されているか
更新日 エラー 最終更新日が記載されているか
Markdown構文 警告 見出しやリストの形式が正しいか
リンク切れ エラー 内部リンクが有効か
コードブロック 警告 言語指定があるか、閉じられているか
更新頻度 警告 90日以上更新されていないか

GitHub Actionsとの連携

CIパイプラインにコンテキスト検証を組み込むことで、品質を自動的に保証します。 PRが作成されるたびに、コンテキストの妥当性がチェックされ、問題があれば即座にフィードバックされます。この自動化により、「レビュアーが見落とす」リスクを最小化できます。

チーム全体の開発フローにコンテキスト検証を組み込むことで、誰かが間違ったコンテキストをコミットする前に防ぐことができ、コンテキストの品質が継続的に保たれます。

# .github/workflows/validate-context.yml

name: Validate AI Context

on:
  pull_request:
    paths:
      - '.ai/**'
  push:
    branches:
      - main
    paths:
      - '.ai/**'

jobs:
  validate:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '20'

      - name: Install dependencies
        run: npm ci

      - name: Validate Context
        run: npm run ai:validate

      - name: Check Context Version
        run: |
          # コンテキストが更新された場合、バージョンも更新されているかチェック
          if git diff --name-only ${{ github.event.before }} ${{ github.sha }} | grep -q '.ai/team/context.md'; then
            if ! git diff ${{ github.event.before }} ${{ github.sha }} .ai/team/context.md | grep -q 'バージョン:'; then
              echo "❌ context.mdが更新されましたが、バージョンが更新されていません"
              exit 1
            fi
          fi

      - name: Comment PR
        if: github.event_name == 'pull_request'
        uses: actions/github-script@v6
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: '✅ コンテキストの検証に合格しました!'
            })

セットアップ手順

GitHub Actionsを使ったコンテキスト検証を5分でセットアップできます。

以下の手順に従うことで、すぐにチーム全体でコンテキスト品質の自動チェックを開始できます。

ステップ1: ワークフローファイルの作成
# プロジェクトルートで実行
mkdir -p .github/workflows

上記のYAMLファイルを .github/workflows/validate-context.yml として保存します。

ステップ2: 検証スクリプトの準備

検証スクリプト(.ai/scripts/validate-context.ts)が存在することを確認します。まだない場合は、自動検証スクリプトセクションのコードを使用してください。

# スクリプトの存在確認
ls -la .ai/scripts/validate-context.ts

# package.jsonスクリプトの確認
npm run ai:validate --dry-run
ステップ3: 初回実行テスト

ローカルで動作確認してから、GitHubにプッシュします。

# ローカルで検証スクリプトを実行
npm run ai:validate

# 問題なければコミット
git add .github/workflows/validate-context.yml
git commit -m "feat: Add context validation workflow"
git push
ステップ4: GitHub Actionsの動作確認
  1. GitHubリポジトリの「Actions」タブを開く
  2. 「Validate AI Context」ワークフローが表示されることを確認
  3. テスト用にコンテキストファイルを変更してPRを作成
# テスト用の変更
echo "## テストセクション" >> .ai/team/context.md
git checkout -b test/context-validation
git add .ai/team/context.md
git commit -m "test: Trigger context validation"
git push origin test/context-validation
  1. PRを作成し、Checksタブで検証結果を確認
ステップ5: チームへの展開

GitHub Actionsが正常に動作することを確認したら、チームに周知します。

【重要】コンテキスト検証の自動化について

.aiディレクトリ配下の変更があるPRでは、自動的にコンテキストの検証が実行されます。

✅ 検証内容:
- Markdownの構文チェック
- 必須セクションの存在確認
- リンク切れの検出
- バージョン番号の更新確認

❌ 検証エラーの場合:
1. GitHub ActionsのログでエラーメッセージHを確認
2. ローカルで `npm run ai:validate` を実行して詳細確認
3. エラーを修正して再プッシュ

詳細: [コンテキスト管理ガイド](.ai/README.md)

トラブルシューティング

GitHub Actionsでよく遭遇する問題と解決方法です。

問題1: "npm ci" が失敗する
Error: The process '/usr/bin/npm' failed with exit code 1

原因: package-lock.jsonがコミットされていないか、依存関係の不整合

解決方法:

# package-lock.jsonを生成
npm install

# コミットしてプッシュ
git add package-lock.json
git commit -m "chore: Add package-lock.json"
git push
問題2: "npm run ai:validate" が見つからない
Error: missing script: ai:validate

原因: package.jsonai:validateスクリプトが定義されていない

解決方法:

// package.jsonに追加
{
  "scripts": {
    "ai:validate": "tsx .ai/scripts/validate-context.ts"
  }
}
問題3: パス指定が動作しない
# .aiディレクトリの変更なのにワークフローがトリガーされない

原因: pathsフィルターの設定ミス

解決方法:

# 正しい設定
on:
  pull_request:
    paths:
      - '.ai/**'           # ✅ 正しい
      # - 'ai/**'          # ❌ 間違い(先頭のドットが必要)
      # - '.ai/*'          # ❌ 間違い(サブディレクトリが対象外)
問題4: PRへのコメントが失敗する
Error: Resource not accessible by integration

原因: GitHub Actionsの権限不足

解決方法:

# ワークフローファイルに権限を追加
jobs:
  validate:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write  # ← これを追加

または、リポジトリの設定で権限を変更:

  1. Settings → Actions → General
  2. "Workflow permissions"で"Read and write permissions"を選択
問題5: バージョンチェックが誤検知する
❌ context.mdが更新されましたが、バージョンが更新されていません

原因: 軽微な変更でバージョンを更新しなかった、またはバージョン形式が異なる

解決方法:

方法A: バージョンを更新する(推奨)

<!-- .ai/team/context.md -->
> バージョン: 2.3.0 → 2.3.1

方法B: 検証ロジックを調整する

# 軽微な変更(typo修正など)はバージョン更新不要とする
- name: Check Context Version
  run: |
    if git diff --name-only ${{ github.event.before }} ${{ github.sha }} | grep -q '.ai/team/context.md'; then
      # コミットメッセージに"[skip-version]"がある場合はスキップ
      if git log -1 --pretty=%B | grep -q '\[skip-version\]'; then
        echo "⏭️  バージョンチェックをスキップ"
        exit 0
      fi

      if ! git diff ${{ github.event.before }} ${{ github.sha }} .ai/team/context.md | grep -q 'バージョン:'; then
        echo "❌ context.mdが更新されましたが、バージョンが更新されていません"
        exit 1
      fi
    fi
問題6: ワークフローが遅すぎる
# 検証に3分以上かかる

原因: 毎回すべての依存関係をインストールしている

解決方法: キャッシュを活用する

- name: Setup Node.js
  uses: actions/setup-node@v3
  with:
    node-version: '20'
    cache: 'npm'  # ← キャッシュを有効化

- name: Install dependencies
  run: npm ci

さらに高速化:

# 検証に必要な最小限のパッケージのみインストール
- name: Install dependencies
  run: |
    npm install --no-save tsx glob chalk
    # npm ci の代わりに最小限のパッケージのみ

高度な設定

複数の検証を並列実行
jobs:
  validate-syntax:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Validate Markdown syntax
        run: npm run ai:validate -- --syntax-only

  validate-links:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Validate internal links
        run: npm run ai:validate -- --links-only

  validate-content:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Validate content structure
        run: npm run ai:validate -- --structure-only
検証結果をPRコメントに詳細表示
- name: Comment PR with details
  if: github.event_name == 'pull_request'
  uses: actions/github-script@v6
  with:
    script: |
      const fs = require('fs');
      const report = fs.readFileSync('.ai/validation-report.md', 'utf8');

      github.rest.issues.createComment({
        issue_number: context.issue.number,
        owner: context.repo.owner,
        repo: context.repo.repo,
        body: report
      });

オンボーディングへの活用

新メンバーのオンボーディングは、チームの生産性を測る重要な指標です。 適切に整備されたコンテキストがあれば、新メンバーは「何から学べばいいか」が明確になり、自律的にキャッチアップできます。従来は数週間かかっていたオンボーディングが、数日に短縮される——これは決して誇張ではなく、多くのチームで実現されている現実です。

コンテキストファイルは、最高のオンボーディング資料になります。プロジェクトの歴史、技術的決定の背景、よくあるつまずきポイントまで、すべてが一箇所に集約されているからです。新メンバーは、AIエージェントという最高の先生とともに、コンテキストを読みながら実践的に学んでいくことができます。

新メンバー向けコンテキストパッケージ

最初の一歩を間違えないための、ステップ・バイ・ステップガイドです。 何を、どの順番で読めばいいのか、どうやってエージェントを設定すればいいのか、最初のタスクは何をすればいいのか——新メンバーが抱えるすべての疑問に答えるドキュメントを用意することで、メンターの負担を大幅に軽減しながら、オンボーディングの品質を標準化できます。

# .ai/onboarding/README.md

# 新メンバーオンボーディングガイド

ようこそ!このガイドでは、AIエージェントを活用した開発の始め方を説明します。

## セットアップ(10分)

### 1. コンテキストファイルの確認

以下のファイルを順番に読んでください:

1. **必読**(所要時間: 15分)
   - [ ] `.ai/team/context.md` - プロジェクト全体像
   - [ ] `.ai/team/architecture.md` - アーキテクチャ
   - [ ] `.ai/team/conventions.md` - コーディング規約

2. **推奨**(所要時間: 30分)
   - [ ] `.ai/team/glossary.md` - 用語集
   - [ ] `.ai/team/adr/` - 重要な技術的決定(最新5件)

3. **必要に応じて**
   - [ ] 担当ドメインの`.ai/domains/*/context.md`

### 2. エージェントの設定

#### Claude Codeを使う場合

```bash
# .claudefiles を作成
cat > .claudefiles << 'EOF'
# 常に読み込むファイル
.ai/team/context.md
.ai/team/architecture.md
.ai/team/conventions.md
EOF

Cursorを使う場合

.cursorrulesファイルは既に設定済みです。
設定内容を確認: cat .cursorrules

GitHub Copilotを使う場合

各ファイルの先頭コメントにコンテキストを含める方式を採用しています。
テンプレートは.ai/templates/を参照してください。

3. 動作確認

以下のクエリでエージェントをテストしてください:

エージェントに質問:
「このプロジェクトの認証方式は何ですか?トークンの有効期限も教えてください」

期待する回答:
「JWT認証を使用しています。アクセストークンの有効期限は15分、
リフレッシュトークンは7日です。詳細はADR-001を参照してください」

✅ 正しく答えられたら、セットアップ完了です!

最初のタスク(1時間)

チュートリアル: シンプルな機能追加

新メンバーは、まず以下の「練習タスク」を行ってください:

タスク: ユーザープロフィール取得APIの追加

ステップ1: コンテキストを読み込む

# エージェントに以下を読み込ませる
@.ai/team/context.md
@.ai/domains/users/context.md
@src/modules/users/users.controller.ts (既存の参考コード)

ステップ2: エージェントに依頼

以下の機能を実装してください:

エンドポイント: GET /api/v1/users/:userId/profile
目的: 指定されたユーザーのプロフィール情報を取得
レスポンス: { id, name, email, avatar, createdAt }

要件:
- Clean Architectureのパターンに従う
- 既存のUsersModuleを拡張
- エラーハンドリングを含める
- JWTトークンで認証

ステップ3: レビュー観点

生成されたコードを以下の観点で確認:

  • Clean Architectureの各層が正しく実装されている
  • エラーハンドリングがカスタムエラークラスを使用
  • TypeScriptの型が適切
  • 命名規則が統一されている

ステップ4: PRを作成

通常のフローでPRを作成し、メンターにレビュー依頼してください。

よくある質問

Q: エージェントが古い情報を参照しているようです

A: コンテキストファイルの「最終更新日」を確認してください。
古い場合は、最新版をgit pullしてください。

Q: エージェントの提案が規約と違います

A: .ai/team/conventions.mdを明示的に読み込ませてください:

@.ai/team/conventions.md を読んで、コーディング規約に従ってください

Q: どのコンテキストを読み込めばいいかわかりません

A: 迷ったら、まず.ai/team/context.mdだけ読み込んでください。
エージェントが必要に応じて他のファイルを推奨してくれます。

次のステップ

オンボーディングが完了したら:

  1. チームの定例会に参加
  2. 実際のタスクをアサイン
  3. 困ったら遠慮なく質問!

### メンター向けオンボーディングガイド

**新メンバーの成功は、メンターの準備にかかっています。** 適切なサポート体制を整えることで、新メンバーは早期に生産的になり、チーム全体の士気も向上します。

コンテキスト共有を活用したオンボーディングは、従来の「メンターが口頭で説明する」スタイルとは根本的に異なります。メンターの役割は「知識の伝達者」から「学習のファシリテーター」へと変化し、新メンバーが自律的に学習できるようサポートすることが中心となります。

#### オンボーディング前の準備(新メンバー参加1週間前)

**初日から円滑にスタートできるよう、事前準備を徹底します。**

##### コンテキストの最新化チェック

```bash
# 1. すべてのコンテキストファイルが最新であることを確認
npm run ai:validate

# 2. 更新日時が古いファイルをチェック
find .ai -name "*.md" -mtime +90  # 90日以上更新されていないファイル

# 3. 必要に応じて更新
# 特に新メンバーが関わるドメインは最新情報に
オンボーディングパッケージの準備
# .ai/onboarding/ ディレクトリを最新化
# 以下のファイルが揃っているか確認:
ls -la .ai/onboarding/
# → README.md (メインガイド)
# → day1-checklist.md (初日のタスク)
# → week1-checklist.md (第1週のタスク)
# → tutorial.md (練習タスク)
アクセス権限の確認

新メンバーが必要なすべてのリソースにアクセスできることを確認:

リソース 確認事項 担当
Gitリポジトリ 読み取り/書き込み権限 Tech Lead
Slack #onboarding, #dev チャンネル参加 Admin
AIツール Claude Code / Cursor ライセンス Admin
ドキュメント Notion / Confluence アクセス Tech Lead
開発環境 VPN、AWSコンソールなど DevOps

初日のメンターセッション(30分)

最初の30分で、オンボーディングの全体像を示します。

アジェンダ
  1. 自己紹介とチーム紹介 (5分)

    • チームメンバーの簡単な紹介
    • プロジェクトの概要説明
  2. コンテキスト共有の説明 (10分)

    「このプロジェクトでは、AIエージェントと一緒に開発します。
    
    重要な知識は全て .ai/ ディレクトリに集約されており、
    エージェントに読み込ませることで、チーム全体の知識を
    活用できます。
    
    今日は、まずこの仕組みを理解し、エージェントの設定を
    完了させましょう。」
    
  3. オンボーディングガイドの説明 (10分)

    • .ai/onboarding/README.mdを一緒に開く
    • 1週間のロードマップを説明
    • 質問のタイミングと方法を伝える
  4. 最初のタスク確認 (5分)

    • 初日に完了すべきタスクを確認
    • 困った時の連絡方法を再確認
初日に新メンバーがやること
## Day 1 チェックリスト

### 午前中
- [ ] 開発環境のセットアップ(.ai/onboarding/setup.md参照)
- [ ] .ai/team/context.md を読む
- [ ] Claude Code / Cursor をインストール・設定
- [ ] テストプロジェクトをビルド・実行

### 午後
- [ ] チュートリアルタスクに着手
- [ ] 担当ドメインのコンテキストを読む
- [ ] メンターと進捗確認(15分)

週次チェックイン(毎週金曜日、30分)

定期的な振り返りで、ブロッカーを早期に発見します。

チェックインのフォーマット
## 週次チェックイン - Week 1

### 今週完了したこと
- [x] 開発環境のセットアップ
- [x] チュートリアルタスク完了
- [x] 認証モジュールの理解
- [ ] 決済モジュールの理解(50%)

### 困ったこと・質問
- Q: エージェントがたまに古い実装パターンを提案する
  A: .ai/team/conventions.md を明示的に読み込ませると解決

### 来週の目標
- [ ] 決済モジュールの理解完了
- [ ] 最初の実タスクに着手
- [ ] コードレビューを1件実施(学習目的)

### メンターからのフィードバック
- PRのコード品質が素晴らしい。コンテキストがうまく活用できている
- 次週は実タスクにチャレンジしてみよう

よくある問題とメンターの対応

問題1: エージェントが提案するコードが規約と違う

症状: 新メンバーが「エージェントが命名規則を守ってくれない」と困惑

原因: コンテキストの読み込み忘れ、または指示が曖昧

メンターの対応:

# 1. エージェントの設定を確認
# 2. 明示的にコンテキストを指定する方法を教える

「こう指示してみてください:

 @.ai/team/context.md
 @.ai/team/conventions.md

 上記のコンテキストに従って、ユーザープロフィール取得機能を実装してください。
 特に命名規則とエラーハンドリングに注意してください。」
問題2: コンテキストが多すぎて何を読めばいいか分からない

症状: 「ファイルが多すぎて圧倒されている」

メンターの対応:

「最初は以下の3ファイルだけで十分です:

1. .ai/team/context.md (プロジェクト全体の概要)
2. .ai/domains/[担当ドメイン]/context.md
3. .ai/onboarding/tutorial.md

他のファイルは、必要になったときに読みましょう。
全部を最初から読む必要はありません。」
問題3: オンボーディングが予定より遅れている

症状: 2週間経っても基本的なタスクが完了していない

メンターの対応:

## ブロッカーの特定

### 1対1ミーティングで確認すべきこと
- [ ] 開発環境は正しく動いているか?
- [ ] コンテキストの内容は理解できているか?
- [ ] エージェントは適切に動作しているか?
- [ ] チーム内でのコミュニケーションに問題はないか?
- [ ] 個人的な事情(リモート環境の問題など)はないか?

### 具体的なアクション
1. ブロッカーを1つずつ解消
2. より小さいタスクに分割
3. ペアプログラミングでサポート
4. 必要に応じてオンボーディング計画を調整

メンター向けチェックリスト

新メンバーのオンボーディングを確実に成功させるためのチェックリストです。

## メンターチェックリスト

### 参加1週間前
- [ ] コンテキストファイルの最新化
- [ ] オンボーディングパッケージの準備
- [ ] アクセス権限の確認・付与
- [ ] チームへの新メンバー参加通知

### 初日
- [ ] 30分のキックオフミーティング実施
- [ ] .ai/onboarding/README.md を一緒に確認
- [ ] エージェント設定のサポート
- [ ] 初日タスクの進捗確認(15分)

### 第1週
- [ ] 毎日の軽いチェックイン(5分)
- [ ] チュートリアルタスクのレビュー
- [ ] 週次チェックイン実施(金曜日)
- [ ] ブロッカーがあれば即座に対応

### 第2週
- [ ] 実タスクへの移行サポート
- [ ] 最初のPRのレビュー(丁寧に)
- [ ] 週次チェックイン実施
- [ ] チーム定例会への参加促進

### 第3-4週
- [ ] 自律的な作業への移行確認
- [ ] コードレビューの質を確認
- [ ] 週次チェックイン実施
- [ ] オンボーディング完了の判断

### オンボーディング完了時
- [ ] フィードバックセッション実施
- [ ] オンボーディングプロセスの改善点収集
- [ ] .ai/onboarding/ の更新検討
- [ ] 次回新メンバー向けに改善を反映

オンボーディング成功の指標

定量的・定性的な指標で、オンボーディングの成功を測ります。

定量指標
指標 目標 測定方法
オンボーディング期間 2週間以内 最初のPRマージまでの日数
自律度 3週目に80%自律 メンターへの質問頻度
PRの質 初回PRで大きな修正なし レビューコメント数
エージェント活用 80%以上のタスクで活用 本人へのヒアリング
定性指標

週次チェックインで以下を確認:

  • 新メンバーが「次に何をすべきか」を理解している
  • エージェントを効果的に活用できている
  • チームの文化・規約を理解している
  • 困ったときに気軽に質問できている
  • 自信を持ってコードを書けている

トラブルシューティング: メンター向けFAQ

Q: 新メンバーがコンテキストを読んでいない様子

A: 読む動機付けと具体的なメリットを示す

「コンテキストを読むと、こんな風にエージェントが正確に答えてくれます」

(実演)
エージェント: "認証はJWTを使用し、アクセストークンは15分、
               リフレッシュトークンは7日の有効期限です。
               実装はauth/jwt.strategy.tsを参照してください。"

「このように、チーム全体の知識をエージェントが教えてくれるので、
 メンターに聞かなくても自分で進められます。」
Q: エージェントの提案をそのまま受け入れてしまう

A: 批判的思考を促す質問をする

メンター: 「このコード、なぜこの実装を選んだと思う?」
新メンバー: 「エージェントが提案したので...」
メンター: 「他の選択肢もあったはず。例えば、○○という方法もあるよね?
          エージェントの提案を一度立ち止まって検証する習慣をつけよう。」
Q: 質問が来なさすぎて、逆に心配

A: 定期的に様子を確認し、質問しやすい環境を作る

「毎日15時にちょっとした雑談の時間を作ろう。
 進捗だけじゃなく、小さな疑問でも気軽に話せる場を作りたい。」

(Slackで)
「今日はどう?ちょっとした疑問でも、気軽にメンションしてね!」

チーム規模別の戦略

「すべてのチームに同じ戦略」は機能しません。 2人のスタートアップと、50人の組織では、必要なコンテキスト管理の複雑さが全く異なります。重要なのは、今のチームサイズに合った現実的なアプローチを選び、成長に合わせて段階的に進化させることです。

小規模チームが大企業向けの複雑な仕組みを導入しても、オーバーヘッドに潰されるだけです。逆に、大規模チームが小規模向けのシンプルな構造のままでは、すぐにスケールの限界に達します。以下では、チーム規模ごとの最適な戦略と、次のフェーズへの移行タイミングを解説します。

小規模チーム(2-5人)

「とにかくシンプルに」——これが小規模チームの鉄則です。 メンバー全員が顔を合わせてコミュニケーションできる規模では、複雑な仕組みは不要です。1つのファイルに必要な情報を集約し、口頭での合意形成を基本とすることで、オーバーヘッドを最小化しながら、コンテキスト共有のメリットを享受できます。

特徴:

  • コンテキストは1ファイルでOK
  • ADRは簡易版(決定理由を数行)
  • レビューは口頭でも可

推奨構成:

.ai/
├── team/
│   └── context.md    # 全部ここに
└── personal/         # 個人用

中規模チーム(6-20人)

「階層化と専門化」——これが中規模チームの成功の鍵です。 もはや全員が全てを把握することは不可能になり、ドメインごとにチームが分かれ始めます。このフェーズでは、コンテキストを適切に分割し、各チームが自律的に管理できる体制を整えることが重要です。ADRによる意思決定の記録も、この規模から必須になります。

特徴:

  • ドメイン別にコンテキスト分割
  • ADRは必須
  • レビュープロセスを整備

推奨構成:

.ai/
├── team/
│   ├── context.md
│   ├── architecture.md
│   └── adr/
├── domains/
│   ├── auth/
│   ├── payment/
│   └── notification/
└── personal/

大規模チーム(21人以上)

「組織化と自動化」——大規模チームでは、仕組み化なしには回りません。 コンテキストオーナー制による明確な責任分担、定期的なレビュー会による品質維持、自動検証ツールによる継続的な品質保証——これらの組織的な取り組みが必須です。さらに、ベクトルDBを使ったセマンティック検索の導入により、膨大なコンテキストから必要な情報を素早く見つけられる環境を整えます。

特徴:

  • コンテキストオーナーを任命
  • 定期的なコンテキストレビュー会
  • 自動検証ツールが必須
  • セマンティック検索の導入

推奨構成:

.ai/
├── team/
│   ├── context.md
│   ├── architecture.md
│   ├── conventions.md
│   └── adr/
├── domains/
│   ├── domain-a/
│   │   ├── OWNERS          # オーナー情報
│   │   └── context.md
│   └── domain-b/
├── templates/
├── scripts/
│   ├── generate-context.ts
│   ├── validate-context.ts
│   └── search-context.ts   # セマンティック検索
└── personal/

追加施策:

  • 月次のコンテキストレビュー会: 全ドメインのオーナーが集まり、横断的な整合性を確認
  • コンテキスト品質メトリクス: 更新頻度、カバレッジ、エージェント利用時の成功率などを定量評価
  • ベクトルDBでの検索システム: 自然言語で「認証の実装方法」と検索すれば、関連するすべてのコンテキストを取得

次のフェーズへの移行タイミング

「いつ、次のレベルに移行すべきか?」——この判断を間違えると、チーム全体の生産性が大きく低下します。

早すぎる移行は不要な複雑さをもたらし、遅すぎる移行は混乱を招きます。以下のシグナルを見逃さず、適切なタイミングで戦略を進化させることが重要です。

小規模→中規模への移行シグナル

以下の3つ以上に該当したら、中規模戦略への移行を検討すべきタイミングです。

シグナル 具体的な兆候 影響
情報過多 context.mdが1,000行を超え、読むのが苦痛 必要な情報を見つけるのに時間がかかる
専門化の開始 「認証は○○さん」「決済は△△さん」と役割分担が明確化 全員が全てを知る必要がなくなる
口頭確認の限界 Slackでの合意形成に参加者全員を集めるのが困難 決定プロセスが遅延する
新メンバーの困惑 新しいメンバーが「どこから読めばいいか」迷う オンボーディングに時間がかかる
コンフリクト増加 context.mdの編集で頻繁にGitコンフリクトが発生 更新が億劫になる
意思決定の曖昧さ 「なぜこう決めたか」が後から分からない 同じ議論を繰り返す

移行のステップ:

# Step 1: ドメインを特定(チームで議論)
# 例: 認証、決済、通知、ユーザー管理

# Step 2: ディレクトリ構造を作成
mkdir -p .ai/domains/{auth,payment,notification,user}

# Step 3: context.mdから該当部分を抽出
# 認証に関する部分 → .ai/domains/auth/context.md
# 決済に関する部分 → .ai/domains/payment/context.md

# Step 4: チーム共通context.mdはスリム化
# プロジェクト全体の概要と原則のみを残す

# Step 5: ADRディレクトリを作成
mkdir -p .ai/team/adr

# Step 6: 過去の重要な決定をADRとして文書化
# 「なぜJWTを選んだか」「なぜPostgreSQLを選んだか」など

移行時の注意点:

  • 段階的に移行: 一度に全てを変えず、1ドメインずつ分離
  • チームに周知: 新しい構造を全員で共有し、どこに何があるかを明確に
  • 古いファイルは残す: 移行期間中は旧context.mdも参照できるようにしておく
  • リンクを更新: エージェントの設定やREADMEのリンクを新しい構造に合わせる

中規模→大規模への移行シグナル

組織的な管理が必要になる臨界点のサインです。

シグナル 具体的な兆候 影響
更新の停滞 誰も積極的にコンテキストを更新しなくなる 情報が古くなり、信頼性が低下
品質のばらつき ドメインによってコンテキストの質が大きく異なる 一部のドメインだけエージェントが使えない
検索の困難 「あの情報、どこだっけ?」が日常茶飯事 同じ質問が何度も繰り返される
横断的整合性の欠如 ドメイン間で用語や方針が矛盾している エージェントが混乱し、誤った提案をする
オンボーディングの長期化 新メンバーが生産的になるまで4週間以上かかる 採用コストが増大
レビュー負担の増大 コンテキストの変更レビューに1時間以上かかる PRのマージが遅延する

移行のステップ:

# Step 1: コンテキストオーナーを任命
# 各ドメインに責任者を明確化

# OWNERSファイルを作成
cat > .ai/domains/auth/OWNERS <<EOF
# 認証ドメインのオーナー
Primary: @alice
Secondary: @bob

# レビュアー
Reviewers:
  - @charlie
  - @david
EOF

# Step 2: 自動検証ツールのセットアップ
npm install --save-dev tsx glob chalk
npm run ai:validate  # 初回実行

# Step 3: GitHub Actionsでの自動検証
# .github/workflows/validate-context.yml を設定
# (詳細は「GitHub Actionsとの連携」セクション参照)

# Step 4: 定期レビュー会の設定
# カレンダーに月次レビュー会を追加(第1金曜日など)

# Step 5: コンテキスト品質メトリクスの導入
npm run ai:metrics  # メトリクスレポート生成

# Step 6: セマンティック検索の導入(オプション)
# Pinecone、Weaviate、またはPgvectorなどのベクトルDBをセットアップ

移行時の注意点:

  • 役割の明確化: オーナー制度の目的とメリットをチーム全員に説明
  • ツールの習熟: 自動検証ツールの使い方をハンズオン形式でトレーニング
  • レビュー会の実施: 初回のレビュー会でプロセスを確立し、改善点を洗い出す
  • フィードバックループ: 新しい体制で1ヶ月運用し、問題点を収集して改善

移行失敗のアンチパターン

これらの間違いを避けることで、スムーズな移行が実現します。

❌ アンチパターン1: 一気に複雑化
# 悪い例: 小規模チーム(5人)がいきなり大規模向けの構造を導入
❌ コンテキストオーナー制(不要)
❌ 自動検証ツール(過剰)
❌ 月次レビュー会(コストが高い)

結果: オーバーヘッドが大きすぎて誰も使わなくなる

正しいアプローチ: 今のチームサイズに合った最小限の構成から始める

❌ アンチパターン2: 移行を先延ばし
# 悪い例: 15人規模なのに1ファイル運用を続ける
問題:
- context.mdが3,000行超え
- 毎週Gitコンフリクトが発生
- 新メンバーが情報を見つけられない

結果: 「コンテキストは使えない」という諦めムードが蔓延

正しいアプローチ: シグナルを見逃さず、早めに移行を検討

❌ アンチパターン3: 移行を周知せず実施
# 悪い例: 一部のメンバーだけで新構造を作り、告知なしにマージ
結果:
- 他のメンバーが新構造を理解していない
- 古いcontext.mdを参照し続ける
- コンテキストが二重管理になる

解決策: 移行計画を全員に共有し、ハンズオンで説明

移行チェックリスト

実際の移行作業で使える実践的なチェックリストです。

## 小規模→中規模への移行チェックリスト

### 準備フェーズ
- [ ] チームでドメイン分割を議論し、合意
- [ ] 新しいディレクトリ構造を設計
- [ ] 移行計画をドキュメント化
- [ ] チーム全体に移行を周知(Slack + ミーティング)

### 実装フェーズ
- [ ] ディレクトリ構造を作成
- [ ] context.mdからドメイン別に内容を分離
- [ ] ADRディレクトリを作成
- [ ] 過去の重要な決定をADRとして文書化
- [ ] README.mdに新構造の説明を追加

### 移行フェーズ
- [ ] 旧context.mdを deprecated/ に移動
- [ ] エージェント設定を更新
- [ ] チーム内ドキュメントのリンクを更新
- [ ] ハンズオン形式で新構造をチームに説明

### 検証フェーズ
- [ ] 全メンバーが新構造を理解している
- [ ] エージェントが新構造を正しく参照できる
- [ ] 1週間運用して問題点を収集
- [ ] 必要に応じて調整

### 完了
- [ ] 旧context.mdを削除(または完全にアーカイブ)
- [ ] 移行の振り返りを実施
- [ ] ベストプラクティスをドキュメント化
## 中規模→大規模への移行チェックリスト

### 準備フェーズ
- [ ] コンテキストオーナー候補者を選定
- [ ] オーナー制度の目的と役割を文書化
- [ ] 自動検証ツールの仕様を決定
- [ ] レビュー会の頻度と形式を決定

### オーナー制度の導入
- [ ] 各ドメインにオーナーを任命
- [ ] OWNERSファイルを作成
- [ ] オーナーの責任範囲を明確化
- [ ] オーナー向けガイドラインを作成

### 自動化の導入
- [ ] 検証スクリプトを実装
- [ ] package.jsonスクリプトを設定
- [ ] GitHub Actionsを設定
- [ ] 初回実行でエラーを修正

### 定期レビューの確立
- [ ] レビュー会の日程を確定
- [ ] 第1回レビュー会を実施
- [ ] レビュープロセスを文書化
- [ ] 改善点を次回に反映

### メトリクスの導入
- [ ] 測定する指標を決定
- [ ] メトリクス収集スクリプトを実装
- [ ] ダッシュボードを作成(Notion / Grafana等)
- [ ] 月次でメトリクスをレビュー

### 完了
- [ ] 全体の振り返りを実施
- [ ] 成功要因と課題を文書化
- [ ] 次のフェーズ(さらなる拡大)に向けた計画

ケーススタディ

理論だけでは不十分です。実際のプロジェクトで、コンテキスト共有戦略がどのように機能したのか——リアルな事例から学びましょう。

成功事例を見ることで、「自分たちのチームでも実現できる」という確信が得られます。また、他のチームが直面した課題と解決策を知ることで、同じ失敗を避けることができます。以下の2つのケーススタディは、異なる状況(成長するスタートアップ、分散型リモートチーム)でのコンテキスト共有の実践例です。

ケース1: スタートアップの成長に伴う進化

「小さく始めて、大きく育てる」——スタートアップの理想的な進化パターンです。

背景: 創業3人 → 15人に成長

多くのスタートアップは、創業時にコンテキスト管理の重要性を軽視しがちです。しかし、チームが成長し始めると、突然その必要性に直面します。このケースでは、成長の各フェーズでコンテキスト戦略をどう進化させたかを追跡します。

Phase 1: 創業期(3人)

「全員が全てを知っている」段階——必要最小限で十分です。

.ai/
└── context.md  # シンプルに1ファイル

運用:

  • 口頭で合意(Slackで簡単に確認)
  • 週1で更新(金曜日のふりかえりで)
  • ADRなし(決定事項は context.md に直接追記)

この時期の教訓: 完璧を目指さず、まず始めることが重要。1ファイルでも、ないよりは100倍マシ。

Phase 2: 成長期(8人)

「もう全員では把握しきれない」——構造化が必要になる転換点です。

.ai/
├── team/
│   ├── context.md
│   ├── architecture.md
│   └── adr/
└── domains/
    ├── auth/
    └── payment/

運用:

  • ドメイン別に分割(認証チームと決済チームが独立)
  • ADR導入(重要な決定は必ず記録)
  • PRレビュー必須(口頭合意だけでは追いつかない)

この時期の課題: 分割のタイミングが遅れ、一時的に混乱。早めの構造化が重要と学んだ。

Phase 3: 拡大期(15人)

「仕組み化なしには回らない」——組織的な管理体制の確立です。

.ai/
├── team/
├── domains/ (6ドメイン)
├── templates/
└── scripts/
    └── validate-context.ts

運用:

  • ドメインオーナー制(各ドメインに責任者を任命)
  • 自動検証導入(CIでコンテキストの品質をチェック)
  • 月次レビュー会(全ドメインオーナーが集まって整合性確認)

成果: オンボーディング時間が2週間→3日に短縮。新メンバーが「何を読めばいいか明確」と好評。

ケース2: リモートチームでの活用

「顔を合わせられない」からこそ、コンテキストが生命線になります。

背景: 完全リモート、10人、3タイムゾーン(日本、アメリカ東海岸、ヨーロッパ)

リモートワークでは、オフィスでの「ちょっといいですか?」ができません。同期的なコミュニケーションが限られる中で、いかに効率的に知識を共有するか——これがチームの生産性を決定します。

課題:

  • 同期的なコミュニケーションが困難(重なる時間は1日2-3時間のみ)
  • 暗黙知の共有が難しい(「見て学ぶ」ができない)
  • オンボーディングに時間がかかる(質問してもすぐに返事が来ない)

解決策:

  1. 非同期コミュニケーションをコンテキスト化

Slackでの議論を「消えゆく情報」にしない——すべての重要な決定をコンテキストに昇華させます。

タイムゾーンをまたいだ非同期な議論は、結論に至るまで24-48時間かかることもあります。その貴重な議論の成果を、チャットログに埋もれさせてはいけません。決定事項を即座にコンテキストファイルに反映することで、後から参加したメンバーや新メンバーも、議論の背景と結論を理解できます。

# .ai/team/async-decisions.md

## 非同期で決定された事項

### 2024-10-10: データベースインデックス戦略

**議論スレッド**: Slack #backend-discussion
**参加者**: @alice, @bob, @charlie
**決定事項**: 
- 頻繁に検索されるカラムには自動でインデックス
- 複合インデックスの命名規則: `idx_{table}_{col1}_{col2}`

**理由**:
パフォーマンステストの結果、検索クエリが遅延していた

**実装例**:
\`\`\`sql
CREATE INDEX idx_users_email_verified 
ON users(email, is_verified);
\`\`\`
  1. タイムゾーン情報をコンテキストに含める

「いつ、誰に連絡すべきか」を明確にすることで、リモートの摩擦を最小化します。

リモートチームでよくある問題は、「今、この人は起きているのか?」がわからないことです。緊急時の連絡方法、レビューの期待応答時間、同期ミーティングの時間——これらを明文化することで、チーム全体の連携がスムーズになります。

# .ai/team/context.md

## チーム情報

### タイムゾーン
- @alice: JST (UTC+9)
- @bob: EST (UTC-5)
- @charlie: CET (UTC+1)

### 同期ミーティング
- 週次: 火曜 22:00 JST / 09:00 EST / 14:00 CET
- 緊急時: Slack #urgent で呼びかけ

### 非同期コミュニケーション原則
- 重要な決定はSlackスレッドで議論
- 決定事項は24時間以内にコンテキストに反映
- レビューは24時間以内に返答

ツールとワークフロー

手作業に頼らず、ツールで自動化する——これが持続可能なコンテキスト管理の秘訣です。

コンテキスト管理を「頑張って手動で更新する」だけでは、いずれ破綻します。検証、生成、検索といった定型作業は、スクリプトで自動化し、開発者は本質的な内容の改善に集中すべきです。以下のツールとワークフローを整備することで、コンテキスト管理の負担を大幅に軽減できます。

package.json スクリプト統合

npm runで実行できるようにすることで、チーム全体が同じ方法でコンテキストを操作できます。

コンテキスト関連の操作を標準化されたnpmスクリプトとして提供することで、「やり方がわからない」という障壁をなくします。新メンバーでも、npm run ai:validateと入力するだけで、コンテキストの妥当性を確認できます。

{
  "scripts": {
    "ai:validate": "tsx .ai/scripts/validate-context.ts",
    "ai:generate": "tsx .ai/scripts/generate-context.ts",
    "ai:search": "tsx .ai/scripts/search-context.ts",
    "ai:diff": "tsx .ai/scripts/diff-context.ts",
    "ai:changelog": "tsx .ai/scripts/diff-context.ts --changelog --since='30 days ago'",
    "ai:onboarding": "tsx .ai/scripts/onboarding-tracker.ts",
    "precommit": "npm run ai:validate"
  }
}

各スクリプトの使い方

コンテキストを日常の開発ワークフローに組み込むための実践的な使用例です。

それぞれのスクリプトがどのタイミングで、どのように使われるべきかを理解することで、コンテキスト管理が自然な習慣として定着します。

1. npm run ai:validate - コンテキストの検証

タイミング: コミット前、PR作成前、定期チェック

# コンテキストファイルの妥当性をチェック
$ npm run ai:validate

✓ .ai/team/context.md: 構文OK、必須項目すべて存在
✓ .ai/domains/auth/context.md: リンク切れなし
✗ .ai/domains/payment/context.md: バージョン情報が古い(90日以上未更新)

何をチェックするか:

  • 必須セクションの存在確認
  • Markdownの構文エラー
  • 内部リンク切れの検出
  • 更新日時の鮮度確認
  • バージョン番号の整合性
2. npm run ai:generate - コンテキストの自動生成

タイミング: 新機能追加時、大規模リファクタリング後

# コードベースから自動的にコンテキストを生成
$ npm run ai:generate

📊 プロジェクト分析中...
  - TypeScriptファイル: 248個
  - テストファイル: 156個
  - 外部依存: 42個

✓ 技術スタック情報を更新
✓ ディレクトリ構造を更新
✓ APIエンドポイント一覧を生成

📝 .ai/generated/tech-stack.md を更新しました

何を生成するか:

  • 技術スタック一覧(package.jsonから)
  • ディレクトリ構造図
  • APIエンドポイント一覧(ルーティングから)
  • 依存関係グラフ
3. npm run ai:search - コンテキストの検索

タイミング: 特定の技術判断や設計理由を調べたい時

# コンテキスト内を横断検索
$ npm run ai:search "JWT"

🔍 検索結果: 3件

📄 .ai/team/adr/001-jwt-auth.md:12
  > JWT認証を採用。アクセストークン15分、リフレッシュトークン7日

📄 .ai/domains/auth/context.md:45
  > トークンの検証にはjsonwebtokenライブラリを使用

📄 .ai/team/context.md:365
  > 認証: JWT(アクセストークン15分、リフレッシュトークン7日)

使い方のコツ:

  • キーワード検索: npm run ai:search "Redis"
  • 複数キーワード: npm run ai:search "authentication error"
  • 正規表現: npm run ai:search --regex "JWT|OAuth"
4. npm run ai:diff - コンテキストの差分表示

タイミング: PR作成時、月次レビュー時

# mainブランチとの差分を表示
$ npm run ai:diff

📊 コンテキスト変更サマリー:

🆕 追加:
  - .ai/domains/notification/context.md

📝 変更:
  - .ai/team/context.md
    + 通知システムの技術スタック追加
    + バージョン 2.3.0 → 2.4.0

🗑️ 削除:
  - .ai/deprecated/old-api.md

オプション:

  • --branch=<ブランチ名>: 特定ブランチとの比較
  • --since=<日付>: 特定日以降の変更のみ
  • --format=json: JSON形式で出力
5. npm run ai:changelog - コンテキストの変更履歴

タイミング: 定期的なチームレビュー、新メンバーのオンボーディング

# 過去30日間のコンテキスト変更を確認
$ npm run ai:changelog

📅 コンテキスト変更履歴(過去30日間)

2024-10-12 (@alice)
  • JWT有効期限を24時間→15分に変更 (ADR-015)
  • セキュリティ要件を強化

2024-10-10 (@bob)
  • Redis導入の決定 (ADR-014)
  • キャッシュ戦略を追加

2024-10-05 (@charlie)
  • Clean Architecture採用 (ADR-012)
  • レイヤー構造の説明を追加

カスタマイズ:

  • 期間指定: --since='7 days ago' / --since='2024-10-01'
  • 著者フィルタ: --author=alice
  • ドメイン指定: --domain=auth
6. npm run ai:onboarding - オンボーディング進捗追跡

タイミング: 新メンバー参加時、毎日の進捗確認

# 新メンバーの学習進捗をトラッキング
$ npm run ai:onboarding

👤 新メンバー: @new-member
📅 参加日: 2024-10-01(12日経過)

✅ 完了タスク:
  [x] プロジェクトコンテキストの読解
  [x] 開発環境のセットアップ
  [x] 認証モジュールの理解

🔄 進行中:
  [ ] 決済モジュールの理解(50%)

📋 未着手:
  [ ] 通知システムの理解
  [ ] 最初の機能実装

📊 全体進捗: 60% (6/10タスク完了)

メンター向け機能:

  • 進捗レポート生成
  • ブロッカーの自動検出
  • 推奨される次のステップ提案
7. precommit - コミット前の自動検証

タイミング: 自動実行(git commit時)

# Huskyと連携して自動実行
$ git commit -m "Add payment context"

🔍 コンテキスト検証を実行中...
✓ すべてのチェックに合格

[main 1a2b3c4] Add payment context
 1 file changed, 45 insertions(+)

検証内容:

  • コンテキストファイルの構文チェック
  • 必須項目の存在確認
  • リンク切れの検出
  • コミット対象ファイルのみを検証(高速)

セットアップ方法

初回セットアップは5分で完了します。

# 1. 必要な依存関係をインストール
npm install --save-dev tsx glob chalk

# 2. Huskyをセットアップ(precommitフック用)
npm install --save-dev husky
npx husky init

# 3. pre-commitフックを設定
echo "npm run ai:validate" > .husky/pre-commit
chmod +x .husky/pre-commit

# 4. 動作確認
npm run ai:validate

トラブルシューティング:

エラー 原因 解決方法
command not found: tsx tsxがインストールされていない npm install --save-dev tsx
Cannot find module スクリプトファイルが存在しない .ai/scripts/配下にスクリプトを配置
Permission denied 実行権限がない chmod +x .ai/scripts/*.ts

コンテキスト検索スクリプト

「あの議論、どこに書いてあったっけ?」——膨大なコンテキストから必要な情報を素早く見つけるための検索ツールです。

チームのコンテキストが成長するにつれ、「どこに何が書いてあるか」を把握するのが困難になります。grepやファイル検索では、キーワードが一致しないと見つかりません。以下の検索スクリプトは、複数ファイルを横断し、関連性でソートして結果を表示します。

// .ai/scripts/search-context.ts

import fs from 'fs/promises';
import path from 'path';
import { glob } from 'glob';
import chalk from 'chalk';

interface SearchResult {
  file: string;
  line: number;
  content: string;
  context: string[]; // 前後の行
  relevance: number; // 関連度スコア
}

class ContextSearcher {
  async search(query: string, options: {
    caseSensitive?: boolean;
    wholeWord?: boolean;
    contextLines?: number;
  } = {}): Promise<SearchResult[]> {
    const {
      caseSensitive = false,
      wholeWord = false,
      contextLines = 2,
    } = options;

    console.log(chalk.blue(`🔍 Searching for: "${query}"\n`));

    const files = await glob('.ai/**/*.md');
    const results: SearchResult[] = [];

    for (const file of files) {
      const content = await fs.readFile(file, 'utf-8');
      const lines = content.split('\n');

      lines.forEach((line, index) => {
        let searchLine = line;
        let searchQuery = query;

        if (!caseSensitive) {
          searchLine = line.toLowerCase();
          searchQuery = query.toLowerCase();
        }

        let matches = false;
        if (wholeWord) {
          const regex = new RegExp(`\\b${searchQuery}\\b`, caseSensitive ? '' : 'i');
          matches = regex.test(line);
        } else {
          matches = searchLine.includes(searchQuery);
        }

        if (matches) {
          // 前後の行を取得
          const start = Math.max(0, index - contextLines);
          const end = Math.min(lines.length, index + contextLines + 1);
          const context = lines.slice(start, end);

          // 関連度スコアを計算
          const relevance = this.calculateRelevance(line, query, file);

          results.push({
            file,
            line: index + 1,
            content: line.trim(),
            context,
            relevance,
          });
        }
      });
    }

    // 関連度でソート
    results.sort((a, b) => b.relevance - a.relevance);

    return results;
  }

  private calculateRelevance(line: string, query: string, file: string): number {
    let score = 1;

    // 見出し内に含まれる場合はスコアアップ
    if (line.trim().startsWith('#')) {
      score += 5;
    }

    // 太字・強調されている場合
    if (line.includes('**') || line.includes('__')) {
      score += 2;
    }

    // ファイル名が context.md の場合
    if (file.includes('context.md')) {
      score += 3;
    }

    // ADRファイルの場合
    if (file.includes('/adr/')) {
      score += 4;
    }

    // クエリが完全一致する場合
    if (line.toLowerCase().includes(query.toLowerCase())) {
      const occurrences = (line.toLowerCase().match(new RegExp(query.toLowerCase(), 'g')) || []).length;
      score += occurrences * 2;
    }

    return score;
  }

  printResults(results: SearchResult[], maxResults: number = 20): void {
    if (results.length === 0) {
      console.log(chalk.yellow('No results found.'));
      return;
    }

    const displayResults = results.slice(0, maxResults);

    console.log(chalk.green(`Found ${results.length} results (showing top ${displayResults.length}):\n`));

    displayResults.forEach((result, index) => {
      console.log(chalk.bold(`${index + 1}. ${result.file}:${result.line}`));
      console.log(chalk.gray(`   Relevance: ${result.relevance}`));
      console.log(chalk.cyan(`   ${result.content}`));

      // コンテキストを表示(該当行以外はグレーアウト)
      result.context.forEach((contextLine, i) => {
        const lineNum = result.line - 2 + i;
        if (lineNum === result.line) {
          console.log(chalk.yellow(`   ${lineNum}: ${contextLine}`));
        } else {
          console.log(chalk.gray(`   ${lineNum}: ${contextLine}`));
        }
      });

      console.log('');
    });

    if (results.length > maxResults) {
      console.log(chalk.gray(`... and ${results.length - maxResults} more results`));
    }
  }
}

// CLI
if (require.main === module) {
  const args = process.argv.slice(2);

  if (args.length === 0) {
    console.log('Usage: npm run ai:search "your query"');
    console.log('Options:');
    console.log('  --case-sensitive     Case-sensitive search');
    console.log('  --whole-word         Match whole words only');
    console.log('  --context=N          Number of context lines (default: 2)');
    process.exit(1);
  }

  const query = args[0];
  const options: any = {};

  args.slice(1).forEach(arg => {
    if (arg === '--case-sensitive') {
      options.caseSensitive = true;
    } else if (arg === '--whole-word') {
      options.wholeWord = true;
    } else if (arg.startsWith('--context=')) {
      options.contextLines = parseInt(arg.split('=')[1]);
    }
  });

  const searcher = new ContextSearcher();

  searcher
    .search(query, options)
    .then(results => {
      searcher.printResults(results);
    })
    .catch(error => {
      console.error(chalk.red('Search error:'), error);
      process.exit(1);
    });
}

export { ContextSearcher };

使用例:

# 基本的な検索
npm run ai:search "JWT認証"

# 出力例:
# 🔍 Searching for: "JWT認証"
#
# Found 5 results (showing top 5):
#
# 1. .ai/team/context.md:365
#    Relevance: 12
#    認証: JWT(アクセストークン15分、リフレッシュトークン7日)
#    363:
#    364: 3. **セキュリティ**
#    365:    - 認証: JWT(アクセストークン15分、リフレッシュトークン7日)
#    366:    - 認可: RBAC(Role-Based Access Control)
#    367:    - 暗号化: すべての機密データはAES-256
#
# 2. .ai/team/adr/001-jwt-auth.md:1
#    Relevance: 9
#    # ADR-001: JWT認証の採用

# 大文字小文字を区別
npm run ai:search "JWT" --case-sensitive

# 単語全体一致
npm run ai:search "auth" --whole-word

# コンテキスト行数を増やす
npm run ai:search "認証" --context=5

コンテキスト差分表示スクリプト

「先月から何が変わったの?」——コンテキストの変更履歴を視覚的に表示します。

チームメンバーが「最近の変更を把握したい」とき、gitの差分を見るだけでは不十分です。どのファイルのどのセクションがどう変わったのか、一目でわかる形式で表示する必要があります。

// .ai/scripts/diff-context.ts

import { execSync } from 'child_process';
import chalk from 'chalk';

interface ContextDiff {
  file: string;
  additions: number;
  deletions: number;
  changes: Change[];
}

interface Change {
  type: 'add' | 'delete' | 'modify';
  line: number;
  content: string;
}

class ContextDiffer {
  async diff(since: string = '1 week ago'): Promise<ContextDiff[]> {
    console.log(chalk.blue(`📊 Context changes since ${since}\n`));

    // Git diffを取得
    const diffOutput = execSync(
      `git diff --unified=0 '@{${since}}' HEAD -- .ai/`,
      { encoding: 'utf-8' }
    ).toString();

    return this.parseDiff(diffOutput);
  }

  private parseDiff(diffOutput: string): ContextDiff[] {
    const diffs: ContextDiff[] = [];
    const files = diffOutput.split('diff --git');

    for (const fileDiff of files) {
      if (!fileDiff.trim()) continue;

      const fileMatch = fileDiff.match(/a\/(.+?)\s+b\//);
      if (!fileMatch) continue;

      const file = fileMatch[1];
      const changes: Change[] = [];
      let additions = 0;
      let deletions = 0;

      // 変更行を抽出
      const lines = fileDiff.split('\n');
      let currentLine = 0;

      for (const line of lines) {
        if (line.startsWith('@@')) {
          const match = line.match(/@@ -\d+,?\d* \+(\d+)/);
          if (match) {
            currentLine = parseInt(match[1]);
          }
        } else if (line.startsWith('+') && !line.startsWith('+++')) {
          additions++;
          changes.push({
            type: 'add',
            line: currentLine++,
            content: line.substring(1),
          });
        } else if (line.startsWith('-') && !line.startsWith('---')) {
          deletions++;
          changes.push({
            type: 'delete',
            line: currentLine,
            content: line.substring(1),
          });
        }
      }

      if (changes.length > 0) {
        diffs.push({ file, additions, deletions, changes });
      }
    }

    return diffs;
  }

  printDiff(diffs: ContextDiff[]): void {
    if (diffs.length === 0) {
      console.log(chalk.gray('No changes found.'));
      return;
    }

    diffs.forEach(diff => {
      console.log(chalk.bold(`\n📄 ${diff.file}`));
      console.log(chalk.green(`   +${diff.additions} additions`) + ' ' + chalk.red(`-${diff.deletions} deletions`));

      // 変更内容をグループ化して表示
      const grouped = this.groupChanges(diff.changes);

      grouped.forEach(group => {
        console.log(chalk.gray(`\n   Lines ${group.startLine}-${group.endLine}:`));

        group.changes.forEach(change => {
          const color = change.type === 'add' ? chalk.green : chalk.red;
          const prefix = change.type === 'add' ? '+' : '-';
          console.log(color(`   ${prefix} ${change.content}`));
        });
      });
    });

    // サマリー
    const totalAdditions = diffs.reduce((sum, d) => sum + d.additions, 0);
    const totalDeletions = diffs.reduce((sum, d) => sum + d.deletions, 0);

    console.log(chalk.blue('\n' + ''.repeat(60)));
    console.log(chalk.bold('Summary:'));
    console.log(`  Files changed: ${diffs.length}`);
    console.log(chalk.green(`  Total additions: ${totalAdditions}`));
    console.log(chalk.red(`  Total deletions: ${totalDeletions}`));
  }

  private groupChanges(changes: Change[]): { startLine: number; endLine: number; changes: Change[] }[] {
    const groups: { startLine: number; endLine: number; changes: Change[] }[] = [];
    let currentGroup: Change[] = [];
    let startLine = 0;

    changes.forEach((change, index) => {
      if (currentGroup.length === 0) {
        startLine = change.line;
        currentGroup.push(change);
      } else {
        const lastChange = currentGroup[currentGroup.length - 1];
        if (change.line - lastChange.line <= 3) {
          currentGroup.push(change);
        } else {
          groups.push({
            startLine,
            endLine: lastChange.line,
            changes: [...currentGroup],
          });
          startLine = change.line;
          currentGroup = [change];
        }
      }
    });

    if (currentGroup.length > 0) {
      groups.push({
        startLine,
        endLine: currentGroup[currentGroup.length - 1].line,
        changes: currentGroup,
      });
    }

    return groups;
  }

  async generateChangelog(since: string = '30 days ago'): Promise<string> {
    const diffs = await this.diff(since);

    let changelog = `# Context Changelog\n\n`;
    changelog += `## Changes since ${since}\n\n`;

    diffs.forEach(diff => {
      changelog += `### ${diff.file}\n\n`;
      changelog += `- ${diff.additions} additions, ${diff.deletions} deletions\n\n`;

      const significantChanges = diff.changes.filter(c => {
        // 見出しの変更や重要な行のみ
        return c.content.trim().startsWith('#') || c.content.includes('**');
      });

      if (significantChanges.length > 0) {
        changelog += `Key changes:\n`;
        significantChanges.forEach(change => {
          const prefix = change.type === 'add' ? '' : '';
          changelog += `- ${prefix} ${change.content.trim()}\n`;
        });
        changelog += '\n';
      }
    });

    return changelog;
  }
}

// CLI
if (require.main === module) {
  const args = process.argv.slice(2);
  let since = '1 week ago';
  let generateChangelog = false;

  args.forEach(arg => {
    if (arg.startsWith('--since=')) {
      since = arg.split('=')[1].replace(/['"]/g, '');
    } else if (arg === '--changelog') {
      generateChangelog = true;
    }
  });

  const differ = new ContextDiffer();

  if (generateChangelog) {
    differ.generateChangelog(since).then(changelog => {
      console.log(changelog);
    });
  } else {
    differ.diff(since).then(diffs => {
      differ.printDiff(diffs);
    });
  }
}

export { ContextDiffer };

使用例:

# 先週からの変更を表示
npm run ai:diff

# 特定期間の変更
npm run ai:diff -- --since="1 month ago"

# 変更履歴をMarkdownで生成
npm run ai:changelog -- --since="30 days ago" > CONTEXT_CHANGELOG.md

ベストプラクティス集

成功するチームと失敗するチームを分けるのは、細部へのこだわりです。

コンテキスト共有の「理論」は理解できても、実際の運用で成功するかどうかは、日々の小さな習慣にかかっています。以下のベストプラクティスは、数十のチームの成功と失敗から抽出された、実践的な指針です。「やるべきこと」と「やってはいけないこと」を明確に理解することで、チームは正しい道を進めます。

✅ DO: やるべきこと

1. コンテキストは常に最新に保つ

「後で更新しよう」は、永遠に来ません。 重要な決定をした瞬間、アーキテクチャを変更した瞬間——その場でコンテキストを更新する習慣が、チームの成功を左右します。古いコンテキストは、ないよりも悪いのです。

❌ Bad: 3ヶ月前の情報のまま

✅ Good: 重要な決定があったらその日のうちに更新

# 更新タイミング
- アーキテクチャ変更時: 即座
- 技術スタック変更時: 即座
- コーディング規約変更時: 即座
- 新機能追加時: 当日中
- バグ修正時: 重要なものは記録

2. 変更理由を必ず記録

「何を変えたか」だけでなく、「なぜ変えたか」が重要です。 3ヶ月後、あるいは次の担当者が見たときに、「なぜこの決定をしたのか」が理解できなければ、同じ議論を繰り返すことになります。理由を記録することで、組織の学習が蓄積されます。

❌ Bad:
## 変更
認証方式をJWTに変更

✅ Good:
## 変更(2024-10-12)
認証方式をセッションベース→JWTに変更

### 理由
- 水平スケールが困難だった
- モバイルアプリ対応のため
- セッションストアの運用コスト削減

### 参考
ADR-015, Issue #234

3. 具体例を豊富に含める

「抽象的な原則」だけでは、エージェントも人間も理解できません。 「〜すべき」「〜が推奨される」という記述だけでは、実際にどう書けばいいのかわかりません。必ず「Good/Bad」の具体例を含めることで、誰でも一貫した判断ができるようになります。

❌ Bad:
## エラーハンドリング
適切にエラーをハンドリングすること

✅ Good:
## エラーハンドリング
カスタムエラークラスを使用し、contextIDを含めること

\`\`\`typescript
// ✅ Good
throw new UserNotFoundError(`User ${userId} not found`, {
  contextId,
  userId,
  timestamp: new Date()
});

// ❌ Bad
throw new Error('ユーザーが見つかりません');
\`\`\`

4. コンテキストの「鮮度」を保つ

古いコンテキストは、間違ったコンテキストよりも危険です。 「去年決めた方針がまだ有効だと思い込んで実装したら、実は半年前に変更されていた」——こうした事故を防ぐには、定期的な見直しと更新日の明記が不可欠です。

# 各ファイルに更新日を明記
> 最終更新: 2024-10-12 by @alice
> 次回レビュー予定: 2024-11-12

# 定期的なレビューをカレンダーに入れる
- 月次: コンテキストレビュー会(全ドメインオーナー参加)
- 四半期: 大幅な見直し(不要なセクションの削除、構造の再編)

5. エージェントの「読みやすさ」を意識

人間にとって読みやすいMarkdownは、エージェントにも読みやすい——とは限りません。 LLMは階層構造、箇条書き、表形式を好みます。長い文章よりも、構造化された情報の方が正確に理解できます。

❌ Bad: 長文で説明
このプロジェクトではJWT認証を採用しており、アクセストークンの有効期限は15分で、
リフレッシュトークンは7日間有効です。また、セキュリティ上の理由から...(続く)

✅ Good: 構造化して整理
## 認証方式

| 項目 | 設定値 | 理由 |
|-----|--------|------|
| 方式 | JWT | 水平スケール対応 |
| アクセストークン | 15分 | セキュリティ強化 |
| リフレッシュトークン | 7日 | UX考慮 |

### 注意事項
- トークンローテーション必須
- ブラックリスト機構実装済み

❌ DON'T: やってはいけないこと

これらのアンチパターンは、チームに混乱と不信感をもたらします。

1. 個人の判断だけで重要事項を変更

コンテキストは「チームの憲法」です。 一人で勝手に変更することは、チーム全体に影響を与える重大な行為です。必ずチームで議論し、合意を得てから更新しましょう。

❌ Bad:
一人で勝手にアーキテクチャ変更を記述

✅ Good:
チームで議論 → ADR作成 → レビュー → マージ

2. 曖昧な表現を使う

「基本的に」「なるべく」「できれば」——これらの言葉は、解釈の余地を生み、一貫性を損ないます。 エージェントは曖昧な指示を嫌います。明確な基準と例外条件を記述しましょう。

❌ Bad:
基本的にasync/awaitを使ってください

✅ Good:
非同期処理は必ずasync/awaitを使用する

例外:
- レガシーコード(src/legacy/)はコールバックのまま
- イベントリスナーは従来通り

3. コードと��ンテキストの乖離を放置

「コードは最新だけど、コンテキストは古いまま」——これが最もよくある失敗パターンです。 機能を実装したら、必ず関連するコンテキストも更新する。この習慣がなければ、コンテキストは徐々に腐っていきます。

# PRテンプレートに強制チェック項目を追加

## コンテキスト更新チェック
- [ ] この変更により、以下のコンテキストを更新しました:
  - [ ] .ai/team/context.md
  - [ ] .ai/domains/XXX/context.md
  - [ ] ADR追加(該当する場合)
- [ ] コンテキスト更新は不要(理由: _____)

4. すべてをコンテキストに書こうとする

コンテキストは「索引」であり、「百科事典」ではありません。 実装の詳細はコードに、API仕様はOpenAPIに、デザインはFigmaに——それぞれ適切な場所に配置し、コンテキストからリンクするのが正解です。

❌ Bad:
.ai/team/context.md に全APIエンドポイントの詳細を記述(5000行)

✅ Good:
## API設計

詳細は [OpenAPI Spec](./api/openapi.yaml) を参照

### 重要な原則
- RESTful設計
- バージョニング: URL-based (/api/v1/...)
- エラーレスポンス: RFC 7807形式

### 認証
すべてのエンドポイントでJWT認証必須(public除く)

5. コンテキストの「継ぎ足し」だけで整理しない

情報は追加するだけでは、やがて読めなくなります。 定期的な「大掃除」——不要になった情報の削除、重複の統合、構造の再編——が必要です。リファクタリングがコードに必要なように、コンテキストにも必要なのです。

# 四半期ごとの整理作業

## 削除候補
- [ ] 2年前のレガシーシステムの記述(もう存在しない)
- [ ] 試験的に導入して廃止になった機能の説明
- [ ] 担当者が退職して誰も覚えていない決定事項

## 統合候補
- [ ] 認証関連の記述が5箇所に散らばっている → 1箇所に集約
- [ ] 同じ内容が context.md と architecture.md に重複

## 構造見直し
- [ ] ドメイン別の粒度が不均等 → 再分割を検討

トラブルシューティング

問題は必ず起きます。重要なのは、素早く対処する方法を知っていることです。

コンテキスト共有を実践していると、様々な問題に遭遇します。「コンテキストが肥大化した」「更新が追いつかない」「誰も読まなくなった」——これらは、多くのチームが通る道です。問題を早期に発見し、適切に対処することで、コンテキスト管理を持続可能なものにできます。

問題1: コンテキストが大きくなりすぎた

「情報を詰め込みすぎ」は、最もよくある失敗パターンです。

プロジェクトが成長するにつれ、コンテキストファイルも成長します。しかし、1つのファイルに全てを詰め込むと、逆に使いにくくなります。エージェントの処理も遅くなり、人間も読む気が失せます。

症状:

  • context.mdが1000行を超えた(読むだけで疲れる)
  • エージェントの応答が遅い(処理に時間がかかる)
  • 何がどこに書いてあるかわからない(検索しても見つからない)

対策:

# ステップ1: 分割
.ai/team/context.md を以下に分割:
- context.md (概要のみ、300行以内)
- architecture.md
- conventions.md
- tech-stack.md

# ステップ2: ドメイン別に抽出
認証関連 → .ai/domains/auth/context.md
決済関連 → .ai/domains/payment/context.md

# ステップ3: アーカイブ
古い情報 → .ai/archive/yyyy-mm/

ポイント: 分割は早めに。「そろそろ大きいかな」と思ったら、既に遅い。

問題2: 誰もコンテキストを更新しなくなった

「最初は頑張ったけど、だんだん更新されなくなった」——持続性の問題です。

コンテキスト管理が「誰かの仕事」になってしまうと、その人が忙しくなったとたんに更新が止まります。

症状:

  • 最終更新が3ヶ月前
  • 新しい機能のコンテキストがない
  • エージェントが古い情報を参照する

対策:

  1. 更新を開発フローに組み込む

    • PRテンプレートに「コンテキスト更新」を必須項目に
    • 機能開発とコンテキスト更新を同じブランチで
  2. 更新の責任を分散

    • ドメインごとにオーナーを任命
    • 「全員の仕事」ではなく「担当者の仕事」に
  3. 更新を評価対象に

    • コンテキストへの貢献を可視化
    • 定期的な振り返りで改善

問題3: 新メンバーがコンテキストを読まない

「ドキュメントあるよ」と言っても、誰も読まない——活用の問題です。

せっかく整備したコンテキストも、読まれなければ意味がありません。

症状:

  • 新メンバーがコンテキストを知らない
  • 「そんなの書いてあったんですか?」と驚かれる
  • エージェントにコンテキストを読み込ませていない

対策:

  1. オンボーディングで必須化

    • 初日のタスクに「コンテキスト読了」を含める
    • メンターが読了を確認
  2. 読みやすくする

    • 長すぎるファイルは分割
    • 目次、サマリー、図解を追加
    • 「15分で読める」を目標に
  3. 実践で学ばせる

    • 最初のタスクでコンテキストを活用
    • 「読んだらできる」体験を提供

実装例: オンボーディングトラッカー

新メンバーが「何を読んだか」「どこまで理解したか」を追跡するスクリプトを用意することで、オンボーディングを標準化できます。

// .ai/scripts/onboarding-tracker.ts

import fs from 'fs/promises';
import chalk from 'chalk';
import inquirer from 'inquirer';

interface OnboardingChecklist {
  member: string;
  startDate: Date;
  completed: OnboardingItem[];
  pending: OnboardingItem[];
}

interface OnboardingItem {
  id: string;
  title: string;
  file?: string;
  estimatedTime: number; // 分
  completedAt?: Date;
  notes?: string;
}

const ONBOARDING_ITEMS: OnboardingItem[] = [
  {
    id: 'read-context',
    title: 'プロジェクトコンテキストを読む',
    file: '.ai/team/context.md',
    estimatedTime: 15,
  },
  {
    id: 'read-architecture',
    title: 'アーキテクチャ設計を理解',
    file: '.ai/team/architecture.md',
    estimatedTime: 20,
  },
  {
    id: 'read-conventions',
    title: 'コーディング規約を確認',
    file: '.ai/team/conventions.md',
    estimatedTime: 10,
  },
  {
    id: 'setup-agent',
    title: 'AIエージェントのセットアップ',
    file: '.ai/onboarding/agent-setup.md',
    estimatedTime: 10,
  },
  {
    id: 'first-task',
    title: '最初のタスク完了(チュートリアル)',
    file: '.ai/onboarding/first-task.md',
    estimatedTime: 60,
  },
  {
    id: 'review-adr',
    title: '重要なADRを読む(最新5件)',
    file: '.ai/team/adr/',
    estimatedTime: 30,
  },
];

class OnboardingTracker {
  private checklistPath = '.ai/personal/onboarding-progress.json';

  async start(memberName: string): Promise<void> {
    console.log(chalk.blue(`\n🎉 Welcome ${memberName}!\n`));

    const checklist: OnboardingChecklist = {
      member: memberName,
      startDate: new Date(),
      completed: [],
      pending: [...ONBOARDING_ITEMS],
    };

    await this.save(checklist);

    console.log(chalk.green('オンボーディングチェックリストを作成しました\n'));
    await this.showProgress(checklist);
  }

  async markComplete(itemId: string, notes?: string): Promise<void> {
    const checklist = await this.load();

    const itemIndex = checklist.pending.findIndex(item => item.id === itemId);
    if (itemIndex === -1) {
      console.log(chalk.yellow('該当するタスクが見つかりません'));
      return;
    }

    const item = checklist.pending[itemIndex];
    item.completedAt = new Date();
    if (notes) {
      item.notes = notes;
    }

    checklist.completed.push(item);
    checklist.pending.splice(itemIndex, 1);

    await this.save(checklist);

    console.log(chalk.green(`\n✅ 完了: ${item.title}\n`));

    if (checklist.pending.length === 0) {
      console.log(chalk.bold.green('🎊 おめでとうございます!オンボーディング完了です!\n'));
      await this.generateReport(checklist);
    } else {
      await this.showProgress(checklist);
    }
  }

  async showProgress(checklist?: OnboardingChecklist): Promise<void> {
    if (!checklist) {
      checklist = await this.load();
    }

    const totalItems = checklist.completed.length + checklist.pending.length;
    const progress = (checklist.completed.length / totalItems) * 100;

    console.log(chalk.bold(`📊 進捗: ${checklist.completed.length}/${totalItems} (${progress.toFixed(0)}%)\n`));

    // 完了済み
    if (checklist.completed.length > 0) {
      console.log(chalk.green('✅ 完了:'));
      checklist.completed.forEach(item => {
        console.log(`   - ${item.title}`);
      });
      console.log('');
    }

    // 未完了
    if (checklist.pending.length > 0) {
      console.log(chalk.yellow('⏳ 未完了:'));
      checklist.pending.forEach((item, index) => {
        console.log(`   ${index + 1}. ${item.title} (約${item.estimatedTime}分)`);
        if (item.file) {
          console.log(chalk.gray(`      ファイル: ${item.file}`));
        }
      });
    }

    console.log('');
  }

  private async save(checklist: OnboardingChecklist): Promise<void> {
    await fs.writeFile(
      this.checklistPath,
      JSON.stringify(checklist, null, 2),
      'utf-8'
    );
  }

  private async load(): Promise<OnboardingChecklist> {
    try {
      const content = await fs.readFile(this.checklistPath, 'utf-8');
      return JSON.parse(content);
    } catch (error) {
      throw new Error('オンボーディング進捗が見つかりません。npm run ai:onboarding start を実行してください');
    }
  }

  private async generateReport(checklist: OnboardingChecklist): Promise<void> {
    const startDate = new Date(checklist.startDate);
    const endDate = new Date();
    const daysElapsed = Math.floor((endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24));

    console.log(chalk.blue(''.repeat(60)));
    console.log(chalk.bold('\n📈 オンボーディングレポート\n'));
    console.log(`メンバー: ${checklist.member}`);
    console.log(`開始日: ${startDate.toLocaleDateString()}`);
    console.log(`完了日: ${endDate.toLocaleDateString()}`);
    console.log(`所要日数: ${daysElapsed}日\n`);

    console.log(chalk.bold('完了したタスク:\n'));
    checklist.completed.forEach(item => {
      console.log(`- ${item.title}`);
      if (item.notes) {
        console.log(chalk.gray(`  メモ: ${item.notes}`));
      }
    });

    console.log(chalk.blue('\n'.repeat(60)));
  }
}

// CLI
if (require.main === module) {
  const tracker = new OnboardingTracker();
  const command = process.argv[2];

  switch (command) {
    case 'start':
      inquirer
        .prompt([
          {
            type: 'input',
            name: 'name',
            message: 'あなたの名前を入力してください:',
          },
        ])
        .then(answers => {
          tracker.start(answers.name);
        });
      break;

    case 'complete':
      const itemId = process.argv[3];
      if (!itemId) {
        console.log('Usage: npm run ai:onboarding complete <item-id>');
        process.exit(1);
      }

      inquirer
        .prompt([
          {
            type: 'input',
            name: 'notes',
            message: 'メモ(オプション):',
          },
        ])
        .then(answers => {
          tracker.markComplete(itemId, answers.notes);
        });
      break;

    case 'status':
      tracker.showProgress();
      break;

    default:
      console.log('Usage:');
      console.log('  npm run ai:onboarding start           # オンボーディング開始');
      console.log('  npm run ai:onboarding complete <id>   # タスク完了');
      console.log('  npm run ai:onboarding status          # 進捗確認');
      process.exit(1);
  }
}

export { OnboardingTracker };

使用例:

# 新メンバーのオンボーディング開始
npm run ai:onboarding start
# > あなたの名前: Alice
# > オンボーディングチェックリストを作成しました

# タスク完了を記録
npm run ai:onboarding complete read-context
# > ✅ 完了: プロジェクトコンテキストを読む

# 進捗確認
npm run ai:onboarding status
# > 📊 進捗: 3/6 (50%)
# >
# > ✅ 完了:
# >    - プロジェクトコンテキストを読む
# >    - アーキテクチャ設計を理解
# >    - コーディング規約を確認
# >
# > ⏳ 未完了:
# >    1. AIエージェントのセットアップ (約10分)
# >    2. 最初のタスク完了(チュートリアル) (約60分)
# >    3. 重要なADRを読む(最新5件) (約30分)

問題4: エージェントがコンテキストを無視する

「コンテキストを読み込ませたのに、全然従ってくれない」——最もフラストレーションが溜まる問題です。

コンテキストファイルをエージェントに渡しても、期待通りの出力が得られない場合があります。

症状:

  • コンテキストに書いてある規約を守らない
  • 禁止したパターンを使う
  • 古い情報を参照している

原因と対策:

原因1: コンテキストが長すぎる

LLMのコンテキストウィンドウは大きくても、後半の情報は「忘れられやすい」性質があります。

❌ Bad: 1つのファイルに5000行
.ai/team/context.md (5000行)

✅ Good: 分割して重要度順に読み込む
@.ai/team/context.md (重要な概要: 300行)
@.ai/team/conventions.md (必要に応じて)
@.ai/domains/XXX/context.md (該当ドメインのみ)

原因2: 指示が曖昧

「なるべく」「基本的に」といった曖昧な表現では、LLMは確信を持てません。

❌ Bad:
基本的にTypeScriptのstrictモードを使ってください

✅ Good:
## TypeScript設定(必須)

\`tsconfig.json\`\`strict: true\`は変更不可

違反例:
\`\`\`json
// ❌ これは絶対にダメ
{ "strict": false }
\`\`\`

原因3: コンテキストの優先順位が不明

複数のコンテキストがある場合、どれを優先すべきかわからない。

# .ai/team/context.md の冒頭に明記

## このファイルについて

**優先順位: 最高**
このファイルの内容は、プロジェクトの全てのコードに適用されます。
ドメイン別のコンテキストと矛盾する場合、このファイルが優先されます。

## ドメイン別コンテキスト

各ドメインの詳細は `.ai/domains/*/context.md` を参照。
ただし、このファイルの原則に反する記述は無効です。

まとめ

ここまで、チーム開発でのコンテキスト共有について、理論から実践まで包括的に解説してきました。

個人で使えばAIエージェントは強力なツールですが、チーム全体で使うには「共有の仕組み」が不可欠です。3層のレイヤー構造、適切なディレクトリ設計、バージョン管理、レビュープロセス——これらを整備することで、AIエージェントはチーム全体の生産性を飛躍的に向上させる基盤となります。

最も重要なのは「今日から始める」ことです。完璧なコンテキストを目指す必要はありません。小さく始めて、チームとともに進化させていくことが成功の鍵です。

チーム共有コンテキストの価値

成功のための5つの原則

1. 透明性

すべてのコンテキストをGitで管理し、誰でもアクセス可能に

2. 継続性

一度作って終わりではなく、継続的に更新・改善

3. 参加性

全員がコンテキストの改善に貢献できる文化

4. 実用性

理論だけでなく、具体例とともに実践的に

5. 進化性

チームの成長に合わせてコンテキストも進化

今日から始められること

# 30分でできるクイックスタート

1. ディレクトリを作成(5分)
   mkdir -p .ai/team .ai/domains .ai/personal
   touch .ai/team/context.md

2. 基本情報を記入(15分)
   - プロジェクト名と目的
   - 技術スタック
   - 最低限のコーディング規約

3. チームに共有(5分)
   git add .ai/
   git commit -m "docs: 初期コンテキスト作成"
   git push

4. 使ってみる(5分)
   エージェントに .ai/team/context.md を読み込ませて
   何か質問してみる

これだけでも効果を実感できます!

次のステップ

さらに学ぶための資料

関連記事(コンテキストエンジニアリングシリーズ)

📚 シリーズトップページ

  1. コーディングエージェント時代のコンテキストエンジニアリング実践ガイド

    • 個人利用の基礎(前提知識)
  2. コンテキストウィンドウの処理フローと動作メカニズム 完全解説

    • 技術的詳細(深い理解)
  3. プロンプトエンジニアリング2.0 - コンテキストを制する者がAIを制する

    • コンテキスト管理 + プロンプト設計の統合
  4. コーディングエージェントのメモリ設計 - 長期記憶システムの実装

    • 外部化したコンテキストの管理・検索
  5. チーム開発のためのコンテキスト共有戦略

    • チーム活用(組織展開)
  6. コンテキスト駆動開発(CDD) - AIファーストな開発手法

    • 開発手法としての体系化
  7. マルチエージェント時代のコンテキストオーケストレーション

    • 複数エージェントの協調動作
  8. デバッグ駆動コンテキストエンジニアリング

    • トラブルシューティングに特化

チーム全体でAIエージェントを活用し、より良いプロダクトを作りましょう!


付録:各章3行サマリ(忙しい人向けクイックリファレンス)

ここまでお読みいただき、ありがとうございました。

この記事は約13.5万文字と、シリーズ最長のボリュームでした。チーム規模別の戦略、オンボーディング、CI/CD統合など、多岐にわたる内容をカバーしてきましたので、「全体を振り返りたいけど時間がない」という方もいらっしゃるかもしれません。

そこで、各章のエッセンスを3行(課題・解法・成果)で要約したクイックリファレンスを用意しました。

このセクションの使い方

  • 初めて読む方: ざっと目を通して、自分のチームに必要な章を見つける
  • 既読の方: 特定の課題に直面したときに、該当する章をすぐ見つける
  • 上司への説明時: 各施策の価値を「課題→解法→成果」の流れで端的に伝える
  • チーム共有時: メンバーに「どの章を読むべきか」を素早くガイド

ヒント: 各サマリの「課題」が今のチームの悩みに当てはまる章から優先的に読むと効果的です。


3層コンテキスト設計

  • 課題: 個人/ドメイン/全体の境界が曖昧で混線
  • 解法: Layer1(全員)/Layer2(担当)/Layer3(個人)の分離
  • 成果: 一貫性向上、認識ズレ/再発明の抑制

コンテキストのバージョン管理

  • 課題: 「誰が/いつ/なぜ」変更したか追えない
  • 解法: ブランチ運用/コミット規約/セマンティックバージョニング
  • 成果: 変更の説明可能性、事故時のロールバック容易

レビュー可能なコンテキスト設計

  • 課題: レビュー観点が人依存で形骸化
  • 解法: チェックリスト/PRテンプレ/自動検証を標準化
  • 成果: 品質の底上げとレビュー時間の短縮

オンボーディング活用

  • 課題: 新規メンバーが自走できない
  • 解法: コンテキストパッケージと導線(順番/初回タスク)
  • 成果: 立ち上がり期間を数週間→数日に短縮

チーム規模別戦略

  • 課題: 同じ仕組みを全員に当てはめて破綻
  • 解法: 小規模は単純化、中規模は専門化、大規模は組織化
  • 成果: オーバーヘッド最小で段階的スケール

ツールとワークフロー

  • 課題: 手動更新が破綻、属人化
  • 解法: npmスクリプト/CI連携/検証スクリプト
  • 成果: 更新忘れ防止、人は内容改善に集中

トラブルシューティング

  • 課題: 肥大化/更新停滞/未読化
  • 解法: 分割と導線設計/定期運用/習慣化
  • 成果: 持続可能なコンテキスト運用
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?