最終更新: 2025-12-08
このドキュメントでは、PrompsプロジェクトにおけるAPI安定性ポリシーについて説明します。
API安定性ポリシーは、マージコンフリクトを防ぐための重要なルールであり、PrompsのPersistent Feature Branch戦略と密接に関連しています。
Prompsは、Phase(フェーズ)と呼ばれるレイヤー構造で設計されています:
Phase N: Validation Layer
↓ 依存(呼び出すのみ)
Phase 0: Core Parsing Layer
上位のPhase(Phase N)は、下位のPhase(Phase 0)が提供するAPIを使用します。
もし、Phase 0のAPI(例: parse_input() 関数)を変更したらどうなるでしょうか?
// Phase 0 (feature/phase-0) で関数を変更
pub fn parse_input(input: &str) -> Vec<PromptPart>
↓ シグネチャを変更
pub fn parse_input(input: &str, options: ParseOptions) -> Vec<PromptPart>// Phase N (feature/phase-n) で影響が出る
pub fn validate_pattern(input: &str) -> Result<()> {
let parts = parse_input(input); // ← コンパイルエラー!引数が足りない
// ...
}影響範囲:
- Phase 0を変更しただけなのに、Phase N, N+1, N+2... すべてが壊れる
- すべてのfeature branchで修正が必要
- マージコンフリクトが多発
原則: Phase 0のAPIは不変
一度公開したAPIは変更せず、新機能は新しいAPIとして追加します。
// ✅ 良い例: 既存APIを維持し、新APIを追加
pub fn parse_input(input: &str) -> Vec<PromptPart> {
// 既存の実装(変更しない)
}
pub fn parse_input_with_options(input: &str, options: ParseOptions) -> Vec<PromptPart> {
// 新しい実装
}この方法なら、既存のPhase N, N+1, N+2はそのまま動作し、コンフリクトが発生しません。
禁止事項:
// ❌ 悪い例: 関数シグネチャの変更
pub fn parse_input(input: &str) -> Vec<PromptPart>
↓
pub fn parse_input(input: &str, options: ParseOptions) -> Vec<PromptPart>
// ❌ 悪い例: 関数名の変更
pub fn parse_input(...) -> ...
↓
pub fn parse_tokens(...) -> ...
// ❌ 悪い例: 戻り値の型変更
pub fn parse_input(...) -> Vec<PromptPart>
↓
pub fn parse_input(...) -> Result<Vec<PromptPart>, ParseError>許可される方法:
// ✅ 良い例: 既存APIを維持し、新APIを追加
pub fn parse_input(input: &str) -> Vec<PromptPart> {
// 既存の実装(そのまま)
}
pub fn parse_input_v2(input: &str, options: ParseOptions) -> Vec<PromptPart> {
// 新しい実装
}
// または
pub fn parse_input_checked(input: &str) -> Result<Vec<PromptPart>, ParseError> {
// 新しい実装(エラー処理付き)
}禁止事項:
// ❌ 悪い例: フィールドの削除・名称変更
pub struct PromptPart {
pub is_noun: bool,
pub text: String,
}
↓
pub struct PromptPart {
pub part_type: PartType, // is_noun を置き換え
pub text: String,
}この変更は、以下のコードをすべて壊します:
// Phase Nのコード
if part.is_noun { // ← コンパイルエラー!フィールドが存在しない
// ...
}許可される方法:
// ✅ 良い例: フィールドを追加(既存フィールドは維持)
pub struct PromptPart {
pub is_noun: bool, // 既存フィールド(維持)
pub text: String, // 既存フィールド(維持)
pub metadata: Option<Metadata>, // 新しいフィールド(Option型)
}新しいフィールドを Option<T> 型にすることで、既存コードへの影響を最小限に抑えられます。
各Phaseの責務:
Phase N (feature/phase-n):
✅ parse_input() を呼び出す
✅ PromptPart を使用する
❌ parse_input() の実装を変更する
❌ PromptPart の定義を変更する
ファイルレベルのルール:
feature/phase-n での開発:
✅ src/modules/validation.rs を作成(新規ファイル)
✅ src/lib.rs をインポート(エクスポートを使用)
❌ src/lib.rs を編集(Phase 0のコードを変更)
理由:
Phase 0はすべてのPhaseの基盤です。基盤を変更すると、すべての上位レイヤーに影響が及びます。
シナリオ:
// Phase 0 (feature/phase-0) で変更
pub fn parse_input(input: &str) -> Vec<PromptPart>
↓
pub fn parse_input(input: &str, options: ParseOptions) -> Vec<PromptPart>
// Phase N (feature/phase-n) で壊れる
let parts = parse_input(input); // ← コンパイルエラー: 引数が足りない影響: すべての上位レイヤーが壊れる
対策: 既存シグネチャを変更せず、新関数を追加する
pub fn parse_input(input: &str) -> Vec<PromptPart> {
// 既存実装を維持
}
pub fn parse_input_with_options(input: &str, options: ParseOptions) -> Vec<PromptPart> {
// 新しい実装
}シナリオ:
// Phase 0 で関数名を変更
pub fn parse_input(...) -> ...
↓
pub fn parse_tokens(...) -> ...
// Phase N で壊れる
let parts = parse_input(input); // ← コンパイルエラー: 関数が見つからない影響: すべての呼び出し箇所が壊れる
対策: 古い名前を維持し、新しい名前を追加(必要に応じてエイリアス)
pub fn parse_input(input: &str) -> Vec<PromptPart> {
// 既存実装
}
// エイリアス(将来的に parse_input を deprecated にする場合)
pub fn parse_tokens(input: &str) -> Vec<PromptPart> {
parse_input(input) // 内部的に既存関数を呼ぶ
}シナリオ:
// Phase 0 で構造体を変更
pub struct PromptPart {
pub is_noun: bool,
pub text: String,
}
↓
pub struct PromptPart {
pub part_type: PartType, // is_noun を置き換え
pub text: String,
}
// Phase N で壊れる
if part.is_noun { ... } // ← コンパイルエラー: フィールドが存在しない影響: すべてのフィールドアクセスが壊れる
対策: 新しいフィールドを追加し、既存フィールドは削除しない
pub struct PromptPart {
pub is_noun: bool, // 既存フィールド(維持)
pub text: String, // 既存フィールド(維持)
pub part_type: Option<PartType>, // 新しいフィールド
}もし、どうしてもAPIを変更する必要がある場合は、Deprecation(非推奨化)ワークフローを使用します。
// Phase 0
#[deprecated(since = "0.1.0", note = "Use parse_input_v2 instead")]
pub fn parse_input(input: &str) -> Vec<PromptPart> {
// 既存実装を維持(まだ削除しない)
}
pub fn parse_input_v2(input: &str, options: ParseOptions) -> Vec<PromptPart> {
// 新しい実装
}Rust コンパイラは、非推奨APIを使用している箇所で警告を出します:
warning: use of deprecated function `parse_input`: Use parse_input_v2 instead
すべてのfeature branchで、古いAPIから新しいAPIに移行します:
// Phase N (feature/phase-n)
// 古いAPI
let parts = parse_input(input);
↓
// 新しいAPI
let parts = parse_input_v2(input, ParseOptions::default());すべてのfeature branchを確認します:
git checkout feature/phase-1 && git grep "parse_input("
git checkout feature/phase-2 && git grep "parse_input("
git checkout feature/phase-n && git grep "parse_input("古いAPIの使用箇所が0になるまで待ちます。
v1.0.0など、メジャーバージョンアップ時に非推奨APIを削除します:
// v1.0.0: 非推奨APIを削除
// pub fn parse_input(...) -> ... ← この行を削除
pub fn parse_input_v2(...) -> ... // 新しいAPIを維持Prompsは**Dependency Inversion Principle(依存性逆転の原則)**に従っています:
┌─────────────────────────────────────┐
│ Phase N: Validation Layer │
│ - parse_input_checked() │ ← Phase N のAPI
│ - validate_pattern() │
└────────────┬────────────────────────┘
│ 依存(呼び出すのみ、変更しない)
↓
┌─────────────────────────────────────┐
│ Phase 0: Core Parsing Layer │
│ - parse_input() │ ← 不変のAPI
│ - generate_prompt() │
└─────────────────────────────────────┘
特徴:
- 上位レイヤーは下位レイヤーに依存する
- 下位レイヤーは上位レイヤーに依存しない
- 下位レイヤーのAPIは安定している
- 上位レイヤーのAPIは自由に変更できる
各Phaseには、異なる安定性レベルがあります:
| レイヤー | 安定性レベル | 変更ポリシー |
|---|---|---|
| Phase 0 | 不変 | 既存APIは絶対に変更しない |
| Phase 1 | 安定 | v1.0.0以降は変更を避ける |
| Phase 2+ | 半安定 | 非推奨化を経て変更可能 |
| Phase N+ | 不安定 | 自由に変更可能(まだリリースされていない) |
ガイドライン:
- Phase 0: 最も重要。すべてのPhaseの基盤。
- Phase 1: GUIの基盤。Phase 2+が依存。
- Phase 2+: ブロックタイプ。比較的独立しているが、Phase Nが依存する可能性。
- Phase N+: まだリリースされていないため、自由に実験可能。
Rustの型システムは、APIの非互換性を自動的に検出します:
// Phase 0のAPIが変更された場合、上位レイヤーはコンパイルに失敗
let parts = parse_input(input); // ← シグネチャが変わるとコンパイルエラーこれは安全装置として機能します。
GitHub Actionsを使用して、feature branchがdevと互換性があるかチェックします:
# .github/workflows/api-compatibility.yml
name: API Compatibility Check
on:
push:
branches:
- 'feature/**'
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# devをマージしてコンパイルできるかチェック
- name: Check compatibility with dev
run: |
git fetch origin dev
git merge origin/dev --no-commit --no-ff || true
cargo check --all-features
# 失敗したら通知
- name: Notify on incompatibility
if: failure()
run: echo "⚠️ API incompatibility detected"Phase Nで、検証の厳格度を設定できるようにしたい。
// Phase 0を変更
pub fn parse_input(input: &str, strict: bool) -> Vec<PromptPart> {
// ...
}
// Phase 1, 2, 3がすべて壊れる
let parts = parse_input(input); // ← コンパイルエラー: 引数が足りないこの方法では、Phase 1, 2, 3のすべてのfeature branchで修正が必要になります。
// Phase 0: 既存APIを維持
pub fn parse_input(input: &str) -> Vec<PromptPart> {
// 既存の実装(変更しない)
}
// Phase N: 新しい関数を追加
pub fn parse_input_checked(input: &str) -> Result<Vec<PromptPart>, ValidationError> {
let parts = parse_input(input); // Phase 0のAPIを再利用
validate_pattern(&parts)?; // Phase Nの検証を追加
Ok(parts)
}この方法なら:
- Phase 1, 2, 3は影響を受けない(既存のまま動作)
- Phase Nだけが新しいAPIを使用
- コンフリクトなし
-
Phase 0のAPIは不変
- 既存の関数シグネチャを変更しない
- 既存の構造体フィールドを削除しない
- 新機能は新しいAPIとして追加
-
上位レイヤーは下位レイヤーを変更しない
- Phase Nは Phase 0のコードを変更しない
- Phase 0のAPIを呼び出すのみ
-
やむを得ない場合はDeprecation Workflow
- 非推奨マークを付ける
- すべてのレイヤーを移行
- メジャーバージョンアップで削除
- マージコンフリクトが劇的に減少
- 各Phaseが独立して開発できる
- 変更の影響範囲が明確
- Persistent Feature Branch戦略と完璧に一致
- BRANCHING_STRATEGY.md: Persistent Feature Branch戦略
- DESIGN_PHILOSOPHY.md: レイヤードアーキテクチャの詳細
- CONVENTIONS.md: コーディング規約
このドキュメントのAI向け版(簡潔なルールベース)は以下にあります:
.ai-context/development/API_STABILITY.md
API安定性ポリシーについて質問やフィードバックがあれば、GitHubのIssueで気軽にお知らせください!
最終更新: 2025-12-08