このレシピはプラットフォーム自己増殖プロトコルである。他のレシピが「実装済みモジュールをイベント app に配線する」手順なのに対し、これは「モジュール自体を新しく作ってプラットフォームに登録する」手順 — つまり add-<name> レシピを1本増やすためのレシピ。
直近の実例は @event/motion(recipe add-motion / 第47章)で、まさにこのプロトコルで追加された。以下の各ステップで motion を照合先として参照できる。
完了条件(4面テスト):
- 選べる —
event.config.tsの flag /create-event-appの--featuresに現れる(基盤層なら flag 不要と明記されている) - dev で動く —
apps/starterに参照配線が入りyarn workspace starter devで動作する - 1プロンプト —
recipes/add-<name>.tsが自己完結ランブックとして存在しyarn lint:recipesが import 照合を通す - published 章 — マニュアル章が
publishedで/guide/<slug>に現れる
登録点は全部で 18 箇所。漏れると「動くが選べない」「選べるがマニュアルに無い」の非対称が生まれる:
| # | 登録点 | 何を書くか |
|---|---|---|
| 1 | packages/<name>/package.json | パッケージ宣言(exports は TS ソース直) |
| 2 | packages/<name>/tsconfig.json | react-library / base の extends |
| 3 | packages/<name>/src/index.ts | 公開面(全 export の集約点) |
| 4 | apps/starter/package.json | dependencies に "@event/<name>": "*" |
| 5 | apps/starter/next.config.ts | transpilePackages に追加 |
| 6 | apps/starter/src/event.config.ts | features flag(基盤層なら不要) |
| 7 | apps/starter/src/event.config.ts | FEATURES descriptor(基盤層なら不要) |
| 8 | apps/starter/src/app/… | 参照 UI(page / route。features.isEnabled gate) |
| 9 | packages/create-event-app/bin.mjs | KNOWN_FEATURES 配列(基盤層なら不要) |
| 10 | packages/create-event-app/bin.mjs | env 生成 if ブロック(env が要る機能のみ) |
| 11 | apps/manual/src/content/recipes/add-<name>.ts | 自己完結レシピ本体 |
| 12 | apps/manual/src/content/recipes/index.ts | import + RECIPES(依存順の位置) |
| 13 | apps/manual/src/content/chapters/<slug>.ts | マニュアル章本体 |
| 14 | apps/manual/src/content/index.ts | import + CHAPTERS |
| 15 | apps/manual/src/lib/toc.ts | c(n, "…", "published") |
| 16 | apps/manual/src/content/modules-catalog.ts | MODULES エントリ(/modules と統計の真実源) |
| 17 | apps/manual/src/content/packages-catalog.ts | PACKAGES エントリ(/reference の真実源) |
| 18 | AGENTS.md | レシピ表 + パッケージ表に各1行 |
登録点 11〜17 はすべてデータ(TS の配列)なので、登録した瞬間に機械可読レイヤ (/llms.txt / /llms-full.txt / /api/registry — 第48章)にも自動で載る。手書きの同期は AGENTS.md だけ。
1. packages/<name> を scaffold する
命名は @event/<name>(ディレクトリは packages/<name>)。React component を含むかで 2 系統:
React あり(component を export する — motion / cms/ui 型)— packages/<name>/package.json:
{
"name": "@event/<name>",
"version": "0.1.0",
"private": true,
"type": "module",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"typecheck": "tsc --noEmit"
},
"peerDependencies": {
"react": ">=19"
},
"devDependencies": {
"@event/tsconfig": "*",
"@types/react": "^19.2.0",
"typescript": "^5.7.3"
}
}
packages/<name>/tsconfig.json:
{
"extends": "@event/tsconfig/react-library.json",
"include": ["src"]
}
React なし(純関数層 — design / networking 型): peerDependencies の react と @types/react を外し、tsconfig の extends を "@event/tsconfig/base.json" にする。
要点(internal-packages パターン — root CLAUDE.md 準拠):
exportsは TS ソース直("./src/index.ts")。ビルド成果物を作らない
— 消費側 app の transpilePackages がコンパイルする(§2)
- 公開面は
src/index.tsに集約。深い import(@event/<name>/src/foo)はさせない。
subpath が要るときは exports に "./client" 等のキーを足す(例: @event/cms の ./ui / ./client)
- client component は該当ファイル先頭に
"use client"(@event/motionのReveal.tsx/CountUp.tsx参照。
server で描画可能なもの — MotionStyles のような純粋描画 — には付けない)
- 外部依存は原則ゼロ。足すならライセンスとメンテ状況を確認してから
packages/<name>/src/index.ts は doc コメント + 再 export だけにする(motion の実物が手本):
/**
* @event/<name> — 一言でこのパッケージの責務。
*
* 設計判断・配線方法(recipe add-<name> 参照)をここに数行で書く。
*/
export { something } from "./something";
export { SomeComponent, type SomeComponentProps } from "./SomeComponent";
作成後、workspace として認識されることを確認:
yarn install
yarn workspace @event/<name> typecheck
2. starter に依存を配線する
apps/starter/package.json の dependencies に追加:
"@event/<name>": "*"
apps/starter/next.config.ts の transpilePackages 配列に "@event/<name>" を足す (TS ソース直 exports なのでこれが無いとビルドできない)。root で yarn install。
3. event.config.ts — FeatureDescriptor + flag
先に判断: この機能は flag が要るか。
- feature 型(nav / route が増える・機能単位で on/off したい — surveys / booking 型)→ このステップを実施
- 基盤層(全ページ共通・nav も route も増えない — design / i18n / motion 型)→ flag 不要。
このステップと §5 の KNOWN_FEATURES をスキップし、レシピに「feature flag 不要」と明記する(add-motion の書き方を踏襲)
feature 型の場合、apps/starter/src/event.config.ts の features に flag を足し:
features: {
// …既存…
<name>: true, // ← @event/<name> の一言説明
},
FEATURES 配列に descriptor を1つ足す(第42章のフルフロー。nav・env・章番号が自己記述で追従する):
{
key: "<name>",
label: "<日本語ラベル>",
description: "<何が動くか>(@event/<name>、dev の永続先も書く)",
nav: [
{ id: "<name>-admin", group: "<nav グループ>", label: "<nav ラベル>", href: "/admin/<name>", code: "<4文字コード>", order: 1 },
],
env: [
{ name: "SOME_TOKEN", required: false, description: "何に使うか(未設定時の挙動も書く)" },
],
manualChapters: [<n>], // §7 で採番する章番号
// requires: ["cms"], // 依存機能があれば(違反は composeFeatures が起動時 fail-fast)
},
4. starter に参照 UI を置く
apps/starter/src/app/ に最小の page / route を置く。エンジンコードをイベント app に書かない (鉄則 — ロジックはパッケージへ、app には配線だけ)。feature 型は冒頭 gate が作法:
// apps/starter/src/app/admin/<name>/page.tsx(feature 型・admin の作法)
import { notFound, redirect } from "next/navigation";
import { cookies } from "next/headers";
import { features } from "@/event.config";
import { adminAuth } from "@/lib/adminAuth";
export default async function Page() {
if (!features.isEnabled("<name>")) notFound(); // ① 無効機能は 404
const cookie = (await cookies()).get(adminAuth.cookieName)?.value;
if (!adminAuth.isAuthedFromValue(cookie)) redirect("/login"); // ② 未認証は /login
// …ここからパッケージの export を呼ぶ配線だけ…
}
基盤層は gate 不要で layout / page に直接配線する。実例(add-motion §2〜3 の転写):
import { MotionStyles, Reveal } from "@event/motion";
// layout の <head> に <MotionStyles /> を1回 → page で <Reveal>…</Reveal>
5. create-event-app に登録する
packages/create-event-app/bin.mjs の KNOWN_FEATURES 配列(ファイル冒頭)に "<name>" を足す:
const KNOWN_FEATURES = ["cms", "sessionCard", /* …既存… */, "<name>"];
これで node packages/create-event-app/bin.mjs <event> --features=…,<name> が通り、 生成される event.config.ts の flag に反映される(starter 複製方式なので §2〜4 の配線は自動で入る)。
- env が要る機能なら、同ファイル §4 の
.env.example生成に if ブロックを1つ足す:
if (selected.includes("<name>")) {
envVars.set("SOME_TOKEN", { required: false, description: "何に使うか" });
}
- 依存機能があるなら自動補完行も(
sessionCard→cms/mcp→dbと同じ形):
if (selected.includes("<name>") && !selected.includes("cms")) selected.push("cms");
基盤層(flag 無し)はこのステップ全体をスキップ(starter 複製にソースごと入るため)。
6. recipes/add-<name>.ts — 1プロンプト・ランブック
apps/manual/src/content/recipes/add-<name>.ts を Recipe 型(recipes/types.ts)で書く。 自己完結が絶対条件: ファイルパス・完全なコード・コマンド・検証まで含め、「〜を適宜書く」で終わらせない。 body のコード block には実装をそのまま転写する(要約・擬似コードにしない)。
import type { Recipe } from "./types";
const recipe: Recipe = {
id: "add-<name>",
title: "<機能>を追加する",
goal: "<何が完成するか1文>",
prerequisites: ["new-event"],
features: ["<name>"], // 基盤層なら []
chapters: [<n>],
steps: <目安手数>,
ready: true,
body: `…自己完結ランブック(手本: add-design / add-networking / add-motion)…`,
};
export default recipe;
apps/manual/src/content/recipes/index.ts に import 1行 + RECIPES 配列へ依存順の位置に挿入する。
recipe-lint が import を照合する: body 内コード block に書いた @event/<name> からの named import 文(import { X } … 形式)は tooling/recipe-lint/lint.mjs が packages/<name>/package.json の exports を辿って実 export 集合と静的照合する (yarn lint:recipes)。存在しない API 名を書くと CI が落ちる — 推測 API はここで構造的に排除される。 逆に言えば、レシピに書く import 名は必ず src/index.ts から export しておくこと。
7. マニュアル章 — chapters/<slug>.ts + 2 つの登録点
章番号 n は apps/manual/src/lib/toc.ts の既存最大 + 1 で採番する。
apps/manual/src/content/chapters/<slug>.ts(content/types.ts の Chapter 型):
import type { Chapter } from "../types";
/** 第<n>章 — 一言。 */
const chapter: Chapter = {
n: <n>,
slug: "<slug>",
title: "<章タイトル>",
packages: ["@event/<name>"], // サイト機能の章なら []
body: `…What/Why/How。設計判断を実 export・実コードで書く(手本: design-customization)…`,
};
export default chapter;
登録点は2つ:
apps/manual/src/content/index.ts— import 1行 +CHAPTERS配列に追加(n 昇順 sort 済みなので位置は末尾でよい)apps/manual/src/lib/toc.ts— 該当 Part(拡張系は Part VIII「プラットフォーム拡張」)に
c(<n>, "<toc タイトル>", "published") を足す。"published" にしない限り /guide に現れない
8. カタログ + AGENTS.md を同期する
3ファイルに各1エントリ(登録点 16〜18):
apps/manual/src/content/modules-catalog.tsのMODULES—/modulesページとトップ統計の真実源:
{
key: "<name>",
label: "<日本語ラベル>",
pkg: "@event/<name>",
status: "ready",
what: "<何がどう動くか — 設計判断込みで2〜3文>",
enable: ["<name>: true(基盤層なら recipe add-<name>(flag 不要))", "<必要 env>", "<配線の要点>"],
chapters: [<n>],
},
apps/manual/src/content/packages-catalog.tsのPACKAGES—/referenceの真実源:
{
name: "@event/<name>",
entry: ["."],
summary: "<1行サマリ>",
exports: [
{ name: "mainExport / SubExport", desc: "<何をするか>" },
],
},
AGENTS.md— レシピ表に1行(| <やりたいこと> | \add-<name>\| 実装済み |)+
パッケージ表に1行(| \@event/<name>\ | <役割> | \.\ |)
9. 検証
yarn workspace @event/<name> typecheck # パッケージ単体
yarn workspace starter typecheck # 参照配線
yarn workspace manual typecheck # レシピ・章・カタログ
yarn lint:recipes # レシピ import ↔ 実 export の照合
yarn build # turbo 全 workspace ビルド
yarn workspace starter dev # → 参照 UI が動く(feature 型は flag off で 404 になることも確認)
yarn dev:manual # → /recipes/add-<name> と /guide/<slug> が表示される
完了条件(4面テスト再掲): ① create-event-app --features=<name> で選べる(基盤層は「flag 不要」がレシピに明記) ② starter dev で動く ③ yarn lint:recipes green の自己完結レシピがある ④ /guide/<slug> が published で読める。 この4面が揃って初めて「プラットフォームに追加された」と言える — コードが merge されただけの状態を完了と呼ばない。