チャチャチャおもちゃの抹茶っちゃ

ゲームのこととかプログラミングのこととか。気が向いたら書く。ブログタイトルは友人が考えました。

【OpenSpec】触ってみたので、ワークフローとGitHub運用を整理しておく

はじめに

最近 OpenSpec を一通り触ってみた。

触っただけだとそのうち忘れそうだったので、備忘録がてらまとめておく。 この記事は導入手順の説明というより、実際に触ってみて分かったワークフローの感触GitHub でどう運用するとよさそうか を整理する記事です。

細かい導入方法やコマンド体系は公式リポジトリを見たほうが早い。

github.com

この記事では、特に次のあたりを書いていく。

  • OpenSpec の基本ワークフローと、それぞれのフェーズの特徴
  • GitHub でチーム開発するときの運用案
  • SpecKit と比べてどう感じたか
  • 人間が直接使いそうなコマンド

なお、ここでの内容は現時点の自分の理解と、実際に applyarchive まで通してみたときの感触ベース。 大規模運用でどうなるかはまだ未知数なところもある。

TL;DR

先に雑にまとめるとこんな感じ。

  • OpenSpec は explore -> propose -> apply -> archive の流れで整理されている
  • 特に main specchange spec が分かれている構造が印象的だった
  • apply は手戻りを許容したまま進める前提で見たほうがよさそう
  • archive は、実装者が見るものとレビュアーが見るものを分けやすい
  • GitHub 運用では、commit 粒度と archive の扱いを最初に決めておかないとぶれやすい
  • SpecKit と比べて「より扱いやすい」とまではまだ断言しないが、境界は見えやすい

前提

試したAIエージェントツールは下記の2種類(自分が契約しているやつ)

  • claude-code
  • codex-cli

一旦2種類で同時に使えるかとかも見たくて試してました。

OpenSpec の導入手順についてはあまり書かない

自分が気にしたほうがよいと思ったのはむしろ、

  • OpenSpec がどういう単位で仕様を持つのか
  • 各フェーズで何をやるのか
  • 人間がどこで介入するとよいのか
  • GitHub のレビューとどう噛み合わせるか

のほうだった。

なので以下は、そのへんを中心に書く。

OpenSpec の基本ワークフロー

OpenSpec の全体像はかなりシンプルで、ざっくり言うとこう。

(任意) explore
   ↓
propose
   ↓
apply
   ↓
archive

それぞれのフェーズの役割はこんな感じ。

フェーズ 何をやるか 特徴
explore 仕様化前の壁打ち、前提整理、調査 実装しない。考えるためのフェーズ
propose 変更提案の仕様を作る proposal / design / specs / tasks を作る
apply 仕様に沿って実装する tasks.md を中心に進める
archive change を確定側へ寄せる changes/ の作業内容を整理し、main spec へ反映する

explore

ここは「手を動かす前に考える」ためのフェーズ。

  • 何を作りたいのかまだ曖昧
  • 既存コードを見ながら方針を考えたい
  • 選択肢の比較をしたい
  • まずリスクや前提を洗いたい

みたいなときに使うやつ。

自分の使い方としては、ここで明確にモードを切り替えるというより、

  • 「この変更を入れるとどこに影響するか」
  • 「先にリスクを洗いたい」
  • 「方針だけ整理したい」

みたいなときに適宜やる、くらいの距離感になりそうだった。

この手のこと自体は、普通に LLM に雑に聞いてもある程度は達成できると思う。 そのうえで OpenSpec の explore に意味があるとすると、実装に進まないためのガードレールがあること だと思う。

小さい修正なら省略でよさそうだし、画面追加や認証まわりみたいに影響範囲が広いものほど使いどころがありそう。

propose

ここがいわゆる仕様作成フェーズ。

propose で主に扱うのは次の4つ。

proposal.md
design.md
specs/<機能名>/spec.md
tasks.md

それぞれの役割はざっくりこう。

ファイル 役割 見るポイント
proposal.md なぜやるか、何をやるか スコープが妥当か
design.md どう作るか、どの判断を採るか 設計判断が妥当か
spec.md 何を満たすべきか Requirement と Scenario が明確か
tasks.md 実装タスクのチェックリスト 粒度と順番が適切か

一番大事なのはやはり spec.md だと思う。 Scenario がそのままテスト観点に近いので、後でレビューするときにもかなり効く。

この中では design.md がわりと重要そうだった。

もちろん文量は膨らみやすいのだけれど、コード全部を見るよりはだいぶ楽だし、実装や file diff を見る前に「想定とズレていないか」を確認しやすい。 SpecKit のときは spec.mdplan.md を頑張って見るか、出来上がった file diff 側で判断することが多かったので、そこは少し見やすくなりそうに感じている。

apply

apply は「仕様がある前提で実装を進める」フェーズで、中心になるのは tasks.md。 ここは「素直に流れるフェーズ」というより、手戻り込みで進めるフェーズ と見たほうがよさそうだった。

  • 今どこまで進んだか分かる
  • archive 前に完了確認しやすい
  • 途中で止めても再開しやすい
  • 人間が見ても今の状態を把握しやすい

という感じで、進捗管理の軸になりやすい。

ポイントは、apply を「tasks を機械的に消化するだけのフェーズ」と考えないほうがよさそうなこと。 実装中に、

  • 要件が足りない
  • 設計がずれている
  • タスクの順番が悪い

みたいなことに気づいたら、いったん止まって spec.mddesign.mdtasks.md を直してから戻ればよい。 この折り返しが普通にやれるのはわりと大事。

つまり apply は実装フェーズではあるのだけれど、仕様と実装の整合を取り続けるフェーズ でもある、という理解がしっくり来た。

archive

ここは役割分担が分かりやすい。

  • 実装者は進行中の changes/ を見る
  • レビュアーは archive 後の main spec を見る

という分かれ方ができるので、レビュアーの認識範囲とかなり合っている気がする。

この構造だと、レビュー時に毎回 change spec を全部振り返らなくてもよい。 少なくとも今の自分には、ここが SpecKit よりだいぶ見やすそうに映っている。

SpecKit では過去の spec をちゃんと振り返る運用にあまり乗れなかったし、メンテもしきれなかった。 それに比べると、OpenSpec の main specchange spec の分離は、何を source of truth として見るかを整理しやすい。

実際に archive まで通してみると、ここは概念的な確定操作というより、まず openspec/changes/<name>openspec/changes/archive/YYYY-MM-DD-<name>/ に移す、実質 mv に近い。 過去の change 自体は archive 配下に履歴として残る。

そのうえで、change 配下に delta spec があると、別で openspec/specs/<capability>/spec.md への sync も走る。 つまり archive で終わりではなく、change 側の差分が main spec 側へセマンティックにマージされる、という理解が実態に近かった。

ただ、新規 capability だと main spec の PurposeTBD - created by archiving change ... の雛形がそのまま残る。 archive は main spec の叩き台までは作ってくれるけれど、最終的な文面整形は人間かエージェントが別でやる必要がある。

とはいえ、

  • main spec 側の仕上げは見る必要がある
  • archive した直後に長く放置すると、main spec の仕上げと approve / merge の間にズレる余地がある

ので、archive したら main spec の仕上げまで含めて短い間隔で片付けて、その流れでレビューと merge まで持っていく運用がよさそう。

GitHub でのチーム開発運用をどうするか

ここがたぶん一番悩ましいところ。

OpenSpec 自体のワークフローは分かりやすいけれど、GitHub の PR 運用に載せるときは、

  • どの粒度で commit するか
  • どの粒度で PR を切るか
  • いつレビューするか
  • どこを重点的に見るか
  • archive をいつやるか

を決めないと、わりとすぐに散らかりそうだった。

1PR型と2段階型は両方使う前提がよさそう

ここはどちらかに寄せ切るより、両方使う前提 のほうがよさそう。

大分類としては 1PR型2段階PR型 の2つで見れば十分で、Draft 先出しは別系統というより 1PR型 の中で早めに方向性レビューを入れる亜種として扱うのが収まりがよい。

例えば issue テンプレートに、

  • 1PR で進める
  • 2段階PRで進める

のチェックを置いて、必要なら 1PR 側で Draft 先出しを選ぶ、くらいがちょうどよさそう。

運用プランのパターン

ざっくり考えると、大きくは次の2パターンで整理できる。

パターン 概要 向くケース メリット デメリット
1PR型 propose -> apply -> archive を1本のPRでやる。必要なら propose 後に Draft を先に開く 部分修正、小さめ機能追加、1人担当、方向性レビューを早めに入れたい変更 1つの change をそのまま閉じられる。Draft 亜種なら早い段階で手戻りも減らしやすい archive 後は main spec の仕上げからレビュー/merge までを短く回したい
2段階PR型 changes を作るPRと、それを実装するPRを分ける 機能追加、画面追加、影響範囲が広い変更 複数changeの合意を先に取りやすい PR が増える

1. 1PR型

たぶん一番現実的。

流れとしてはこんな感じ。

1. propose
2. 仕様を軽く見直す
3. apply
4. archive
5. PR レビュー

部分修正や、そこまで大きくない機能追加ならこれで十分だと思う。 この型を採るなら、PR 内の commit の切り方はルール化したい。

  • propose コミット
  • 実装コミット群
  • archive コミット

これは「できれば」ではなく、ほぼ必須だと思っている。

方向性レビューを早めに入れたいなら、この 1PR 型の中で propose 後に Draft PR を開く亜種を使う、という整理でよさそう。

2. 2段階PR型

こっちは重めの変更向け。

PR1: changes を作って仕様レビュー
PR2: その changes を実装して archive まで進める

新機能追加、画面追加、権限設計が絡む変更、外部連携、移行を含む変更あたりはこのやり方がかなり合いそう。 特に、実装前に複数の change を切って合意したいケースではやりやすい。

その代わり、当然 PR 数は増える。 小さい修正にこれを毎回やると重い。

1PR型の亜種: Draft 先出し

これは 1PR 型の中で、仕様レビューのタイミングだけ前に寄せる形。

1. propose 後に Draft PR を開く
2. スコープや仕様だけ先にコメントをもらう
3. apply と archive を進める
4. Ready for review にする

PR は増やしたくないけれど、仕様レビューは早めに入れたい、というときに使いやすい。 その代わり、archive まで進んだら main spec の仕上げも含めて approve / merge までをあまり空けない運用にしたい。

規模別にどのプランが合いそうか

雑に当てはめるとこんな感じ。

変更規模 合いそうな運用
部分修正 文言修正、小さいバグ修正、軽微なAPI変更 1PR型
機能追加 API追加、画面の一部機能拡張、バックエンド処理追加 1PR型 or 1PR型(Draft先出し)
画面追加 新規画面、複数画面にまたがる導線追加 1PR型(Draft先出し) or 2段階PR型
影響範囲が広い変更 認証、権限、課金、データ移行、外部連携 2段階PR型

Draft で見るか、archive 後に見るか

運用上の分かれ目は、結局ここに集約されそう。

Draft で先に見る場合

  • archive 前に仕様や方向性をレビューできる
  • 仕様修正も archive 前に吸収しやすい
  • archive 後の修正を減らしやすい

archive 後に見る場合

  • レビュアーは main spec を見ればよい
  • 見るべき source of truth がはっきりする
  • その代わり、archive 後の修正が必要になったときの扱いは決めておきたい

結局ここは、Draft 型で早めに見るのか、archive 後の main spec を基準に見るのか、という話だと思う。

コミット粒度

OpenSpec を入れるとどうしても markdown とコード変更が一緒に発生するので、全部を1コミットに押し込むと見づらい。

いったんこんな粒度がよさそう。

1. propose を作ったコミット
2. 実装コミット
3. テスト調整コミット
4. archive コミット

もちろん実装の途中ではもっと細かく刻んでもよいけれど、少なくとも propose / apply / archive は分ける前提にしたい。 特に archive コミットは絶対に分けたい。

PR 粒度

PR 粒度は change に寄せるのが自然そう。

加えて、

  • 1 PR に複数の機能を押し込まない
  • unrelated な fix をついでに入れない
  • OpenSpec の change 名と PR のタイトルをある程度対応させる

くらいはルールにしたほうがよさそう。

レビューのタイミングと観点

AI が出す量を全部人間が細かく追うのは、たぶん現実的ではない。 というか、それをやると AI を入れたうまみがかなり減る。

なので、「どこを見るか」を分けたほうがよい。

propose 時点で見たいもの

仕様レビューのタイミングでは、主にこのへん。

  • スコープが妥当か
  • 機能名が変じゃないか
  • Requirement が抜けていないか
  • Scenario がレビュー可能な粒度になっているか
  • 認証、認可、セキュリティ、権限境界の考慮漏れがないか
  • design.md の方針が想定とズレていないか

逆にこの段階では、実装詳細の細かいコードスタイルまでは見なくてよい。

PR レビューで見たいもの

実装まで入った PR なら、見る順番はかなり割り切ってよさそう。

  1. openspec/specs/.../spec.md
  2. design.md
  3. file diff を見て、想定外のファイルが混ざっていないか
  4. リスクの高い実装だけ見る
  5. テストは必要なところを摘んで見る
  6. archive 配下の artifacts

TDD 前提で spec を起点にテストを書いているなら、spec を見ればテスト観点もある程度は読める。 テストが大量にあると全部を網羅的にレビューするのは現実的ではないので、main spec と design を先に見て、そこから必要な実装とテストに降りるほうが合っていそう。

全部見なくてよいものは何か

ここはかなり割り切ってよいと思っている。

  • 細かい propose の生成過程を全部は追わない
  • archive の単純な移動っぽい差分は全部精読しない
  • AI が吐いた説明文を一字一句レビューしない

その代わり、

  • spec.md
  • design.md
  • file diff に想定外のファイルがないか
  • 認証、認可、セキュリティ、課金、データ破壊、移行
  • 必要なところでテストが担保になっているか

は時間をかけてでも見たい。

要するに、全部同じ重みで見ない ということ。

レビュー時の見る順番

もう少し具体的に書くと、自分は次の順番がよさそう。

1. 何の change かを把握する
2. archive 後の main spec を見る
3. design を見る
4. file diff を見て想定外のファイルがないか確認する
5. 気になる実装を見る
6. 必要なテストだけ見る
7. archive の差分は最後に確認する

この順番なら、「何を満たす実装なのか」と「そもそも余計な変更が混ざっていないか」の両方を見やすい。

修正が入るときの考え方

レビュー指摘で直すときも、自分で「あ、これ違うな」と気づいて直すときも、本質的には同じだと思っている。 違いは archive 前か後か だけ。

まだ archive 前なら

一番素直。

  • spec.md を直す
  • 必要なら design.mdtasks.md も追従させる
  • 必要なら validate する
  • そのあと実装とテストを直す

propose が終わったあとでも、apply の途中でも、同じ change の中で育て直せばよさそう。

archive 済みなら

ここは2択になりそう。

  • 仕様変更を伴わないなら、そのまま修正する
  • 仕様変更を伴うなら、archive コミットをなくして spec 修正からやり直すか、別 PR で新しい change にする

後者はプロジェクトや PR の状態次第だけれど、少なくとも「archive したものに何となく追記していく」は避けたい。

CI に OpenSpec のコマンドを入れるべきか

ここはまだ悩んでいる。

候補としては validate が一番自然。

例えばこんな感じ。

openspec validate --all

入れる理由は普通にあって、

  • 1つ壊れた spec が他の PR にも影響しうる
  • フォーマット崩れを共有ブランチに持ち込みたくない

あたりは素直に効きそう。

一方で、これは本来ローカルで済ませたい話でもあるし、CI を増やしすぎたくない気持ちもある。 なので validate を CI に入れるべきかは、現時点ではまだ結論を出していない。 少なくとも archive の自動化や、CI に LLM を噛ませるようなことはやりたくない。そこは決定的であってほしい。

SpecKit との差

先に印象だけ書くと、

  • SpecKit はレビューがしんどかった
  • SpecKit はドキュメント量が多くなりがちだった
  • 過去ドキュメントを後から読み返す運用には、今のところあまり乗っていない
  • source of truth がどれか分かりづらくなる感じが少しあった
  • analyzechecklist は自分はあまり活かしきれた感覚がなかった

という感じ。

比較表

観点 OpenSpec SpecKit 今の所感
ワークフロー explore -> propose -> apply -> archive が明確 analyze / clarify / plan / tasks など複数段階 OpenSpec のほうが流れを覚えやすい
仕様の持ち方 main specchange spec を分離 ドキュメントが増えやすい OpenSpec のほうが境界は見えやすい
ドキュメント量 まだ何とも言えない 多くなりがちだった 規模に対して過剰になるとつらい
apply 中の運用 手戻り込みで artifacts を更新しながら進めやすい 自分はあまりしっくり来なかった OpenSpec のほうが戻り先は見えやすい
archive / 確定 明示的な archive がある Git 運用に寄せる印象 OpenSpec のほうがレビュアーの見る場所を寄せやすい
過去資料の活用 これから次第 まだ有効活用できていない ここはどちらも運用設計が必要

いまの仮説

SpecKit でしんどかったのは、ツールそのものが悪いというより、扱っていた変更規模に対して少し重かった可能性もある。 その結果として、

  • ドキュメントが増える
  • どれを見ればよいか迷う
  • source of truth がぶれる
  • レビューが重くなる

あたりが起きていたのかなと思っている。

OpenSpec もまだ実運用でどこまで扱いやすいかは分からない。 ただ、少なくとも今触った範囲では、仕様と進行中変更の境界が見えやすい という印象はあった。

付録: 人間も使いそうなコマンド

AI エージェントが内部で使うコマンドも多いのだけれど、人間が直接使いそうなのはこのへん。

コマンド 用途 使いどころ
openspec list change 一覧を見る 何が進行中か確認したいとき
openspec list --specs 確定 spec 一覧を見る 今ある機能一覧を見たいとき
openspec show <name> 中身を確認する レビューや確認用
openspec status --change <name> change の状態を見る apply 前後や archive 前の確認
openspec validate <name> その change/spec を検証する 手修正したあと
openspec validate --all 全体検証 CI やローカル最終確認
openspec archive <name> change を archive する 実装完了後の整理
openspec view 状態を俯瞰する 人間が一覧を見たいとき

特に人間が直接使うなら、

  • list
  • show
  • status
  • validate

あたりが実用的そう。

おわりに

OpenSpec については、SpecKit で見えてきた課題に対してどうなんだろう、という気持ちで触り始めた。

特に良かったのは、

  • main specchange spec が分かれていること
  • レビュアーが最終的に main spec を見ればよい構造になっていること
  • design.md を見ることでコード差分に降りる前にズレを見つけやすそうなこと
  • archive で change の終わりと確定側の境界が明確になること

のあたり。

もちろん、これでルールが不要になるとは全く思っていない。 むしろ、GitHub 上でどうレビューするか、1PR型と2段階型をどう使い分けるか、archive をいつ切るか、commit をどう分けるか、みたいなところは最初から詰める必要がある。

しばらくは、このへんの最適なルールを模索しつつ、SpecKit でしんどかった点がどこまで改善されるのかを見ていきたい。

【zsh】自分のシェル環境を紹介する

TL;DR

dotfilesがそこそこ育ってきたので、どんな設定をしているか紹介。chezmoi で管理していて、zsh + Neovim + tmux + fzf + lazygit あたりを中心に設定を行っている。

github.com

はじめに

毎日向き合うターミナル。便利にすればそれだけ楽になるし、見た目を良くすればそれだけモチベーションにつながる。暇な合間でちまちまdotfilesを育ててきた。

最初は .bashrc.vimrc だけのシンプルな構成だったのが、zsh への移行、Neovim の導入、fzf による検索体験の強化……と徐々に進化して、今ではそれなりの規模になってきたので、ここで一度まとめておこうと思う。

利用環境

このdotfilesは主にLinux環境で使っている。手元のWindowsマシンからクラウド上のLinux VMSSHで繋いで作業するスタイルで、仕事でもプライベートでもだいたいこの構成。

Windows側のターミナルソフトは長いこと RLogin を使っていたんだけど、最近 Windows Terminal を試してみている。以前のWindowsの標準ターミナルは貧弱であまり使う気にならなかったけど、最近のWindows Terminal は意外と悪くない。ショートカットキーやフォント、透明度などの一通りのカスタマイズができるようになっている。

今回のスクリーンショットWindows Terminalのもの。

現在のターミナル環境

普段のzshはこんな感じ。Powerlevel10kのRainbowテーマを設定調整したもので、gitのステータスや実行時間なども右側に表示される。

透過して後ろのブラウザとかが見えるのは良い

tmuxでNeovim、lazygit、Claude Codeを並べると、ターミナルだけでIDEっぽい開発環境が作れる。

拡張次第ではIDEにそのままできちゃう

dotfiles管理: chezmoi

dotfilesの管理には chezmoi を使っている。

chezmoiはGo製のdotfilesマネージャで、dotfilesのよくある管理方法としてはシンボリックリンクを張る方式(stowなど)があるけど、chezmoiは実ファイルをコピーして展開する方式を取っている。リポジトリ側のファイルを dot_ プレフィックスで管理していて、chezmoi apply するとそれが ~/.zshrc などの実際のパスに展開される。

# 新しいマシンへのセットアップはワンライナーで完了
$ sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply mattyan1053

これだけで新しいマシンにすべての設定が展開されるので、環境構築がかなり楽になる。

テンプレート機能

chezmoiのいいところのひとつがテンプレート機能。.tmpl ファイルを使えば、OS やマシンごとに設定を分岐できる。たとえば .zshrc.tmpl の中で、zshのパスが環境によって違う場合に分岐させたりしている。

外部プラグインの管理

外部プラグインの管理は .chezmoiexternals/ 配下の .toml ファイルで行っている。Gitリポジトリをcloneしてくる形式で、powerlevel10k や各種zshプラグインをまとめて管理できる。

# 例: zsh-autosuggestions の外部定義
[".zsh/zsh-autosuggestions"]
    type = "git-repo"
    url = "https://github.com/zsh-users/zsh-autosuggestions.git"
    refreshPeriod = "168h"

refreshPeriod を設定しておくと、chezmoi update のタイミングで定期的に最新版を取得してくれる。プラグインを個別にcloneして管理する手間がなくなるので、かなり楽。

普段の運用

設定ファイルを編集したいときは、直接 ~/.zshrc を編集するのではなく、chezmoi edit ~/.zshrcリポジトリ側のファイルを編集して chezmoi apply で反映する流れ。逆に、手元で直接編集してしまった場合は chezmoi re-addリポジトリに取り込める。

プラグインを個別にcloneして管理する手間がなくなるので、かなり楽。

シェル: zsh + Powerlevel10k

メインのシェルはzshbashから移行した経緯があり、bashでログインした場合も自動でzshに切り替わるようにしている。

zshの設定というと oh-my-zsh がよく紹介されていて試したこともあったが、結果的には採用していないことにした。oh-my-zsh は確かに便利なんだけど、使わないプラグインが大量に付いてきたり、中身がブラックボックスになりがちだったりするのがあまり好きじゃなかった。自分で必要なプラグインだけを選んで入れるほうが、何が動いているか把握できるし、dotfilesを自分で育てている感じがあって楽しい。

Powerlevel10k

プロンプトには Powerlevel10k を採用。設定はかなりカスタマイズしていて、2行表示のRainbowスタイルにしている。

左側にはディレクトリ、右側にはgitのステータス、コマンドの実行時間、現在時刻などを表示している。

見た目を華やかにしたくて、デフォルトの記号をいろいろ絵文字に差し替えた。たとえばgitステータスまわりだと、設定するのは下記の部分。

# ブランチアイコン
typeset -g POWERLEVEL9K_VCS_BRANCH_ICON=''

# ディレクトリのアイコン
typeset -g POWERLEVEL9K_DIR_HOME_VISUAL_IDENTIFIER_EXPANSION=''
typeset -g POWERLEVEL9K_DIR_DEFAULT_VISUAL_IDENTIFIER_EXPANSION=''
typeset -g POWERLEVEL9K_DIR_DEFAULT_NOT_WRITABLE_VISUAL_IDENTIFIER_EXPANSION=''

# behind/ahead
(( VCS_STATUS_COMMITS_BEHIND )) && res+=" ${clean}⬇️ ${VCS_STATUS_COMMITS_BEHIND}"
(( VCS_STATUS_COMMITS_AHEAD  )) && res+="${clean}⬆️ ${VCS_STATUS_COMMITS_AHEAD}"
# merge
[[ -n $VCS_STATUS_ACTION     ]] && res+="  ${conflicted}🔀 ${VCS_STATUS_ACTION}"
# conflict
(( VCS_STATUS_NUM_CONFLICTED )) && res+="  ${conflicted}${VCS_STATUS_NUM_CONFLICTED}"
# staged / unstaged
(( VCS_STATUS_NUM_STAGED     )) && res+="  ${modified} ${VCS_STATUS_NUM_STAGED}"
(( VCS_STATUS_NUM_UNSTAGED   )) && res+="  ${modified} ${VCS_STATUS_NUM_UNSTAGED}"

プラグイン構成

zshプラグインは以下を導入している。

プラグイン 用途
zsh-completions 補完の強化
zsh-autosuggestions コマンドのサジェスト表示
zsh-syntax-highlighting コマンドのシンタックスハイライト
zsh-history-substring-search 履歴の部分一致検索(Ctrl+↑/↓)
fzf-tab Tab補完をfzfで表示
zsh-fzf-history-search 履歴検索をfzfで表示

自作プラグイン: docker compose exec mode

最近追加した機能として、zsh-docker-compose-exec-mode という自作のzshプラグインがある。

Dockerコンテナ内で作業するとき、毎回 docker compose exec app ... と打つのが面倒だったのが開発動機。このプラグインを使うと、dce start <service> でモードに入り、以降のコマンドが自動的に docker compose exec 経由でコンテナ内で実行されるようになる。

# コンテナを起動しておく
$ docker compose up -d myapp

# exec モードに入る
$ dce start myapp
コンテナ 'myapp' に入りました。終了するには 'dce end'。

# 以降のコマンドは自動的にコンテナ内で実行される
[in myapp]$ ls /app
# 実際には docker compose exec -i myapp ls /app が実行される

[in myapp]$ npm test
# 実際には docker compose exec -i myapp npm test が実行される

# モードを抜ける
$ dce end
exec モードを終了しました。

仕組みとしては、zshのZLE(Zsh Line Editor)のウィジェットを差し替えて、Enterキーが押されたタイミングでコマンドバッファの先頭に docker compose exec -i <service> を自動付与している。dce 自体のコマンドはそのまま通すようにしているので、モード中でも dce enddce status はちゃんと使える。

プロンプトにも [in myapp] のようなマーカーが表示されるので、今どのコンテナに向けてコマンドを打っているかが一目でわかる。--root オプションでroot実行もできるし、DCE_PROMPT_MODE 環境変数でマーカーの表示位置も変えられる。

普段の開発で docker compose exec をあまりに多用するので作ってみた。使用感はまだ試用中。

ファジーファインダー: fzf

fzfはいろんな場面で使っていて、いくつかカスタム関数を用意している。

  • fgitlog: gitログをfzfで表示して、Enterでコミットの詳細が見られる。サッとログを確認したいときに便利
  • rgf: ripgrepの検索結果をfzfに渡して、batでプレビュー表示する。該当行がハイライトされるので見やすい
  • fcheckout: ブランチ一覧をfzfで出して、選んだブランチにチェックアウトする
  • fcd: ディレクトリをfzfで検索してcdする

他にも、fzf-tabでTab補完をfzfに置き換えたり、履歴検索をfzfで行ったりしている。デフォルトのものよりもだいぶ使いやすくなる。

エディタ: Neovim

普段の開発では基本的にVSCodeを使っているんだけど、ターミナルで作業しているときにちょっとしたファイルの編集をしたい場面がちょくちょくある。設定ファイルをサッと直したいとか、diffを見ながらその場で修正したいとか。そういうときにNeovimがあると、わざわざVSCodeに切り替えなくて済むので便利。

設定は ~/.config/nvim/init.luaLuaで書いていて、プラグイン管理は Packer。そこまでコアに使い込んでいるわけではないので、重すぎない拡張をいくつか入れるくらいがちょうどいい。

導入プラグイン

  • Telescope: ファイル検索やgrep。<Leader>ff でファイル検索、<Leader>fg でlive grep
  • nvim-tree: ファイルツリー。<Leader>e でトグル
  • nvim-treesitter: シンタックスハイライトの強化
  • bufferline: バッファをタブのように表示。Shift+L/H で移動
  • nvim-web-devicons: アイコン表示

最低限の構成だけど、Telescopeでファイル検索やgrepができるだけで素のvimとは体験がだいぶ違う。ちょっとしたエディタ+αくらいの立ち位置で、自分の使い方にはこれくらいが合っている。

ターミナルマルチプレクサ: tmux

tmuxもそれなりにカスタマイズしている。

主なキーバインド

キー 動作
Ctrl+a プレフィックスキー(デフォルトの Ctrl+b から変更)
h/j/k/l ペイン間移動(Vim風)
H/J/K/L ペインリサイズ
| 縦分割
- 横分割
Ctrl+z ペイン最大化
Shift+←/→ ウィンドウ切り替え
m/M マウスOFF/ON切り替え

プレフィックスCtrl+a にしているのは、Ctrl+b だとVimの操作と被ることがあるため。ペイン移動はもちろんVim風の hjkl で、分割は |(縦)と -(横)で直感的にわかるようにしている。

Git: コミットメッセージを楽にする

Gitmoji コミットテンプレート

コミットメッセージのフォーマットは Gitmoji を採用していて、:emoji: [scope] message の形式で統一している。

lazygit カスタムコマンド

lazygitに Ctrl+c でGitmoji選択メニューを出すカスタムコマンドを設定している。

  1. 絵文字をメニューから選択(日本語で説明付き)
  2. スコープを入力(任意)
  3. コミットメッセージを入力
  4. 確認して完了
:sparkles: [API] Add user auth functions

みたいなコミットメッセージが、メニューに沿って入力するだけで作れる。絵文字の種類を覚えていなくても、「新機能を導入する」「バグを修正する」のような説明から選べるので楽。

絵文字は頻度別にカテゴリ分けしていて、High Frequency(sparkles, bug, recycle など)、Dependencies & Build、Management & Operations、Specific Contexts と整理している。実際よく使うものが上に来るようにしているので、選ぶのも速い。

CI

GitHub Actionsで簡単なCIも回している。chezmoiのテンプレート構文のチェックと、dry-runでの適用テストを行って、壊れた設定をmainにマージしないようにしている。

エイリアス

地味だけど便利なエイリアスたち。

alias ll='ls -l --color=auto'
alias history='history -i'       # タイムスタンプ付き
alias lgit='lazygit'
alias gl='git log --oneline --graph --decorate'
alias cdgitroot='cd $(git rev-parse --show-toplevel)'

cdgitroot は地味に使用頻度が高い。Gitリポジトリのどこにいてもルートに戻れるので、深い階層で作業しているときに重宝する。

進化の変遷

gitのログを見返してみると、このdotfilesの進化が見えてきて面白い。

  1. 初期: .bashrc.vimrc だけのシンプル構成。chezmoiで管理を始める
  2. bash: Git補完・プロンプト追加、fzfの導入、エイリアス整備
  3. zsh移行: Powerlevel10k導入、各種zshプラグイン追加
  4. ツール充実: Neovim + Telescope、tmux設定、lazygit導入
  5. fzf強化: fgitlog、rgf、fcheckout、fcdなどのカスタム関数追加
  6. 最近: lazygitのGitmojiコマンド、docker compose execモード、CI追加

おわりに

dotfilesは一度作ったら終わりじゃなくて、今後も新しいツールを試したり、既存の設定を見直したりしながら、育てていきたい。

リポジトリは公開しているので、気になる設定があれば参考にどうぞ。

github.com

【Cline】MCPサーバをPodman環境で動かす

TL;DR

github.com

こいつをローカルやDocker環境ではなく、Podman環境で動かしたい

注意

ローカルに落として動かすだけで、本番環境運用とかではないので、ファイルパーミッションなどはガサツに設定するのも認めています

やり方 (Git Tools)

どれを動かしても良いのだけれど、チュートリアル的に良いと思ったのでこの中のGit Toolsを動かす

イメージのビルド

$ git clone git@github.com:modelcontextprotocol/servers.git ./mcp-servers
$ cd mcp-servers/git/src
$ vi Dockerfile

Dockerfileを以下のように修正する。(--mountがうまく動かなくてイメージのビルドに失敗するため)

(略)

COPY uv.lock uv.lock # 追加
COPY pyproject.toml pyproject.toml # 追加

# Install the project's dependencies using the lockfile and settings
RUN --mount=type=cache,target=/root/.cache/uv \
    # --mount=type=bind,source=uv.lock,target=uv.lock \ # コメントアウト
    # --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ # コメントアウト
    uv sync --frozen --no-install-project --no-dev --no-editable

(略)

WORKDIR /app
 
COPY --from=uv /root/.local /root/.local
COPY --from=uv /app/.venv /app/.venv # Podmanコンテナはrootユーザがローカルユーザにマッピングされるためchownを外す

(略)

あとはビルドしておく

$ podman build -t mcp/git .

Clineで動かす

vscode-serverの設定を変更する

.vscode-server/data/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json

{
  "mcpServers": {
    "github.com/modelcontextprotocol/servers/tree/main/src/git": {
      "command": "podman",
      "args": ["run", "--rm", "-i", "-v", "/home/mattyan1053:/home/mattyan1053", "--privileged", "mcp/git"],
      "disabled": false,
      "autoApprove": []
    }
  }
}

ボリュームマウントのパスは許可したいところをホスト側と揃えるようににマッピングする