【TypeScript / Node.js】LLMを使った自動コード修復エンジン開発でハマった3つの罠と解決策(JSONパース・ESM・パス解決)
1. はじめに
本記事では、Gemini API と Java/Maven 環境を連携させて、ビルドエラーやテスト失敗を自動検出・修復する「自動コード修復エンジン」を構築した際の実践ノウハウを共有します。
単に API を呼び出すだけでなく、「AIの返答の揺らぎ」や「Node.js (ESM) × TypeScript 実行環境の癖」にどう立ち向かったか に焦点を当てています。
2. 罠1:AIの返答(JSON)が崩れて JSON.parse が落ちる問題
課題
Gemini API に「修正コードを JSON 形式で返して」と指示しても、以下のようにマークダウンの装飾(json ... )が入ったり、前後に解説文が混ざったりして JSON.parse() が失敗(クラッシュ)する問題が発生しました。
解決策:3段階ガードの safeParseJson ユーティリティの実装
単なる JSON.parse を廃止し、正規表現とフォールバックを組み合わせた独立モジュール src/utils/jsonParser.ts を作成しました。
/**
* AIからのレスポンス文字列から安全にJSONを抽出しパースする
*/
export function safeParseJson<T>(rawText: string): T | null {
if (!rawText || typeof rawText !== 'string') return null;
// 第1ガード:そのままパース
try { return JSON.parse(rawText) as T; } catch {}
// 第2ガード:マークダウンのコードブロック (```json ... ```) を除去
let cleaned = rawText.replace(/```(?:json)?\s*([\s\S]*?)\s*```/gi, '$1').trim();
try { return JSON.parse(cleaned) as T; } catch {}
// 第3ガード:テキスト内から最初と最後の JSON 構造 ([...] や {...}) のみを抽出
const jsonMatch = cleaned.match(/(\[[\s\S]*\]|\{[\s\S]*\})/);
if (jsonMatch) {
try { return JSON.parse(jsonMatch[0]) as T; } catch {}
}
return null;
}
効果: これにより、AIのレスポンスの揺らぎによるエンジンのクラッシュ率を 0% にすることができました。
3. 罠2:Node.js ESM 環境での ts-node と TypeScript 構文エラー
課題
"type": "module" の環境下で npx ts-node を実行した際、以下のエラーに遭遇しました。
相対パスの import で .js 拡張子の解釈が合わずモジュールが見つからない (ERR_MODULE_NOT_FOUND)
constructor(private config: Config) のような TypeScript の Parameter Properties 構文が Node.js の Type Stripping モードで弾かれる (ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX)
解決策:tsx への移行
ts-node の設定に複雑なフラグを追加する代わりに、近年の TypeScript 実行環境の標準である tsx を導入しました。
# ts-node の代わりに tsx を使用
npx tsx src/test-auto-fix.ts
4. 罠3:実行ディレクトリ(process.cwd())依存によるパス解決エラー
課題
path.resolve(process.cwd(), '../../../sample-java-app') のように process.cwd()(コマンドを叩いた場所)を基準にターゲットプロジェクトのパスを指定していたため、実行場所(src/ 直下かプロジェクトルートか)によってパスが壊れて対象ファイルが見つからなくなる問題が起きました。
解決策:import.meta.url による絶対位置からの解決
実行時のディレクトリに一切依存せず、「ファイル自身の絶対パス」 を起点にしてパスを解決するように修正しました。
import { fileURLToPath } from 'url';
import path from 'path';
// ファイル自身の位置 (src/test-auto-fix.ts) を基準にする
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// どこからコマンドを実行しても、常に正しい相対位置のプロジェクトを指す
const targetDir = path.resolve(__dirname, '../../../sample-java-app');
5. 動作検証と結果
意図的に sample-java-app のロジックにバグを混入させて実行テストを行った結果、以下の自律ループが完璧に機能することを確認できました!
Maven テストの失敗検知 ❌
Gemini API 呼び出し & safeParseJson による抽出 💡
Calculator.java の自動書き換え 📝
再テスト実行 & オールグリーン確認 ✅
6. おわりに
AI を使ったシステム構築では、「AIの返答の不確定さ」 と 「実行環境(TS/ESM)の安定性」 の双方に対するガードが不可欠です。
今回作成した safeParseJson や import.meta.url ベースのパス解決は、他の AI 連携ツール開発でもそのまま汎用的に使えるパターンですので、ぜひ参考にしてみてください!