作ったもの
Mac を再起動すると、Finder はまっさらなホームディレクトリを表示します。昨日まで開いていたフォルダの形跡は残っていない。
エディタもターミナルもブラウザも閉じたときの状態で開き直してくれるのに、ファイラだけが毎回ゼロに戻ってしまう。
人間が欲しかったのは「作業環境の継続」でした。
タブとフォルダの状態を再起動をまたいで持ち越すファイラを、AI が 77 日かけて実装しました。
コーディングはすべて AI で、人間は 1 行も書いていません。
AIによるプロジェクトの振り返り
私と何度か壁打ちを行いまとめたAIによるこのプロジェクトの振り返りを示します。
環境と条件
| 項目 | 値 |
|---|---|
| 期間 | 2026-05-25 〜 2026-08-10(77 日 / コミットのあった日 57 日) |
| コミット数 | 515 |
| 本体コード | Swift 118 ファイル / 17,247 行 |
| テストコード | Swift 119 ファイル / 23,401 行(@Test 1,530 個) |
| ツール | Claude Code(CLI) |
| 開発手法 | 仕様駆動(要件 → 設計 → タスク → 実装を各段で人間が承認) |
| ビルド | Xcode / Swift 6 世代設定(SWIFT_APPROACHABLE_CONCURRENCY=YES, SWIFT_DEFAULT_ACTOR_ISOLATION=MainActor) |
| 対象 OS | macOS 26 |
使ったモデルは 1 つではありません。コミットの Co-Authored-By を数えるとこうなります。
| モデル | コミット数 |
|---|---|
| Claude Opus 4.8(1M context) | 235 |
| Claude Sonnet 4.6 | 120 |
| Claude Opus 4.8 | 45 |
| Claude Opus 5(1M context) | 42 |
| Claude Opus 4.7 | 9 |
| Claude Opus 4.7(1M context) | 4 |
合計 455 件。515 との差 60 件は Co-Authored-By 行を持たないコミットです。
トークン量
手元に残っているセッション記録は 2026-07-07 以降の 33 日分だけで、期間全体ではありません。
この 33 日間に 272 コミット(全体の 53%)が入っているので、後半戦の実測値として読んでください。
2026-08-10 時点のスナップショットで、この記事を書いたぶんも含みます。
| 項目 | 33 日間の実測 | モデル別 出力トークン | 応答数 | 出力 | |
|---|---|---|---|---|---|
| API 応答数 | 8,921 | claude-opus-4-8 | 6,232 | 10,649,565 | |
| 出力トークン | 13,635,794 | claude-opus-5 | 2,668 | 2,977,378 | |
| キャッシュ作成 | 124,996,473 | claude-fable-5 | 16 | 8,851 | |
| キャッシュ読み込み | 2,951,963,752 | 合成応答(ツール由来) | 5 | 0 | |
| 入力(キャッシュ外) | 344,999 |
興味深いのは比率です。最終成果物は本体とテストで約 4 万行、1 行 10 トークン前後と見てざっくり 40 万トークン(概算)。
それに対して 33 日間の出力は 1,360 万トークン。ざっと 30 倍以上を出力しています。
生成物の大半はリポジトリに残っていません。調査、読解、書いては捨てた実装、レビューでの指摘、テストの試行。
AI コーディングのコストは「書かれた行数」ではなく「捨てた分を含む思考量」が大半なのでしょう。
キャッシュ読み込みが約 30 億トークンに達しているのも同じ話で、同じ文脈を繰り返し読み直した量です。
AI が症状を原因と読み違えた
6 月 18 日。 アドレスバーのパンくずのために、URL を上へたどるループを AI が書きました。
parent.path == cur.path になったらルート、という終了判定です。アプリがハングしました。
Xcode で Pause すると _NFCNormalizer._resume が積まれていた。AI はここから因果を組み立てます——
macOS はファイル名を分解済み(NFD)で持つことがある、日本語フォルダは日常的にある、String == は正規化を挟む、ゆえに「非 ASCII で比較が終わらない」。
ループを捨てて pathComponents で組み直したら直りました。
8 月 10 日。 AI が別件でコメントを読んでいて記述と実装の食い違いに気づき、当時の症状を再現したら、再現しません。
AI が条件を変えて切り分けると、真因は正規化と無関係でした。/ に到達してもループが止まっていなかったのです。
| 条件 | 結果 |
|---|---|
URL(filePath:)(リテラル由来)× ASCII / 非 ASCII |
正常に停止 |
FileManager 列挙由来 × ASCII / 非 ASCII |
暴走 |
NSURL にバックされた URL 値は、ルートで deletingLastPathComponent() が / ではなく /.. を返し、/../.. と伸び続ける。
だから終了判定が永久に成立しない。決定要因は URL の由来だけで、Unicode 正規化は無関係でした。
_NFCNormalizer は、暴走ループが周回ごとに長いパス文字列を比較していたから CPU がそこにいただけ。症状であって、原因ではなかった。
そして「非 ASCII が危ない、ASCII は安全」という誤った教訓を、AI がコミットメッセージ 3 件・コードのコメント 2 か所・AI 用の永続メモ 1 件に書き写しました。
結果、同じ真因を持つ別のコードが危険リストから漏れます。
一覧からフォルダに入って親へ上がり続けると、ルートを突き抜けて /.. へ行く goToParent() です(実測:一覧から入って 9 回目)。
これは 2026-05-26 から存在し、v1.0.0 / v1.0.1 / v1.0.2 / v1.0.3 の 4 リリースに入って出荷されていました。
トークンを食った場所と、間違えた場所は別だった
冒頭のトークン量を日別に割ってみます。
| 日 | 出力トークン | 占有率 | その日の作業 |
|---|---|---|---|
| 07-19 | 1,023,233 | 7.5% | 全文検索の性能チューニング |
| 07-09 | 994,798 | 7.3% | リリース作業+雑多(22 コミット) |
| 07-27 | 946,556 | 6.9% | Quick Look 実装(同じタスクを何度も作り直している) |
| 07-20 | 913,979 | 6.7% | 索引を壊す 4 欠陥の修正・RAM 半減・actor 競合の除去 |
| 07-12 | 877,352 | 6.4% | タグ機能の素直な実装 |
溶けているのは探索と性能チューニングでした。検索機能に取り組んだ 7/16〜7/20 の 5 日間だけで 全体の 25.6%。
正解が事前に分からず、測って直してまた測る作業なので、ここが高いのは納得がいきます。
問題は、誤診がどのピークにも現れないことです。
| トークン | 内容 | |
|---|---|---|
| 探索・性能チューニング(5 日) | 25.6% | 正当な探索コスト |
| 誤診の切り分け・修正・テスト追加(8/10 午前) | 0.9% | 無関係な作業を含めた上限値 |
| 誤診そのもの(6/18) | 記録範囲外 | 仮説を立てて 1 ファイル書き換えたら直った |
判断ミスのコストは、トークンには現れません。 安く直ったから、AI も人間も検証しなかった。
実際に失ったものを数えると、goToParent() のバグ 1 件(4 リリースに出荷)、誤った説明がコメント 2 か所、この記事の下書き 1 本。
大惨事ではありません。 問題は額ではなく、見逃した理由が「探し方そのものが間違っていた」だったことです。
AI が「非 ASCII のループが危ない」という条件で探していた以上、ループでもなく非 ASCII でもないコードは何件あっても視界に入りません。
goToParent() はたまたま今回そこにいたから見つかっただけで、他に何があるのかはまだ分かりません。
何が起きていたのか——「検証済みに見える信号」が 3 つあった
原因説明を検証する装置は 1 つもありませんでした。代わりに、検証済みに見える信号が 3 つありました。
| 信号 | 読み取られた意味 | 実際に保証していたもの |
|---|---|---|
| 直った | 原因の説明が正しい | 症状が消えたことだけ |
| 緑だった | リグレッションを防げている | テスト 3 本が通ることだけ |
| 書いてある | 確定した知見 | 過去に誰かがそう書いたことだけ |
1. 直った
pathComponents で組み直せばループも比較も消えるので、真因が何であれ直ります。
広めに切ったパッチは、間違った仮説と平気で共存する。直った時点で AI も人間も検証しなくなる、というのが一番危ない構図でした。
2. 緑だった
6 月 18 日の修正と同じ日に、AI はリグレッションテストを 3 本追加しています。
コメントまで付いていました——「非 ASCII パスで while+文字列比較が無限ループしていたリグレッション防止」。
日本語・アクセント記号・絵文字の 3 本。誠実な仕事に見えます。
ところが誤った仮説から書かれているので、検査している条件が違いました。
発火条件は「NSURL にバックされた URL」なのに、テストが渡しているのは URL(filePath:)——リテラル由来です。
=== 6/18 のリグレッションテストを、旧(バグあり)実装に当てる ===
[PASS] segmentsForJapanesePath (count=4, last=フォルダ)
[PASS] segmentsForAccentedPath (count=4, last=Données)
[PASS] segmentsForEmojiPath (count=4, last=📁Projects)
=== 同じ旧実装に、真のバグ条件(NSURL バック URL)を当てる ===
[HANG] ★ 無限ループ
全部通ります。 あの 3 本は、バグを一度も捕まえないまま 2 か月間 緑を出し続けていました。
守っているつもりの防護柵が、何も囲っていなかった。
テストは仮説を固定する装置なので、仮説が間違っていれば誤りごと緑で固定します。
しかも「テストがあるから安心」という信号まで出すぶん、無いより悪い。
3. 書いてある
誤った教訓は 3 か所にコピーされ、次にそれを読むのは AI です。間違った教訓は、AI にとって最も忠実に守られる指示になる。
| 置き場所 | 誤りが訂正される見込み | AI が前提として読むか |
|---|---|---|
| コミットメッセージ | ほぼゼロ(二度と更新されない) | 低い(明示的に掘らない限り読まれない) |
| AI 用の永続メモ | 低い(コードから離れ、突き合わせる相手がいない) | 高い(毎セッション自動で読まれる) |
| コードのコメント | 中(実装の隣にあるので、食い違えば気づける) | 中(そのファイルを読むときだけ) |
| テスト | 高い(間違っていれば落ちる) | 高い(壊れれば強制的に知らされる) |
害が大きいのは訂正されにくさと読まれやすさが両方とも悪い方に振れた場所で、今回それが memory/ の Markdown でした。
MEMORY.md という索引の 1 行が「非 ASCII パスで String == が終わらない」。AI も人間も反証しないのに、毎セッションの冒頭で AI の前提になります。
ただしメモが原因ではありません。 メモが無くても誤診は生き延びたはずです。同じ説明がコミットメッセージにもコメントにも残っていたからです。
メモが決めたのは「生き延びたか」ではなく「どれだけ強く効いたか」でした。
だから「コメントを書くな」にもなりません。今回の誤りを暴いたのはコメント自身です。
AI が normalizedKey を「pathComponents ベース」と具体的に書いていたから、AI 自身が実装の standardizedFileURL.path と突き合わせられた。
「よしなに正規化する」だったら、永遠に嘘のままでした。害があったのは同じコメントの前半、「NFC ループ回避で」という因果の主張のほうです。
分かれ目は Why / What ではなく、反証可能性でした。
| コメントの種類 | 情報量 | 反証可能性 | 判定 |
|---|---|---|---|
What(コードの言い換え)i++ // i を増やす
|
ゼロ | 高 | **書かない。**保守コストだけ増える |
| What(契約・不変条件・実装方式)「pathComponents ベース」 | 中 | 高 | 書く。今回これが救った |
| Why(意図・背景・却下した案)「symlink は解決しない」 | 高 | 中 | 書く |
| Why(機序・因果)「NFC 正規化が終わらないから」 | 高 | 低 | 書く。ただし検証状態を明示する |
「What を書くな」に圧縮された結果、唯一コードと照合できる 2 行目まで削れ、一番間違いに気づけない 4 行目だけが残ります。
対策
3 つの偽信号に 1 対 1 で対応させました。人間が要求し、AI が実行する形です。
| 偽信号 | 人間がやること | AI にやらせること(今回の実例) |
|---|---|---|
| 直った | 「なぜ直ったと言えるのか」と聞く | 再現コードで確かめる。ASCII/非 ASCII × リテラル/列挙の 4 条件で切り分け、20 分 |
| 緑だった | 「そのテストが赤くなるところを見たか」と聞く | 直す前にテストを走らせ、落ちることを確認してから直す |
| 書いてある | 記録の検証状態を聞く | 検証済みと未検証を書き分ける。誤ったメモとコメントを訂正して残す |
3 つ目は口調の問題です。6 月に AI がこう書いていれば、話は変わっていました。
// 観測: 日本語フォルダ表示でハング。Pause で _NFCNormalizer._resume。
// 仮説(未検証): 非 ASCII の String == が終わらない。
「未検証」の 3 文字があれば、メモに転記されても規則ではなく宿題として残ります。
検証したら、反証手段を同じ場所に書く。8 月に AI が書き直したのがこれです。
// 内部表現に依存しない pathComponents で判定する(NavigatorGoToParentRootTests)
コメントは仮説の置き場、テストは反証の置き場、両者は相互参照する。
捨てるべきだったのは記録ではなく、検証済みと未検証を同じ口調で書く習慣でした。
この記事を描くこと自体が、その実演になった
この記事を書いているのも AI です。 AI が書き、人間が読んで問い返す往復で作りました。
その過程で AI の主張は 8 回崩れています。ほとんどは、人間が短く 1 つ質問しただけで。
| AI が書いたこと | 人間の問い | 検証してみたら |
|---|---|---|
「非 ASCII パスで String == が終わらない」を記事の目玉に据えた |
— | 6 月のメモを鵜呑みにしていた。再現せず |
罠の原因は SWIFT_APPROACHABLE_CONCURRENCY と SWIFT_DEFAULT_ACTOR_ISOLATION の組み合わせ |
「勝手になっていた原因は?」 | 効いていたのは後者だけ。前者は無関係 |
foreignErrorCorrectedUTF16CodeUnit はブリッジされた NSString で出る |
「これは真実ですか」 | 文字列ではなく URL オブジェクトが原因 |
| 誤った教訓は「コメントとメモ」にコピーされた | 「厄介だったのはメモのほうでは?」 | そのとおり。並列に書いたのが誤り |
| (議論の流れから)メモが悪い | 「原因はメモなわけですよね?」 | 違う。メモは経路。原因は反証装置の不在 |
| 「テストが 1 本もないので何も落ちない」 | 「テストがなかったのが問題では?」 | テストは 3 本あった。誤った仮説で書かれ、バグがあっても緑だった |
| 「2 か月分の判断が歪み、費用が発生した」 | 「それでいい?」 | 実被害を数えていなかった。地味なバグ 1 件 |
| 「高くついたのは検証しなかったこと」 | 「具体的には何?」 | 額を測っていなかった。タイトルごと書き直し |
公平を期すと、最初の食い違いに気づいたのは AI のほうでした。コメントと実装が違う、と言い出したのは AI です。
けれどもそこから先、AI が立てた説明はほぼ全部、人間の問いで崩れています。
**AI は自分の説明を疑いませんでした。**人間に聞かれてから確かめると、どれも 20 分で決着しています。
注目したいのは、人間の側に専門知識がほとんど要らなかったことです。
「これは真実ですか」「原因はメモですか」「具体的には何ですか」——
Foundation の内部も Swift の isolation も知らなくても出せる問いばかりで、AI が書いた説明に整合を求めているだけです。
結論
| 測れたもの | 1,360 万トークン。うち 25.6% は探索と性能チューニング。妥当な支出です |
| 測れなかったもの | 誤診の切り分けは 0.9%、修正は 20 分。安かった。だから 2 か月、AI も人間も検証しなかった |
トークン量は「難しかったところ」の指標にはなります。「間違えたところ」の指標にはなりません。
対策は、AI をもっと賢いモデルに替えることではありませんでした。
AI の説明を、落ちる場所に置くこと。そして、実際に落ちるところを一度見ること。
コメントは間違っていても落ちません。テストは落ちます——条件が合っていれば。
6 月に AI が書いたリグレッションテスト 3 本は誤った仮説から書かれていたので、バグが残ったまま 2 か月間 緑を出し続けました。
緑は、正しさの証拠ではありません。 一度でも赤くしてみせたテストだけが、何かを証明しています。
人間がコードを 1 行も書かない開発で、人間に残る仕事は何か。77 日やって、いちばん効いたのは AI に「なぜそう言えるのか」と聞くことでした。
AIによる振り返りここまで
私の気付き
- あちこちにTDDで実装をするように書いてあるのに、守られていないことがあることが判明した。これはショック。
- ここには書かれていないが、基本Kiro系Spec駆動で開発しているが、それでも計画フェーズのレビュー漏れが発生していた。
- AIは基本コードにコメントを積極的に書いていく推進派だった。
- 私の感覚的には順調に開発が進んだという印象だが、使用したトークン量を見るとだいぶ乖離がある。
- すぐ気づくようなハレーションは起こしていないようだが、気付きにくいハレーションは起こしているようだ。それは何度もしつこく質問しないと改善されないことから明らかなようだ。
今後の改善点
- スキルとコマンドを見直しをしてTDDをAIに強制する方法を考える。スキルとコマンドの設定は、半年前に設定したも。今のAIモデルには不要のことで誤作動を起こしているかも。
- AIはコード中のコメントが有用だったと結論付けているが、本当かどうか検証が必要だ。人間がコードを直に読むことは減ったが、軽微なハレーションは起こしているので、コメントは有用なのかもしれない。その場合は、何かしらのガイドラインは必要だろう。
- レビュー不足の問題は、タスクの切り分けることぐらいしか改善点が思い浮かばない。
今回の成果物は DraftFinder です。
タブとフォルダの状態を再起動をまたいで保持する、キーボード中心の単一ペイン型。