todo-loop
プロジェクトの TODO.md を、無人のセッションで 1 SP ずつ消化するループを起動・確認・停止する。実行できるストーリーが尽きたら lens-next で 10 案を出し、上位 3 件を 1 SP ずつに分解して積み足して続ける。時刻を指定した連続実行(「14 時の着手で終わり」)と、利用上限が明けたら同じ SP から再開する機能つき。「TODO 作って消化して」「〇時まで回して」「TOD
Files16
View on GitHubSKILL.mdplugins/todo-loop/skills/todo-loop/SKILL.md
GitHubtodo-loop
<プロジェクト>/TODO.md のストーリーを、1 SP = 1 セッションの無人の claude -p で上から消化する。
runner の本体は scripts/run.sh、各セッションへの指示書は scripts/story-prompt.md(消化)と scripts/refill-prompt.md(積み足し)。
報告 PDF は scripts/report.py、材料台帳は scripts/ledger.py、定時の雛形は scripts/schedule.sh と templates/launchd.plist.tmpl。
状態は <プロジェクト>/.local/todo-loop/(以下 <状態>。runs/ に回ごとのログと history.log、progress/ に進捗ファイル、reports/ に報告、ledger/ に台帳、running.pid、drive.out)。
プロジェクトごとの設定(すべて <状態>/ に置く。どれも無ければ従来の動作)
プロジェクト固有の値・処理はここに置き、本体(plugins/todo-loop/)には書かない。
| ファイル | 役割 | いつ使われるか |
|---|---|---|
lens |
1 行目が積み足しの観点(パス・URL・文)。TODO_LOOP_LENS が優先 |
積み足し(passes/ が無いとき) |
passes/<NN>_<名前>/lens |
観点の複数回。番号順に 1 回ずつ lens-next を回す。回ごとに TODO.md を検査し、崩れた回だけその回の前に戻す | 積み足し(あれば lens より優先) |
passes/<NN>_<名前>/prompt-addon.md |
その回の指示書の末尾に足す | その回 |
passes/<NN>_<名前>/pre.sh |
その回の直前にプロジェクトのディレクトリで実行(観点を作り直す)。失敗しても前の観点で続ける | その回の直前 |
pre-refill.sh |
積み足しの直前に 1 回実行(passes の前) | 積み足しの直前 |
pre-run.sh |
起動ごとに 1 回、ストーリーを選ぶ前に実行(別の実行先への送り出し・戻りの取り込みの受け口)。失敗しても続ける。DRY では実行しない | 起動の最初 |
story-addon.md |
消化の指示書(story-prompt.md)の末尾に足す | 各ストーリー |
refill-addon.md |
積み足しの指示書(refill-prompt.md)の末尾に足す | 積み足しの各回 |
collect.sh |
材料台帳の材料を JSON 行({"text", "kind"?, "id"?, "at"?})で標準出力に出す。これか ledger/ があると台帳を使う |
積み足しの直前 |
send.sh |
送信。send.sh <run|create> <PDF のパス or 空> に本文を標準入力で渡す。無ければ repo の .local/todo-loop/notify.sh(本文だけ)、それも無ければ送らない |
起動の終わり・利用上限の待ち |
launchd/ |
scripts/schedule.sh が書き出す定時の plist(登録は人が行う) |
— |
同梱の観点(lenses/)
どのプロジェクトにも当てられる観点。プロジェクトの観点とは別の回として passes/ に足す(既存の lens は passes/01_<名前>/lens へ移す。passes があると lens は読まれない)。
| ファイル | 探索の向き |
|---|---|
lenses/sources.md |
信頼できる情報源を増やし、1 本の情報源への依存を減らす(判断ごとの情報源の偏りを数え、性質の違う 2 本目を道具に組み込む) |
付け方: passes/<NN>_sources/lens の 1 行目に lenses/sources.md のパス(repo ルートからの相対)を書き、そのプロジェクトの今の情報源の置き場・既知の偏りを passes/<NN>_sources/prompt-addon.md に書く(本体の観点にはプロジェクト固有の名前を書かない)。足したら TODO_LOOP_DRY=1 TODO_LOOP_REFILL_ONLY=1 で回の一覧を出して確かめる(DRY は回と観点を出すだけで、前処理も claude も走らせない)。
起動するとき
- 引数を決める。 足りない値は聞かずに既定で埋め、報告に書く。
until: この時刻を過ぎたら新しいセッションを始めない(その直前に始まった回が最後)。無ければ 1 件だけ実行して終わる。end: 各セッションをこの時刻で打ち切る。無ければuntilの 1 時間後。lens: 積み足しの観点。<状態>/passes/か<状態>/lensがあれば、それを runner が読むのでTODO_LOOP_LENSを渡さない(上の表)。無ければ、プロジェクトにTODO-lens.mdがあればそれ。無ければ user の言葉から 1 枚書く(「何を良くしたいか」を 3〜10 行。user が観点を言っていなければ、プロジェクトの CLAUDE.md の目的から書き、<プロジェクト>/TODO-lens.mdに置く)。
- TODO.md を用意する。 無ければ
templates/TODO.mdを写し、{{目的}}と「積まない物」をそのプロジェクトの CLAUDE.md から埋める。あれば運用節の見出しの書式(## S-NN — <項目> | <調査|実装> | <fable|opus> | <状態>)が runner と同じかだけ確かめる。 - 二重起動を避ける。
<プロジェクト>/.local/todo-loop/running.pidの pid が生きていれば起動しない(status を返す)。別のセッションが同じ TODO.md に積んでいる最中なら、python3 heaven/tools/peers.py --grep <プロジェクト名>で確かめ、積み終わるまで待つ(積み足しと番号がぶつかる)。 - dry-run で最初の 1 件を確かめる:
TODO_LOOP_DRY=1 bash <skill>/scripts/run.sh <プロジェクト>。 - 起動する(端末を閉じても残るように nohup。セッションの終了で止まらない):
mkdir -p <プロジェクト>/.local/todo-loop && touch <プロジェクト>/.local/todo-loop/drive.out nohup env TODO_LOOP_UNTIL=<until> TODO_LOOP_END=<end> TODO_LOOP_LENS=<lens> \ bash <skill の絶対パス>/scripts/run.sh <プロジェクトの絶対パス> \ >> <プロジェクトの絶対パス>/.local/todo-loop/drive.out 2>&1 & - 動いたことを確かめてから報告する: 10 秒後に
running.pidの pid が生きていて親が 1 になっていること、runs/に最初の回のログがあること、TODO.md の該当ストーリーが選ばれていること(drive.outの「選択:」行)。報告には pid・最初のストーリー・終わる条件・止め方を書く。
status / stop
- status:
running.pidの生死、drive.outの末尾、runs/history.logの末尾 5 行、TODO.md の状態ごとの件数を出す。 - stop:
running.pidの pid をkillし、走っている回のclaude -pはpgrep -fl "ストーリー ID: S-"で該当 pid を見てkill <pid>(名前で一括に止めない)。止めた回のストーリーはdoingのまま残り、次の起動で進捗ファイルから続く。
定時にするとき
- 既定は時刻での自動起動なし。 runner は明示して呼んだときだけ動く(この Skill の起動・手で打つ
run.sh)。定時は、欲しいプロジェクトが user の指示で有効にするときだけ設定する。user が定時を頼んでいないのに、起動のついでに定時を書き出したり勧めたりしない。 bash <skill>/scripts/schedule.sh <プロジェクト> <HH[:MM],...> [--refill-only] [--env KEY=VALUE ...]で<状態>/launchd/<ラベル>.plistを書き出す。--refill-onlyは積み足しだけの定時(TODO_LOOP_REFILL_ONLY=1。passes があれば全部の回を回す)。消化の定時は--env TODO_LOOP_UNTIL=HH:MM等を足す。- 道具は launchctl を呼ばない。表示された登録の手順は常駐登録なので、user が行う(無人セッションでは
launchctlを止めている)。
採点(opt-in)
無人セッションが書いた 証跡 を、作業者と別のモデルに再実行させて確かめる(scripts/grade.sh)。既定は off。
- 1 件:
bash <skill>/scripts/grade.sh <プロジェクト> story <S-NN> [--note]。前回の採点以降に done になった全件:bash <skill>/scripts/grade.sh <プロジェクト> batch(常に--note相当)。 - runner から:
TODO_LOOP_GRADE=1を付けて起動すると、doneになった回ごとにgrade.sh story <ID> --noteを呼び、結果の 1 行を報告の本文に(採点)…として足す(stderr は<状態>/runs/grade.err)。定時にするならschedule.shに--env TODO_LOOP_GRADE=1。 - 採点者: 既定は Codex(
codex exec)。見つからなければ作業者(見出し 4 列目)と違う Claude モデル(opus → sonnet、それ以外 → opus)。TODO_LOOP_GRADER=codex|claudeとTODO_LOOP_GRADE_MODELで差し替える(作業者と同じモデルは終了コード 3)。 - 結果:
<状態>/audits/<YYYY-MM-DD>_<ID>[_<TAG>].md。最終行が判定: 合格|不一致|判定不能、終了コードは 0 / 1 / 2(3 は引数・ストーリーの誤り)。batch の台帳はaudits/graded.tsv。 --noteでは、不一致のとき 引き継ぎ 欄に採点: 不一致(<監査ファイル>)。を 1 回だけ追記する。見出しの状態は変えない(直すかどうかは人が決める)。- そのほかの環境変数(
TODO_LOOP_GRADE_*)はgrade.shの先頭コメントにある。
runner の振る舞い(説明を求められたとき・設定を変えるとき)
- 選び方:
doingがあればそれ → 無ければ上から、todo/approvedで依存がすべて済んだもの。blocked/needs-user/awaiting-approvalは飛ばす。 - 1 セッションの打ち切り 3600 秒(
TODO_LOOP_LIMIT_SEC)、endまでの残りに縮める。打ち切り・途中終了はdoingに戻して次の回が進捗ファイルから続ける。2 回続いたら残りを分け直させ、4 回でblocked。時刻指定が無ければTODO_LOOP_COUNT件(既定 1)で終わる。 - 利用上限: メッセージの「resets 3pm」の時刻(読めなければ 3 時間後)+2 分まで待ち、同じストーリーを続きから。再開が
until以降なら止める。上限の回は中断回数に数えない。passes の途中で上限に当たったら、残りの回は見送る。 - 積み足し: 回ごとに、末尾追記・見出しが読める・ID の重複 0・U+FFFD 0・1 件以上増えた、のどれかが崩れたら TODO.md をその回の前に戻す(
history.logにrefill <回> ok|fail|limit)。積み足しの後に 1 件も実行できなければ止まる(空回りしない)。 - 報告: 各セッションは
<状態>/reports/<run|create>/<起動の時刻>/に 8 項目の JSON(課題・やったこと・まとめ・つまづいたこと・工夫したところ・何が嬉しいのか・発展可能性・完了したかどうか)を書き、report.py checkが OK になるまで直す。runner は起動の終わりにreport.py buildで PDF にする(報告の無いストーリーは TODO.md から補完し「報告なし」と明記。末尾に毎回、承認待ちの一覧と各ストーリーの 外部作用 欄)。Chrome はTODO_LOOP_CHROME→ macOS の標準の位置 → PATH の chromium / google-chrome の順に探し、無ければ HTML だけ作る。TODO_LOOP_NO_REPORT=1で作らない。 - 材料台帳(育つ機構の入力):
collect.shの材料と、history.logから本体が足す材料(同じストーリーの中断 2 回以上・blocked 化・積み足しの失敗)を<状態>/ledger/pending.jsonlに番号つきで貯める(ledger.py)。積み足しのセッションはledger/pending.mdを観点と合わせて読み、ストーリーの- 範囲:に材料の番号を書く。書かれた番号は次の sweep でresolved.jsonlに移る。ランナー自身(設定・足し書き・道具)の直しはawaiting-approvalのストーリーとして積み、外部作用 欄に差分を書く(本体に効く直しはそう明記)。 - 無人セッションでは commit・push・gh・launchctl・crontab・sudo・公開・Discord 投稿を
--disallowedToolsで止める。外部に出る操作はawaiting-approvalで止まり、user が見出しをapprovedに書き換えた回だけ実行する。 - 採点:
TODO_LOOP_GRADE=1のときだけ、doneになった回の直後に別のモデルで採点する(上の「採点(opt-in)」)。不一致でも状態は変えず、引き継ぎ 欄に追記するだけ。 - 試験:
bash <skill>/tests/run_tests.sh(偽の claude と一時ディレクトリだけで全機能を確かめる。実の TODO.md・claude には触れない)。