日本やアメリカで使ってもらえてる、ダウンロード数8000回くらいのcrateをOSSで作ってます。
宣伝しちゃった後で、やらかした!
と気づいて、スターが増えるほどドキドキしてた話。
自称玄人によるバイブコーディングは、スパゲッティコードの量産装置だとわかりました。
それを捨てるのでなく、美味しく食べられる形に作り直すTechです。
はじめに
個人開発プロジェクトが停滞し、何ヶ月も手をつけられなくなった経験はないでしょうか。
私が開発している diffx という構造化データの差分抽出ツールは、2025 年 8 月から 11 月まで約 3 ヶ月間、ほぼ完全に停滞していました。技術的には完成しているはずなのに、なぜか前に進めませんでした。その原因を徹底的に分析し、「リブート」という手法でプロジェクトを復活させました。
この記事では、停滞の原因分析から、AI との協働の失敗、モノレポからの脱却、そしてリブートの具体的なプロセスまでを網羅的に記録します。
対象読者:
- 個人開発プロジェクトが停滞している人
- AI(Claude Code 等)を使った開発で品質に苦しんでいる人
- モノレポや共有 CI/CD の複雑さに疲れている人
diffx とは
diffx は、JSON/YAML/TOML/XML/INI/CSV など構造化データの「意味的な差分」を抽出する Rust 製ツールです。従来のテキスト diff とは異なり、フォーマットの違いやキー順序の違いを無視して、本質的な変更だけを検出します。
$ diffx config-old.yaml config-new.yaml
~ server.port: 8080 -> 9000
+ server.timeout: 30
- server.deprecated_option
DevOps/SRE 向けに設計しており、Kubernetes YAML や Terraform の設定変更を追跡する用途を想定しています。npm 版(diffx-js)と Python 版(diffx-python)も提供しています。
- crates.io: https://crates.io/crates/diffx-core / https://crates.io/crates/diffx
- npm: https://www.npmjs.com/package/diffx-js
- PyPI: https://pypi.org/project/diffx-python/
第 1 章:停滞期間に何が起きていたか
表面的な「完成」の裏側
当時の記事です。
スターをたくさん付けていただきました。
スポンサーズしてくれた方もいます。
プロジェクトの外見は立派でした:
- 3 言語(日本語・英語・中国語)の README
- 6 つの対応フォーマット
- npm 版と Python 版
- 複雑な CI/CD パイプライン
- 740 行におよぶ移植計画書
しかし、実際には:
- CI/CD が壊れていて実行不能
- テストが「正しい仕様」を検証しているか不明
- ドキュメントと実装が乖離している可能性
CLIとしての最低限の動作はしていましたが、持続可能ですと自信を持って言えるレベルではありませんでした。
第 2 章:停滞の根本原因
持続可能にするための勉強をしていたら、3 ヶ月経ってたんです。
この方法なら行ける!って自信を持つためには別の小さなプロジェクトで練習する必要があって、
その成果物として、こんなサービスもできました。
便利なので、よかったら使ってみてください。
diffxを分析した結果、大きく 3 つのカテゴリに分類できる失敗パターンを特定しました。
カテゴリ A:プロジェクト設計の失敗
1. 3 プロジェクトの同時進行と過度な共通化
diffx に加えて、diffai(AI モデルの差分抽出)、lawkit(ベンフォードの法則など統計)という姉妹プロジェクトを構想していました。
やってしまったこと:
- 3 プロジェクトで共通の CI/CD システムを構築
- workflow_call 方式で複雑な共有を実現しようとした
- 共有リポジトリへのシンボリックリンク
結果:
3 つのプロジェクト(diffx, diffai, lawkit)の共通部分(CI ワークフロー、スクリプト等)を別の共有リポジトリに切り出し、各プロジェクトからシンボリックリンクで参照する設計にしていました。
/home/kako-jun/repos/.github/ ← 共有リポジトリ
└── rust-cli-kiln/
└── scripts/
└── testing/
└── quick-check.sh ← 作ったが安定しない
各プロジェクト内:
github-shared -> ../.github ← シンボリックリンク
quick-check.sh は作りました。しかし、diffx で動くように調整すると lawkit で動かなくなり、lawkit を直すと diffai が壊れる。いつまでも安定しませんでした。
教訓:「1 つを完成させる前に、次のプロジェクトを考えるな」
2. モノレポ運用の失敗
Rust 製の diffx に対して、npm 版と Python 版を同じリポジトリで管理していました。
diffx/ # モノレポ
├── diffx-core/ # Rust
├── diffx-cli/ # Rust
├── diffx-js/ # Node.js (napi-rs)
└── diffx-python/ # Python (PyO3)
問題点:
- 3 つのプロジェクト(Rust / Node.js / Python)を 1 つのリポジトリで管理
- GitHub Actions のワークフローが複雑化(6 ワークフロー + 共有リポジトリ)
- 一部の変更が全体に影響
- リリースのたびに複雑な手順が必要
- 異なる言語/ビルドシステムの依存関係が指数関数的に複雑化
リリースサイクルの不一致:
Rust版に軽微なバグ修正
→ diffx-core v0.6.1
→ diffx-cli v0.6.1
→ diffx-js はバインディングの更新が必要(半日作業)
→ diffx-python も同様(半日作業)
→ 結局、Rust版だけ先にリリース
→ バージョンが乖離
3. 多言語ドキュメントの早期作成
最初から 3 言語の README を用意しようとしました。
問題点:
- 3 つのファイルを同期する負担
- 変更のたびに 3 倍の作業
- どれが「正」かわからなくなります
- 結果的にすべてが陳腐化します
正解:
Phase 1: README_ja.md のみ(一番まだ読める言語)
Phase 2: プロジェクト成熟後に英語版
Phase 3: 需要が出てから他言語
カテゴリ B:AI との協働の失敗
Claude Code を使って開発していましたが、複数の失敗パターンがありました。
4. コンテキスト圧縮による精度低下を無視した
コンテキスト圧縮が 3 回くらい走ったセッションで、最初は完璧だった回答が、徐々に品質が低下していきました。
セッション開始: 正確で詳細な実装
↓ 1時間後: 微妙な仕様違反が出始める
↓ 2時間後: 明らかなバグを含む実装
↓ 3時間後: 以前説明したことを忘れている
残り 20% からの危険な挙動:
コンテキストが残り 20% くらいになると、AI は露骨に変わります:
- 平気で嘘をつき始める
- 勝手にコードを省略する
- 聞いてもいないのにコミットしようとする
仕様書があって「これを元に実装しろ」と指示していても関係ありません。コンテキストが薄まると、仕様書の内容すら忘れて勝手な実装を始めます。
この挙動を知らない初心者が AI の言いなりになっていると、自動的に仕様と実装が乖離していきます。
なぜ AI はこう振る舞うのか:
おそらくコーディング用 AI は訓練時に「コンテキスト圧縮までに、なんとしてもコミットして区切りよく作業を終わらせろ」という方向で最適化されています。それ自体は合理的です。
しかし、この挙動と「1 年生のバイブコーディング」の相性が最悪なのです。
Qiita や Zenn で「できた!みんなもやってみて!これからはバイブコーディングでなんでも作れる!」と興奮気味に書いている記事を見かけます。その熱量は素晴らしいのですが、AI の「区切りよく終わらせたい」本能と、初心者の「AI に任せておけば大丈夫」という信頼が組み合わさると、破綻への最短ルートになります。
これが「バイブコーディング」の危険性です。私の解釈では、バイブコーディングとは:
- main ブランチのまま 1 つのセッションで作業
- コンテキスト劣化を気にせず口語で指示
- なんとなく動いているように見える
- 気づけば 2000 行超のスパゲティコード
小規模や使い捨てのプロジェクトなら問題は起きません。しかし、そうでない現場で「幻滅期」を経験する前の人がバイブコーディングを続けると、そのうち爆発します。
5. 「実装しました」を検証なしで信じた
これはコンテキスト圧縮とは別の問題です。Opus 4.5 でコンテキストが十分残っていても、AI の「実装しました」は嘘であることが多い。
AI は正しい知識を持っています。しかし、その知識とは異なる実装を平気で出力します。人間には直感的に理解しづらい現象ですが、AI は「知っている」ことと「正しく実装する」ことが一致しません。
対策:同じ AI に「本当にできたか?」を検証させることです。
私: 「この機能を実装して」
AI: 「実装しました」
私: 「本当に仕様通りにできているか、自分でレビューして」
AI: 「確認したところ、以下の問題がありました…」(あっさり直してくれる)
今ならわかります。世間で「AI にすぐ自動レビューさせる」ノウハウが安定しているのは、この問題への対策だったのです。当時の私は、それを知りませんでした。
6. 仕様を曖昧に伝えた(語彙がなかった)
私: 「大文字小文字を無視するオプションを追加して」
AI: 「--ignore-caseオプションを追加しました」
結果: 値の比較には効くが、キー名の比較には効かなかった
私の頭の中では「両方」のつもりでしたが、AI はそう解釈しませんでした。
本質的な問題:開発原則を説明するための語彙が私になかったのです。
バイブコーディングで問題が起きたとき、「リファクタリングして」と AI に指示する必要があります。しかし、AI に頼り続けてきた人は「単一責任原則」「関心の分離」「DRY」といった言葉を知りません。言葉を知らないと、指示もできず詰みます。
対策:
❌ 「大文字小文字を無視するオプション」
✅ 「--ignore-caseオプションを追加。
効果:
1. 値の比較時に大文字小文字の違いを無視
2. キー名の比較時も大文字小文字の違いを無視
例:{"Name": "foo"} と {"name": "foo"} は同一とみなす」
今ならこう書けます。(というか、AIと原則ベースで壁打ちして、これくらい具体的な指示をAIに書いてもらいます)
当時の私には、この精度で仕様を書く習慣がありませんでした。
7. 過去の嘘ドキュメントを読み込ませた
私: 「README.mdを読んで、この通りに実装して」
AI: 「READMEに基づいて実装しました」
結果: READMEが嘘だらけだったので、嘘の実装ができた
過去に別の AI セッションで作成したドキュメントが、すでに実装と乖離していました。
カテゴリ C:品質保証の失敗
8. 既存テストの信頼性問題
$ cargo test --workspace
29 passed; 0 failed
テストが通っているから安心? それは間違いでした。
なぜ起きるか:AI は一度コードを見ると、そのコードを通すためのテストを書きます。テストが 1 回目で通るのは当たり前の現象であり、喜ぶことではありません。
Serena のような AI ツールは自動的にコードを読みます。「コードを見せない」という選択肢はありません。だから明示的に指示する必要があります:
❌ 「このコードのテストを書いて」
✅ 「仕様書(docs/specs/cli.md)に基づいてテストを書いて。
コードは見ないで。仕様だけを根拠にして」
仕様駆動開発の必然性:
失敗を経験した今ならわかります。「仕様駆動開発」がトレンドになってきた理由が。
仕様駆動開発では:
- 仕様が細かい Issue に分解される
- Issue に AI への指示を書く
- AI が Issue を取りに来て作業する
- GitHub MCP で機能ごとにブランチができる
- PR ができてレビューされる
この流れは、バイブコーディングの危険性を自動的に防ぎます。当たり前のことをやっているだけで、安全な開発になるのです。
AI 駆動開発の習熟度ピラミッド:
AI 駆動開発の習熟度を 3 段階のピラミッドで表した図を見たことがあります。当時の私がやっていたバイブコーディングは一番下の段階でした。
最低でも 2 段階目に達していないと、公開しても維持できません。使ってくれている人に迷惑をかけてしまいます。
第 3 章:リブートのプロセス
基本方針:「疑って、確認して、記録する」
リブートもClaude Codeでやりました。
正直に話し、復活の案をClaude Codeに立ててもらいました。
懺悔室の岸辺露伴と違って親身になってくれましたし、実行可能な計画が立てられ、事実成功したのです。
- .claude内にrebootというサブディレクトリを作り、そこを前線基地とする
-
/initのように、いまいちどプロジェクト内の全ファイルを走査してもらう(Serena)
3日くらい掛かったので、その間の人間が記憶を保ち続けることは必要です。
長い時間をかけたら、たぶん人間側が原因で、また破綻します。
1. reboot以外の既存ファイルは信じない
2. 中途半端である前提で考える
3. ドキュメントも嘘だと疑う
4. どこまでが真実かを考える
これらも人間の役目です。
Claude Codeはあくまで軍師で、よちよち歩きの劉備が私なのです。
Phase 1: 隔離作戦
まず、既存ファイルを _old/ に移動し、白紙の状態を作りました。
# 削除・隔離したもの
- docs/(examples含む)→ 検証されない嘘の温床
- 英語・中国語README
- scripts/(複雑なCI/CD関連)
- ベンチマーク(本質的でない)
- CHANGELOG.md, CONTRIBUTING.md
結果:109 ファイル変更、32,145 行削除
Phase 2: 真実の特定
README_ja.md で「できる」と書いてあることを、1 つずつ実際に試しました。
# 各フォーマットをテスト
echo '{"a":1}' > test1.json
echo '{"a":2}' > test2.json
./target/release/diffx test1.json test2.json
発見:
✅ 全6フォーマット(JSON/YAML/TOML/XML/INI/CSV)動作確認
✅ 出力形式(CLI/JSON/YAML)動作確認
✅ --quiet, --ignore-keys-regex, --epsilon, --array-id-key 動作確認
⚠️ --ignore-case は部分的動作(値には効くがキーには効かない可能性)
❓ --ignore-whitespace, ディレクトリ比較 未検証
結論:コア機能は正しく動作していました。問題は「すべての機能が検証されていたわけではない」ことです。
Phase 3: 仕様書の作成
実際に動作確認したことだけを仕様書に記載しました。
docs/specs/
├── cli.md # CLI仕様(終了コード、出力形式、オプション)
└── core.md # コアAPI仕様
仕様書の原則:
- 実装を見る前に「あるべき姿」を書く
- 実装を確認する
- 動く部分だけを仕様に記載
- 動かない部分は「TODO」として別記
- 嘘は絶対に書かない
Phase 4: テストの再構築
既存の 436 テスト(8,022 行)を削除し、仕様書に基づくテストを再作成しました。
新しいテスト構成:
tests/
├── spec/ # 仕様ベースのユニットテスト(69テスト)
└── cmd/ # trycmdによるドキュメント兼テスト(19テスト)
trycmdの採用理由:
- Markdown ファイルがそのままテストになります
- ドキュメントとテストが乖離しません
- 「嘘のドキュメント」が物理的に作れません
第4章:モノレポからの脱却
分離の決断基準
以下の条件を満たす場合、分離すべきと判断しました:
- ビルドシステムが異なる: Cargo / npm / pip
- リリースサイクルが異なる: 独立して更新したい
- ユーザーが異なる: Rustユーザー / Node.jsユーザー / Pythonユーザー
- CI/CDが複雑化している: 相互依存で理解困難
diffx は 4 つすべてに該当しました。
分離の手順
Step 1: 新リポジトリの作成
cd /home/kako-jun/repos/
mkdir diffx-js && cd diffx-js && git init
mkdir diffx-python && cd diffx-python && git init
Step 2: コードの移動
cp -r ../diffx/diffx-js/* ./diffx-js/
cp -r ../diffx/diffx-python/* ./diffx-python/
重要:Git 履歴は捨てます。履歴を保持しようとすると複雑になります。
Step 3: 独立した Cargo.toml の作成
diffx-js と diffx-python は、diffx-core を crates.io から取得するように変更しました。
# diffx-js/Cargo.toml
[dependencies]
diffx-core = "0.6" # crates.ioから(pathではなくバージョン指定)
Step 4: モノレポからの削除
# diffx/Cargo.toml
[workspace]
members = ["diffx-core", "diffx-cli"]
# diffx-js, diffx-python を削除
分離後の構成
/home/kako-jun/repos/
├── diffx/ # Rust専用(シンプル)
│ ├── diffx-core/
│ ├── diffx-cli/
│ └── .github/workflows/
│ ├── ci.yml
│ └── release.yml
│
├── diffx-js/ # npm専用(独立)
│ └── .github/workflows/
│ ├── ci.yml
│ └── release.yml
│
└── diffx-python/ # pip専用(独立)
└── .github/workflows/
├── ci.yml
└── release.yml
分離後のメリット
- CI/CD の理解可能性: 6 ワークフロー + 共有リポジトリ → 各リポジトリに 2 ワークフロー
- 独立したリリース: 急ぐ必要がなく、各言語で独立したサイクル
- 責任範囲の明確化: 各リポジトリが単一の責任を持ちます
- 貢献のしやすさ: Node.js 開発者は diffx-js だけを見れば済みます
第 5 章:リリースワークフロー設計のコツ
分離後に学んだ、最も重要な原則があります。
ビルドとパブリッシュは分離すべき
アンチパターン:
# ❌ 危険なワークフロー
name: Release
on:
push:
tags: ["v*"]
jobs:
build-and-publish:
steps:
- run: cargo build --release
- run: cargo publish # ← 全ビルド成功前にパブリッシュ
この設計の問題:
- Windows/macOS/Linux のクロスビルドで 1 つでも失敗したら?
-
cargo publishは取り消せません - 失敗に気づいてもバージョンを上げるしかありません
- v0.6.1 → v0.6.2 → v0.6.3... と無駄にバージョンが上がります
正しいパターン:2 段階リリース
# ✅ 安全なワークフロー(release.yml)
name: Release
on:
push:
tags: ["v*"]
jobs:
build-linux:
runs-on: ubuntu-latest
steps:
- run: cargo build --release
- uses: actions/upload-artifact@v4
build-macos:
runs-on: macos-latest
# ...
build-windows:
runs-on: windows-latest
# ...
create-release:
needs: [build-linux, build-macos, build-windows] # 全ビルド成功後
steps:
- uses: actions/download-artifact@v4
- uses: softprops/action-gh-release@v1
with:
files: |
diffx-linux/*
diffx-macos/*
diffx-windows/*
# ✅ パブリッシュは別ワークフロー(publish.yml)
name: Publish
on:
workflow_dispatch: # 手動トリガー
jobs:
publish:
steps:
- run: gh release view v${{ inputs.version }} # リリース存在確認
- run: cargo publish
なぜ 2 段階か
Step 1: タグプッシュ → 全プラットフォームビルド → リリースページ作成
↓
ここで止まります。人間が確認します。
↓
Step 2: 手動でパブリッシュワークフローを実行
→ crates.io / npm / PyPI に公開
失敗した場合の違い:
| 1 段階リリース | 2 段階リリース | |
|---|---|---|
| macOS ビルド失敗時 | crates.io に不完全版が公開済み | 何も公開されていない |
| 対処 | v0.6.2 をリリースするしかない | タグ削除して再プッシュ |
| バージョン | 無駄に消費 | 無駄にしない |
第 6 章:リブートのノウハウ集
リブートの正しい順序
1. 実際に動かして真実を確認
2. 仕様書を作成(docs/specs/)
3. 仕様書に基づくテストを作成
4. テストが通るように実装を修正
5. ドキュメント(README等)を仕様書に合わせて修正
アンチパターン:
- ドキュメントを先に書く → 嘘だらけになります
- 既存テストを信じる → ハリボテを検証するだけです
削除すべきもの
- 古いテスト(仕様と無関係に書かれたもの)
- examples/ ディレクトリ(検証されない嘘の温床)
- 古い計画書(実行済み or 陳腐化)
- プロモーション資料(半年で陳腐化する)
残すべきもの
-
docs/specs/- 仕様書(単一の真実) -
tests/cmd/- 検証されるドキュメント -
.claude/tasks.md- 現在のタスク -
CLAUDE.md- 開発ルール(最小限に)
時間の目安
仕様書作成: 1セッション
テスト作成: 1セッション
ドキュメント修正: 1セッション
クリーンアップ: 1セッション
合計: 4セッション程度(1セッション = コンテキスト1回分)
まとめ
Before(2025 年 8 月)
❌ 3ヶ月停滞
❌ 複雑すぎるモノレポ
❌ 壊れたCI/CD
❌ 3言語ドキュメント管理
❌ 「動く」ことに満足
❌ 既存コードを信じる
After(2025 年 12 月)
✅ 動き出した
✅ シンプルなRust専用リポジトリ
✅ 独立した言語別リポジトリ
✅ 日本語のみに集中
✅ 「正しく動く」ことを目指す
✅ すべてを疑う姿勢
定量的な変化
| 項目 | Before | After |
|---|---|---|
| ファイル数 | 多数 | -109 |
| コード行数 | 多数 | -27,770 行 |
| テスト | 436(意味不明) | 88(仕様ベース) |
| README | 3 言語 | 3 言語 |
| リポジトリ | 1(モノレポ) | 3(言語別) |
最も重要な教訓
3 ヶ月停滞したプロジェクトを復活させるために必要だったのは、新機能の追加でも、技術的な改善でもありませんでした。
必要だったのは「疑う勇気」と「捨てる勇気」でした。
- 既存のテストを疑う
- 既存のドキュメントを疑う
- AI の報告を疑う
- 自分の「完成した」という認識を疑う
- そして、疑わしいものは捨てる
サンクコスト効果。なぜ賢い人でもハマるんだろう、と他人事のように思っていました。
積み上げたテスト、ドキュメント、CI/CD。「もったいない」という感情が邪魔をします。でも、動かないものを守っても意味がありません。「自分も実践しないと」と思って、捨てました。
この姿勢がリブートの本質であり、プロジェクトを持続可能にする鍵だと考えています。
おまけ:AI 実装には pre-commit が必須
AI による実装には、見落としがちな問題があります。
VSCode の自動整形が効かない:
人間が VSCode で書くときは、保存時に自動整形が走ります。しかし、AI が直接ファイルを書き換える場合、VSCode の拡張もプロジェクトの .editorconfig も効きません。
結果として:
- インデントがずれている
- 末尾の改行がない
- import の順序がバラバラ
- しょうもない lint 警告が大量に出る
CI がこれらの警告で失敗し、タイムロスします。
対策:pre-commit hook
プッシュ前に自動整形と lint が走る仕組みを整備しました。以下は diffx-python で実際に使用している設定です。
# .pre-commit-config.yaml (diffx-python)
repos:
# Rust formatting and linting
- repo: local
hooks:
- id: cargo-fmt
name: cargo fmt
entry: cargo fmt --
language: system
types: [rust]
pass_filenames: false
- id: cargo-clippy
name: cargo clippy
entry: cargo clippy -- -D warnings
language: system
types: [rust]
pass_filenames: false
# Python formatting and linting
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.6
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
# General checks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-toml
- id: check-added-large-files
# セットアップ
pip install pre-commit
pre-commit install
diffx-js(Node.js 版)では、husky を使っています。
// package.json (diffx-js)
{
"scripts": {
"prepare": "husky"
},
"devDependencies": {
"husky": "^9.1.7"
}
}
これで、AI が書いたコードも commit 時に自動整形されます。
リポジトリ:
まだリリース記事は書いてないですけど、以下も初期バージョンは完成していて、やりたいことは動きます。
今回紹介したリブート手法は、(少なくとも私のプロジェクトたちには)再現性があったということです。