効率の良い AI駆動開発について考える
2025.11.09

どもです。
最近AIに頼りすぎて、思考能力が低下しているのではないかと悩んでいる今日このごろなのですが、皆さん如何お過ごしでしょうか。
とはいえ、AI駆動開発が当たり前となっている昨今切っても切れない存在となっておりますね。
今日は、既存プロダクトへの導入や、AI開発の進め方や自分自身がソースについて理解するためのAI活用などをと。
既存プロダクトへの導入
AI駆動開発というと何かと「0 → 1」の例でバイブコーディングの方法だったりが多いのですが、既存プロダクトへの導入や「99 → 100」へのブラッシュアップの方法など自分ながらの手順を紹介できればと思っております。
まず、既存プロダクトに導入するのですが、専ら今はAIエージェント戦国時代。
Claude codeが良いだの、cusorが良いだの、Github copilotが良いだの、devinが良いだのと、AIエージェントさんも天下を取ろうと活動が活発な状況で、どれが最適で優秀なのかは日々日々変化する状況です。
そんな状況の中で「Github copilotの最適なセッティング」など個別エージェントのconfig設定なんてしていると間に合いません。色んなAIエージェントを使うことになった場合 AIエージェント用のドキュメントも乱立してしまいます。
そのために、AIエージェント共通のドキュメントを用意するのですが、
AI用のディレクトリがあるだけでもAIコーディングエージェントの精度が飛躍的に向上するようです。
また、同じスレッドで長いやり取りを行っているとトークンの上限に達してしまい、新しくスレッドを作成してやり取りを行うことになるのですが、その際メモリも解放され最初からやり取りしないといけなくなったりする羽目にならないように、タスク進捗などもファイルに落として置くことが良いでしょう。
中身は
- 要件定義/仕様書
- プロジェクト構造/データ構造
- 開発進捗用タスク
- 技術スタック
- 最新のドキュメント情報 etc…
と言った感じで、コンテキストエンジニアリングにおける「Write(記述/外部メモの利用)」に相当するものらしく、Spec駆動開発でも採用されているみたいです。
こういう形で、AI リーダブルなドキュメントを置くことによって、バイブコーディングの精度も向上が見込めるようです。
最近は、最低限こちらのドキュメントを作成するようにしております。
ai_docs ├── architecture.md ├── data-struture.md ├── directory-struture.md ├── implementation-tasks.md ├── requirement.md └── tech-stack.md
docsの名前が良いというのも見かけるのですが、Github pagesのデフォルトがdocsディレクトリだったりして使用済みの場合も多々ありますので、当方はai_docsと命名してファイルを作成しております。
作成の際も、AIエージェントに任せる形です。「ソースを解析して上記の形でファイルを作成して」とお願いした結果。
https://github.com/retrodig/damdara/tree/main/ai_docs
こちらのレポジトリ見覚えある方もいらっしゃるのではないでしょうか。
そう。以前 Rustでつくる ふっかつのじゅもん の記事で作成したレポジトリで、途中で飽きてしまって(いけないいけない)しばらく放置していたのですが、久々手を付けようと思ったのですが、進捗がどこまでだったのか(分かってはいましたが)自分自身のためにも一度まとめたいなとAIエージェント用にドキュメントを生成しました。
この次のステップとして「Webでも扱える様に、WASM化」する予定だったのですが、これもどこまで行っていたかうる覚えだったので、Claude codeの方に「まずは、implementation-tasksの方に書き出してみて」とお願いしました。
そう、前述でもあったようにAIエージェントに作業させる場合、トークン上限でやり取りが最初からってことも多々ありますので、作業させる前は最低限「task」のドキュメントを生成して進捗も管理するのを心掛けていたりします。
今回「WASM化」を進めるに当たって作成してもらったドキュメントが良い感じにまとまっていたので順番に見ていきたいと思います。
生成(追加)されたタスクドキュメント
https://github.com/retrodig/damdara/blob/main/ai_docs/implementation-tasks.md#architecture-changes
Architecture Changes項目以下が該当する箇所になるのですが、まず 1. WASM Module Structure を見てみると、
// src/wasm.rs (NEW)と
との様に新規でファイル作成する流れになっています。 アトリビュートでwasm_bindgenを指定しWASMで使用できるようにしております。
続けて 2. Web-Specific I/O Implementations では、WebInput (NEW): を新規作成する流れですね。
// src/input/web_input.rs (NEW)
pub struct WebInput {
pending_actions: VecDeque<PlayerAction>,
}
impl WebInput {
pub fn queue_action(&mut self, action: PlayerAction) {
self.pending_actions.push_back(action);
}
}
impl PlayerInput for WebInput {
fn get_player_action(&mut self, _: &mut dyn FnMut()) -> PlayerAction {
self.pending_actions.pop_front()
.unwrap_or(PlayerAction::Attack)
}
}
InputもWasm用で用意していたつもりがしていなかったのが把握できました。
Outputに関しては、CLIでは即時Printlnで出力しているところをWasmではバッファとして保持し任意のタイミングで出力します。
// src/output/buffer_output.rs (EXISTING - VERIFIED READY)
pub struct BufferOutput {
buffer: Vec<String>,
}
// ✅ Already implements MessageOutput trait
// ✅ Already captures messages for later retrieval
self.messages.display();
self.messages.clear();
実行に関しては共通の処理で、traitによって処理が分岐されます。
CLI
impl MessageOutput for CliOutput {
fn output(&mut self, message: &str) {
println!("{}", message);
}
}
Web
impl MessageOutput for BufferOutput {
fn output(&mut self, message: &str) {
self.buffer.push(message.to_string());
}
}
traitの力が十分に発揮するところ。
続けて、
TypeScript Definitions (generated by wasm-bindgen):
は、TypeScriptの定義ファイル作成の流れですね。
// pkg/damdara.d.ts (GENERATED)
export class WasmGame {
constructor();
// Player management
create_player(name: string): PlayerState;
generate_password(): string;
load_from_password(password: string): PlayerState;
....
ここからは実際にタスクとして分割されています。
Estimated Effort: 1 day Priority: Critical (Blocker for others)
Cargo.tomlに必要なクレートを追加していく流れです。
[dependencies]
wasm-bindgen = "0.2"
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.6"
[dev-dependencies]
wasm-bindgen-test = "0.3"
[lib]
crate-type = ["cdylib", "rlib"] # Add cdylib for WASM
更にWASM targetとして、wasm32-unknown-unknownを追加。
[build] target = "wasm32-unknown-unknown"
wasm-packもインストール
cargo install wasm-pack
npm packageも追加となります。続いて、タスク6.2になります。
Estimated Effort: 3 days Priority: High Dependencies: Task 6.1
ありがたいことに推定所要時間も出してくれています。
こちらのタスクでは主に、Web側はWasmGameのstructを設け、メソッドを追加しそちらを操作していく流れになりそうですね。
#[wasm_bindgen]
pub struct WasmGame {
player: Option<Player>,
output_buffer: BufferOutput, // ✅ Already exists!
}
#[wasm_bindgen]
impl WasmGame {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self {
player: None,
output_buffer: BufferOutput::new(),
}
}
pub fn create_player(&mut self, name: &str) -> Result<JsValue, JsValue> {
let player = Player::new(name);
let state = serde_wasm_bindgen::to_value(&player.summary())
.map_err(|e| JsValue::from_str(&e.to_string()))?;
self.player = Some(player);
Ok(state)
}
}
それにしてもこれに3日工数見積はどうなのでしょう。。工数については最後考えていきましょう。
続いて、
Estimated Effort: 2 days Priority: High Dependencies: Task 6.2
ですが、ウェブ用の入力受付のweb_input.rsもwasmで扱えるように調整する流れですね。
#[cfg(target_arch = "wasm32")] pub mod web_input;
pub struct WebInput {
action_queue: VecDeque<PlayerAction>,
number_queue: VecDeque<usize>,
}
続けて
Estimated Effort: 4 days Priority: High Dependencies: Task 6.3
こちらでは戦闘の処理が行える様にWasmGameのstructに諸々を紐づけていきます。
// Battle requires lifetime for input/output references
// Solution: Use 'static lifetime with heap allocation
pub struct WasmGame {
player: Option<Player>,
monster: Option<Monster>,
web_input: Box<WebInput>, // Heap-allocated for stable address
buffer_output: Box<BufferOutput>,
battle: Option<Battle<'static>>, // 'static lifetime
}
続けて
Estimated Effort: 2 days Priority: Medium Dependencies: Task 6.2
こちらのタスクでは各々のマスターデータを返却する処理を追加する流れですね。
こちらはモンスター一覧。
#[wasm_bindgen]
pub fn get_monster_list() -> JsValue {
serde_wasm_bindgen::to_value(&MONSTER_MASTER).unwrap()
}
#[wasm_bindgen]
pub fn get_monster(id: u8) -> JsValue {
serde_wasm_bindgen::to_value(&MONSTER_MASTER[id as usize]).unwrap()
}
その他にも、アイテム、呪文、ステータスなどを返却しウェブで扱えるようにします。
続いて、
Estimated Effort: 1 day Priority: High Dependencies: Task 6.2
こちらでは、エラーハンドリングを追加する流れですね。
#[derive(Serialize)]
pub struct WasmError {
message: String,
code: String,
}
キャッチしたエラーもJsValueとして返却します。
fn to_js_error(msg: &str) -> JsValue {
JsValue::from_str(msg)
}
と、大体内部のコードの改修はこの辺りまでで、ここからはビルドタスクやテストを作成する流れになっている様子です。
Estimated Effort: 1 day Priority: High Dependencies: Task 6.1
wasm-packのbuild scriptを用意。
#!/bin/bash wasm-pack build --target web --out-dir pkg
Cargo.tomlに追加。
[profile.release] opt-level = "z" # Optimize for size lto = true codegen-units = 1 panic = "abort" # Reduce binary size
gitignoreも追加します。
pkg/ target/wasm32-unknown-unknown/
Estimated Effort: 3 days Priority: High Dependencies: Tasks 6.2-6.6
続いてテストを作成する流れなのですが、ここまでが優先度高のタスクぽいです。
wasmのテストに関しては、「wasm-bindgen-test」を用いて実行します。
#[cfg(test)]
mod wasm_tests {
use wasm_bindgen_test::*;
#[wasm_bindgen_test]
fn test_create_player() {
let mut game = WasmGame::new();
let result = game.create_player("ゆうてい");
assert!(result.is_ok());
}
}
様々なテストケースを準備し、
wasm-pack test --headless --firefox wasm-pack test --headless --chrome
firefox,chromeをヘッドレスで実行できるように準備します。
Estimated Effort: 5 days Priority: Medium Dependencies: Task 6.8
こちらでは、実際にウェブで利用するシーンを想定したexamplesを用意する流れとなっています。
import init, { WasmGame } from './pkg/damdara.js';
async function run() {
await init();
const game = new WasmGame();
// Create player
const player = game.create_player("ゆうてい");
console.log(player);
// Start battle
game.start_battle(0); // Slime
// Player attacks
const result = game.player_action("attack");
const messages = game.get_messages();
messages.forEach(msg => console.log(msg));
}
run();
開発サーバーもviteを使う形です。
{
"scripts": {
"dev": "vite",
"build": "vite build"
}
}
Estimated Effort: 2 days Priority: Medium Dependencies: Task 6.9
こちらのタスクでは、上記のWasmの仕様に関して、ドキュメント化する流れとなっています。
Wasm用として、WASM.mdを新たに設けるのは良いですね。
Estimated Effort: 2 days Priority: Low Dependencies: Task 6.8
こちらでは、GitHub Actions workflowを設ける流れですね。
良いですね。pushやプルリクマージでwasm成果物がビルドされ、npmパッケージとしてpublishも行う様子です。
Estimated Effort: 2 days Priority: Low Dependencies: Task 6.9
最後のこちらはパフォーマンスに関してです。
パフォーマンス向上のためBundle sizeが小さくなるように工夫したり、OS依存のところをWasm側も考慮しクレートに置換するような流れぽいです。
そして最後に、単体テスト、結合テスト、クロスプラットフォームテスト、パフォーマンステストが追加されていく流れぽいです。
ということで、タスクに関しては主にこのような流れで、ありがたいことに最後、工数見積(各週のスケジュール)も出してくれています。
Total Duration: 2-3 weeks (assuming full-time work)
Week 1:
- Day 1-2: Tasks 6.1-6.2 (Setup + Core module)
- Day 3-4: Task 6.3 (Web input)
- Day 5: Task 6.4 start (Battle integration)
Week 2:
- Day 6-8: Task 6.4 complete (Battle integration)
- Day 9: Task 6.5 (Master data export)
- Day 10: Task 6.6 (Error handling)
Week 3:
- Day 11: Task 6.7 (Build config)
- Day 12-14: Task 6.8 (Testing)
- Day 15-17: Task 6.9 (Example app)
- Day 18-19: Task 6.10 (Documentation)
- Day 20-21: Tasks 6.11-6.12 (CI/CD + optimization)
全部で、21日ほど。と、こんなにかかるか? 遅くても1週間あれば実装完了になりそうな気がするが、
ある程度バッファを大きく取ったスケジュールで見積もってくれているみたいですね。
既存プロダクトのリファクタリング案
という感じで、半ば放置してたプロジェクトを自分が思い出したい気持ちも含め、AI駆動開発を本格的に導入してみました。
まず大事なのは、AIリーダブルなドキュメントの配置。それは固有のAIエージェントに依存しないようにディレクトリを設け、Spec駆動開発でも用いられているフォーマットに沿ったドキュメントを用意します。
続いて、作業を行う際はタスクファイルで管理していくのを徹底します。
これらによって、例え同スレッドでずっとやり取りを行いトークンの上限に達し新たにスレッドを作成しなしても途切れることなく作業を進めて行きことができるでしょう。
今回は、機能追加として「Wasm対応」を追加していく流れだったのですが、機能追加のみならず「リファクタリング案を出して」と指示するだけでも、上記のような形でリファクタリング案をまとめてくれるので、そちらからAI駆動開発導入を進めるのも良いかもしれません。
AI駆動開発を本格的に行う流れになり、「書く」ということよりも専ら「読む」作業が増えて来たのですが、その「読む」ことすらAIに任せれば、基本指示するだけで開発が進んで行くのではないでしょうか。
と、言いつつもどんどん思考する回数が減り、脳が劣化していっているのを感じている今日このごろで段々と不安も覚えだしたので、AIを全く使わないAI デトックスデーを設けるのも良いのでは。と企んでおります。
と、言った感じで今日はここまで、
ではではぁ。
またまたぁ。

















