soujoでこんなことがあります
-
tasks.mdのチェックボックスは全部埋まった。動かしてみたら、最初に決めた受け入れ条件のうち 3 つが、どの層にも割り当てられていなかった。 -
2 週間ぶりにリポジトリを開いた。「次すること」は書いてある。でもその前に、そもそもこのプロジェクトはどこまで来ていて、何が未解決のまま先送りされているのか、思い出せない。
私? ええ。5ヶ月放置してるものがあります……。
-
「このプロジェクトでは外部依存を増やさない」と最初に決めてたけれど、3 日後のセッションで、AIモデルは何の悪気もなくライブラリを足したりする。
前回の記事で、層序(Soujo)は「どこで止まっても、次の一手が書いてある」ことを約束しました。
使っているうちに分かったのは、次回やることを残すだけでは足りない場面が二つあることに気がつきました。
- プロジェクトを数週間空けてしまったとき。
- 層が尽きたとき。ちゃんと仕様を満たしたのか不明になったり不安になったりする。
v0.2 から v0.4 で足したものは、ほぼこの二つです。
前回「未決」と書いたことについての対応
| 前回の未決 | 現在 |
|---|---|
LOG.md が長くなったときの巻き取り |
v0.2.0 soujo log rotate で解消 |
| Codex がフックを信頼したときの挙動 | 未決のまま。Codex では引き続き $close で止まる |
モデルがプラグインの置き場所へ cd して、そこの .soujo/ を読む |
v0.3.0 で CLI 側が拒否するようになった |
spec / plan はコミットしないので、最初の layer done まで Stop フックが警告する |
未決のまま。converge も同じ扱い |
1. 日誌が膨らむ問題 — soujo log rotate
LOG.md は 1 エントリ 3 行で、上限は CLI が守ります。でも「1 エントリの上限」は「ファイルの上限」ではない。層を 30 個閉じれば 90 行になり、読み返す気が失せます。
soujo log rotate は、今月より前のエントリを月ごとに .soujo/LOG-YYYY-MM.md へ移して、log: rotate <月数> でコミットします。
守っていることが二つあります。
- 最初のエントリより前の本文と、最後のエントリは残す。
soujo resumeの前回:が消えないように。 - 他に未コミットの変更があるときは何もしない。途中で止まったら、再実行で同じ回転を続きから終える。エントリを二度動かさない。
いつ回すかは覚えなくてよい。soujo next check が「2 ヶ月分以上動かせる」ときに勧めてきます。
2. 数週間ぶりに戻る問題 — soujo brief と「節目」
resume の 4 行は「直前の続き」を見せるものです。3 日空けたくらいならそれで足ります。
3 週間空くと足りない。L7 の続きをやれと言われても、L1〜L6 で何を決めて、何を先送りしたのかが頭にない。会話履歴は当然ない。
soujo brief は、プロジェクトが今どこにいるかを 5 行で出します。
材料は LOG.md の「節目」エントリです。v0.3.0 から、工程の境目でスキルが 節目: のエントリを残します。
| いつ | 誰が書く | 何を |
|---|---|---|
spec が終わったとき |
spec スキル | 何が決まり、何が開いたままか |
plan が層を足したとき |
plan スキル | 同上 |
| 最後の層を閉じたとき | go スキル | 同上 |
つまり LOG には二種類のエントリがあります。層ごとの「やったこと」と、工程ごとの「ここまでで何が確定したか」です。このうちbrief は後者だけを読むようにしました。
resume は空白の日数を数えていて、3 日以上空いていれば 再開: の行の末尾に「先に soujo brief」と付けます。resume スキルはそれを見て brief も走らせ、4 行 + 5 行を返します。
結局、私たちは9行読むことになりますが、それでも会話履歴を読み返すよりはるかに認知負荷は軽いです。
なぜ 3 日かというと、私の経験から3日を選んでいます。
2日目くらいまでは全体像を覚えてますが、3日目は記憶があやふやになることが多いからです。もちろん複数のプロジェクトを抱えてる方は、もっと早くあいまいになるかと思います。
3. 「最初に決めたこと」が守られない問題 — 原則に鍵をつける
前回の記事で、Spec-kit の constitution には触れませんでした。プロジェクト全体の原則を一つのファイルに置いて、以後のすべての判断の根拠にする仕組みです。これは良い考えだと思いつつ、文書が一つ増えてしまうので、採用は見送っていました。
ただv0.3までの段階だとまだまだ仕様としては薄いなあ、と思っていました。
そこでv0.4.0 で、SPEC.md の内容を増やすこととしました。
たとえば
## 原則
- P1 依存ゼロ — 実行時依存を追加しない
- P2 五行 — 標準出力は 5 行以内
- P3 拒否優先 — 忠実に記録できないときは何も書かない
## 受け入れ基準
- A1 …
- A2 …
のようにしました。
形式は CLI が検査します。
- 原則は私の経験から 7 つまで。もちろん少ないと思う方もいらっしゃると思いますが、最近のAIモデルはあまり条件を増やさないほうがいいらしいので、このあたりにしておきたいと思います。
- 各行は
- P<n> <名前> — <判定できる 1 文>。 - 鍵(P1, A3)は二度と使い回さない、振り直さない。A3 を削ったら A3 は欠番とします。あとから LOG や commit に書かれた「A3」が別のものを指さないように。
- 満たさない行があれば
soujo next checkが行番号つきで警告するようにしました。ただし## 原則## 受け入れ基準の見出しがない SPEC は検査しない。これは以前からのsoujoと互換性をとるためです。
原則は SPEC の他の節、各層の完了条件、既存コードより優先します。
そして go は、完了条件が原則を破らずには満たせないときだけ、質問を 1 つします。原則の鍵を名指しして、番号付きの選択肢で。それ以外は確認なしで進む。前回書いた「AIモデルの邪魔をしない」はそのままです。
review も原則を読みます。違反があれば表の先頭に、鍵つきで 1 行ずつ。
spec スキルは、質問 7 問のうち 1 問で原則を決めます。候補は、それまでの回答、設定ファイル、リポジトリに書かれた慣習から拾う。決まらなければ節を空のままにする。「未定」と書くと next check に警告されるので。
converge 仕様を満たしたかどうか
これが v0.4.0 の中心です。
Spec-kit に /speckit.converge という工程があります。実装後にコードを spec と照合し、足りないものをタスクとして追記する機能です。
層序では、層が尽きたときに plan へ戻る設計でした。これだと「plan が最初に書き漏らした受け入れ基準」は拾えません。
全部の層が終わっても、A3 が抜けたまま「完了」になる。
v0.4.0 から、最後の層を閉じた go と resume は plan ではなく converge を指します。
こんなイメージです。
spec → plan → go → go → … → converge → (足りなければ) go → converge → … → plan
converge がやること:
-
SPEC.mdの鍵つき行(P/A)とPLAN.mdを読む。 -
コードは、
layer:とwip:コミットが触ったファイルと、各基準の語で検索して見つかるファイルだけ読む。リポジトリ全体は読みません。コンテキストを守るためです。
-
各基準・各原則を分類すると以下のようになります。
| 分類 | 意味 |
|---|---|
met |
満たしている。path:line の証拠つき |
missing |
該当するコードがない |
partial |
一部だけ |
contradicts |
原則に反している |
unrequested |
誰も頼んでいないのに存在するコード |
- 未完の層の完了条件がまだ閉じていないギャップを、1 ギャップ = 1 層として
PLAN.mdの末尾に追記する。完了条件の末尾に鍵と分類を付ける:(A3 partial)。原則違反を先に。未完の層は合計 12 まで。 -
PLAN.mdの既存行には一切触らない。SPEC もコードも変えません。 - ギャップがなければ「収束した」と記録し、
NEXT.mdをplanに向ける。
unrequested は層にしません。消すかどうかは人が決めることなので、節目エントリの「開いたまま」の行に載せるだけです。
soujo next check が PLAN.md に警告を出している状態では、何も足さずに止まる。壊れた PLAN に追記して、さらに壊すよりよい。
converge の effort は high 固定です。spec plan と同じ扱い。層を掘るのは medium でよくても、掘った結果を仕様と突き合わせる工程は考えさせる。
5. 小さいが効いたもの
| 変更 | なぜ |
|---|---|
| ホストのプラグインディレクトリの中では動かない | モデルがスキルの置き場所へ cd して、そこの .soujo/ を読み、init やコミットがそこに落ちることがあった。実パスに .claude/plugins か .codex/plugins があれば、フックは黙り、他のコマンドは 1 行出して終了する |
ホームディレクトリ以下のパスを ~ で表示 |
端末からコピーした 1 行に、ユーザー名が乗らないように。警告文も含む |
soujo resume | head -1 で EPIPE の stack trace を出さない |
静かに終わる。ディスク満杯など本当の書き込み失敗は 1 行で報告して失敗する |
.soujo/ の中で同じ実体を指すファイル(symlink、大文字小文字違い、ハードリンク)を拒否 |
LOG.md → PLAN.md の symlink で、layer done が PLAN にログを追記していた |
別の soujo が書いている途中の一時ファイルを消さない |
二つ同時に走ったときに、片方が片方の書きかけを消していた |
| 空白の日数を暦日で数える | 時刻差だと「金曜夜〜月曜朝」が 2 日になる |
どれも「忠実に記録できないなら何も書かない」の一点に集約されます。
記録の道具が記録を壊すのが、いちばん困りますので。
Spec-kit のアイデアを導入する
前回、Spec-kit については「文書が多く長く、複数フェーズの状態を頭で追う必要があって大変」と書きました。それは今も変わりません。
ただ v0.4 で、Spec-kit から二つの考え方を採用しました。
| Spec-kit | 層序 | 違い |
|---|---|---|
| constitution(別ファイル) |
SPEC.md の ## 原則(7 行まで、鍵つき) |
ファイルを増やさない。形式を CLI が検査する |
| converge(残作業をタスクに追記) |
converge(残作業を層として PLAN に追記) |
1 ギャップ = 1 層 = 30 分。追記だけで既存行を触らない。読むコードを絞る |
まだ未決のこと
-
spec/plan/convergeはコミットしないので、次のlayer doneまで Stop フックが警告する。 - Codex がフックを信頼したときの挙動をどうするか
更新方法は以下の通りです
npm install -g https://github.com/SilentMalachite/Soujo/archive/refs/heads/main.tar.gz
claude plugin marketplace update soujo && claude plugin update soujo@soujo
codex plugin marketplace upgrade soujo && codex plugin add soujo@soujo
soujo init は既存ファイルを上書きしないので、v0.1 で作ったプロジェクトの SPEC.md CLAUDE.md AGENTS.md はそのままです。スキルはそれでも動きます。原則と converge の行を使いたければ、templates/ から手で写してください。
おわりに
遺跡の発掘では、区画や層を掘り終えても「終わった」とは言いません。
図面と遺物と日誌を突き合わせて、記録に穴がないことを確かめてから、初めて終了したと言えます。「記録ではじまり記録で終わる」ので。
「一応、全部の層を剥ぎ終わった」と「記録が揃った」は別のことです。
converge はその突き合わせを、30 年遅れで考古学の理屈をソフトウェアに持ち込んだものです。
まだ未完成だと思います。よろしくお願いします。