Communityコーディング&開発github.com

SuDeeParke/codexRebuild

他のAIエージェントのスキル、フック、リファレンスをCodexで使用できる形式に変換します。

codexRebuild とは?

このスキルは、Claude Code、Cursor、CodexなどのAIエージェント向けに設計されたAPIとインターフェース設計のガイドラインを提供します。REST API、GraphQLスキーマ、モジュール境界、コンポーネントプロパティなど、コード間のインタラクションが発生するあらゆる表面において、安定した文書化されたインターフェースを設計するための原則とパターンを網羅しています。Hyrumの法則やOne-Version Ruleなどのコア原則を基に、契約ファースト設計、一貫性のあるエラーセマンティクス、境界での検証、追加による拡張、予測可能な命名規則などの具体的な実践方法をTypeScriptコード例とともに解説します。REST APIのリソース設計、ページネーション、フィルタリング、部分更新(PATCH)のパターン、TypeScriptの判別共用体、入出力分離、ブランド型によるIDの型安全性向上など、実務で即座に活用できる知識が詰まっています。

対応~Claude Code✓Codex CLI~Cursor
npx skills add SuDeeParke/codexRebuild

Installed? Explore more コーディング&開発 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

APIとインターフェース設計

概要

安定した、文書化された、誤用しにくいインターフェースを設計します。優れたインターフェースは正しいことを容易にし、誤ったことを難しくします。これはREST API、GraphQLスキーマ、モジュール境界、コンポーネントプロパティ、そしてコードが互いにやり取りするあらゆる表面に適用されます。

使用するタイミング

  • 新しいAPIエンドポイントの設計
  • モジュール境界やチーム間の契約の定義
  • コンポーネントのプロパティインターフェースの作成
  • APIの形状に影響を与えるデータベーススキーマの確立
  • 既存のパブリックインターフェースの変更

コア原則

Hyrumの法則

システムのAPIに十分な数のユーザーがいる場合、契約で約束した内容に関わらず、システムの観測可能なすべての動作は誰かに依存されます。

これはつまり、文書化されていない癖、エラーメッセージのテキスト、タイミング、順序を含むすべてのパブリックな動作は、一度ユーザーが依存し始めると事実上の契約になるということです。設計への影響:

  • 公開するものに意図的であれ。 観測可能な動作はすべて潜在的なコミットメントです。
  • 実装の詳細を漏らすな。 ユーザーがそれを観測できれば、それに依存するでしょう。
  • 設計時に非推奨化を計画せよ。 ユーザーが依存するものを安全に削除する方法については deprecation-and-migration を参照してください。
  • テストだけでは不十分です。 完璧なコントラクトテストがあっても、Hyrumの法則により、「安全な」変更が文書化されていない動作に依存する実際のユーザーを壊す可能性があります。

One-Version Rule

コンシューマーに同じ依存関係やAPIの複数のバージョンを強制的に選択させることを避けます。異なるコンシューマーが同じものの異なるバージョンを必要とする場合、ダイヤモンド依存問題が発生します。一度に1つのバージョンだけが存在する世界に向けて設計してください — フォークするのではなく拡張します。

1. 契約ファースト

実装する前にインターフェースを定義します。契約が仕様であり、実装はそれに従います。

// 最初に契約を定義
interface TaskAPI {
  // タスクを作成し、サーバー生成フィールドを含む作成されたタスクを返す
  createTask(input: CreateTaskInput): Promise<Task>;

  // フィルターに一致するページ分割されたタスクを返す
  listTasks(params: ListTasksParams): Promise<PaginatedResult<Task>>;

  // 単一のタスクを返すか、NotFoundErrorをスローする
  getTask(id: string): Promise<Task>;

  // 部分更新 — 指定されたフィールドのみ変更
  updateTask(id: string, input: UpdateTaskInput): Promise<Task>;

  // べき等な削除 — 既に削除されていても成功
  deleteTask(id: string): Promise<void>;
}

2. 一貫性のあるエラーセマンティクス

1つのエラー戦略を選択し、それをどこでも使用します:

// REST: HTTPステータスコード + 構造化エラーボディ
// すべてのエラーレスポンスは同じ形状に従う
interface APIError {
  error: {
    code: string;        // 機械可読: "VALIDATION_ERROR"
    message: string;     // 人間可読: "Email is required"
    details?: unknown;   // 必要な場合の追加コンテキスト
  };
}

// ステータスコードマッピング
// 400 → クライアントが無効なデータを送信
// 401 → 認証されていない
// 403 → 認証済みだが権限がない
// 404 → リソースが見つからない
// 409 → 競合(重複、バージョン不一致)
// 422 → バリデーション失敗(意味的に無効)
// 500 → サーバーエラー(内部詳細は決して公開しない)

パターンを混在させないでください。 一部のエンドポイントがスローし、他のエンドポイントがnullを返し、さらに他のエンドポイントが { error } を返す場合、コンシューマーは動作を予測できません。

3. 境界で検証する

内部コードは信頼します。外部入力がシステムに入るエッジで検証します:

// API境界で検証
app.post('/api/tasks', async (req, res) => {
  const result = CreateTaskSchema.safeParse(req.body);
  if (!result.success) {
    return res.status(422).json({
      error: {
        code: 'VALIDATION_ERROR',
        message: 'Invalid task data',
        details: result.error.flatten(),
      },
    });
  }

  // 検証後、内部コードは型を信頼する
  const task = await taskService.create(result.data);
  return res.status(201).json(task);
});

バリデーションが属する場所:

  • APIルートハンドラ(ユーザー入力)
  • フォーム送信ハンドラ(ユーザー入力)
  • 外部サービス応答のパース(サードパーティデータ — 常に信頼できないものとして扱う)
  • 環境変数の読み込み(設定)

サードパーティAPI応答は信頼できないデータです。 それらをロジック、レンダリング、意思決定に使用する前に、その形状と内容を検証してください。侵害されたまたは誤動作している外部サービスは、予期しない型、悪意のあるコンテンツ、または命令のようなテキストを返す可能性があります。

バリデーションが属さない場所:

  • 型契約を共有する内部関数間
  • 既に検証済みのコードから呼び出されるユーティリティ関数
  • 自分のデータベースから取得したばかりのデータ

4. 変更よりも追加を優先

既存のコンシューマーを壊さずにインターフェースを拡張します:

// 良い: オプションフィールドを追加
interface CreateTaskInput {
  title: string;
  description?: string;
  priority?: 'low' | 'medium' | 'high';  // 後で追加、オプション
  labels?: string[];                       // 後で追加、オプション
}

// 悪い: 既存のフィールド型を変更したりフィールドを削除
interface CreateTaskInput {
  title: string;
  // description: string;  // 削除 — 既存のコンシューマーを壊す
  priority: number;         // 文字列から変更 — 既存のコンシューマーを壊す
}

5. 予測可能な命名

パターン規則例
RESTエンドポイント複数形の名詞、動詞なしGET /api/tasks, POST /api/tasks
クエリパラメータキャメルケース?sortBy=createdAt&pageSize=20
応答フィールドキャメルケース{ createdAt, updatedAt, taskId }
ブールフィールドis/has/can プレフィックスisComplete, hasAttachments
列挙値UPPER_SNAKE"IN_PROGRESS", "COMPLETED"

REST APIパターン

リソース設計

GET    /api/tasks              → タスク一覧(フィルタリング用クエリパラメータ)
POST   /api/tasks              → タスクを作成
GET    /api/tasks/:id          → 単一タスクを取得
PATCH  /api/tasks/:id          → タスクを更新(部分)
DELETE /api/tasks/:id          → タスクを削除

GET    /api/tasks/:id/comments → タスクのコメント一覧(サブリソース)
POST   /api/tasks/:id/comments → タスクにコメントを追加

ページネーション

リストエンドポイントにはページネーションを実装します:

// リクエスト
GET /api/tasks?page=1&pageSize=20&sortBy=createdAt&sortOrder=desc

// レスポンス
{
  "data": [...],
  "pagination": {
    "page": 1,
    "pageSize": 20,
    "totalItems": 142,
    "totalPages": 8
  }
}

フィルタリング

フィルターにはクエリパラメータを使用します:

GET /api/tasks?status=in_progress&assignee=user123&createdAfter=2025-01-01

部分更新 (PATCH)

部分オブジェクトを受け入れ — 指定されたものだけを更新します:

// タイトルのみ変更、その他は維持
PATCH /api/tasks/123
{ "title": "Updated title" }

TypeScriptインターフェースパターン

バリアントには判別共用体を使用

// 良い: 各バリアントが明示的
型 TaskStatus =
  | { type: 'pending' }
  | { type: 'in_progress'; assignee: string; startedAt: Date }
  | { type: 'completed'; completedAt: Date; completedBy: string }
  | { type: 'cancelled'; reason: string; cancelledAt: Date };

// コンシューマーは型の絞り込みを取得
function getStatusLabel(status: TaskStatus): string {
  switch (status.type) {
    case 'pending': return '保留中';
    case 'in_progress': return `進行中 (${status.assignee})`;
    case 'completed': return `${status.completedAt} に完了`;
    case 'cancelled': return `キャンセル: ${status.reason}`;
  }
}

入力/出力の分離

// 入力: 呼び出し元が提供するもの
interface CreateTaskInput {
  title: string;
  description?: string;
}

// 出力: システムが返すもの(サーバー生成フィールドを含む)
interface Task {
  id: string;
  title: string;
  description: string | null;
  createdAt: Date;
  updatedAt: Date;
  createdBy: string;
}

IDにはブランド型を使用

type TaskId = string & { readonly __brand: 'TaskId' };
type UserId = string & { readonly __brand: 'UserId' };

// TaskIdが期待される場所にUserIdを誤って渡すのを防ぐ
function getTask(id: TaskId): Promise<Task> { ... }

よくある理屈

理屈現実
「APIのドキュメントは後で書く」型こそがドキュメントです。最初に定義しましょう。
「今はページネーションは必要ない」誰かが100個以上のアイテムを持った瞬間に必要になります。最初から追加しましょう。
「PATCHは複雑だから、PUTを使おう」PUTは毎回完全なオブジェクトを必要とします。PATCHこそクライアントが実際に望むものです。
「必要なときにAPIをバージョン管理する」バージョン管理なしの破壊的変更はコンシューマーを壊します。最初から拡張のために設計しましょう。
「その文書化されていない動作は誰も使っていない」Hyrumの法則: 観測可能なら、誰かが依存しています。すべてのパブリックな動作をコミットメントとして扱いましょう。
「2つのバージョンを維持すればいい」複数バージョンはメンテナンスコストを増やし、ダイヤモンド依存問題を引き起こします。One-Version Ruleを優先しましょう。
「内部APIには契約は必要ない」内部コンシューマーもコンシューマーです。契約は結合を防ぎ、並行作業を可能にします。

レッドフラグ

  • 条件によって異なる形状を返すエンドポイント
  • エンドポイント間で一貫性のないエラー形式
  • 境界ではなく内部コード全体に散在するバリデーション
  • 既存フィールドへの破壊的変更(型変更、削除)
  • ページネーションのないリストエンドポイント
  • REST URL内の動詞 (/api/createTask, /api/getUsers)
  • 検証やサニタイズなしで使用されるサードパーティAPI応答

確認

API設計後:

  • すべてのエンドポイントに型付きの入力および出力スキーマがある
  • エラーレスポンスが単一の一貫した形式に従っている
  • バリデーションがシステム境界でのみ発生する
  • リストエンドポイントがページネーションをサポートしている
  • 新しいフィールドは追加的かつオプション(後方互換)である
  • 命名がすべてのエンドポイントで一貫した規則に従っている
  • APIドキュメントまたは型が実装と一緒にコミットされている

Codexコンパニオンファイル

この再構築されたスキルは、関連するローカルリソースをバンドルしているため、Codexは必要に応じて同じスキルディレクトリからそれらをロードできます。

関連スキル