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?

GitHub Copilot x MUSUHI ではじめる仕様駆動開発

Posted at

第1章 はじめに:Vibe Codingの限界

1.1 AI時代の開発における課題

GitHub Copilotを使い始めて数ヶ月。確かにコードは速く書けるようになった。でも、こんな経験はありませんか?

  • 3ヶ月前に自分が書いたコードの意図が思い出せない
  • AIに生成させたコードの設計方針がバラバラ
  • チームメンバーが「なぜこの実装なのか」を理解できない
  • 機能追加のたびに、過去の実装と整合性が取れなくなる

これが「Vibe Coding(雰囲気コーディング)」の限界です。AIは優れたコーディングパートナーですが、プロジェクト全体の一貫性や設計思想を記憶し続けることはできません

1.2 仕様駆動開発(SDD)という解決策

そこで登場するのが 仕様駆動開発(Specification-Driven Development) です。

従来の開発フロー:

要件(頭の中)→ GitHub Copilotに相談 → コード生成 → 動けばOK

仕様駆動開発:

要件(EARS形式で文書化)→ プロジェクトメモリ(steering/)→ GitHub Copilot → 一貫性のあるコード

MUSUHI(むすひ) は、この仕様駆動開発をGitHub Copilotと組み合わせて実践するためのツールです。

本記事では、OneRoster API Serverの開発を題材に、MUSUHIを使った実践的な開発プロセスを解説します。

第2章 MUSUHIの概要

2.1 MUSUHIとは

MUSUHI(むすひ)は、AI時代の仕様駆動開発を支援するオープンソースツールです。名前の由来は、神道の「産霊(むすひ)」― すべてを生成・発展・完成させる霊的な力を意味します。

主な特徴

  1. マルチプラットフォーム対応

    • GitHub Copilot、Claude Code、Cursor、Windsurf IDE等7つのAIツールに対応
    • 各ツールのディレクトリ構造を自動認識
  2. プロジェクトメモリシステム(steering/)

    • structure.md: アーキテクチャパターンとディレクトリ構成
    • tech.md: 技術スタック、フレームワーク選定理由
    • product.md: ビジネスコンテキスト、製品目的
    • 初回実行時に自動でコードベース分析
  3. EARS形式の要件定義

    • 曖昧さを排除する5つの構造化パターン
    • AIが理解しやすく、人間も読みやすい
  4. 20種類の専門エージェント

    • 要件分析、設計、実装、テスト、セキュリティ監査など

2.2 なぜMUSUHIが必要なのか

課題 GitHub Copilot単体 MUSUHI + GitHub Copilot
一貫性 セッション間で設計方針がブレる steering/でプロジェクト記憶を保持
ドキュメント コード生成のみ、仕様は残らない EARS形式で要件を明文化
保守性 数ヶ月後に意図不明 仕様からトレース可能
チーム開発 個人の暗黙知に依存 仕様とメモリを共有

第3章 環境構築

3.1 前提条件

  • Node.js(v18以上推奨)
  • GitHub Copilot利用環境(VS Code、GitHub Codespaces等)
  • Git

3.2 MUSUHIのインストール

MUSUHIはnpxで即座に実行可能です。グローバルインストール不要。

# プロジェクトディレクトリで実行
npx musuhi

初回実行時、インタラクティブに設定を行います。

? Which AI coding tool are you using?
  ❯ GitHub Copilot (.github/copilot-instructions.md)
    Claude Code (.claudeignore, .claude/)
    Cursor (.cursorrules)
    Windsurf IDE (.windsurfrules)
    Gemini CLI (.gemini/)
    Codex CLI (.codex/)
    Qwen Code (.qwenrules)

GitHub Copilot を選択すると、自動的に以下が生成されます。

.github/
  └─ copilot-instructions.md    # GitHub Copilot向けプロンプト設定
steering/
  ├─ structure.md                # プロジェクト構造
  ├─ tech.md                     # 技術スタック
  └─ product.md                  # 製品コンテキスト

3.3 初期セットアップの確認

# 生成されたファイルを確認
ls -la .github/
ls -la steering/

.github/copilot-instructions.mdには、GitHub Copilotがsteering/とdocs/requirements/を参照するよう指示が記述されています。

# GitHub Copilot Instructions

このプロジェクトはMUSUHIによる仕様駆動開発を実践しています。

## プロジェクトメモリ
- `steering/structure.md`: アーキテクチャ設計
- `steering/tech.md`: 技術選定
- `steering/product.md`: 製品目的

## 要件定義
- `docs/requirements/`: EARS形式の要件仕様

コード生成時は必ずこれらを参照し、一貫性を保ちます。

第4章 OneRoster Japan Profileの仕様確認

ここからは実際のプロダクト開発の一例として、教育分野の名簿情報連携システム「OneRoster API Server」 の開発方法をステップバイステップで説明します。

第4章では、OneRoster Japan Profileの仕様書をもとに、要件定義のための仕様確認を行います。

4.1 プロダクト開発の題材:OneRoster API Server

4.1.1 なぜOneRosterを題材にするのか

OneRosterは、教育分野における実際の国際標準仕様であり、以下の特徴があります。

  • 複雑な仕様:複数のエンティティ、リレーションシップ、認証など、実際のプロダクトに必要な要素が揃っている
  • 日本独自の拡張:Japan Profileとして、metadataによる拡張が定義されている
  • 標準準拠の重要性:仕様に正確に準拠することが求められる(Vibe Codingでは困難)

つまり、MUSUHIの仕様駆動開発が真価を発揮するユースケースと言えます。

4.1.2 OneRosterとは

OneRosterは、IMS Global Learning Consortium(現1EdTech)が策定した、教育機関の情報システム間でデータ連携を行うための国際標準規格です。

解決する課題:

従来、学校の学生情報システム(SIS)、LMS(学習管理システム)、教育アプリ間の連携は、各ベンダー独自の形式で行われていました。

  • 連携のたびに個別開発が必要(コスト増、時間増)
  • データ形式がバラバラ(データ品質の低下)
  • 保守が困難(システム変更のたびに連携が破綻)

OneRosterは、これらを標準化されたREST APIで解決します。

4.2 仕様書を読み解く:GitHub Copilotと協調作業

仕様駆動開発では、仕様書を正確に理解することが第一歩です。ここでは、GitHub Copilotを活用して仕様書を効率的に読み解きます。

4.2.1 仕様書の入手

OneRoster v1,2の仕様書は、1EdTechの公式サイトから入手できます。

4.2.2 GitHub Copilotで仕様書を要約

仕様書は通常、数百ページに及びます。まず、GitHub Copilot Chatに仕様書の要約を依頼します。

@workspace
OneRoster v1.2の仕様書(https://www.imsglobal.org/spec/oneroster/v1p2)
を要約して、以下を教えて:
1. 主要なデータモデル(エンティティ)
2. REST APIエンドポイント
3. 認証方式
4. 必須フィールド

GitHub Copilotの回答例:

OneRoster v1.2仕様書の要約をお答えします。

1. 主要なデータモデル(エンティティ)
OneRoster v1.2には以下の主要なエンティティがあります。

コアエンティティ
User: 学習者と教育者の両方を表現
Org: 学校、学区、部門などの組織
Course: 学習コース
Class: 特定の期間でのコース実施
Enrollment: ユーザーとクラスの関係
AcademicSession: 学期、四半期などの学習期間
補助エンティティ
Category: 分類用カテゴリ
GradingPeriod: 成績評価期間
LineItem: 成績項目
Result: 成績結果
Demographics: 人口統計データ

この要約をもとに、次のステップで詳細な仕様確認を行います。

4.3 データモデルの理解

4.3.1 主要なエンティティ

OneRosterは、教育機関の主要なデータを以下のエンティティで表現します。

エンティティ 説明 主要属性
orgs 組織(学校、学区) name, type, identifier
academicSessions 学年・学期 title, startDate, endDate, type
courses コース(科目) title, courseCode, grades
classes クラス(授業) title, classCode, classType, periods
users ユーザー(生徒・教員) givenName, familyName, role, email
enrollments 受講登録 role, user, class, beginDate, endDate
demographics 属性情報 birthDate, sex, race

4.3.2 エンティティ間のリレーションシップ

GitHub Copilot Chatで、エンティティ間の関係を確認します。

@workspace これらのエンティティの関係性を図で示して

理解すべきリレーションシップ:

image.png

ポイント Mermaidの書式でER図を作成してくれる。

4.4 REST APIエンドポイントの確認

4.4.1 エンドポイント構造の理解

OneRosterは3つのサービスグループを定義しています。

1. Rostering(ロスタリング) - 教育機関の構造管理

GET /orgs
GET /academicSessions
GET /courses
GET /classes
GET /classes/{id}/students     # 特定クラスの生徒一覧
GET /classes/{id}/teachers     # 特定クラスの教員一覧
GET /users
GET /students                  # usersのサブセット(role=student)
GET /teachers                  # usersのサブセット(role=teacher)
GET /enrollments

2. Gradebook(成績簿)

GET /lineItems                 # 評価項目
GET /results                   # 評価結果
PUT /results/{id}              # 成績の更新

3. Resources(学習リソース)

GET /resources                 # 学習教材
GET /courses/{id}/resources    # 特定コースの教材

4.4.2 認証とセキュリティ要件

GitHub Copilot Chatで認証方式を確認します。

@workspace OneRoster v1,2の認証方式は?OAuth 2.0のどのフローを使う?
```

**仕様の確認結果:**

```
OneRoster v1.2の認証方式について、仕様書に基づいてお答えします。

OneRoster v1.2の認証方式
使用される認証プロトコル
OAuth 1.0a(OAuth 2.0ではありません)

OneRoster v1.2では、OAuth 1.0aを標準の認証方式として採用しています。これは、OAuth 2.0よりも古いプロトコルですが、IMS Globalの標準として定められています。

OAuth 1.0aの実装詳細
必須パラメータ
```

すべてのAPIリクエストは、**Bearer Token**を`Authorization`ヘッダーに含める必要があります。

```
GET /students
Authorization: Bearer {access_token}
  :
  :

要件として抽出すべき点:

  • OAuth 2.0トークンエンドポイントの実装
  • すべてのRostering APIへの認証チェック
  • トークンの有効期限管理

4.4.3 共通機能の確認

仕様書に記載されている共通機能:

機能 説明
ページネーション 大量データの分割取得 GET /students?limit=100&offset=0
フィルタリング ステータスによる絞り込み GET /students?filter=status='active'
ソート 並び順の指定 GET /students?sort=familyName
フィールド選択 取得フィールドの制限 GET /students?fields=sourcedId,givenName

要件として抽出すべき点:

  • すべてのコレクションエンドポイントでページネーション対応
  • statusフィルタの実装(active/tobedeleted)
  • ソート機能の実装(familyName、givenName等)

4.5 OneRoster Japan Profileの理解

4.5.1 Japan Profileとは

OneRoster Japan Profile v1.2.1(2024年1月版) は、IMS Japan Society(日本IMS協会)が公開している、日本の教育現場に適合させたプロファイルです。

GitHub Copilot Chatで確認します。

@workspace OneRoster Japan Profileでは、国際版からどのような拡張がされている?
日本独自の要件は?

4.5.2 日本独自の拡張フィールドの確認

仕様書から、国際版のOneRosterに追加されている日本固有のフィールドを抽出します。

1. 氏名のフリガナ

{
  "sourcedId": "student-001",
  "givenName": "太郎",
  "familyName": "山田",
  "metadata": {
    "jp.givenNameKana": "タロウ",
    "jp.familyNameKana": "ヤマダ"
  }
}

2. 日本の学年制度

  • 小学校: 1年〜6年
  • 中学校: 1年〜3年
  • 高等学校: 1年〜3年
{
  "grades": ["1"],  // OneRosterの標準grades
  "metadata": {
    "jp.schoolType": "小学校",
    "jp.gradeLevel": "1年"
  }
}

3. 学期制度

{
  "title": "2024年度 第1学期",
  "type": "term",
  "startDate": "2024-04-01",
  "endDate": "2024-07-20",
  "metadata": {
    "jp.termNumber": "1"  // 1学期、2学期、3学期
  }
}

4. 出席番号

{
  "user": { "sourcedId": "student-001" },
  "class": { "sourcedId": "class-math-101" },
  "metadata": {
    "jp.attendanceNumber": "5"  // 出席番号5
  }
}

4.5.3 文字コードとロケール要件

仕様書から文字コードとロケールの要件を確認:

要件 仕様
文字コード UTF-8必須
日付形式 ISO 8601(YYYY-MM-DD
言語タグ ja-JP

4.5.4 実装時の注意点

仕様確認から得られた実装のポイント:

  1. metadataフィールドの活用

    • 標準仕様を拡張せず、metadataに日本独自フィールドを格納
    • キー名はjp.プレフィックスで名前空間を分離(例:jp.givenNameKana
  2. 互換性の維持

    • 国際版OneRosterクライアントでも動作する(metadataは無視される)
    • Japan Profile対応クライアントは追加情報を活用
  3. 必須フィールドの遵守

    • sourcedId, status, dateLastModifiedは必須
    • sourcedIdはグローバル一意(UUID推奨)

4.6 要件定義への準備:実装スコープの決定

4.6.1 実装すべきAPIの優先順位付け

仕様書の全体像を理解したところで、今回実装する範囲を決定します。

GitHub Copilot Chatで相談します。

@workspace
OneRosterの仕様を理解しました。今回は教育系SaaSとの連携を想定して、
Students、Teachers、Classesを中心に実装したい。
優先度の高いエンドポイントを提案して。

実装スコープの決定:

エンドポイント 優先度 Japan Profile対応 理由
POST /oauth/token - 認証基盤(必須)
GET /students ✅ フリガナ 最も利用頻度の高いエンティティ
GET /students/{id} ✅ フリガナ 詳細取得は必須
GET /teachers ✅ フリガナ 教員情報も頻繁に利用
GET /teachers/{id} ✅ フリガナ 詳細取得は必須
GET /classes ✅ 出席番号 クラス情報は連携の中心
GET /classes/{id} ✅ 出席番号 詳細取得は必須
GET /classes/{id}/students ✅ 出席番号 クラス所属生徒の取得
GET /enrollments ✅ 出席番号 受講登録情報
GET /courses ✅ 学年情報 コース情報
GET /academicSessions ✅ 学期 学期情報

4.6.2 仕様確認のまとめ

第4章で確認した内容を整理します。

データモデル:

  • 7つの主要エンティティ(orgs、users、classes等)
  • エンティティ間のリレーションシップ
  • 必須フィールド(sourcedId、status、dateLastModified)

REST API:

  • 3つのサービスグループ(Rostering、Gradebook、Resources)
  • OAuth 2.0認証(Client Credentials Flow)
  • 共通機能(ページネーション、フィルタリング、ソート)

Japan Profile拡張:

  • フリガナ(jp.givenNameKana、jp.familyNameKana)
  • 学年制度(jp.schoolType、jp.gradeLevel)
  • 学期(jp.termNumber)
  • 出席番号(jp.attendanceNumber)

次のステップ:

第5章では、この仕様確認をもとに、EARS形式で要件定義を行います。仕様書の内容を「実装可能な要件」に変換していきます。


第5章 要件定義

第4章でOneRoster Japan Profileの仕様を確認しました。第5章では、この仕様をもとに実装可能な要件に落とし込みます。

5.1 GitHub Copilot Chatでプロジェクトメモリ生成

要件定義の前に、まずプロジェクトメモリ(steering/) を作成します。これにより、GitHub Copilotがプロジェクトの設計方針を理解し、一貫したコード生成が可能になります。

5.1.1 @workspace steering コマンドの実行

GitHub Copilot Chatで、以下のコマンドを実行します。

@workspace steering

GitHub Copilotの出力例:

プロジェクトの分析を開始します。コードベースを確認して、プロジェクトメモリ(steering/)を生成します。

🎯 Orchestrator AI - プロジェクト全体管理

OneRoster Japan Profile API Server の実装を開始します。

まず、プロジェクトの現状を分析した結果、以下のステップで進めることを推奨します。

📋 推奨ワークフロー

ステップ A: プロジェクト初期化(steering ファイル未作成の場合)
  - steering/product.md の作成
  - steering/tech.md の作成
  - steering/structure.md の作成
  → コマンド: @steering を実行してプロジェクトメモリを自動生成

ステップ B: 要件定義(steering 作成済みの場合)
  - OneRoster v1.2 仕様の分析
  - EARS形式での要件定義
  - Japan Profile 拡張要件の明文化
  → コマンド: @requirements-analyst を実行

ステップ C: アーキテクチャ設計(要件定義済みの場合)
  - レイヤードアーキテクチャ設計
  - データモデル設計
  - API設計
  → コマンド: @system-architect を実行

現在のステータス: steering/ ファイルが未作成
推奨: まずステップ A から始めましょう

それでは、@steering を実行してプロジェクトメモリを作成します。

ポイント:

  • MUSUHIのオーケストレーターが、プロジェクトの状況を自動分析
  • 現在の状態に応じて、次に実行すべきステップを自動提示
  • ユーザーは提示されたコマンドを実行するだけ

5.1.2 @requirements-analystによる段階的なSRS作成

オーケストレーターの指示に従い、@requirements-analystコマンドを実行します。MUSUHIのRequirements Analystエージェントは、段階的にSRS(Software Requirements Specification)を作成し、要所要所で進捗確認を行います。

@requirements-analyst
OneRoster v1.2 Japan Profileの要件定義を作成してください。
以下を含めてください:
1. 日本独自のデータモデル拡張(カナ氏名、学年・クラス、保護者情報等)
2. 日本独自のエンドポイント
3. OAuth 1.0a認証の実装要件
4. 個人情報保護法対応
5. 学年度・学期システム(4月始まり)

@requirements-analystの出力例:

✅ [1/10] SRS Part 1(概要・スコープ)作成完了

📝 保存ファイル: srs-oneroster-japan-profile-v1.0-part1.md

含まれる内容:
- セクション 1: ドキュメント概要(目的、スコープ、用語定義、参照文書)
- セクション 2: システム概要(システムコンテキスト、目的、対象ユーザー、環境)
- セクション 3: ユーザーペルソナ(3名)
- セクション 4: システム特性(信頼性、パフォーマンス、スケーラビリティ、セキュリティ、保守性、ユーザビリティ)

進捗: 10% 完了

次に進みますか?

次のステップ:
Part 2: 機能要件(日本独自拡張 - User, Org, Class, AcademicSession拡張)

a) はい、Part 2を作成
b) いいえ、ここで一時停止
c) 別のファイルを先に作成(ファイル名を指定してください)

ここで「a」を選択すると、MUSUHIは自動的に次のパートを作成します。

✅ [2/10] SRS Part 2(日本独自機能要件)作成完了

📝 保存ファイル: srs-oneroster-japan-profile-v1.0-part2.md

含まれる内容:
- REQ-JP-001〜004: 日本独自データモデル拡張(EARS形式)
  - カナ氏名フィールド(jp.givenNameKana, jp.familyNameKana)
  - 学年・クラス表現(jp.gradeLevel, jp.schoolType)
  - 保護者情報(agentSourcedIds, jp.guardianType)
  - 出席番号(jp.attendanceNumber)
- REQ-JP-005〜006: 日本独自エンドポイント要件
  - 学年度別クラス取得
  - 保護者による生徒情報取得

進捗: 20% 完了

次のステップ:
Part 3: OAuth 1.0a認証要件

a) はい、Part 3を作成
b) いいえ、ここで一時停止

ポイント:

  • MUSUHIは一気に全てを作成せず、段階的に確認を取りながら進める
  • ユーザーは各ステップで内容を確認し、方向性を修正できる
  • 各Partが完了するたびに、EARS形式の要件とAcceptance Criteriaが自動生成される
  • 進捗率が表示されるため、全体のどの位置にいるかが明確

この段階的アプローチにより、数百ページに及ぶSRSドキュメントも、ユーザーの確認を取りながら確実に完成させることができます。

5.2 プロジェクトメモリの確認と活用

5.2.1 生成されたsteering/ファイルの確認

GitHub Copilotが生成した3つのsteering/ファイルを確認します。

ls steering/
# 出力: product.md  tech.md  structure.md

これらのファイルは、GitHub Copilotが常に参照するプロジェクトの記憶となります。

5.2.2 GitHub Copilotがsteering/を参照することを確認

試しに、GitHub Copilot Chatで質問してみましょう:

@workspace このプロジェクトのアーキテクチャパターンは?

GitHub Copilotの回答例:

このプロジェクトはレイヤードアーキテクチャを採用しています。

- Presentation Layer: controllers/とroutes/
- Business Layer: services/
- Data Access Layer: models/ (Prisma)

`steering/structure.md`に詳細が記載されています。

ポイント:

  • GitHub Copilotは、質問に対してsteering/structure.mdを参照して回答している
  • これにより、セッションをまたいでも一貫した設計方針が維持される
  • チームメンバーも同じsteering/を参照することで、統一された開発ができる

5.3 @requirements-analystで詳細なEARS要件定義

5.1.2で@requirements-analystがSRSの作成を開始しました。ここでは、MUSUHIが自動生成する詳細なEARS形式の要件を確認します。

5.3.1 EARS形式とは

MUSUHIでは、EARS (Easy Approach to Requirements Syntax) で要件を記述します。これは曖昧さを排除するための要件記述フォーマットです。

5つのパターン:

パターン 構文 用途
Event-Driven WHEN [トリガー], the [システム] SHALL [応答] イベント駆動
State-Driven WHILE [状態], the [システム] SHALL [応答] 状態依存
Error Handling IF [エラー], THEN the [システム] SHALL [応答] エラー処理
Optional WHERE [条件], the [システム] SHALL [応答] オプション機能
Ubiquitous The [システム] SHALL [応答] 常時適用

5.3.2 @requirements-analystが自動生成する要件の例

5.1.2で開始したSRS作成を続けると、MUSUHIが自動的に以下のようなEARS形式の詳細要件を生成してくれます。

  • 日本独自のデータモデル拡張要件(REQ-JP-001〜004)
  • 日本独自のエンドポイント要件(REQ-JP-005〜006)
  • OAuth 1.0a認証要件(REQ-AUTH-001〜002)
  • 個人情報保護法対応要件(REQ-PRIVACY-001〜003)
  • 学年度・学期システム要件(REQ-TERM-001〜003)
  • 各要件にAcceptance Criteria(受入基準)付き

@requirements-analystが自動生成する要件の例(一部抜粋):

# OneRoster v1.2 Japan Profile 詳細要件定義

## 1. 日本独自のデータモデル拡張

### REQ-JP-001: カナ氏名フィールド
**Ubiquitous**
The system SHALL store and return kana representations of given name and family name
in metadata fields "jp.givenNameKana" and "jp.familyNameKana" for all User entities.

**Acceptance Criteria:**
- カナ氏名はカタカナ(全角)で格納
- 両フィールドとも最大255文字
- 空白文字は許容するが、必須フィールド

### REQ-JP-002: 学年・クラス表現
**Ubiquitous**
The system SHALL support Japanese grade level representation using metadata field
"jp.gradeLevel" with values "1年" through "6年" for elementary school.

**Acceptance Criteria:**
- 小学校: 1年〜6年
- 中学校: 1年〜3年
- 高等学校: 1年〜3年
- 学校種別は "jp.schoolType" に格納(小学校/中学校/高等学校)

### REQ-JP-003: 保護者情報
**Ubiquitous**
The system SHALL store guardian information in User entity with role "guardian"
linked to student via "agentSourcedIds" field.

**Acceptance Criteria:**
- 保護者は独立したUserエンティティとして管理
- 生徒との関係は agentSourcedIds で表現
- 関係種別は metadata.jp.guardianType で指定(父/母/その他)

### REQ-JP-004: 出席番号
**Ubiquitous**
The system SHALL include "jp.attendanceNumber" in Enrollment metadata
representing the student's class roster number.

**Acceptance Criteria:**
- 1以上の整数値
- 同一クラス内で一意
- 学年度ごとに変更可能

## 2. 日本独自のエンドポイント

### REQ-JP-005: 学年度別クラス取得
**Event-Driven**
WHEN a client sends GET request to /classes?schoolYear={year},
the system SHALL return classes for the specified Japanese school year.

**Acceptance Criteria:**
- schoolYearパラメータは4桁の年(例: 2024)
- 4月1日〜3月31日を1学年度として扱う
- schoolYearが指定されない場合は現在の学年度

### REQ-JP-006: 保護者による生徒情報取得
**Event-Driven**
WHEN a guardian user requests GET /users/{guardianId}/students,
the system SHALL return all students linked to the guardian.

**Acceptance Criteria:**
- OAuth認証で保護者の権限確認
- 自分が保護者である生徒のみ取得可能
- 生徒情報にはクラス、担任教員情報を含む

## 3. OAuth 1.0a認証の実装要件

### REQ-AUTH-001: OAuth 1.0a署名検証
**Ubiquitous**
The system SHALL validate OAuth 1.0a signatures using HMAC-SHA256
for all API requests to Rostering endpoints.

**Acceptance Criteria:**
- oauth_signature_methodは "HMAC-SHA256" のみ許可
- oauth_timestampは現在時刻±5分以内
- oauth_nonceの重複チェック(5分間保持)

### REQ-AUTH-002: クライアント認証情報管理
**Ubiquitous**
The system SHALL store client credentials (consumer key and secret)
with expiration date and scope restrictions.

**Acceptance Criteria:**
- consumer keyは32文字以上のランダム文字列
- consumer secretは暗号化して保存
- スコープは roster.read, roster.write で制御

## 4. 個人情報保護法対応

### REQ-PRIVACY-001: アクセスログ記録
**Ubiquitous**
The system SHALL log all access to personal information including
user ID, accessed data, timestamp, and purpose.

**Acceptance Criteria:**
- アクセスログは最低1年間保持
- ログには以下を記録: 誰が、いつ、何を、なぜアクセスしたか
- ログ自体も暗号化して保存

### REQ-PRIVACY-002: データ保持期間
**Ubiquitous**
The system SHALL automatically mark users with status "tobedeleted"
after graduation or withdrawal, and delete after retention period.

**Acceptance Criteria:**
- 卒業後3年間はstatus="tobedeleted"で保持
- 3年経過後は物理削除
- 削除前に管理者へ通知

### REQ-PRIVACY-003: 個人情報マスキング
**Event-Driven**
WHEN a user without appropriate permission requests user data,
the system SHALL mask sensitive fields (email, phone, address).

**Acceptance Criteria:**
- emailは "****@example.com" 形式でマスク
- 電話番号は下4桁のみ表示
- 住所は市区町村まで表示

## 5. 学年度・学期システム(4月始まり)

### REQ-TERM-001: 日本の学年度定義
**Ubiquitous**
The system SHALL define Japanese school year starting from April 1st
and ending on March 31st of the following calendar year.

**Acceptance Criteria:**
- schoolYear "2024" は 2024年4月1日〜2025年3月31日
- AcademicSession.type = "schoolYear" で学年度を表現
- startDate="2024-04-01", endDate="2025-03-31"

### REQ-TERM-002: 3学期制対応
**Ubiquitous**
The system SHALL support Japanese three-term system
(1st: Apr-Jul, 2nd: Sep-Dec, 3rd: Jan-Mar).

**Acceptance Criteria:**
- 第1学期: 4月〜7月(metadata.jp.termNumber="1")
- 第2学期: 9月〜12月(metadata.jp.termNumber="2")
- 第3学期: 1月〜3月(metadata.jp.termNumber="3")
- 各学期は親の学年度AcademicSessionを持つ

### REQ-TERM-003: 2学期制対応
**Optional**
WHERE a school uses two-term system, the system SHALL support
前期 (Apr-Sep) and 後期 (Oct-Mar).

**Acceptance Criteria:**
- 前期: 4月〜9月(metadata.jp.termNumber="1")
- 後期: 10月〜3月(metadata.jp.termNumber="2")
- 学期制の選択は学校(Org)のmetadataで指定

ポイント:

  • 1つのコマンドで15個以上の詳細要件を自動生成
  • 各要件にAcceptance Criteria(受入基準) 付き
  • 実装とテストの指針が自動的に明確化
  • 個人情報保護法への対応も要件として自動生成
  • EARS形式で曖昧さを排除(WHEN、WHILE、IF、WHERE、The system SHALL)
  • OneRoster v1.2標準仕様への準拠を明記
  • Japan Profile拡張を要件として明文化

5.3.3 ステップ3: @system-architect でアーキテクチャ自動設計

@requirements-analystが要件定義を完了したら、次は@system-architectエージェントに依頼します。同様に、MUSUHIが自動的にアーキテクチャを設計してくれます

以下のコマンドを実行するだけ:

@system-architect
requirements.mdに基づいてOneRoster Japan Profile実装のアーキテクチャを設計してください。
- レイヤードアーキテクチャ(API, Domain, Infrastructure)
- データモデルの拡張戦略
- 認証レイヤー(OAuth 1.0a)

これだけで、MUSUHIが自動的に以下を生成してくれます。

  • 3層アーキテクチャ(API、Domain、Infrastructure)の設計図
  • 詳細なディレクトリ構成
  • Prismaスキーマ設計(Japan Profile対応)
  • TypeScript型定義(JapanProfileMetadata等)
  • OAuth 1.0a認証フローとMiddleware実装方針
  • ADR(アーキテクチャ決定記録)

@system-architectが自動生成する設計書の例(一部抜粋):

# OneRoster Japan Profile アーキテクチャ設計書

## 1. アーキテクチャ概要

### 1.1 レイヤードアーキテクチャ

```
┌─────────────────────────────────────────┐
│        API Layer (Presentation)         │
│  - REST Controllers                     │
│  - Request Validation                   │
│  - Response Formatting (OneRoster JSON) │
└─────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────┐
│      Domain Layer (Business Logic)      │
│  - Entities (Student, Teacher, Class)   │
│  - Domain Services                      │
│  - Business Rules Validation            │
└─────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────┐
│   Infrastructure Layer (Data Access)    │
│  - Repositories (Prisma ORM)            │
│  - Database Migrations                  │
│  - External API Integration             │
└─────────────────────────────────────────┘

        ← Authentication Layer (OAuth 1.0a) →
```

### 1.2 ディレクトリ構成

```
src/
├── api/                      # API Layer
│   ├── controllers/
│   │   ├── students.controller.ts
│   │   ├── teachers.controller.ts
│   │   └── classes.controller.ts
│   ├── middleware/
│   │   ├── oauth.middleware.ts       # OAuth 1.0a認証
│   │   ├── validation.middleware.ts   # リクエスト検証
│   │   └── error-handler.middleware.ts
│   └── routes/
│       └── v1.routes.ts
│
├── domain/                   # Domain Layer
│   ├── entities/
│   │   ├── user.entity.ts            # BaseEntity拡張
│   │   ├── student.entity.ts         # User拡張 + Japan Profile
│   │   ├── teacher.entity.ts
│   │   └── class.entity.ts
│   ├── services/
│   │   ├── student.service.ts
│   │   ├── teacher.service.ts
│   │   └── enrollment.service.ts
│   └── value-objects/
│       ├── kana-name.vo.ts           # カナ氏名のバリデーション
│       ├── school-year.vo.ts         # 学年度計算ロジック
│       └── attendance-number.vo.ts
│
├── infrastructure/           # Infrastructure Layer
│   ├── repositories/
│   │   ├── student.repository.ts
│   │   ├── teacher.repository.ts
│   │   └── class.repository.ts
│   ├── database/
│   │   ├── prisma/
│   │   │   └── schema.prisma
│   │   └── migrations/
│   └── external/
│       └── sis-integration.ts        # 既存SISとの連携
│
└── shared/                   # 共通
    ├── types/
    │   └── oneroster.types.ts        # OneRoster型定義
    └── utils/
        └── metadata-builder.ts       # Japan Profile metadata生成
```

## 2. データモデルの拡張戦略

### 2.1 Prismaスキーマ設計

```prisma
// schema.prisma

model User {
  sourcedId        String   @id @default(uuid())
  status           Status   @default(active)
  dateLastModified DateTime @default(now()) @updatedAt

  // OneRoster標準フィールド
  username         String   @unique
  givenName        String
  familyName       String
  role             UserRole
  email            String?

  // Japan Profile拡張(JSON型で柔軟に)
  metadata         Json     @default("{}")

  // リレーション
  enrollments      Enrollment[]
  demographics     Demographics?
  agents           User[]         @relation("Guardian")
  guardedStudents  User[]         @relation("Guardian")
}

model Enrollment {
  sourcedId        String   @id @default(uuid())
  status           Status   @default(active)
  dateLastModified DateTime @default(now()) @updatedAt

  // リレーション
  userId           String
  user             User     @relation(fields: [userId], references: [sourcedId])
  classId          String
  class            Class    @relation(fields: [classId], references: [sourcedId])

  role             EnrollmentRole
  primary          Boolean  @default(false)
  beginDate        DateTime?
  endDate          DateTime?

  // Japan Profile拡張
  metadata         Json     @default("{}")  // jp.attendanceNumber等
}
```

### 2.2 Japan Profile Metadata型定義

```typescript
// shared/types/japan-profile.types.ts

export interface JapanProfileUserMetadata {
  'jp.givenNameKana': string;
  'jp.familyNameKana': string;
  'jp.guardianType'?: '' | '' | 'その他';
}

export interface JapanProfileEnrollmentMetadata {
  'jp.attendanceNumber': number;
}

export interface JapanProfileAcademicSessionMetadata {
  'jp.termNumber': '1' | '2' | '3';
  'jp.termSystem': '3学期制' | '2学期制';
}

export interface JapanProfileCourseMetadata {
  'jp.schoolType': '小学校' | '中学校' | '高等学校';
  'jp.gradeLevel': string;  // "1年"〜"6年"
}
```

## 3. 認証レイヤー(OAuth 1.0a)

### 3.1 OAuth 1.0aフロー

```
Client                    OneRoster API Server
  │                              │
  │ 1. Request with OAuth params │
  │ ──────────────────────────→ │
  │    oauth_consumer_key        │
  │    oauth_signature_method    │
  │    oauth_timestamp           │
  │    oauth_nonce               │
  │    oauth_signature           │
  │                              │
  │                        2. 署名検証
  │                           - Base String生成
  │                           - HMAC-SHA256計算
  │                           - 署名比較
  │                              │
  │ 3. Authorized Request        │
  │ ←──────────────────────────  │
  │    200 OK + JSON Response    │
```

### 3.2 OAuth Middleware実装方針

```typescript
// api/middleware/oauth.middleware.ts

export class OAuthMiddleware {
  async validateSignature(req: Request): Promise<boolean> {
    // 1. OAuth パラメータ抽出
    const params = this.extractOAuthParams(req);

    // 2. Base String生成
    const baseString = this.generateBaseString(
      req.method,
      req.url,
      params
    );

    // 3. Consumer Secret取得(DB)
    const client = await this.clientRepository.findByKey(
      params.oauth_consumer_key
    );

    // 4. 署名計算
    const expectedSignature = crypto
      .createHmac('sha256', client.consumerSecret)
      .update(baseString)
      .digest('base64');

    // 5. 署名比較
    return params.oauth_signature === expectedSignature;
  }

  async validateTimestamp(timestamp: string): Promise<boolean> {
    const now = Date.now() / 1000;
    const diff = Math.abs(now - parseInt(timestamp));
    return diff <= 300; // 5分以内
  }

  async validateNonce(nonce: string): Promise<boolean> {
    // Redisでnonce重複チェック(5分間保持)
    const exists = await this.redis.exists(`nonce:${nonce}`);
    if (exists) return false;

    await this.redis.setex(`nonce:${nonce}`, 300, '1');
    return true;
  }
}
```

## 4. アーキテクチャ決定記録(ADR)

### ADR-001: Japan Profile拡張はJSON型metadataフィールドで管理

**背景:**
OneRoster標準仕様を変更せず、Japan Profile拡張を実装する必要がある。

**決定:**
Prismaスキーマに`metadata Json`フィールドを追加し、Japan Profile固有のフィールドを格納する。

**理由:**
- 国際版OneRosterクライアントとの互換性維持
- スキーマ変更なしで柔軟に拡張可能
- TypeScript型定義で型安全性を確保

**影響:**
- メリット: 標準準拠、拡張性
- デメリット: JSON検索のパフォーマンス低下(インデックス追加で対応)

### ADR-002: OAuth 1.0a署名検証はMiddlewareで実装

**背景:**
すべてのAPIエンドポイントで認証が必要。

**決定:**
Express Middlewareとして実装し、全ルートに適用。

**理由:**
- 認証ロジックの一元管理
- テストが容易
- 横断的関心事として適切

**影響:**
- すべてのリクエストで署名検証のオーバーヘッド
- キャッシュ戦略で最適化

5.3.4 MUSUHIが自動的に次のステップを提示

@system-architectが設計を完了すると、MUSUHIが自動的に次に何をすべきかを指示してくれます。ユーザーは指示に従ってコマンドを実行するだけです。

MUSUHIからの自動提示:

✅ アーキテクチャ設計が完了しました。

次のステップ:
ステップ4: @software-developer でコア機能実装
  コマンド実行: @software-developer Prismaスキーマを作成してください

ステップ5: @test-engineer でテスト設計
  コマンド実行: @test-engineer EARS要件に基づいたテストケースを生成してください

ステップ6: @security-auditor でセキュリティ監査
  コマンド実行: @security-auditor OAuth 1.0a実装を監査してください

それでは、ステップ4から始めましょう。

MUSUHIの自動化フロー:

ユーザー入力
    ↓
@workspace steering → プロジェクトメモリ自動生成
    ↓
@requirements-analyst → SRS・詳細要件自動生成(段階的に進捗確認)
    ↓
@system-architect → アーキテクチャ自動設計
    ↓
MUSUHIが次のステップを自動提示
    ↓
@software-developer → コード自動生成
    ↓
@test-engineer → テスト自動生成
    ↓
@security-auditor → セキュリティ自動監査
    ↓
完成

ポイント:

  • ユーザーは各ステップでコマンドを実行するだけ
  • MUSUHIが自動的にプロジェクトを進めてくれる
  • 各エージェントが専門知識を持って自動生成
  • 仕様書からコードまで一貫性を自動維持

5.4 第5章のまとめ

第5章では、MUSUHIエージェントによる自動化を実践しました。

手動作業(従来の開発)

  • 仕様書を読み込んで理解(数時間)
  • 要件をドキュメントに整理(数時間)
  • アーキテクチャを設計(数日)
  • 設計書を作成(数時間)
  • 次のタスクを計画(数時間)

MUSUHIによる自動化(本記事の手法)

  1. @workspace steering → オーケストレーターが状況分析・プロジェクトメモリ自動生成(5分)
  2. @requirements-analyst → SRS作成と15個以上の詳細EARS要件を段階的に自動生成(10分)
    • Part 1〜10まで段階的に進捗確認
    • Acceptance Criteria付き
    • 個人情報保護法対応も自動生成
  3. @system-architect → アーキテクチャ全体を自動設計(5分)
    • Prismaスキーマ
    • TypeScript型定義
    • OAuth 1.0a実装方針
    • ADR(アーキテクチャ決定記録)
  4. MUSUHIが次のステップを自動提示 → 何をすべきかを自動指示

🚀 従来は数日かかっていた作業が、わずか20分で完了!

MUSUHIの真価:

  • コマンド1つで専門的な成果物を自動生成
  • 一貫性のある仕様書からコードまで自動維持
  • 次に何をすべきかを自動指示(迷わない)
  • 各エージェントが専門知識を持って自動サポート

次のステップ:

第6章では、この設計をもとに実際のコーディングを行います。同様に、@software-developerエージェントに依頼すれば、MUSUHIが自動的にコードを生成してくれます。


第6章 コーディング

6.1 GitHub Copilot Chatでの設計確認

GitHub Copilot Chatを開き、以下のように問いかけます。

@workspace REQ-STU-001 (生徒一覧取得) を実装したい。
steering/の設計方針に従って、必要なファイルとコード構造を提案して。

GitHub Copilotの提案例:

以下のファイルが必要です。

1. src/routes/students.ts
   - Express Routerでルーティング定義

2. src/controllers/student-controller.ts
   - リクエストハンドリング

3. src/services/student-service.ts
   - ビジネスロジック(Prismaでデータ取得)

4. src/validators/student-validator.ts
   - リクエストバリデーション

5. tests/integration/students.test.ts
   - APIテスト

レイヤードアーキテクチャに従い、各層の責務を分離します。

6.2 実装:Before vs After

Before(Vibe Coding)

GitHub Copilotに「生徒一覧取得APIを作って」と言うだけ:

// すべてが1ファイルに詰め込まれる
app.get('/students', async (req, res) => {
  const students = await prisma.student.findMany();
  res.json(students);
});
// エラーハンドリング?認証?テスト?考慮なし

After(MUSUHI + GitHub Copilot)

src/routes/students.ts

import { Router } from 'express';
import { StudentController } from '../controllers/student-controller';
import { authenticateOAuth } from '../middleware/auth';

const router = Router();
const controller = new StudentController();

// REQ-STU-004: 認証必須
router.use(authenticateOAuth);

// REQ-STU-001: 生徒一覧取得
router.get('/', controller.getStudents);

// REQ-STU-002: 生徒詳細取得
router.get('/:id', controller.getStudentById);

export default router;

src/controllers/student-controller.ts

import { Request, Response, NextFunction } from 'express';
import { StudentService } from '../services/student-service';

export class StudentController {
  private service = new StudentService();

  // REQ-STU-001, REQ-STU-005, REQ-STU-006
  getStudents = async (req: Request, res: Response, next: NextFunction) => {
    try {
      const { limit, offset, status } = req.query;

      const students = await this.service.findAll({
        limit: limit ? parseInt(limit as string) : undefined,
        offset: offset ? parseInt(offset as string) : undefined,
        status: status as string | undefined,
      });

      res.json({
        students: students,
        // OneRoster v1,2 準拠のレスポンス形式
      });
    } catch (error) {
      next(error);
    }
  };

  // REQ-STU-002, REQ-STU-003
  getStudentById = async (req: Request, res: Response, next: NextFunction) => {
    try {
      const { id } = req.params;
      const student = await this.service.findById(id);

      if (!student) {
        // REQ-STU-003: エラーハンドリング
        return res.status(404).json({ error: 'Student not found' });
      }

      res.json({ student });
    } catch (error) {
      next(error);
    }
  };
}

src/services/student-service.ts

import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export class StudentService {
  async findAll(options: {
    limit?: number;
    offset?: number;
    status?: string;
  }) {
    // REQ-STU-006: ステータスフィルタリング
    const where = options.status
      ? { enrollmentStatus: options.status }
      : {};

    // REQ-STU-005: ページネーション
    return await prisma.student.findMany({
      where,
      take: options.limit,
      skip: options.offset,
    });
  }

  async findById(id: string) {
    return await prisma.student.findUnique({
      where: { id },
    });
  }
}

6.3 実装中のGitHub Copilot活用

実装中も、GitHub Copilotに常に要件ID(REQ-XXX-NNN) を参照させることで、一貫性を保ちます。

プロンプト例:

REQ-STU-004の認証ミドルウェアを実装して。
steering/tech.mdに従ってOAuth 2.0を使用。

生成されるコード(src/middleware/auth.ts):

import { Request, Response, NextFunction } from 'express';
import OAuth2Server from 'oauth2-server';

const oauth = new OAuth2Server({
  model: require('../models/oauth-model'),
});

// REQ-STU-004: OAuth 2.0認証
export const authenticateOAuth = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  const request = new OAuth2Server.Request(req);
  const response = new OAuth2Server.Response(res);

  try {
    await oauth.authenticate(request, response);
    next();
  } catch (error) {
    // REQ-STU-004: 認証失敗時は401
    res.status(401).json({ error: 'Unauthorized' });
  }
};

6.4 テストコードの生成

@workspace REQ-STU-001〜REQ-STU-006の統合テストを書いて。
Jestとsupertestを使用。

生成されるテスト(tests/integration/students.test.ts):

import request from 'supertest';
import app from '../../src/app';

describe('Students API', () => {
  let authToken: string;

  beforeAll(async () => {
    // OAuth tokenを取得
    authToken = await getTestToken();
  });

  // REQ-STU-001
  test('GET /students should return student list', async () => {
    const response = await request(app)
      .get('/students')
      .set('Authorization', `Bearer ${authToken}`)
      .expect(200);

    expect(response.body.students).toBeInstanceOf(Array);
  });

  // REQ-STU-003
  test('GET /students/{invalid-id} should return 404', async () => {
    const response = await request(app)
      .get('/students/invalid-id')
      .set('Authorization', `Bearer ${authToken}`)
      .expect(404);

    expect(response.body.error).toBe('Student not found');
  });

  // REQ-STU-004
  test('GET /students without token should return 401', async () => {
    await request(app)
      .get('/students')
      .expect(401);
  });

  // REQ-STU-005
  test('GET /students with pagination should work', async () => {
    const response = await request(app)
      .get('/students?limit=10&offset=0')
      .set('Authorization', `Bearer ${authToken}`)
      .expect(200);

    expect(response.body.students.length).toBeLessThanOrEqual(10);
  });
});

第7章 まとめ

7.1 仕様駆動開発で得られたもの

本記事では、MUSUHIとGitHub Copilotを組み合わせた仕様駆動開発を、OneRoster Japan Profile API Serverの実装を通じて実践しました。

Vibe Codingとの比較

項目 Vibe Coding MUSUHI + GitHub Copilot
開発速度(初期) 速い やや遅い(要件定義に時間)
開発速度(中長期) 低下(整合性崩壊) 安定(設計方針が明確)
コード品質 バラバラ 一貫性あり
保守性 低い(意図不明) 高い(要件トレース可能)
チーム開発 困難(暗黙知依存) 容易(仕様共有)

実践して分かったこと

1. GitHub Copilotが「文脈」を理解する

  • steering/のプロジェクトメモリにより、セッションをまたいでも一貫した設計提案
  • 要件ID(REQ-XXX-NNN)で、「なぜこのコードなのか」が明確

2. 標準仕様への準拠が容易

  • OneRoster Japan Profileのような複雑な仕様も、EARS形式で段階的に定義
  • metadataフィールドの日本独自拡張も、要件として明文化することで実装漏れを防止

3. チーム開発の品質向上

  • 新メンバーはsteering/ + requirements/を読めば、プロジェクトの全体像を把握
  • コードレビュー時、要件IDで議論の基準が明確

7.2 MUSUHIを使う際の注意点

  1. 初期投資は必要

    • steering/とEARS形式の要件作成に時間がかかる
    • 小規模・短期プロジェクトでは過剰な場合も
  2. 要件定義の精度が成果を左右する

    • 曖昧な要件では、AIも曖昧なコードを生成
    • EARS構文に慣れるまでの学習コストあり

7.3 次のステップ

本記事では Students API を中心に解説しましたが、実際のプロジェクトでは:

  1. 他のエンティティの実装(Teachers、Classes、Enrollments等)
  2. OpenAPI仕様の自動生成(Swaggerドキュメント)
  3. CI/CDパイプライン(要件ベースの自動テスト)
  4. MUSUHIエージェント活用(セキュリティ監査、パフォーマンステスト)

を進めることで、より堅牢な OneRoster API Server が完成します。

7.4 結論

GitHub Copilotは強力なコーディングパートナーですが、プロジェクトの記憶と設計の一貫性を保つには、明示的な仕様管理が不可欠です。

MUSUHIは、その仕様管理を自然にワークフローに組み込み、GitHub Copilotとの協調開発を実現します。

「Vibe Codingから卒業したい」「チームでAIツールを効果的に使いたい」「標準仕様に準拠した堅牢なAPIを開発したい」と考えている方は、ぜひMUSUHIを試してみてください。


参考リンク

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?