この記事は約7分で読めます。
筆者プロフィール: ソフトウェアエンジニア。「知った気にならない。いつまでも学び続ける」を信条に、業務と個人開発の両輪で技術を磨いています。AI 駆動開発で複数の個人開発アプリを構築・運用中。
👉 ポートフォリオ: 筆者ホームページ
AIの回答を "y" と一言承認しただけで、ファイルの実装が丸ごと消える ― そんな挙動をするツールを、なぜ自分でわざわざ作ったのか。
「AIにコードを書かせて、そのままローカルに反映する」。言葉にすると簡単そうですが、実際に自分の手でこの仕組みを組んでみると、便利さと危うさが本当に紙一重であることを何度も思い知らされました。この記事では、社内で使えるAIアシスタント基盤「NewtonX」と、それを外部から操作するためのSDK「NewtonX ADK」を使って、チャットでのやり取りだけでローカルのプロジェクトに変更を反映できるCLI/GUIツールを自作した話をまとめます。
なお、NewtonX ADK は商用のクローズドソースな SDK のため、内部実装や詳細な仕様には触れません。ここで書くのは、あくまで「それをラップして自分たちの用途に合わせたツールを作る側」の設計と、その過程で得た学びです。
この記事の対象読者
- 社内AI・LLM基盤とローカル開発環境を繋ぐツールを作ろうとしている方
- 「バイブコーディング」を自社の環境でも実現したいと考えている方
- AIにファイル操作を任せる仕組みの安全設計に興味がある方
- CLI と GUI でロジックを共有する設計、Streamlit特有の実行モデルへの対処に興味がある方
作ったもの
一言で言うと、「NewtonXとの対話」と「ローカルのソースコード」を橋渡しするクライアントです。CLI(コンソール)版とGUI(Streamlitを使ったブラウザ)版の2つのインターフェースを持ち、機能は完全に同じです。
できることは大きく3つです。
- 一覧の閲覧 ― NewtonX上のアシスタント/チャット/フォルダの一覧を見る
- 文脈を踏まえた質問 ― NewtonXに質問(プロンプト)を送る。このとき、質問内容に関連しそうなローカルのソースコードを自動で添付するので、NewtonXは今のプロジェクトの状態を踏まえて回答できる
-
自動マージ ― NewtonXが「このファイルをこう直してください」と提案してきたコードを、人間の承認を挟んだ上でローカルの
target_projectフォルダに自動反映する
最後の「自動マージ」が最大の特徴であり、同時に一番設計に気を使った機能です。
なぜ作ったか
いわゆる「バイブコーディング」――チャットで会話しながらコードを書き進めていくスタイル――を、社内のAI基盤上でも実現したいというのが出発点でした。ただし NewtonX 自体は Web のチャット UI であり、「回答をコピーしてエディタに貼り付ける」という手作業が挟まると、対話のテンポも、ファイルの取り違えのリスクも無視できません。であれば、質問の送信からファイルへの反映までを一気通貫でツール化してしまおう、というのがモチベーションでした。
アーキテクチャ設計のポイント
レイヤーを割って、CLIとGUIでロジックを共有する
最初はCLI(main.py)だけのシンプルな構成でしたが、GUIを追加するにあたって「同じビジネスロジックをCLIとGUIの両方から呼べるようにする」ことが課題になりました。最終的には以下のようなレイヤー構成に落ち着きました。
ポイントは、CLI/GUIそれぞれの「入出力」の部分だけを薄いレイヤーとして切り出し、それ以外のロジック(NewtonXとの通信、プロンプトの組み立て、diffの生成、ファイルへの反映)は完全に共通化したことです。これにより、GUIを追加してもCLI側の挙動は一切変わらない、という状態を保てました。
「対話のブロッキング」をどう共通化するか
これが一番技術的に面白かった部分です。CLIでは「ユーザーに y/N を聞いて、入力があるまで処理を止める」というブロッキングな書き方が自然にできます。しかしStreamlit(GUIで使ったフレームワーク)は、ボタンが押されるたびにスクリプト全体を最初から再実行するという独特の実行モデルを持っており、「回答が来るまで関数の途中で待つ」ということが素直にはできません。
この差を吸収するために、「ユーザーへの確認が必要になったらイベントを yield し、呼び出し側が回答を send() で送り返すと処理が再開する」というジェネレータベースの設計を導入しました。CLI側はそのジェネレータをその場でブロッキング駆動し、GUI側は駆動中のジェネレータを session_state に保持しておいて、ボタンが押されるたびに1ステップだけ進める、という形です。同じジェネレータ関数を、駆動の仕方(ドライバ)だけ変えて両方から使い回せるようにしたのが、この設計の肝でした。
CLIドライバは上の一連の流れをその場でブロッキング実行するだけですが、GUIドライバは yield で止まったジェネレータを session_state に退避し、次にボタンが押されたタイミングで send() を呼んで1ステップだけ進める、という違いがあります。ジェネレータ側は自分がCLIから駆動されているのかGUIから駆動されているのかを一切知らない、という点がこの設計のシンプルさを支えています。
自動マージの安全設計
「AIの回答をそのままローカルファイルに書き込む」機能は、便利さと引き換えに事故のリスクを常に抱えています。実際に運用しながら、以下のような防御を積み重ねました。
| 防御策 | 内容 |
|---|---|
| 人間の承認を必須化 | コードブロックを検出しても即座には書き込まず、変更前後のdiffを表示してファイルごとにy/Nで確認する |
| バックアップの自動作成 | 既存ファイルを上書きする際は、必ず ファイル名.bak<タイムスタンプ> として元の内容を保存する |
| 書き込み範囲の制限 | NewtonXがどんなパスを提案してきても、対象プロジェクトフォルダの外側には絶対に書き込めないよう、パスの正規化と検証を二段階で行う |
| 原子的な書き込み | 一時ファイルに書いてから置き換える方式にし、書き込み中にプロセスが落ちてもファイルが壊れた状態で残らないようにする |
苦労したこと・学んだこと・気づいたこと
ここが今回一番書きたかった部分です。設計書通りにきれいに進んだわけではなく、実際に使いながら何度も痛い目を見て、そのたびに設計を直してきました。
1. 「ファイルの一部だけ」をうっかり承認すると、実装ごと消える
このツールは、NewtonXが出力したコードブロックをそのファイルの新しい中身として丸ごと書き込みます。部分的な差分だけを賢く合成する機能はあえて持たせていません。つまり、もしNewtonXが「この関数だけ直しました」というつもりで一部分だけを回答してしまい、それをよく確認せずに y と答えると、そのファイルの他の関数はすべて消えてファイルが上書きされます。
実際に運用中、この「部分回答の誤承認」が起きかけたことがありました。差分画面で削除行(-)が異常に多いことに気づいて事なきを得ましたが、この経験から「diff確認画面は"必ず人間が読む"ことを前提にした最後の砦である」という設計思想を強く意識するようになりました。パッと見で危険な変更だと分かるよう、diffの見せ方そのものにも気を配るべきだと学びました。
2. 固定バイト数のファイルプレビューが、AIに"それっぽい嘘"を言わせていた
初期の実装では、NewtonXに送るプロジェクト情報として、各ファイルの先頭8KBだけを一律にプレビューとして添付していました。ところが、あるファイルが8KBを超えていたため、肝心の実装部分がまるごとNewtonXから見えていない状態になっていたことが後から判明しました。
このとき何が起きたかというと、「なぜ自動反映が動かないのか」と尋ねたところ、NewtonXは実在しない環境変数や関数を、あたかも実際のコードを読んだかのように"根拠付きで"説明してくるという誤診断をしてきました。ソースが見えていないはずなのに、行番号や変数名まで添えて「ここに書いてあります」と言われると、こちらもつい信じてしまいそうになります。典型的なハルシネーションですが、それが「情報が足りない」というシグナルではなく「もっともらしい説明」として出てくる怖さを、身をもって知りました。
対策として、固定バイト数でのプレビューをやめ、質問文から抽出したキーワードに関連するファイルだけは省略なしの全文を送り、関連しないファイルはファイル名一覧のみを送る、という二択方式に変更しました。あわせて、プロンプト自体にも「全文が渡っていないファイルについて推測で断定しないこと」という指示を明示しました。地味な変更ですが、AIに「見えていないものを見えているかのように語らせない」ための最も効果的な対策は、結局のところ「見えているものを正しく伝える」ことに尽きるのだと感じました。
3. 「機密ファイルを送らない」は、思ったより自明ではなかった
上記の調査の過程で、.env(NewtonXへの接続情報を含む機密ファイル)が除外設定の既定値に入っておらず、ユーザーが自分で除外設定をしない限り毎回のプロンプトに中身が送信されうる状態だったことが分かりました。「送ってはいけないファイルを除外する」という発想自体はあっても、その除外リストの初期値を作る責任が誰にあるのか、意識していないと簡単に抜け落ちる、というのは良い学びでした。今は .env や秘密鍵ファイルなどはユーザー設定に関係なく常に除外されるようにしています。
4. パスの正規化を一箇所に統一しないと、静かにバグる
NewtonXが提案してくるファイルパスは、時々 target_project/foo.py のようにプロジェクトフォルダ名を含めて出力してくることがあります。これを正規化する処理はあったのですが、実際にはコードの別の場所で正規化前のパスを使って再計算してしまっている箇所があり、結果として target_project/target_project/... という入れ子ディレクトリが生成されるバグを踏みました。
原因は、「パスを正規化する」という責務が複数箇所に散らばっていたことです。修正では、処理の入り口で一度だけ正規化し、以降の差分プレビュー・ハッシュ比較・実際の書き込みのすべてが、その正規化済みのパスだけを参照するように統一しました。「安全のためのチェックを入れたつもりが、経路によっては素通りしてしまう」というのは、この手の防御的なコードでは繰り返し起きがちな失敗パターンだと感じます。
5. 「自動マージが失敗しても、会話の記録だけは絶対に残す」
当初、チャットの質問と回答をログに保存する処理は、自動マージ(コード抽出→差分確認→適用)がすべて完了した後にだけ実行される作りになっていました。そのため、コード検出ロジックが検証コマンドの例示(bashのコードブロックなど)を誤ってファイル変更提案として検出してしまい、確認待ちのまま処理が止まる、といったケースでは、NewtonXとのやり取り自体が画面には表示されているのに、履歴ファイルには一切残らないという不具合がありました。
これは「メインの機能(自動マージ)に付随する処理(履歴保存)」という捉え方が間違っていたのだと気づき、履歴保存を自動マージより前に行うよう順序を入れ替え、さらに自動マージの区間全体を独立した try/except で囲んで、想定外の例外が起きても会話の継続自体はブロックしないようにしました。便利機能の失敗が、記録という一番地味だけれど一番大事な処理まで巻き込んで壊れる、という設計はできるだけ避けるべきだと学びました。
まとめ ― AIに任せる部分と、人間が最後に確認する部分の境界線
一連の開発を通じて強く感じたのは、「AIに任せる部分」と「人間が最後に確認する部分」の境界線をどこに引くかが、この手のツールの品質のほぼすべてだということです。AIの回答は便利ですが、それを無条件に信用してファイルへ反映する仕組みは危険すぎますし、逆に毎回全部人間がチェックする前提を置かないと、部分回答による実装消失のような事故につながります。
今回のツールでは、
- 変更は必ずdiffで人間に見せてから適用する
- バックアップと原子的書き込みで、失敗しても必ず元に戻せる状態を保つ
- AIに渡す情報自体を正しく設計し、ハルシネーションの温床を減らす
- 便利機能(自動マージ)の失敗が、土台となる機能(会話記録)を巻き込んで壊れないようにする
という原則を、事故や不具合を踏むたびに少しずつ言語化してきました。
「バイブコーディング」は、コードを書くスピードを大きく上げてくれる一方で、その速さに見合うだけの"安全網"を人間側で用意しておかないと、あっという間に取り返しのつかない変更を積み重ねてしまう ― そのバランス感覚こそが、この種のツールを作る上で一番学びになった部分でした。NewtonXという社内AI基盤があったからこそ踏み込めた領域でもあり、AIを"信頼して使う"ということの難しさと面白さを、実装を通じて体感できた開発でした。
関連記事
- AI駆動開発で開発期間90%短縮 — 未経験エンジニアが3週間で48,000行のアプリを作った全記録 — AIと二人三脚で開発を進める「バイブコーディング」の実践記録。本記事の安全設計と合わせて読むと、AI駆動開発のスピードと安全性の両立が見えてきます
- Qiita CLI × Claude Code で記事管理を自動化 — トレンド分析・SEO最適化・予約投稿を仕組み化した全記録 — AIツールとローカル環境を繋ぐ別の仕組み化事例。同じ「AIとローカルを繋ぐ」というテーマを別角度から扱っています