AIエージェントが生み出すありがちなボイラープレートを削ぎ落とす作業環境設定
AIエージェントを実際の開発環境に導入してみると、すぐに底が見えてしまいます。ドメインの文脈を理解しないまま機械的にファクトリーパターンを乱用したり、全く役に立たないユーティリティ関数ばかりを無限に吐き出したりしがちです。GitClearが2024年に発表した6億2,300万行のコミット分析レポートによると、AIツールの普及後、コードの重複は81%増加し、既存コードを整理するリファクタリングの割合は25%から10%未満へと急落しました。コンテキストウィンドウの最新性バイアス(Recency Bias)により、エージェントはリスクを避けるため、学習データ内で最も一般的な標準パターンへと逃避し続けます。チームで共有するアーキテクチャルールや同期ロジックをコンテキストファイルとして明示し、エージェントの誤作動を抑え込む必要があります。
1. レガシーコードベースにおけるルール逸脱の防止
開発ツールごとにバラバラな指示を統合・管理するには、プロジェクトのルートディレクトリに「唯一の真実のソース(Single Source of Truth)」として機能する AGENTS.md を配置することから始めます。CursorやClaude Codeなどのツールはこのルールを読み込みます。単一の .cursorrules ファイルはすでに遥か昔に廃止されました。.cursor/rules/ ディ렉トリ内にMDCファイルとして分割して配置し、Claude Code環境では CLAUDE.md 内で @AGENTS.md を読み込むことで、ルールが形骸化・漏れ出る現象を防ぎます。
`yaml
description: Backend API and Domain Service architectural constraints
globs: apps/api//*.ts, services//*.ts
alwaysApply: false
Backend Architectural Constraints
Mandatory Patterns
- Use Canonical Response DTOs located in
apps/api/src/common/dto.
- All database mutations must go through the Unit of Work pattern defined in
services/shared/uow.
Explicit Negative Constraints
- NEVER create single-child base classes or single-implementation interfaces.
- NEVER write wrapper functions that merely pass arguments to lower-level services.
- NEVER implement strategy or factory patterns when standard conditional logic (if/switch) suffices.
- NEVER add defensive "just-in-case" try-catch blocks that return dummy fallback values without rethrowing.
`
コンテキストの総量が800行、約2,000トークンを超えると、エージェントは上部に記載された制約条件を無視し始めます。AGENTS.md には絶対に譲れない禁止事項だけを残し、詳細なアーキテクチャ決定記録(ADR)は docs/context/ ディ렉トリへ切り出すべきです。
制約条件の適用手順
- 作業内容: 核心となるモジュールを1つ選び、禁止パターンと例外ルールを指定します。
- 実行方法:
- プロジェクトルートに
AGENTS.md を作成し、単一実装インターフェースの禁止や防御的な try-catch の禁止条件を記述します。
.cursor/rules/backend-constraints.mdc に対象ファイルパス(globs)と YAML Frontmatter の制約を設定します。
CLAUDE.md に @AGENTS.md 構文を追加し、環境を合わせます。
- 期待される結果: 不要なボイラープレートの生成が減り、コード修正に費やしていた時間が週あたり約4時間削減されます。
2. エージェントコードの検証パイプライン
一見すると問題なさそうに見えるエージェントのコードの裏には、罠が隠れています。Veracodeの2024年の研究結果によると、AIが提案したコードの45%で OWASP Top 10 レベルのセキュリティ脆弱性が発見されました。失敗しそうなロジックを try-catch で安易に囲み、空のオブジェクトや null を返してエラーを隠蔽する行為は、人間が執筆するときよりも47%も頻繁に発生しています。Read-Modify-Write 操作における楽観的ロックの考慮漏れや、ループ内でのDB呼び出しによる N+1 問題も頻繁に発生します。
| 検証領域 |
詳細検証項目 |
リスクパターンおよびエージェントの罠 |
マージ遮断基準 |
| セキュリティ脆弱性 |
Parameterized Query(プレースホルダ)の使用有無、テナント分離、未承認パッケージの確認 |
文字列結合による SQL インジェクション、ハルシネーションによって生成された未検証パッケージの呼び出し |
入力値検証の漏れ、出所不明な外部依存関係の追加時に遮断 |
| パフォーマンスボトルネック |
ORMの遅延読み込み(Lazy Loading)、Hot path 内の多重ループ、DBインデックス |
ループ内での個別にエンティティを巡回するクエリ、メモリ上でのテーブル全体のフィルタリング |
ループ内にDBおよび外部API呼び出しが存在する場合に遮断、ページネーション未適用時に遮断 |
| 型安全性 |
Strict Type 検証、Boundary(境界)例外処理、排他・コンカレンシー制御 |
as any の乱用、Empty Catch ブロックによるエラー隠蔽 |
any および無分別な as キャストが存在する場合に遮断、ロギングのない Catch ブロックを遮断 |
エージェントが生成するテストコードは、実装コードをそのままコピー&ペーストしてなぞるだけの「同義語反復(トートロジー)テスト」になりがちです。このようなテストは、実際のビジネスロジックの欠陥を全く検知できません。「すでに存在するユーティリティがある場合、エージェントが新規作成したユーティリティは削除し、既存コードを再利用する」というルールを強力に適用する必要があります。
手動検証手順
- 作業内容: セキュリティ、パフォーマンス、型を検証するチェックリストを作業テンプレートと CI ゲートに組み込みます。
- 実行方法:
- PR テンプレートに Parameterized Query、N+1 クエリ防止、
any 禁止項目を追加します。
- Git Pre-commit Hook に静的解析ツールを設定し、
as any やループ内の await を検知した場合はコミットを弾くようにします。
- レビュー時、エージェントが勝手に新規作成したユーティリティを既存の共通モジュールへ強制的に置き換えます。
- 期待される結果: N+1 クエリやメモリーリークといった欠陥が本番環境へ流出する事故を防ぎます。
3. 思考停止を防ぐ分解プロンプティング
複雑な決済・精算ロジックやステートマシンベースの注文処理を単一のプロンプトで流し込むと、エージェントはツール呼び出しのループに陥るか、形だけのスカスカなコードしか吐き出さなくなります。スキーマ設計、API インターフェース、例外処理、ビジネスルールを一度に処理しようとして、トークンの割り当て順序が混乱するためです。
`
[ステップ1: データモデリング] -> DBエンティティ、Zodスキーマ生成
│
▼ (出力結果をコンテキストとして伝達)
[ステップ2: インターフェース定義] -> API DTO、Custom Error、サービスシグネチャ定義
│
▼ (ステップ1+2の出力結果をコンテキストとして伝達)
[ステップ3: ビジネスロジック実装] -> トランザクション、状態遷移、コンカレンシー制御の完成
`
このプロセスを手動でやり取りするのは非常に手間がかかります。CLI 自動化スクリプト(scripts/agent-decomposed-build.ts)を作成し、前のステップの出力結果が次のステップの入力コンテキストとして入るように連携させておくのが賢明です。
`typescript
import { execSync } from 'child_process';
import * as fs from 'fs';
interface TaskPipeline {
featureName: string;
stage1Prompt: string;
stage2Prompt: string;
stage3Prompt: string;
}
async function runDecomposedAgentPipeline(pipeline: TaskPipeline) {
console.log([Stage 1] Executing Data Modeling for ${pipeline.featureName}...);
const stage1Output = execSync(claude --print "${pipeline.stage1Prompt}").toString();
fs.writeFileSync(./tmp/${pipeline.featureName}_stage1.ts, stage1Output);
console.log([Stage 2] Executing Interface Definition...);
const stage2InputPrompt = ${pipeline.stage2Prompt}\n\nContext Models:\n${stage1Output};
const stage2Output = execSync(claude --print "${stage2InputPrompt}").toString();
fs.writeFileSync(./tmp/${pipeline.featureName}_stage2.ts, stage2Output);
console.log([Stage 3] Executing Business Logic Implementation...);
const stage3InputPrompt = ${pipeline.stage3Prompt}\n\nContext Models:\n${stage1Output}\n\nContext Contracts:\n${stage2Output};
const stage3Output = execSync(claude --print "${stage3InputPrompt}").toString();
fs.writeFileSync(./src/services/${pipeline.featureName}.service.ts, stage3Output);
console.log([Pipeline Complete] Business logic generated cleanly without cognitive stagnation.);
}
`
分解プロンプティングスクリプトの構築
- 作業内容: 3段階のプロンプトテンプレートを確立し、それを順次実行する CLI スクリプトを導入します。
- 実行方法:
- データモデリング、インターフェース定義、ビジネスロジック実装の3つにプロンプトを分割します。
scripts/agent-decomposed-build.ts を作成し、前の出力が次のプロンプトのコンテキストに入るよう接続します。
- 複雑なモジュールを新規作成する際、このスクリプトを実行します。
- 期待される結果: エージェントが回答を出せずにフリーズ(思考停止)する現象がなくなり、手動でコードを作り直す手戻り率が20%台から5%未満へと低下します。
4. 決定根拠をコード内に残す設定
AIコーディングツールを使えば使うほど、「なぜこのように実装したのか」という理由が抜け落ちた「書き込み専用コード」が蓄積していきます。アーキテクチャ選択におけるトレードオフが記録されていないコードは、後から人間が手を加える際に大きな負担となります。エージェントがコードを生成する際、TSDoc 標準コメントをソースコード内に記述するよう AGENTS.md で強制する必要があります。
`typescript
/**
- @description Processes deferred settlement payouts for multi-vendor orders.
- @why Uses pessimistic database locking on the Wallet entity instead of optimistic locking because payout calculation involves high-frequency concurrent balance updates.
- @tradeoff Slight P95 latency increase under high contention in exchange for 0% financial drift.
- @complexity Time: O(N log N) due to vendor sorting | Space: O(N) for batch processing buffer.
*/
export async function processDeferredSettlement(orderId: string): Promise {
if (account.hasOutstandingBalance()) {
this.applySettlementHold(account);
}
}
`
開発者ごとにエージェントの設定がズレるのを防ぐには、AGENTS.md を唯一の真実のソース(SSOT)として配置し、コミット時に CLAUDE.md や .cursor/rules/global.mdc へ同期するスクリプト(tools/sync-agent-rules.ts)を運用します。
`typescript
import * as fs from 'fs';
import * as path from 'path';
const AGENTS_MD_PATH = path.join(__dirname, '../AGENTS.md');
const CLAUDE_MD_PATH = path.join(__dirname, '../CLAUDE.md');
const CURSOR_RULE_PATH = path.join(__dirname, '../.cursor/rules/global.mdc');
function syncRules() {
if (!fs.existsSync(AGENTS_MD_PATH)) {
console.error('Error: AGENTS.md does not exist.');
process.exit(1);
}
const baseRules = fs.readFileSync(AGENTS_MD_PATH, 'utf-8');
const claudeContent = # AUTOMATICALLY GENERATED FROM AGENTS.md - DO NOT EDIT DIRECTLY\n\n${baseRules};
fs.writeFileSync(CLAUDE_MD_PATH, claudeContent);
const mdcHeader = ---\ndescription: Global Agent Rule Sync\nglobs: **/*\nalwaysApply: true\n---\n\n;
fs.writeFileSync(CURSOR_RULE_PATH, ${mdcHeader}${baseRules});
console.log('Successfully synchronized AGENTS.md to CLAUDE.md and Cursor MDC rules.');
}
syncRules();
`