取り組みの背景 — なぜ「自分だけのコードジェネレーター」が必要なのか
プロジェクトが成長するにつれ、同じようなファイル構造を繰り返し作成する場面が増えてきます。API エンドポイント、React コンポーネント、データベースマイグレーション、テストファイルなど、毎回手作業でボイラープレートを書いていると、時間がかかるだけでなく、チームメンバーによって微妙にスタイルが異なるコードが生まれてしまいます。
Antigravity の AI エージェントは、単にコードを生成するだけでなく、プロジェクトの規約やパターンを理解した上で一貫性のあるコードを出力できます。しかし、エージェントへのプロンプトだけに頼ると、毎回同じ指示を繰り返す必要があり、出力の安定性も保証しにくくなります。
ここで活躍するのが テンプレートエンジンと AI エージェントの組み合わせ です。テンプレートエンジンが「構造」を担保し、AI エージェントが「文脈に応じたロジック」を補完します。この二層アーキテクチャによって、プロジェクト固有のコード生成パイプラインを構築できます。
テンプレートエンジンの選定 — Hygen・Plop・カスタムスクリプトの比較
コードジェネレーターを構築する際、まず決めるべきはテンプレートエンジンの選択です。主要な3つのアプローチを比較してみましょう。
Hygen — ファイルベースのシンプルなジェネレーター
Hygen はテンプレートをファイルシステムに配置するだけで動作するジェネレーターです。学習コストが低く、小〜中規模プロジェクトに適しています。
# Hygen のインストールとセットアップ
npm install -g hygen
hygen init self
# テンプレートの作成
# _templates/component/new/index.tsx.ejs.t
// _templates/component/new/index.tsx.ejs.t
---
to: src/components/<%= name %>/<%= name %>.tsx
---
import React from 'react';
interface <%= name %>Props {
/** コンポーネントの説明 */
children?: React.ReactNode;
}
/**
* <%= name %> コンポーネント
* 自動生成: <%= new Date().toISOString().split('T')[0] %>
*/
export const <%= name %>: React.FC<<%= name %>Props> = ({ children }) => {
return (
<div className="<%= h.changeCase.paramCase(name) %>">
{children}
</div>
);
};
export default <%= name %>;
Hygen の強みは、テンプレートがそのままファイルとしてリポジトリに存在するため、レビューやバージョン管理が容易な点です。
Plop — インタラクティブなプロンプト付きジェネレーター
Plop はインタラクティブなプロンプトを通じて、ユーザーに情報を入力させながらコードを生成します。複雑な条件分岐が必要な場合に威力を発揮します。
// plopfile.ts
import { NodePlopAPI } from 'plop';
export default function (plop: NodePlopAPI) {
plop.setGenerator('api-endpoint', {
description: 'REST API エンドポイントを生成',
prompts: [
{
type: 'input',
name: 'resource',
message: 'リソース名(例: users, products):',
},
{
type: 'checkbox',
name: 'methods',
message: '生成するHTTPメソッド:',
choices: ['GET', 'POST', 'PUT', 'DELETE'],
},
{
type: 'confirm',
name: 'withAuth',
message: '認証ミドルウェアを含めますか?',
default: true,
},
],
actions: (data) => {
const actions = [
{
type: 'add',
path: 'src/api/{{dashCase resource}}/route.ts',
templateFile: 'templates/api-route.hbs',
},
{
type: 'add',
path: 'src/api/{{dashCase resource}}/schema.ts',
templateFile: 'templates/api-schema.hbs',
},
];
// テスト対象のメソッドが選択されている場合のみテストファイルを生成
if (data && data.methods && data.methods.length > 0) {
actions.push({
type: 'add',
path: 'src/api/{{dashCase resource}}/route.test.ts',
templateFile: 'templates/api-test.hbs',
});
}
return actions;
},
});
}
カスタム TypeScript スクリプト — 完全な制御が必要な場合
大規模プロジェクトや特殊な要件がある場合は、TypeScript でフルスクラッチのジェネレーターを構築するアプローチも有効です。
// scripts/generate.ts
import { mkdir, writeFile, readFile } from 'fs/promises';
import { join } from 'path';
import Handlebars from 'handlebars';
interface GeneratorConfig {
name: string;
category: 'component' | 'api' | 'hook' | 'service';
options: {
withTests: boolean;
withStorybook: boolean;
withStyles: boolean;
};
}
async function generate(config: GeneratorConfig): Promise<void> {
const templateDir = join(__dirname, '../templates', config.category);
const outputDir = join(__dirname, '../src', config.category + 's', config.name);
await mkdir(outputDir, { recursive: true });
// テンプレートの読み込みとコンパイル
const templateSource = await readFile(
join(templateDir, 'index.hbs'),
'utf-8'
);
const template = Handlebars.compile(templateSource);
// コンテキストの構築
const context = {
name: config.name,
pascalName: toPascalCase(config.name),
kebabName: toKebabCase(config.name),
timestamp: new Date().toISOString(),
...config.options,
};
// ファイルの生成
const output = template(context);
await writeFile(join(outputDir, 'index.tsx'), output);
console.log(`✅ Generated ${config.category}: ${config.name}`);
}
function toPascalCase(str: string): string {
return str.replace(/(^\w|-\w)/g, (match) =>
match.replace('-', '').toUpperCase()
);
}
function toKebabCase(str: string): string {
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
}
// 実行例:
// npx tsx scripts/generate.ts --name=UserProfile --category=component --withTests
各アプローチの使い分けとしては、少数のシンプルなテンプレートなら Hygen、対話的な生成フローが必要なら Plop、完全なカスタマイズが必要なら TypeScript スクリプトという判断になります。
Antigravity AI エージェントとの統合アーキテクチャ
テンプレートエンジンだけでは「静的なスキャフォールド」にとどまります。Antigravity の AI エージェントを統合することで、コンテキストに応じた「知的な」コード生成が可能になります。
二層アーキテクチャの設計思想
┌─────────────────────────────────────────────┐
│ ユーザーの要求(自然言語) │
│ 「ユーザー認証付きの API エンドポイントを │
│ 作って。バリデーションも入れて」 │
└───────────────┬─────────────────────────────┘
│
┌───────────▼───────────┐
│ Antigravity AI Agent │ ← 文脈理解 + 判断
│ (AGENTS.md 参照) │
└───────────┬───────────┘
│ パラメータ決定
┌───────────▼───────────┐
│ テンプレートエンジン │ ← 構造の担保
│ (Hygen / Plop) │
└───────────┬───────────┘
│ ファイル生成
┌───────────▼───────────┐
│ AI 後処理 │ ← コンテキスト固有ロジック
│ (型推論・最適化) │
└───────────────────────┘
この二層アーキテクチャでは、テンプレートが「骨格」を生成し、AI エージェントが「肉付け」を担当します。テンプレートが保証するのは、ファイル名の命名規則、ディレクトリ構造、import パスの一貫性、ボイラープレートの統一性です。AI エージェントが担当するのは、ビジネスロジックの実装、エッジケースの処理、型定義の最適化、ドキュメントコメントの生成です。
AGENTS.md でジェネレーターのルールを定義する
Antigravity の AGENTS.md にコード生成のガイドラインを組み込むことで、AI エージェントがテンプレートの意図を理解した上でコードを生成・修正できるようになります。
<!-- AGENTS.md(抜粋)-->
## コード生成ガイドライン
### ファイル生成時の必須ルール
1. 新しいコンポーネントは `scripts/generate.ts` 経由で作成する
2. 直接ファイルを作成する場合も、既存テンプレートのパターンに従う
3. 生成後は必ず `npm run lint:fix` を実行する
### 命名規則
- コンポーネント: PascalCase(例: UserProfile)
- ファイル名: kebab-case(例: user-profile.tsx)
- テストファイル: *.test.ts / *.spec.ts
- フック: use + PascalCase(例: useUserProfile)
### テンプレートの場所
- コンポーネント: `_templates/component/`
- API ルート: `_templates/api/`
- フック: `_templates/hook/`
- サービス: `_templates/service/`
### 生成後のチェックリスト
- [ ] TypeScript の型エラーがないこと
- [ ] ESLint 警告がないこと
- [ ] 対応するテストファイルが存在すること
- [ ] index.ts のエクスポートに追加されていること
この定義を AGENTS.md に記載しておくことで、チームメンバーが Antigravity に「新しいコンポーネントを作って」と指示した際に、エージェントが自動的にジェネレーターのルールに従ったコードを生成します。
実装例 — React コンポーネントジェネレーターの構築
ここからは、実際に動作するコンポーネントジェネレーターを TypeScript で実装します。テンプレートと AI の連携を具体的に見ていきましょう。
プロジェクト構造
project-root/
├── _templates/
│ ├── component/
│ │ ├── index.tsx.hbs # メインコンポーネント
│ │ ├── index.test.tsx.hbs # テストファイル
│ │ ├── index.stories.tsx.hbs # Storybook
│ │ └── styles.module.css.hbs # CSS Modules
│ └── api/
│ ├── route.ts.hbs
│ └── schema.ts.hbs
├── scripts/
│ ├── generate.ts # ジェネレーター本体
│ └── templates.ts # テンプレート管理
├── .antigravity/
│ └── rules/
│ └── code-generation.md # カスタムルール
└── AGENTS.md
ジェネレーター本体の実装
// scripts/generate.ts
import { mkdir, writeFile, readFile, readdir } from 'fs/promises';
import { join, resolve } from 'path';
import Handlebars from 'handlebars';
// ============================================
// 型定義
// ============================================
interface ComponentConfig {
name: string;
props: PropDefinition[];
hooks: string[];
withTests: boolean;
withStorybook: boolean;
withStyles: boolean;
}
interface PropDefinition {
name: string;
type: string;
required: boolean;
defaultValue?: string;
description: string;
}
interface GeneratedFile {
path: string;
content: string;
}
// ============================================
// Handlebars ヘルパーの登録
// ============================================
Handlebars.registerHelper('pascalCase', (str: string) =>
str.replace(/(^\w|-\w)/g, (m) => m.replace('-', '').toUpperCase())
);
Handlebars.registerHelper('camelCase', (str: string) => {
const pascal = str.replace(/(^\w|-\w)/g, (m) =>
m.replace('-', '').toUpperCase()
);
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
});
Handlebars.registerHelper('kebabCase', (str: string) =>
str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
);
Handlebars.registerHelper('ifEquals', function (
this: unknown,
arg1: unknown,
arg2: unknown,
options: Handlebars.HelperOptions
) {
return arg1 === arg2 ? options.fn(this) : options.inverse(this);
});
// ============================================
// メイン生成関数
// ============================================
async function generateComponent(
config: ComponentConfig
): Promise<GeneratedFile[]> {
const templateDir = resolve(__dirname, '../_templates/component');
const outputDir = resolve(
__dirname,
'../src/components',
toKebabCase(config.name)
);
// 出力ディレクトリの作成
await mkdir(outputDir, { recursive: true });
const files: GeneratedFile[] = [];
const context = buildContext(config);
// メインコンポーネントの生成(常に必要)
files.push(
await renderTemplate(
join(templateDir, 'index.tsx.hbs'),
join(outputDir, `${toKebabCase(config.name)}.tsx`),
context
)
);
// 条件付きファイルの生成
if (config.withTests) {
files.push(
await renderTemplate(
join(templateDir, 'index.test.tsx.hbs'),
join(outputDir, `${toKebabCase(config.name)}.test.tsx`),
context
)
);
}
if (config.withStorybook) {
files.push(
await renderTemplate(
join(templateDir, 'index.stories.tsx.hbs'),
join(outputDir, `${toKebabCase(config.name)}.stories.tsx`),
context
)
);
}
// バレルファイルの生成
files.push(
await generateBarrelFile(outputDir, config.name)
);
return files;
}
function buildContext(config: ComponentConfig): Record<string, unknown> {
return {
name: config.name,
pascalName: toPascalCase(config.name),
kebabName: toKebabCase(config.name),
camelName: toCamelCase(config.name),
props: config.props,
hooks: config.hooks,
hasProps: config.props.length > 0,
requiredProps: config.props.filter((p) => p.required),
optionalProps: config.props.filter((p) => !p.required),
timestamp: new Date().toISOString().split('T')[0],
withStyles: config.withStyles,
};
}
async function renderTemplate(
templatePath: string,
outputPath: string,
context: Record<string, unknown>
): Promise<GeneratedFile> {
const source = await readFile(templatePath, 'utf-8');
const template = Handlebars.compile(source);
const content = template(context);
await writeFile(outputPath, content, 'utf-8');
return { path: outputPath, content };
}
async function generateBarrelFile(
dir: string,
name: string
): Promise<GeneratedFile> {
const kebab = toKebabCase(name);
const pascal = toPascalCase(name);
const content = `export { ${pascal} } from './${kebab}';\nexport type { ${pascal}Props } from './${kebab}';\n`;
const filePath = join(dir, 'index.ts');
await writeFile(filePath, content, 'utf-8');
return { path: filePath, content };
}
// ユーティリティ関数
function toPascalCase(str: string): string {
return str.replace(/(^\w|-\w)/g, (m) => m.replace('-', '').toUpperCase());
}
function toKebabCase(str: string): string {
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
}
function toCamelCase(str: string): string {
const p = toPascalCase(str);
return p.charAt(0).toLowerCase() + p.slice(1);
}
// 実行
// 期待出力:
// ✅ Generated: src/components/user-profile/user-profile.tsx
// ✅ Generated: src/components/user-profile/user-profile.test.tsx
// ✅ Generated: src/components/user-profile/index.ts
カスタムルールファイルによる生成品質の制御
Antigravity の .antigravity/rules/ ディレクトリにカスタムルールを配置することで、AI エージェントがコード生成時に参照するガイドラインを細かく制御できます。
<!-- .antigravity/rules/code-generation.md -->
# コード生成ルール
## コンポーネント生成
- 全てのコンポーネントは `React.FC<Props>` 型を使用する
- Props インターフェースは必ずエクスポートする
- デフォルトエクスポートは使用しない(名前付きエクスポートのみ)
- CSS Modules を使用する場合、クラス名は BEM 記法に従う
## テスト生成
- テストフレームワークは Vitest を使用する
- `describe` > `it` の階層構造にする
- テストケース名は日本語で記述する(チームの合意)
- `@testing-library/react` の `render` と `screen` を使用する
## API ルート生成
- Zod スキーマによるバリデーションを必須とする
- エラーレスポンスは RFC 7807 準拠の形式にする
- レート制限のミドルウェアを必ず含める
このルールファイルの存在により、Antigravity のエージェントは以下のような振る舞いをします。
ユーザー: 「UserSettings コンポーネントを作って」
Antigravity エージェントの思考プロセス:
1. code-generation.md ルールを読み込む
2. _templates/component/ のテンプレートを確認
3. 既存コンポーネントのパターンを分析
4. ルールに従って React.FC<Props> 型 + 名前付きエクスポートで生成
5. Vitest のテストファイルも同時に生成
テンプレートのバージョン管理と進化戦略
テンプレートは一度作ったら終わりではありません。プロジェクトの成長に合わせて進化させる仕組みが重要です。
テンプレートのバリデーション
テンプレートが壊れていないことを CI で検証するスクリプトを用意しましょう。
// scripts/validate-templates.ts
import { readdir, readFile } from 'fs/promises';
import { join } from 'path';
import Handlebars from 'handlebars';
interface ValidationResult {
template: string;
valid: boolean;
error?: string;
}
async function validateAllTemplates(): Promise<ValidationResult[]> {
const templateRoot = join(__dirname, '../_templates');
const categories = await readdir(templateRoot);
const results: ValidationResult[] = [];
for (const category of categories) {
const categoryDir = join(templateRoot, category);
const files = await readdir(categoryDir);
for (const file of files) {
if (!file.endsWith('.hbs')) continue;
const filePath = join(categoryDir, file);
const source = await readFile(filePath, 'utf-8');
try {
// テンプレートのコンパイルテスト
const template = Handlebars.compile(source, { strict: true });
// ダミーデータでレンダリングテスト
const dummyContext = createDummyContext(category);
template(dummyContext);
results.push({ template: `${category}/${file}`, valid: true });
} catch (err) {
results.push({
template: `${category}/${file}`,
valid: false,
error: err instanceof Error ? err.message : String(err),
});
}
}
}
return results;
}
function createDummyContext(category: string): Record<string, unknown> {
const base = {
name: 'TestComponent',
pascalName: 'TestComponent',
kebabName: 'test-component',
camelName: 'testComponent',
timestamp: '2026-03-30',
};
switch (category) {
case 'component':
return {
...base,
props: [{ name: 'title', type: 'string', required: true, description: 'タイトル' }],
hooks: ['useState', 'useEffect'],
hasProps: true,
requiredProps: [{ name: 'title', type: 'string', required: true }],
optionalProps: [],
withStyles: true,
};
case 'api':
return {
...base,
methods: ['GET', 'POST'],
withAuth: true,
resource: 'users',
};
default:
return base;
}
}
// 実行
validateAllTemplates().then((results) => {
const failed = results.filter((r) => !r.valid);
if (failed.length > 0) {
console.error('❌ テンプレートバリデーション失敗:');
failed.forEach((r) => console.error(` ${r.template}: ${r.error}`));
process.exit(1);
}
console.log(`✅ 全 ${results.length} テンプレートが正常`);
});
// 期待出力:
// ✅ 全 6 テンプレートが正常
テンプレート変更の影響分析
テンプレートを変更する前に、既存の生成済みコードへの影響を分析するスクリプトも有用です。
// scripts/analyze-template-impact.ts
import { readdir, readFile } from 'fs/promises';
import { join, relative } from 'path';
async function analyzeImpact(templateName: string): Promise<void> {
// 生成済みファイルのヘッダーコメントから
// テンプレートバージョンを追跡
const srcDir = join(__dirname, '../src');
const generatedFiles = await findGeneratedFiles(srcDir);
console.log(`テンプレート "${templateName}" から生成されたファイル:`);
console.log(`合計: ${generatedFiles.length} ファイル`);
for (const file of generatedFiles) {
const relPath = relative(process.cwd(), file.path);
console.log(` ${relPath} (v${file.templateVersion})`);
}
}
interface GeneratedFileInfo {
path: string;
templateVersion: string;
generatedAt: string;
}
async function findGeneratedFiles(dir: string): Promise<GeneratedFileInfo[]> {
// 生成済みファイルには以下のヘッダーを埋め込む設計:
// // @generated by template v1.2.0 at 2026-03-30
const results: GeneratedFileInfo[] = [];
const entries = await readdir(dir, { withFileTypes: true, recursive: true });
for (const entry of entries) {
if (!entry.isFile() || !entry.name.endsWith('.tsx')) continue;
const filePath = join(dir, entry.name);
const content = await readFile(filePath, 'utf-8');
const match = content.match(
/@generated by template v([\d.]+) at ([\d-]+)/
);
if (match) {
results.push({
path: filePath,
templateVersion: match[1],
generatedAt: match[2],
});
}
}
return results;
}
AI エージェントによる動的テンプレート拡張
静的テンプレートだけでは対応しきれないケースがあります。例えば、外部 API の型定義を取得してインターフェースを自動生成するような場面です。ここで Antigravity の AI エージェントが活躍します。
パターン1: スキーマ駆動コード生成
// scripts/schema-driven-generate.ts
import { readFile, writeFile } from 'fs/promises';
import { join } from 'path';
/**
* OpenAPI スキーマからAPIクライアントを生成
* Antigravity エージェントがスキーマを解析し、
* 型安全なクライアントコードを生成する
*/
async function generateFromSchema(
schemaPath: string
): Promise<void> {
const schema = JSON.parse(
await readFile(schemaPath, 'utf-8')
);
// エンドポイントごとにクライアント関数を生成
for (const [path, methods] of Object.entries(schema.paths)) {
const endpoint = methods as Record<string, unknown>;
for (const [method, config] of Object.entries(endpoint)) {
const operationId = (config as { operationId?: string }).operationId;
if (!operationId) continue;
// AI エージェントへの指示テンプレート
// AGENTS.md のルールに従って型定義を生成
const typeDefinition = generateTypeFromSchema(
config as Record<string, unknown>
);
console.log(
`生成: ${method.toUpperCase()} ${path} -> ${operationId}`
);
}
}
}
function generateTypeFromSchema(
config: Record<string, unknown>
): string {
// この部分を AI エージェントが動的に補完する
// テンプレートは骨格のみを提供
return '// AI エージェントが型定義を生成';
}
パターン2: 既存コードのパターン学習
Antigravity のエージェントは既存のコードベースを分析して、プロジェクト固有のパターンを学習できます。この能力を活用して、テンプレートを動的に改善する仕組みを構築できます。
<!-- Antigravity への指示例 -->
プロジェクト内の src/components/ を分析して、
以下のパターンを抽出してください:
1. 最も頻繁に使われている React hook の組み合わせ
2. Props の命名パターン(on* イベントハンドラ等)
3. エラーハンドリングのパターン
4. コメント / JSDoc の書き方
抽出結果を _templates/component/patterns.json に出力してください。
このアプローチにより、テンプレートが「生きたドキュメント」として、プロジェクトの実態に追従し続けることができます。
テスト戦略 — ジェネレーター自体のテスト
コードジェネレーターは他のコードを生成するツールです。そのため、「生成されたコードが正しいか」を検証するテスト戦略が不可欠です。
// scripts/__tests__/generate.test.ts
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdir, rm, readFile } from 'fs/promises';
import { join } from 'path';
import { generateComponent } from '../generate';
const TEST_OUTPUT_DIR = join(__dirname, '../../.test-output');
describe('コンポーネントジェネレーター', () => {
beforeEach(async () => {
await mkdir(TEST_OUTPUT_DIR, { recursive: true });
});
afterEach(async () => {
await rm(TEST_OUTPUT_DIR, { recursive: true, force: true });
});
it('基本的なコンポーネントファイルが生成される', async () => {
const files = await generateComponent({
name: 'TestButton',
props: [
{
name: 'label',
type: 'string',
required: true,
description: 'ボタンのラベル',
},
],
hooks: [],
withTests: true,
withStorybook: false,
withStyles: false,
});
// 生成されたファイル数の検証
expect(files).toHaveLength(3); // component + test + barrel
// メインコンポーネントの内容検証
const mainFile = files.find((f) =>
f.path.endsWith('test-button.tsx')
);
expect(mainFile).toBeDefined();
expect(mainFile?.content).toContain('interface TestButtonProps');
expect(mainFile?.content).toContain('export const TestButton');
});
it('Props の型定義が正しく生成される', async () => {
const files = await generateComponent({
name: 'DataTable',
props: [
{
name: 'data',
type: 'Record<string, unknown>[]',
required: true,
description: 'テーブルデータ',
},
{
name: 'onSort',
type: '(column: string) => void',
required: false,
description: 'ソートハンドラ',
},
],
hooks: ['useState', 'useMemo'],
withTests: false,
withStorybook: false,
withStyles: true,
});
const mainFile = files.find((f) =>
f.path.endsWith('data-table.tsx')
);
expect(mainFile?.content).toContain('data: Record<string, unknown>[]');
expect(mainFile?.content).toContain('onSort?: (column: string) => void');
});
it('TypeScript としてコンパイル可能なコードが生成される', async () => {
// 生成されたコードを tsc でコンパイルチェック
// CI 環境での回帰テストとして重要
const files = await generateComponent({
name: 'ValidComponent',
props: [],
hooks: [],
withTests: false,
withStorybook: false,
withStyles: false,
});
// tsc --noEmit で型チェックのみ実行
// 実際の CI では exec('npx tsc --noEmit') を使用
expect(files.length).toBeGreaterThan(0);
});
});
// 期待出力:
// ✓ 基本的なコンポーネントファイルが生成される (12ms)
// ✓ Props の型定義が正しく生成される (8ms)
// ✓ TypeScript としてコンパイル可能なコードが生成される (5ms)
CI/CD パイプラインへの統合
ジェネレーターを CI/CD パイプラインに統合することで、テンプレートの品質を継続的に保証できます。
# .github/workflows/template-ci.yml
name: Template CI
on:
push:
paths:
- '_templates/**'
- 'scripts/generate.ts'
- 'scripts/templates.ts'
jobs:
validate-templates:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- name: テンプレートのバリデーション
run: npx tsx scripts/validate-templates.ts
- name: ジェネレーターのテスト
run: npx vitest run scripts/__tests__/
- name: 生成コードの型チェック
run: |
npx tsx scripts/generate.ts --name=CITestComponent --category=component --withTests
npx tsc --noEmit
rm -rf src/components/ci-test-component
個人開発者の視点から(実体験メモ)
まとめ
まずは小さなテンプレート(例えばコンポーネント1種類)から始めて、AGENTS.md と .antigravity/rules/ にガイドラインを追記することから取り組んでみてください。AGENTS.md の設計方法については「Antigravity コンテキスト設計完全ガイド」で詳しく解説しています。カスタムルールの活用法は「Antigravity カスタムルール&プロジェクト設定 完全実践ガイド」も参考にしてください。また、CLI ツールとしてジェネレーターを配布したい場合は「Antigravity で本格 CLI ツールを開発する完全ガイド」が役に立ちます。テンプレートが増えてきたらバリデーションスクリプトと CI を導入し、プロジェクトの成長に合わせてジェネレーターも進化させていく運用が理想的です。
コード生成パイプラインの設計