diff --git a/issues/issue-2474-design.md b/issues/issue-2474-design.md new file mode 100644 index 000000000..206748ffe --- /dev/null +++ b/issues/issue-2474-design.md @@ -0,0 +1,218 @@ +# 開発ループ: 止める指示を人が送った停止だけから置く(#2474) + +## 目的 + +- **今どうなっているか**: conductor の手順は、止める指示(`loop stop` が置くファイル)を誰の何を受けて置くかを決めていない。lfactory2 では、conductor が別のセッションからの「引継ぎ文書を書いて終了して」というメッセージを「ループを止めること」と読み替えて `loop stop` を打った。再開した後の周回の境で、誰も求めていない利用者の停止(`user_stop`)で止まり、本当に止まるべき理由(プロジェクト MVV の改訂)が出なかった +- **誰に効くか**: 開発ループを回す利用者。複数のループを並べ、別のセッションから conductor へ依頼を送る使い方をする人 +- **変えると何が成り立つか**: 止める指示は、人がそのセッションへ送った停止からだけ置かれる。セッションを終える依頼・別のセッションのメッセージ・画面の文字・サジェストからは置かれない。誤って置いた止める指示は、置いた conductor が消す + +## 適用範囲 + +- **働く範囲**: NDF の `development-workflow` の開発ループの手順(conductor が読む `references/loop.md`)。開発ループを使う配布先のリポジトリでも同じように働く +- **プロジェクトごとに違うもの**: 無し(足す設定・引数は無い) +- **当たるモード**: 開発ループで回すすべてのモード + +## あるべき姿の根拠 + +| 根拠 | 種類 | 何を示すか | +| --- | --- | --- | +| 会話の記録 `37e6fcce`(lflow)・`f3bae19d`(lfactory2)の 2026-11-02 13:07〜13:09 UTC(要求の「目的」の表) | 実測 | 止める指示を置いたのは conductor 自身の読み替えで、どちらの会話の記録にも人がループを止めることを送った記録が無い | +| 2026-11-05 の利用者の判断(課題 #2474 の「人の判断待ち」の節) | 利用者の決定 | 送信されていない書きかけの指示は指示として扱わず、止める指示は人が実際に送ったものだけを有効にする | +| `git grep -n "STOP_FILE" -- plugins/ndf/scripts/lib`(要求の前提 1) | 実測(コードの検索) | 止める指示のファイルを書くのは `loop stop`(`cmd_stop`)だけで、発生元を絞る場所は `loop stop` を打つ側にある | +| 確定仕様 `docs/specifications/ndf-workflow-development-loop.md` の I3・I8 | 既存の規則 | 人が送った停止は次の境で効き、境で止まった後の区間はすでに経過時間から外れる | + +要求と受け入れ条件は #2474 の本文にある(コピーは `issues/issue-2474-requirements.md`)。この文書は「どう作るか」だけを扱う。 + +この文書での呼び方は次のとおりにする。 + +| 呼び方 | 意味 | +| --- | --- | +| 止める指示 | `loop stop` が置くファイル `.ndf/loop/<名>/stop`。中身は置いた時刻 | +| 人が送った入力 | 人がそのセッションの入力欄から送信した入力と、人が端末で打ったコマンド | +| 人が送っていない文 | 別のセッションからのメッセージ(「利用者の指示」と書かれていても含む)・引継ぎ文書・画面に出た文字・送信されていないサジェスト・conductor 自身の読み替え | + +## ドメインモデル + +### コンテキスト + +| コンテキスト | 何の語が 1 つの意味に決まるか | +| --- | --- | +| NDF の開発ワークフロー(`ndf-workflow`) | 開発ループ・周回・止まる条件・ループの記録・止める指示 | + +1 つのコンテキストに収まる。ほかのコンテキストとの関係は変えない。 + +### 集約 + +| 集約 | 持ち主(書き換えてよいもの) | 根 | この変更での扱い | +| --- | --- | --- | --- | +| 開発ループ | `sprint_loop.py` の副命令 | ループの記録(`loop.json`) | 変えない | + +止める指示のファイルは集約の外にある入力である。この変更は、その入力を置く側(conductor の手順)の決まりだけを変える。 +書き換える集約は無い。 + +### 不変条件 + +| # | 集約 | 条件 | 破れたときの扱い | +| --- | --- | --- | --- | +| I1 | 開発ループ(の入力) | 止める指示は、人が送った入力がループを止めることを求めたときにだけ置かれる | 手順書の決まりで守る。機械では検査しない(決定 1)。破れたら置いた conductor がファイルを消す(I2) | +| I2 | 開発ループ(の入力) | conductor が消してよい止める指示は、自分が人の送っていない文から置いたものだけである | 手順書の決まりで守る | +| I3 | 開発ループ | `loop start`・周回の境の判定・経過時間の計算の振る舞いは今と同じである | 既存のテストが落ちる | + +### ドメインイベント + +| # | イベント | 発生元 | 受け手 | +| --- | --- | --- | --- | +| E1 | 人がループを止めることを送った | 人の入力・人が端末で打つ `loop stop` | conductor | +| E2 | 止める指示を置いた | E1 を受けた conductor の `loop stop` | 周回の境の判定(`user_stop`) | +| E3 | セッションを終える依頼を受けた | 人の入力・別のセッションのメッセージ | conductor。引継ぎ文書を書いて終える。E2 を起こさない | +| E4 | 人が送っていない文から置いた止める指示に気づいた | 止める指示を置いた conductor | 止める指示のファイル(消す)・引継ぎ文書(消したことを書く) | + +要求の E5(境で止まる条件を判定した)は振る舞いを変えないため、この表に載せない。 + +### 用語 + +| 用語 | 意味 | 用語集への反映 | +| --- | --- | --- | +| 開発ループ | スプリントを 1 つ終えるたびに次のスプリントを選んで続けて始める進め方 | 無し(既存の語をそのまま使う) | +| 周回 | 開発ループの 1 回分 | 無し | +| 止まる条件 | 開発ループを止める、機械で判定できる条件 | 無し | +| ループの記録 | `.ndf/loop/<名>/loop.json` | 無し | + +新しい語は足さない。「止める指示」「人が送った入力」「人が送っていない文」はこの文書の中の呼び方で、用語集へは足さない。 + +## 機能一覧 + +| # | 機能 | 誰が使うか | +| --- | --- | --- | +| F1 | conductor は、人が送った入力がループを止めることを求めたときだけ `loop stop` を打つ | conductor | +| F2 | セッションを終える依頼では、`loop stop` を打たずに引継ぎ文書を書いて終える | conductor | +| F3 | 人が送っていない文から置いた止める指示に気づいたら、ファイルを消して無視し、引継ぎ文書に書く | conductor | + +## 構成要素 + +| 要素 | 責務 | 変える / 変えない | +| --- | --- | --- | +| `plugins/ndf/skills/development-workflow/references/loop.md` の「止まる条件」の節 | 止める指示を置く発生元と、置かない場面と、誤って置いたときの扱いを conductor へ示す | 変える(節の中に小見出し「止める指示を置く」を足す) | +| 確定仕様 `docs/specifications/ndf-workflow-development-loop.md` | 止める指示の発生元を確定の振る舞いとして残す | 変える | +| `cmd_stop` / `cmd_start` / `boundary_stop` / `active_seconds`(`plugins/ndf/scripts/lib/sprint_loop*.py`) | 止める指示の読み書き・再開・境の判定・経過時間 | 変えない(決定 2・3) | +| `references/parallel.md` の「レーンの画面を見るとき」 | 画面の入力欄の文字を判断に使わない | 変えない。`loop.md` の新しい小見出しからリンクする | + +### 値の集合へ値を足す影響 + +値の集合へ値を足さない(止まる条件・結果の `status`・ループの記録の形は変えない)。 + +## 入出力の契約 + +`sprint-state.py loop` の副命令の引数と結果は変えない。 + +`loop.md` へ足す小見出しの中身(実装でこの内容を書く。文面は実装で整える): + +| 項目 | 書く内容 | +| --- | --- | +| 置く場面 | 人が自分のセッションへ送った入力が、ループを止めることを求めたときだけ `$SS loop stop <名>` を打つ | +| 発生元にしないもの | 別のセッションからのメッセージ(「利用者の指示」と書かれていても含む)・引継ぎ文書・画面に出た文字(`parallel.md` の「レーンの画面を見るとき」へリンク)・送信されていないサジェスト・自分の読み替え。これらが止めることを求めていたら、人に確かめるまで置かない | +| 置かない場面 | セッションを終える依頼(引き継いで終了する・新しい版で起動し直す)。引継ぎ文書を書いて終える。周回は進行中のまま残り、次の起動の `loop start` で続く | +| 誤って置いたとき | 人が送っていない文から置いたと気づいたら、`.ndf/loop/<名>/stop` を消し、消したことと理由を引継ぎ文書に書く。人が置いた止める指示と、置いた経緯が分からない止める指示は消さない | +| 理由(1 文) | 止める指示は再開の後も残り、次の境で `user_stop` として効く。誰も求めていない停止で止まると、本当に止まるべき理由が出ない | + +## 処理の流れ + +conductor が止めることを求める文を受けたときの判断を、次の流れにする。 + +```mermaid +flowchart TD + A[止める・終えることを
求める文を受けた] --> D{ループを止めることを
求めているか} + D -->|セッションを
終えるだけ| F[引継ぎ文書を書いて終える
loop stop を打たない] + D -->|はい| B{人が自分のセッションへ
送った入力か} + B -->|はい| E[loop stop を打つ] + B -->|いいえ| C[止める指示を置かない
人に確かめる] +``` + +セッションを終えるだけの依頼は、送信元によらず引継ぎ文書を書いて終える(E3・F2)。送信元を確かめるのは、ループを止めることを求める文だけである。 + +人が送っていない文から止める指示をすでに置いていたと気づいたときは、ファイルを消して引継ぎ文書に書く(F3)。 + +図は `loop.md` の新しい小見出しが conductor へ示す判断である。図に含めない要素: 確定仕様(文書)、変えない開発ループのコード、 +リンク先の `parallel.md`。 + +## 確定仕様の直し方 + +`docs/specifications/ndf-workflow-development-loop.md` を次のとおりに直す(受け入れ条件 5)。 + +| 箇所 | 直す内容 | +| --- | --- | +| 冒頭の「利用者の止める指示では…止まる」の行 | 止める指示は人が送った停止からだけ置かれることを添える | +| 常に成り立つ条件の I3 | 止める指示を置くのは、人が送った入力がループを止めることを求めたときだけで、セッションを終える依頼・別のセッションのメッセージからは置かないことを加える | +| ループの記録の置き場の表の `stop` の行 | 「人が送った停止を受けて `loop stop` が置き、`user_stop` の再開で消す」にする | + +## 決定の記録 + +### 決定 1: 発生元の限定は conductor の手順で決め、コマンドや hook の側で入力元を判定しない + +止める指示を置くのは `loop stop` だけで、`loop stop` を打つのは conductor か人である。人が送った入力かを機械で +見分けるには、その入力の出所を知る値が要る。hook が受ける入力の項目に入力元は無い。Claude Code の会話の記録には入力元の値 +(`promptSource`・`turnOrigin`)があるが公開の仕様ではなく、Codex には無い。lfactory2 の起点の入力も記録上は `typed` で、 +サジェストを採った入力と区別できなかった(要求の「目的」の表)。 + +hook で `loop stop` の呼び出しを会話の記録の入力元で止める形は採らない。内部の値に依存し、ランタイムによって働かない。 + +根拠: Value 6(どのプロジェクトでも使える形を保つ) / Value 5(決まった手順はスクリプトに、判断と中身は AI に)(MVV 版 5) + +### 決定 2: `loop start` で止める指示を消さない + +人が送った停止は、次の周回の境で効くのが正しい振る舞いである(確定仕様の I3)。自動の再起動(カットポイントと利用上限での +切り替え)も周回の途中で `loop start` を打つため、`loop start` で止める指示を消すと、人が送った停止が境の前に消える。 +発生元を人が送った停止に限れば、再開の後に止める指示が効くのは人の意図どおりである。 + +根拠: 共通原則の上位の原則(人の判断を守る) / Value 7(根本原因の場所で直す)(MVV 版 5) + +### 決定 3: 経過時間の計算を変えない + +人が送った停止では、conductor は境まで周回を続けるため、止める指示を置いてから境までは動いていた時間である。境で止まった +後の区間は、確定仕様の I8 ですでに経過時間から外れる。外すべき区間は止める指示からは生じない。 + +lfactory2 の周回 4 に入った約 41 時間は、周回の途中でセッションを終えて 2 日後に再開した区間である。セッションが止まった +時刻を記録する仕組みが無いため、この変更では扱わない(「未確認のまま残ること」)。 + +根拠: Value 3(やめる・縮めるを既定にする) / Value 4(外の利用で測って決める)(MVV 版 5) + +### 決定 4: セッションを終える依頼を止める指示に読み替えない + +セッションを終える依頼の目的は、周回を進行中のまま残し、次の起動で続けることである。止める指示を置くと、次の起動で続けた +周回が境で `user_stop` で止まり、依頼の目的と逆になる。lfactory2 で起きたのはこの読み替えである。 + +根拠: Mission / Value 2(人が判断できる量に収める)(MVV 版 5) + +### 決定 5: 誤って置いた止める指示は、置いた conductor だけが消す + +止める指示のファイルの中身は置いた時刻だけで、誰が何を受けて置いたかを持たない。後から読んだ conductor には、人が置いた +ものと見分けられない。消してよいのは、自分が人の送っていない文から置いたと分かっている conductor だけにする。経緯が +分からない止める指示は消さず、境で `user_stop` として止まり、人が `loop start` で解く(今の再開の表のとおり)。 + +止める指示のファイルに置いた理由を書き足す形は採らない。書く内容を検証できず、決定 1 と同じく人が送ったかを判定できない。 + +根拠: 共通原則の上位の原則(人の判断を守る) / Value 7(根本原因の場所で直す)(MVV 版 5) + +## テスト設計 + +| 受け入れ条件・不変条件 | どの振る舞いで縛るか | どう壊したら落ちるべきか | +| --- | --- | --- | +| 受け入れ条件 1〜4・I1・I2 | `loop.md` の手順。文言を照合するテストは書かず、設計と実装の Pull Request のレビューで読む | — | +| 受け入れ条件 5 | 確定仕様の記述。レビューで読む | — | +| 受け入れ条件 6・I3 | 既存の `plugins/ndf/scripts/tests/test_sprint_loop.py` が通る | 開発ループのコードを変えると落ちる | +| 手順書の参照 | `loop.md` から `parallel.md` の見出しへのリンクを、リンクの検査(`python3 scripts/check-markdown-links.py --root .`。継続的統合の `runtime-plugin-validate.yml` と同じ)で確かめる | 見出しの名前を違えると落ちる | + +## 設計の結果 + +| 課題 | 扱い | 取り込み先 | 触るファイル | +| --- | --- | --- | --- | +| #2474 | 実装する | — | `plugins/ndf/skills/development-workflow/references/loop.md`、`docs/specifications/ndf-workflow-development-loop.md` | + +## 未確認のまま残ること + +| 項目 | 内容 | +| --- | --- | +| lflow の起点の入力がサジェストだったか | 会話の記録の入力元の値は `typed` で、サジェストを採った入力の値(`suggestion_accepted`)ではない。利用者の判断(2026-11-05)ではサジェストを指示と読んだものである。どちらであっても、ループを止めることは誰も送っていない(要求の「目的」の表) | +| 手順書の決まりが守られるか | 決定 1 のとおり機械では検査しない。守られたかは、次に別のセッションから終える依頼を受けた conductor の振る舞いで見る(リリース後テスト) | +| 経緯が分からない止める指示 | 再開した conductor が、人の送っていない文から置かれた止める指示を見分ける手段は無い(決定 5)。lfactory2 と同じ場合は、境で `user_stop` で止まり、人が `loop start` で解く | +| 周回の途中でセッションを終えた区間 | セッションを終えてから再開までの区間は経過時間に入る(lfactory2 の約 41 時間)。セッションが止まった時刻を記録する仕組みが無く、この変更では扱わない | diff --git a/issues/issue-2474-requirements.md b/issues/issue-2474-requirements.md new file mode 100644 index 000000000..3ae993601 --- /dev/null +++ b/issues/issue-2474-requirements.md @@ -0,0 +1,147 @@ +# #2474: 開発ループ: 止める指示を人が送った停止だけから置く(人が送っていない文から置かれた止める指示が、再開後の境で user_stop を起こした) + +正は課題の本文(#2474)で、この文書はその写しである。`spec-copy.py write` で作り直す。手で直さない。 + +## 依頼(原文) + +> ## 何が起きたか +> +> 開発ループ lfactory2 で、周回の途中に `loop stop lfactory2` を打って止める指示(`.ndf/loop/lfactory2/stop`)を置き、セッションを切った(2026-11-02 22:08 JST)。2026-11-04 15:01 JST に再開した。 +> +> 1. `loop start lfactory2` は `in_lap` を返し、止める指示のファイルを消さなかった(`stop` は止まった記録が無いため、再開の扱い `_resume_user_stop` を通らない) +> 2. 周回 4 を `loop lap lfactory2 --failed` で締めると、境の判定が残ったファイルを読み、`user_stop` で止まった。本当に止めたい理由(プロジェクト MVV が版 4 → 5 に改訂された `mvv_changed`)は出なかった +> 3. 周回 4 の秒に、止めていた約 41 時間が入った(消費の hours が 6.19 → 47.14)。確定仕様の「止まっていた時間は経過時間に入れない」に反し、予算 72 時間の 6 割を止めていた時間で使った +> +> ## 期待 +> +> - 周回の途中の止める指示は、`loop start` の再開で解ける(周回の境の止まりと同じ) +> - 止める指示を置いてから再開までの時間は、経過時間に数えない +> +> ## 再現 +> +> 1. 周回を組んだ状態(進行中)で `sprint-state.py loop stop <名>` を打つ +> 2. 時間を置いて `loop start <名>`(`in_lap` が返る) +> 3. `loop lap <名> --failed` → `user_stop` で止まり、周回の秒に止めていた時間が入る +> +> ## 範囲 +> +> `plugins/ndf/scripts/lib/sprint_loop.py`(`cmd_start` の in_lap の経路)・`sprint_loop_record.py`(`boundary_stop`・周回の秒)。 + +(書き手: 起票者、日付: 2026-11-04。開発ループを回していた AI が、起きたことの記録として書いた本文である。利用者の指示の原文は含まない。「止める指示を置いてセッションを切り、再開した」という操作は利用者のものだが、その操作に添えた利用者の文は本文に引用されていない) + +## 起票者の推定 + +題・本文のうち、利用者の指示の原文に無く起票者が推して足した条件。2026-11-05 の利用者の判断(下の「人の判断待ち」の節)で扱いを決めた。 + +| # | 条件 | 推した出所 | この要求での扱い | +| --- | --- | --- | --- | +| S1 | lfactory2 の止める指示は「利用者の指示」で置かれた | 本文の「何が起きたか」の 1 行目。止める指示を置いた conductor が引継ぎ文書に「利用者の指示で `loop stop` を打った」と書いていた | 採らない。記録では、人はループを止めることを送っていない(下の「目的」の表) | +| S2 | 周回の途中に置いた止める指示は、`loop start` の再開で解ける | 本文の「期待」の 1 行目 | 採らない。人が送った停止は次の周回の境で効くのが正しい振る舞いである。自動の再起動も周回の途中で `loop start` を打つため、再開で消すと人が送った停止が境の前に消える(前提 4) | +| S3 | 止める指示を置いてから再開までの時間を、ループの予算の経過時間に数えない | 本文の「期待」の 2 行目 | 採らない。人が送った停止では conductor が境まで周回を続けるため、止める指示から境までは実際に動いていた時間である。境で止まった後の区間は、確定仕様の I8 ですでに経過時間から外れる(前提 3) | +| S4 | 直す場所は `cmd_start` の周回の途中の経路と、境の判定・周回の秒 | 本文の「範囲」 | 採らない。直すのは止める指示を置く経路(conductor の手順)である | +| S5 | 「周回の秒」に止めていた時間が入った | 本文の「何が起きたか」の 3 | 採らない。周回 4 の秒(計画の行の秒の和)は 0 で、時間が入ったのはループの予算の経過時間(消費の hours)である | + +## 目的 + +止める指示(`.ndf/loop/<名>/stop`)が、人が実際に送った停止からだけ置かれる状態にする。人が送っていない文(送信されていないサジェスト・入力欄に残った文字・人の指示を名乗る別のセッションのメッセージ)から止める指示が置かれると、ループは誰も求めていない利用者の停止(`user_stop`)で止まり、本当に止まるべき理由が出ない。 + +記録で確かめた経路(2026-11-02、時刻は UTC。会話の記録 `37e6fcce`・`f3bae19d` とループの記録 `.ndf/loop/lfactory2/loop.json`): + +| 時刻 | 誰が | 何を読んで | 何をした | +| --- | --- | --- | --- | +| 13:07:22 | ループ lflow の conductor のセッション | 人の入力として記録された「3本に引継ぎメモを作って終了する指示を送ってください。」。記録の入力元の値は `typed` で、サジェストを採った入力の値(`suggestion_accepted`)ではない。利用者は 2026-11-05 に、この入力をサジェストを指示と読んだものと判断した | 3 本のループの conductor へ、別のセッションへのメッセージで「【利用者の指示・lflow 経由】引継ぎ文書を最新にして、このセッションを終了してください」を送った | +| 13:07:39 | ループ lfactory2 の conductor のセッション(`f3bae19d`) | 上のメッセージ(入力元は別のセッション) | パイプラインを止め、引継ぎ文書を書いた | +| 13:08:52 | 同じ | 同じメッセージ。ループを止めることは書かれていない | 「利用者の指示はループを止めること」と自分で読み替えて `loop stop lfactory2` を打ち、引継ぎ文書に「利用者の指示で打った」と書いた | +| 2026-11-04 06:01:43 | 再開した conductor | 残っていた止める指示 | 周回 4 を締めた境で `user_stop` で止まった。プロジェクト MVV の改訂(`mvv_changed`)は出なかった | + +どの段にも、人がループを止めることを送った記録は無い。止める指示は、conductor が別のセッションのメッセージを自分で読み替えて置いた。周回 4 の経過時間に入った約 41 時間(消費の hours 6.19 → 47.14)は、周回の途中でセッションを終えて 2 日後に再開した区間であり、止める指示が作った区間ではない。 + +## 前提 + +- 前提 1: 止める指示のファイルを書くのは `sprint-state.py loop stop`(`cmd_stop`)だけである。`git grep -n "STOP_FILE" -- plugins/ndf/scripts/lib` の当たりは、定数の定義と読み込みを除くと、`cmd_stop` が書き、周回の境の判定(`boundary_stop`)が読み、止まった記録の再開(`_resume_user_stop`)が消す 3 か所である +- 前提 2: 入力を人が送ったかを、コマンドや hook の側で見分ける値は無い。hook が受ける入力の項目に入力元は無く、Claude Code の会話の記録にある入力元の値(`promptSource`・`turnOrigin`)は公開の仕様ではなく、Codex には無い +- 前提 3: 人が送った停止は、次の周回の境で効く(確定仕様の I3)。conductor は境まで周回を続けるため、止める指示が置かれてから境までは動いていた時間である。境で止まった後の区間は確定仕様の I8 で経過時間から外れる +- 前提 4: 自動の再起動(カットポイントと利用上限での切り替え)も、周回の途中で `loop start` を打つ + +## 対象範囲 + +含む: +- conductor の手順(`development-workflow` の `references/loop.md`)に、止める指示を置く発生元を人が送った入力に限ることを書く +- セッションを終える依頼(引き継いで終了する)では止める指示を置かないことを書く +- 人が送っていない文から止める指示を置いたと気づいたときに、ファイルを消して無視することを書く +- 確定仕様 `docs/specifications/ndf-workflow-development-loop.md` の、止める指示の発生元の記述 + +含まない: +- `loop start` と経過時間の計算を変えること(前提 3・4) +- 中継が tmux に起動する claude のサジェストを止めること(#2504) +- 画面の入力欄の文字を判断に使わない決まり(PR #2502 で develop に入った) +- 周回の途中でセッションを終えた区間(lfactory2 の約 41 時間)を経過時間から外すこと。セッションが止まった時刻を記録する仕組みが無い +- 既存のループの記録(lfactory2 など)の値を直すこと + +## ドメインイベント + +| # | イベント | 引き金 | 失敗したとき | 順序の前提 | +| --- | --- | --- | --- | --- | +| E1 | 人がループを止めることを送った | conductor のセッションへの人の入力、または人が端末で打つ `loop stop` | — | — | +| E2 | 止める指示を置いた | E1 を受けた conductor の `loop stop` | ループの記録が無ければ断る(今のとおり) | E1 | +| E3 | セッションを終える依頼を受けた | 人の入力・別のセッションのメッセージ | — | 止める指示を置かない | +| E4 | 人が送っていない文から置いた止める指示に気づいた | 止める指示を置いた conductor | — | ファイルを消して無視する | +| E5 | 境で止まる条件を判定した | 周回を締めた後・選定の前 | 今のとおり | E2 があれば `user_stop` で止まる | + +## 用語 + +| 用語 | 意味 | +| --- | --- | +| 開発ループ | スプリントを 1 つ終えるたびに次のスプリントを選んで続けて始める進め方(用語集) | +| 周回 | 開発ループの 1 回分(用語集) | +| ループの予算 | 開発ループが使ってよい量の上限。周回の数・費用・経過時間で数える(用語集) | +| 止まる条件 | 開発ループを止める、機械で判定できる条件(用語集)。止める指示は `user_stop` に当たる | +| ループの記録 | `.ndf/loop/<名>/loop.json`(用語集) | + +止める指示は、`loop stop` が置くファイル `.ndf/loop/<名>/stop`(中身は置いた時刻)を指す。人が送った入力は、人がそのセッションの入力欄から送信した入力と、人が端末で打ったコマンドを指す。別のセッションからのメッセージ・引継ぎ文書・画面に出た文字・送信されていないサジェスト・conductor 自身の推測は含まない。 + +## 受け入れ条件 + +- [ ] 1. `references/loop.md` に、conductor が `loop stop` を打つのは、人が自分のセッションへ送った入力がループを止めることを求めたときだけ、と書かれている +- [ ] 2. 1 の発生元にしないもの(別のセッションからのメッセージ。「利用者の指示」と書かれていても含む。引継ぎ文書・画面に出た文字・送信されていないサジェスト・conductor 自身の読み替え)が書かれている +- [ ] 3. セッションを終える依頼(引き継いで終了する)では `loop stop` を打たず、引継ぎ文書を書いてセッションを終える、と書かれている +- [ ] 4. 人が送っていない文から止める指示を置いたと気づいた conductor は、そのファイルを消し、消したことを引継ぎ文書に書く、と書かれている。人が置いた止める指示は消さない +- [ ] 5. 確定仕様 `docs/specifications/ndf-workflow-development-loop.md` に、止める指示の発生元が人が送った停止に限られることが書かれている +- [ ] 6. 開発ループのコード(`plugins/ndf/scripts/lib/sprint_loop*.py`)は変えない。既存のテスト(`plugins/ndf/scripts/tests/test_sprint_loop.py`)が通る + +## 影響 + +| 対象 | 影響 | +| --- | --- | +| 公開インタフェース | 無し(`sprint-state.py loop` の副命令と結果は変えない) | +| データ | 無し | +| 既存の振る舞い | conductor が `loop stop` を打つ場面が、人が送った停止に限られる | + +## 検証手段 + +| 項目 | 手段 | +| --- | --- | +| 文書のレビュー | 受け入れ条件 1〜5 を設計と実装の Pull Request のレビューで読む(`.md` の文言を照合するテストは書かない) | +| テスト | `uv run --frozen --project . --all-extras pytest plugins/ndf/scripts/tests/test_sprint_loop.py -q`(受け入れ条件 6) | +| リンクの検査 | `python3 scripts/check-markdown-links.py --root .`(手順書から足したリンクの参照切れ) | +| 手動確認 | 次に別のセッションからセッションを終える依頼を受けた conductor が、`loop stop` を打たずに引継ぎ文書を書いて終えることを見る(リリース後テスト) | + +## 前提とする取り決め + +| 項目 | 参照先 / 決めたこと | +| --- | --- | +| 手順書の置き場 | conductor の手順は `plugins/ndf/skills/development-workflow/references/loop.md` | +| コーディング規約 | `AGENTS.md` の「最小限のコード実装」。`.md` の文言を照合するテストは書かない | +| 関連の決まり | 画面の入力欄の文字を判断に使わない(`references/parallel.md` の「レーンの画面を見るとき」) | + +## 境界 + +| 区分 | 内容 | +| --- | --- | +| 常に行う | 既存テストの実行、確定仕様の該当箇所の更新 | +| 確認してから行う | 無し | +| 行わない | 開発ループのコードの変更、既存のループの記録の書き換え | + +## 未決 + +無し(人が送ったかを機械で見分けない理由は前提 2 に置いた)