From 69a634970c7b7d76cb467e8e2157b2d6e42df244 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Sun, 1 Nov 2026 01:21:42 +0000 Subject: [PATCH 1/5] =?UTF-8?q?Docs:=20#2164=20=E3=81=AE=E8=A6=81=E6=B1=82?= =?UTF-8?q?=E3=81=A8=E5=8F=97=E3=81=91=E5=85=A5=E3=82=8C=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=E3=81=AE=E5=86=99=E3=81=97=E3=82=92=E7=BD=AE=E3=81=8F=EF=BC=88?= =?UTF-8?q?=E7=89=88=E6=95=B0=E3=82=92=E9=85=8D=E5=B8=83=E3=81=AE=E3=82=B9?= =?UTF-8?q?=E3=83=86=E3=83=83=E3=83=97=E3=82=92=E6=B5=81=E3=81=99=E6=99=82?= =?UTF-8?q?=E7=82=B9=E3=81=AB=E6=B1=BA=E3=82=81=E3=82=8B=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- issues/issue-2164-requirements.md | 159 ++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 issues/issue-2164-requirements.md diff --git a/issues/issue-2164-requirements.md b/issues/issue-2164-requirements.md new file mode 100644 index 000000000..b82f8b644 --- /dev/null +++ b/issues/issue-2164-requirements.md @@ -0,0 +1,159 @@ +# #2164: スプリントのプランが作った時点の版数を配布と本番のプランへ書き込み、並行する別のスプリントがその版を先に出すとプランを作り直すまで止まる + +正は課題の本文(#2164)で、この文書はその写しである。`spec-copy.py write` で作り直す。手で直さない。 + +## 依頼(原文) + +この課題には利用者が書いた文が無い。本文と追記はどれも AI が書いた(書き手の判定は各引用の下)。利用者の指示の原文として扱う文は無く、下の受け入れ条件は承認ゲート 1 で利用者が確かめる。 + +> スプリントのプランは、作った時点の版数(開発版 `10.23.0-dev.1`・本番 `10.23.0`)を配布と本番のプランへ書き込む。プランを作ってから配布するまでの間に、並行する別のスプリントが同じ版数を本番へ出すと、そのプランの配布と本番のプランは使えなくなる。 +> +> - スプリント m2087(#2087・#2104・#2142)は 2026-10-25 に `/work/ai-plugins/.ndf/tmp/sprint-m2087/sprint.json` を作り、`7-release.json`・`9-release-prod.json` が 10.23.0 を持った +> - その後、chrome-cdp(PR #2153)が 10.23.0 として本番へ出た +> - 2026-10-29、conductor は 10.23.0 の scripts でプランを作り直した(`/work/ai-plugins/.ndf/tmp/sprint-m2087b/sprint.json`、開発版 10.24.0-dev.1)。作り直しの間、キューは進まなかった +> - 古いプランのまま流したときに、配布の工程が版数の衝突を検出して止まるのか、そのまま進むのかは確かめていない +> +> (「どこで見つけたか」「なぜこの変更の範囲外なのか」「直さないと何が起きるか」「由来: PR #2160」の節は略さず課題の起票時の本文にある) + +(書き手: 起票者(スプリント m2087 の振り返りで範囲外の課題として起票した AI)、日付: 2026-10-29 ごろ。根拠: 本文が `out-of-scope` の節の形で、「由来 PR #2160」を持つ) + +> ## 再発(2026-10-31、スプリント m26-1) +> +> - 別のスプリント m1426 が並行して develop へ入り、先に v10.27.1 まで本番へ出した。m26-1 のスプリント PR https://github.com/devbasex/ai-plugins/pull/2268 は版の行で衝突し、conductor が版を 10.27.0 → 10.27.2 へ書き換えて流し直した(本番 PR https://github.com/devbasex/ai-plugins/pull/2281 ・ https://github.com/devbasex/ai-plugins/pull/2282 ) +> - 2 回目の発生。並行スプリントのたびに起きる前提で扱う + +(書き手: 起票者(m26-1 の conductor)、日付: 2026-10-31) + +> ## 受け入れ条件 +> +> 1. スプリントの配布と本番のプランは、版数をプランを作る時点で書き込まない。配布のステップを流す時点に、その時点の develop と main の版から決める +> 2. プランを作ってから配布するまでの間に、並行する別のスプリントがその版を出しても、プランを作り直さずに、次に空いている版で配布が進む +> 3. 本番のプランは、同じスプリントの開発版が使った版の系列(接尾辞を外した版)で出す。決めた版数は配布と本番の結果 JSON に載る +> 4. 版数を明示して作ったプランは、その版がすでに出ていれば配布の前に止まり、別の版で黙って進めない + +(書き手: 起票者(棚卸しで足した AI)、日付: 2026-10-31 ごろ) + +> 同じ事象を PR #2247 の作業中に確認した。 +> +> 同じ事象を PR #2298 の作業中に確認した。 + +(書き手: 起票者(`out-of-scope` の追記の形)、日付: 2026-10-31) + +## 起票者の推定 + +題・本文はすべて起票者が書いた。承認ゲート 1 で利用者が確かめるまで、利用者の決定として扱わない。 + +| # | 条件 | 推した出所 | この要求での扱い | +| --- | --- | --- | --- | +| S1 | 版数をプランを作る時点では決めず、配布のステップを流す時点に、その時の本番の版から決めるのがよい | 本文「起票者の推定」(m2087 の作り直しの経緯) | 受け入れ条件 1・2 | +| S2 | 古いプランのまま流すと、配布の工程が既に使われた版数でタグか CHANGELOG を作ろうとして止まる | 本文「起票者の推定」(推測。確かめていない) | **採らない**。2026-11-01 に使い捨ての worktree で `release-steps.py bump --plugin ndf --to 10.27.5-dev.1 --base develop` を打つと、develop・main とも 10.27.5 のところで「10.27.5 → 10.27.5-dev.1 へ上げた」と終了コード 0 で進み、5 ファイルを書き換えた。開発版の配布は止まらず、版を戻したまま develop へ入る。止まるのは本番の `release` が「タグ ndf--v10.27.5 は既にある」を見たときで、開発版の PR はすでに develop へマージ済みになっている(`release-steps.py` の `cmd_release`)。受け入れ条件 5 の根拠 | +| S3 | 受け入れ条件 1〜4 の文(本文の「受け入れ条件」の節) | 棚卸し | 受け入れ条件 1〜4 として引き継ぎ、観測できる形へ書き直した。2 の「配布」は開発版の配布と読む(1 が「配布と本番」を分けて書くため)。本番は 3 と 6 で扱う | +| S4 | 本番の版が、承認資料に書いた版とずれるときの扱い(本文に記述なし。この要求で足した) | 承認ゲート 2 の承認資料は版数を名前と中身に持つ(`issues/approval-ndf-v<版>.md`)。共通原則の Value 10 の但し書き(承認資料に載せる材料の正しさは効率の順位に当てない) | 受け入れ条件 6。問わずに選んだ案: 本番は承認資料の版で出し、その版がすでに出ていれば本番の前に止まって人へ戻す。別の版へ黙って移すと、人が承認した資料と出したものの版が食い違うため | +| S5 | 同じプランの配布のステップを流し直したとき、一度決めた版を使い続ける(本文に記述なし。この要求で足した) | 配布のステップは失敗後の流し直しがある(`bump` の「打ち直し」の扱い) | 受け入れ条件 7。流し直すたびに版を決め直すと、自分が出した開発版を「使われた版」と見て次の版へ進んでしまうため | + +## 目的 + +- 並行する別のスプリントが先に版を出しても、プランを作り直さずに配布まで進める(Vision: 人が手を止めるのは承認の 2 回に寄る。2 回の再発では conductor の作り直しと版の書き換えで止まった) +- 古いプランが既に使われた版より古い版へ戻して配布する事故を、配布の前に止める + +## 前提 + +- 前提 1: 対象は `supervise.py` が組むスプリントのプランのうち、配布を雛形で組むもの(`.ndf/release.json` の `form: package-plugin`。版数が要るのは `delivery.needs_version` が真の経路だけ)。雛形で組まない経路は版数を持たないので変えない +- 前提 2: 「使われた版」(この要求の中だけの言い方。リリース済み版より広く開発版も含む)は、開発版なら origin のベースブランチ(`.ndf/worktree.json` の `base_branch`。ai-plugins は develop)の `plugin.json` の版、正式版なら origin のリリースタグ(`--v<版>`)と本番のブランチの版で判定する。どちらも配布のステップの開始時に `git fetch` した後の値を読む +- 前提 3: 開発版の版の決め方は「版と配布の方針」に従う。ベースブランチの版が未リリースの正式版 X の開発版 `X-dev.N` なら `X-dev.(N+1)`、そうでなければ(ベースブランチの版が正式版、または X のタグがすでにある)最新のリリースタグの版を上げ幅で上げた Y の `Y-dev.1` +- 前提 4: 上げ幅(PATCH / MINOR / MAJOR)はプランを作る時点で指定でき、指定が無ければ PATCH とする(マイルストーンの間は PATCH で出す運用に合わせる)。上げ幅を版の決め方へどう渡すかは設計が決める +- 前提 5: m26-1 でスプリント PR が「版の行で衝突した」仕組み(スプリントの PR が版の行を持った理由)は調べていない。この要求は配布と本番のプランが版を決める時点だけを扱い、スプリント PR の衝突は受け入れ条件 2 が満たされたときに起きなくなるかを設計で確かめる + +## 対象範囲 + +含む: +- `supervise.py new sprint` / `new close` が組む開発版と本番のプラン(MVV 判定で進めるもの・`pace: normal` の配布を含む)が、版をどの時点に決めるか +- 版を名前に持つもの(worktreeのパス・ブランチ名 `release/v<版>`・承認資料のパス・CHANGELOG の節・タグ)が、決めた版に揃うこと +- 配布の `bump` が、既に使われた版以下へ版を戻さないこと +- 決めた版を開発版と本番の結果 JSON(`metrics.version`)に載せること + +含まない: +- 雛形で組まない配布の経路(`delivery` の行ごとの経路) +- 上げ幅を中身から自動で決めること(前提 4 のとおり指定か既定の PATCH) +- スプリントでない単独の `supervise.py new release` の版の決め方(版を明示して作るので、受け入れ条件 4 の止まり方だけが当たる) +- 他のプラグインの上げ幅(`bump-others`・`changed-plugins`)の決め方 +- 承認ゲート 2 の承認の記録の形(`approved_sha` で中身を照合する仕組み) + +## ドメインイベント + +| # | イベント | 引き金 | 失敗したとき | 順序の前提 | +| --- | --- | --- | --- | --- | +| E1 | スプリントのプランを作った(版数を持たない、または利用者が明示した版を持つ) | conductor の `supervise.py new sprint` | 宣言が欠ければ今と同じく作らずに止まる | — | +| E2 | 並行する別のスプリントが、ベースブランチか本番のブランチへ版を出した | 別のスプリントの配布 | — | E1 の後、E3 の前(起きないこともある) | +| E3 | 開発版の配布のステップが、ベースブランチ・本番のブランチ・タグを読んで開発版の版を決めた | キューが配布のプランに入った | origin を読めなければ版を推定せず止まる(受け入れ条件 8) | E1 | +| E4 | 明示した版がすでに出ていると分かり、配布の前に止まった | E3 で明示の版を照合した | — | E3 | +| E5 | 開発版をベースブランチへ出し、決めた版を結果 JSON に載せた | E3 | 今の失敗の経路(judge)のまま | E3 | +| E6 | 承認ゲート 2 で利用者が承認した(承認資料は E3 で決めた版の正式版を持つ) | 利用者 | — | E5 | +| E7 | 本番のステップが、承認資料の版がまだ出ていないことを確かめた | キューが本番のプランに入った | すでに出ていれば本番の前に止まり人へ戻す(受け入れ条件 6) | E6 | +| E8 | 本番へ出し、タグを打ち、決めた版を結果 JSON に載せた | E7 | 今の失敗の経路(judge・judge-record)のまま | E7 | + +## 用語 + +| 用語 | 意味 | +| --- | --- | +| 開発版 | 次に出す正式版の版数へ接尾辞(`-dev.N`)を付けた版。ベースブランチへ載る | +| 正式版 | 接尾辞の無い版。本番のブランチへ載り、リリースタグを持つ | + +## 受け入れ条件 + +- [ ] 1. 版数を明示せずに作ったスプリントのプラン(`sprint.json` と、開発版・本番のプランのファイル)は、具体的な版数(`10.27.6-dev.1` のような文字列)を持たない。worktreeのパス・ブランチ名・承認資料のパスも、プランを作った時点では版を含まない +- [ ] 2. 開発版の配布のステップは、流した時点の origin のベースブランチの版と最新のリリースタグから版を決める。例: ベースブランチが `10.27.5`・最新のタグが `ndf--v10.27.5`・上げ幅 PATCH なら `10.27.6-dev.1`。ベースブランチが `10.27.6-dev.1` でタグ `ndf--v10.27.6` が無ければ `10.27.6-dev.2` +- [ ] 3. プランを作った後、配布の前に別のスプリントがベースブランチか本番のブランチへ版を出しても、同じプランをそのまま流すと、2 の規則で次に空いている版に決まり開発版の配布が終わる。conductor がプランを作り直す・版を書き換える操作は要らない(作った時点のベースブランチが `10.27.5`、配布の前に別のスプリントが `10.27.6` を本番へ出したなら `10.27.7-dev.1`) +- [ ] 4. 版数を明示して作ったプランは、明示した版がすでに出ていれば(開発版はベースブランチの版と同じかそれより大きい版がある、正式版は同じ版のタグがある)、`bump` より前に止まり、止まった理由と既に出ている版を結果 JSON に載せる。別の版へ移って進まない +- [ ] 5. 配布の `bump` は、新しい版がベースブランチの版以下(semver の比較で同じか小さい)なら、ファイルを書き換えずに止まる(例: ベースブランチが `10.27.5` のとき `--to 10.27.5-dev.1` は止まる)。同じステップの打ち直し(作業場所がすでに新しい版で、ベースブランチの版より大きい)は今と同じく ok で返る +- [ ] 6. 本番のプランは、同じスプリントの開発版の版の系列(接尾辞を外した版。承認資料が持つ版)で出す。その版のタグがすでにあれば、本番のブランチへ入れる前に止まり、人へ戻す(止まった理由・既に出ている版・次に空いている版を結果 JSON に載せる)。別の版で黙って進めない +- [ ] 7. 同じプランの開発版の配布のステップを流し直すと、最初に決めた版を使う(自分が出した開発版を「使われた版」と見て次の版へ進まない) +- [ ] 8. 版を決めるときに origin のベースブランチ・本番のブランチ・タグのどれかを読めなければ、版を推定せずに止まり、読めなかったものを結果 JSON に載せる +- [ ] 9. 開発版と本番の結果 JSON(`release-steps.py release` の出力)の `metrics.version` に、決めた版が載る +- [ ] 10. 版数を明示して作ったプランで、明示の版がまだ出ていなければ、今と同じ版・同じステップで配布が進む(退行しない) + +## 非機能の条件 + +| 大項目 | 条件 | +| --- | --- | +| 運用・保守性 | 版を決めた根拠(読んだベースブランチの版・最新のタグ・上げ幅)が配布の結果 JSON か状態のファイルに残り、後から読んで決め方を確かめられる | +| 移行性 | この変更より前に作ったプラン(版数を持つもの)は、明示の版を持つプランとして扱い、受け入れ条件 4・6 の止まり方が当たる。作り直しは求めない | + +## 影響 + +| 対象 | 影響 | +| --- | --- | +| 公開インタフェース | 変わる。`supervise.py new sprint` / `new close` の `--version`(`close` は `--prod` も)が、雛形の配布で必須から任意になる(明示すれば今と同じ)。上げ幅を受ける手段が加わる。互換の経路あり(今の呼び方はそのまま動く) | +| データ | プランのファイル(`sprint.json`・開発版と本番のプラン)の版の持ち方が変わる。決めた版の置き場が加わる | +| 既存の振る舞い | `release-steps.py bump` が版を戻す呼び方を止める。開発版・本番のプランのworktree・ブランチ・承認資料の名前が流した時点で決まる | + +## 検証手段 + +| 項目 | 手段 | +| --- | --- | +| テスト | `uv run --frozen --project . --all-extras pytest plugins/ndf/scripts/tests -q -n 4`(版の決め方・`bump` の止まり方・プランが版を持たないことを単体で確かめる。受け入れ条件 2〜8 は一時の git リポジトリでベースブランチ・タグを作って確かめる) | +| 静的解析・型検査 | リポジトリの CI と同じ検査(`python3 scripts/check-skill-frontmatter.py`・`python3 plugins/ndf/scripts/instructions-check.py --root .`・`claude plugin validate .`) | +| 手動確認 | 次に並行するスプリントで、プランを作り直さずに開発版と本番が出たか(conductor の手当ての記録に版の書き換えが 0 件か)をリリース後テストで確かめる | + +## 前提とする取り決め + +| 項目 | 参照先 / 決めたこと | +| --- | --- | +| プロジェクト構造 | 版の決め方は `plugins/ndf/scripts/` の配布の共通の層に 1 か所で置き、開発版と本番・スプリントと最終の検査で分けない(Value 6)。実験版の置き場は使わない(既定で働くプランと配布を変えるため安定版の経路) | +| コーディング規約 | `AGENTS.md` のポリシーと、版数の扱いは `docs/versioning-and-distribution.md` の「版の付け方と開発版の配布」に従う | +| テスト戦略 | 単体テストで版の決め方と止まり方を、プランを組む関数のテストでプランが版を持たないことを確かめる。`.md` の文言は照合しない | + +## 境界 + +| 区分 | 内容 | +| --- | --- | +| 常に行う | 既存のテストの実行、配布のステップの結果 JSON の形を保つ | +| 確認してから行う | `--version` の必須を外す公開インタフェースの変更(承認ゲート 1・2 で人が見る) | +| 行わない | 版の規則そのもの(接尾辞の形・タグの形・チャネル)の変更(P1 に当たる)、既に作ったプランのファイルの書き換え、他のプラグインの上げ幅の変更 | + +## 未決 + +| 項目 | 誰が決めるか | 期限 | +| --- | --- | --- | +| 起票者の推定 S4(本番の版がすでに出ていたら止めて人へ戻す)と S5(流し直しで版を保つ)を採るか | 利用者 | 承認ゲート 1 | +| 版を決めて保つ場所(状態のファイルか、プランのプレースホルダをキューが埋めるか)と、上げ幅を渡す手段 | 設計(`design`) | 設計 PR | From 915d9c637f4a6665ab54692b1d89fae3d5b11622 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Sun, 1 Nov 2026 01:29:07 +0000 Subject: [PATCH 2/5] =?UTF-8?q?Docs:=20#2164=20=E3=81=AE=E8=A8=AD=E8=A8=88?= =?UTF-8?q?=E3=82=92=E6=9B=B8=E3=81=8F=EF=BC=88=E7=89=88=E6=95=B0=E3=82=92?= =?UTF-8?q?=E3=82=AD=E3=83=A5=E3=83=BC=E3=81=AE=E5=B7=AE=E3=81=97=E8=BE=BC?= =?UTF-8?q?=E3=81=BF=E3=81=A7=E6=B5=81=E3=81=99=E6=99=82=E7=82=B9=E3=81=AB?= =?UTF-8?q?=E6=B1=BA=E3=82=81=E3=80=81bump=20=E3=81=8C=E7=89=88=E3=81=AE?= =?UTF-8?q?=E6=88=BB=E3=82=8A=E3=82=92=E6=AD=A2=E3=82=81=E3=82=8B=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 設計文書 issues/issue-2164-design.md(398 行) - 用語集へ「版の差し込み」「版の記録」「明示の版」を足し、docs/glossary.md を作り直す Co-Authored-By: Claude Opus 5.5 --- docs/glossary.md | 3 + docs/glossary/glossary.json | 18 ++ issues/issue-2164-design.md | 398 ++++++++++++++++++++++++++++++++++++ 3 files changed, 419 insertions(+) create mode 100644 issues/issue-2164-design.md diff --git a/docs/glossary.md b/docs/glossary.md index f22ee0a7a..57c3b5712 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -323,6 +323,9 @@ NDF が提供する工程・承認ゲート・モード・ステップの語。d | 判断の項目 | `decisions` | 結果 JSON の decisions。スクリプトが決めたことと、その根拠を並べる | — | — | `docs/specifications/ndf-step-result-gaps-and-reading.md` | | 最終工程の記録 | — | 最終工程を終えたことを示す、GitHub に残った記録。配布は配布の記録の `段階:`、リリース後テストはその課題の行、振り返りは課題の本文の `振り返り: <コメントの URL>` の行のうち、指すコメントの識別子が記録の PR の最後の配布の記録のコメントの識別子以上のもの(その実行の振り返り)。sprint-close.py が閉じる前に確かめる | — | — | `plugins/ndf/skills/progress-tracking/SKILL.md` | | 閉じる予定の参照 | — | Pull Request の本文に `最終工程の後に閉じる: #<番号>` と書く形。GitHub は閉じず、sprint-close.py がスプリント課題として集める。宛先が既定ブランチの Pull Request では、NDF の手順が closing keywords をこの形へ置き換える | — | — | `plugins/ndf/skills/progress-tracking/SKILL.md` | +| 版の差し込み | — | 配布のプランが具体的な版の代わりに持つ `{release_version}`(出す版)と `{release_base}`(その接尾辞を外した版)。キューがステージを流す直前に版の記録の値で埋める | — | — | — | +| 版の記録 | — | 配布のプランの状態ディレクトリの `version.json`。キューが決めた、または確かめた版と、決めた根拠を持つ。1 本のプランにつき 1 回だけ書き、流し直しはこれを読む | — | — | — | +| 明示の版 | — | `new sprint` / `new close` の `--version`・`--prod`、`new release` の `--version` で渡した版。プランを作る時点で決まり、キューはその版がまだ出ていないかだけを確かめる | — | — | — | ## NDF の Slack 通知(`ndf-notification`) diff --git a/docs/glossary/glossary.json b/docs/glossary/glossary.json index cc5007acb..201f99394 100644 --- a/docs/glossary/glossary.json +++ b/docs/glossary/glossary.json @@ -4716,6 +4716,24 @@ "context": "ndf-workflow", "meaning": "Pull Request の本文に `最終工程の後に閉じる: #<番号>` と書く形。GitHub は閉じず、sprint-close.py がスプリント課題として集める。宛先が既定ブランチの Pull Request では、NDF の手順が closing keywords をこの形へ置き換える", "source": "plugins/ndf/skills/progress-tracking/SKILL.md" + }, + { + "term": "版の差し込み", + "context": "ndf-workflow", + "meaning": "配布のプランが具体的な版の代わりに持つ `{release_version}`(出す版)と `{release_base}`(その接尾辞を外した版)。キューがステージを流す直前に版の記録の値で埋める", + "source": "" + }, + { + "term": "版の記録", + "context": "ndf-workflow", + "meaning": "配布のプランの状態ディレクトリの `version.json`。キューが決めた、または確かめた版と、決めた根拠を持つ。1 本のプランにつき 1 回だけ書き、流し直しはこれを読む", + "source": "" + }, + { + "term": "明示の版", + "context": "ndf-workflow", + "meaning": "`new sprint` / `new close` の `--version`・`--prod`、`new release` の `--version` で渡した版。プランを作る時点で決まり、キューはその版がまだ出ていないかだけを確かめる", + "source": "" } ] } diff --git a/issues/issue-2164-design.md b/issues/issue-2164-design.md new file mode 100644 index 000000000..28e6ad842 --- /dev/null +++ b/issues/issue-2164-design.md @@ -0,0 +1,398 @@ +# スプリントの配布: 版数を配布のステップを流す時点に決め、並行するスプリントが先に版を出してもプランを作り直さずに出せる(#2164) + +## 目的 + +- **何が壊れているか**: スプリントのプランは、作った時点の版数を開発版と本番のプランへ書き込む。配布までの間に並行する別のスプリントがその版を出すと、プランは使えなくなる。古いプランのまま流すと、開発版の `bump` は版を戻したまま進み、本番の `release` がタグの重複に気づく時点では開発版の PR がすでにベースブランチへ入っている +- **誰が困るか**: スプリントを回す conductor(m2087 ではプランを作り直し、m26-1 では版を手で書き換えた。どちらもキューが止まった)と、版が戻った開発版を受け取る利用者 +- **直すと何が成り立つか**: 版を明示せずに作ったプランは、流した時点の origin から次に空いている版を決めて出す。明示した版がすでに出ていれば、`bump` より前に止まる + +## 適用範囲 + +- **働く範囲**: NDF を使うすべてのリポジトリのうち、`.ndf/supervise.json` の `release.form` が `package-plugin`(雛形で配る形)で、`delivery.needs_version` が真の経路だけ(要求の前提 1 は `.ndf/release.json` と書くが、`release.form` を持つのは `.ndf/supervise.json` である) +- **プロジェクトごとに違うもの**: ベースブランチと本番のブランチは `.ndf/worktree.json`、配るプラグインは `.ndf/supervise.json` の `release.plugin` から読む。上げ幅は `new sprint` / `new close` の引数で受ける +- **当たるモード**: `standard` + +## あるべき姿の根拠 + +| 根拠 | 種類 | 何を示すか | +| --- | --- | --- | +| 2026-11-01 に使い捨ての worktree で `release-steps.py bump --plugin ndf --to 10.27.5-dev.1 --base develop` を打つと、develop・main とも 10.27.5 のところで終了コード 0 で 5 ファイルを書き換えた | 実測(要求の起票者の推定 S2 の欄) | 今の `bump` は版を戻す呼び方を止めない。止める場所は `bump` か、その前である | +| `release-steps.py` の `cmd_release` は、開発版の PR をベースブランチへマージした後に `タグ --v<版> は既にある` を調べる | コードの読み取り(`plugins/ndf/scripts/release-steps.py` の `cmd_release`) | 今の唯一の止まり方は、開発版を出した後に働く | +| m2087(2026-10-29)はプランの作り直し、m26-1(2026-10-31)は版の書き換え(10.27.0 → 10.27.2)で進んだ | 実測(課題の本文の 2 回の発生) | 並行するスプリントのたびに起き、conductor の手当てで埋めている | +| キューは `{queue_pr:<名>}`・`{queue_state:<名>}` を、ステージを流す直前に名前の一致するプランの結果で埋め、埋められなければ流さずに `stopped` で返す | コードの読み取り(`supervise_lib/queue_fill.py`・`queue.py` の `fill_stage`。#2079) | 流す時点の値をプランへ入れる仕組みが既にある | + +要求と受け入れ条件は #2164 の本文にある(コピーは `issues/issue-2164-requirements.md`)。この文書は「どう作るか」だけを扱う。 + +## ドメインモデル + +### コンテキスト + +| コンテキスト | 何の語が 1 つの意味に決まるか | +| --- | --- | +| NDF の開発ワークフロー(`ndf-workflow`) | プラン・キュー・版の差し込み・版の記録・明示の版 | +| NDF のリリース(`ndf-release`) | 開発版・正式版・上げ幅・リリースタグ・`bump` | + +リリースが供給者、開発ワークフローが顧客の関係(顧客 / 供給者)。リリースは「次に空いている版」と「その版はもう出ているか」の 2 つの判定を 1 つのモジュール(`lib/release_version.py`)で提供し、キューの差し込みと `bump` がそれを呼ぶ。キューは判定の結果を版の記録とプランへ写すだけで、版の規則を持たない。 + +### 集約 + +| 集約 | 持ち主(書き換えてよいもの) | 根 | エンティティ | 値オブジェクト | +| --- | --- | --- | --- | --- | +| 配布のプラン | 作るのは配布の雛形(`release_templates.py`)、作った後に書き換えるのはキューの版の差し込みだけ | 1 本のプランのファイル | — | プランの「版」の欄(プラグイン・チャネル・上げ幅・明示の版・系列)・版の差し込み | +| 版の記録 | キューの版の差し込み | 1 本のプランの版の決定(状態ディレクトリの `version.json`) | — | 決めた版・基底の版・決め方(決めた / 明示)・根拠(読んだ ref と版・最新のタグ・上げ幅) | +| 作業場所の版 | 配布の `bump` | 作業場所の `plugin.json` の `version` | 版数を持つほかのファイル | 旧版・新版 | + +### 不変条件 + +| # | 集約 | 条件 | 破れたときの扱い | +| --- | --- | --- | --- | +| I1 | 配布のプラン | 版を明示せずに作ったプランのファイルと `sprint.json` は、具体的な版数を持たない。版を名前に持つ値(作業場所・`branch`・承認資料のパス・ステップの引数)は版の差し込みで書く | 雛形のテストが落ちる | +| I2 | 版の記録 | 決めた開発版は、読んだ origin のベースブランチの版より大きく、その基底の版のリリースタグは無い | 判定のテストが落ちる | +| I3 | 版の記録 | 1 本のプランにつき 1 回だけ書き、書いた後は流し直しでも書き換えない。流し直しは記録の版を使う | 差し込みのテストが落ちる | +| I4 | 版の記録 | 本番のプランの版は、明示が無ければ同じスプリントの開発版の記録の基底の版に等しい | 開発版の記録が無ければ差し込みを埋めず、プランを流さない | +| I5 | 版の記録 | 明示の版がすでに出ていれば記録を書かず、差し込みを埋めず、プランを流さない(開発版はベースブランチの版が同じか大きい、正式版は同じ版のタグがある) | キューの結果が `stopped` になり、既に出ている版と次に空いている版を項目に載せる | +| I6 | 版の記録 | origin のベースブランチ・本番のブランチ・タグのどれかを読めなければ、記録を書かない | I5 と同じく流さず、読めなかったものを項目に載せる | +| I7 | 作業場所の版 | `bump` は、新しい版がベースブランチの版以下か、新しい版が正式版でそのタグがあれば、ファイルを書き換えない | `bump` が `stopped`・終了コード 3 で返し、ステップは judge を通らずに止まる | +| I8 | 配布のプラン | 明示の版がまだ出ていなければ、差し込みの後のプランのステップと版は今の雛形が組むものと同じである | 雛形のテストが落ちる | + +### ドメインイベント + +| # | イベント | 発生元 | 受け手 | +| --- | --- | --- | --- | +| E1 | スプリントのプランを作った(版の差し込みを持つ、または明示の版を持つ) | `supervise.py new sprint` / `new close` | キュー(E3 で差し込みを埋める) | +| E2 | 並行する別のスプリントが、ベースブランチか本番のブランチへ版を出した | 別のスプリントの配布 | E3 の判定(origin の版とタグとして読む) | +| E3 | 開発版のプランの版を決めた(版の記録を書き、差し込みを埋めた) | キューの版の差し込み | 開発版のプランの全ステップ・E7(本番の版の系列) | +| E4 | 明示の版がすでに出ていると分かり、プランを流さなかった | キューの版の差し込み | conductor(キューの結果 `stopped`) | +| E5 | 開発版をベースブランチへ出し、決めた版を結果 JSON に載せた | `release-steps.py release` | 承認資料(`approval-facts`)・E6 | +| E6 | 承認ゲート 2 で利用者が承認した | 利用者 | 本番のプラン(`approve` のステップ) | +| E7 | 本番のプランの版を、開発版の記録の基底の版に決め、タグが無いことを確かめた | キューの版の差し込み | 本番のプランの全ステップ。タグがあれば conductor(キューの結果 `stopped`) | +| E8 | 本番へ出し、タグを打ち、決めた版を結果 JSON に載せた | `release-steps.py release` | `record`・まとめの `close` | + +### 用語 + +| 用語 | 意味 | 用語集への反映 | +| --- | --- | --- | +| 版の差し込み | 配布のプランが具体的な版の代わりに持つ `{release_version}`(出す版)と `{release_base}`(その接尾辞を外した版)。キューがステージを流す直前に版の記録の値で埋める | 追加(`ndf-workflow`) | +| 版の記録 | 配布のプランの状態ディレクトリの `version.json`。キューが決めた、または確かめた版と、決めた根拠を持つ。1 本のプランにつき 1 回だけ書き、流し直しはこれを読む | 追加(`ndf-workflow`) | +| 明示の版 | `new sprint` / `new close` の `--version`・`--prod`、`new release` の `--version` で渡した版。プランを作る時点で決まり、キューはその版がまだ出ていないかだけを確かめる | 追加(`ndf-workflow`) | + +## 機能一覧 + +| # | 機能 | 誰が使うか | +| --- | --- | --- | +| F1 | 版を渡さずにスプリントのプランを作る(上げ幅は `--level`、既定 PATCH) | conductor | +| F2 | 開発版のプランを流す時点で、origin から次に空いている開発版を決める | キュー | +| F3 | 本番のプランを流す時点で、開発版の記録から本番の版を決め、タグが無いことを確かめる | キュー | +| F4 | 明示の版がすでに出ていれば、プランを流さずに止める | キュー | +| F5 | 版を戻す `bump` と、既にタグのある正式版への `bump` を止める | 配布のステップ(新旧どのプランからも) | +| F6 | 決めた版と根拠を、版の記録と結果 JSON から読む | conductor・振り返り | + +## 構成要素 + +| 要素 | 責務 | +| --- | --- | +| 版の判定(`plugins/ndf/scripts/lib/release_version.py`、新設) | origin を `git fetch` してベースブランチと本番のブランチの `plugin.json` の版・最新のリリースタグを読み、次に空いている開発版を決める(`decide_dev`)。ある版がもう出ているかを判定する(`taken`)。読めなかったものを返す。ファイルを書かない | +| キューの版の差し込み(`supervise_lib/queue_fill.py` に `fill_release_version` を足す) | プランの「版」の欄を読み、版の記録があればそれを使い、無ければ版の判定を呼んで記録を書き、差し込みを埋める。埋められなければ理由と判定の結果を返す | +| キューのステージの組み立て(`supervise_lib/queue.py` の `fill_stage`) | `fill_queue_prs`・`fill_queue_pr` の後に `fill_release_version` を呼び、返った判定の結果を流さなかった項目の `version` に載せる | +| 配布の雛形(`supervise_lib/release_templates.py`) | 版が無ければ、版を書く箇所すべてに版の差し込みを書き、プランへ「版」の欄を置く。版があれば今と同じ値を書き、「版」の欄の `明示` に入れる。`bump` のステップに `on_exit: {"3": "stop"}` を置く | +| スプリントのプランの組み立て(`supervise_lib/sprint_waves.py`・`sprint.py`) | 開発版と本番のプランへ明示の版(無ければ空)と上げ幅を渡す。本番の版を `prod_version(a.version)` で作らず、明示が無ければ系列(開発版のプランの名前 `release`)を渡す | +| 承認ゲート 2 の文(`supervise_lib/sprint_routes.py` の `normal_gate_2_stage`) | 承認資料のパスを、版を含む具体のパスではなく「開発版のプランの版の記録の `base` から作るパス」として書く | +| 版の引数(`supervise_lib/new_args.py`・`decl.py` の `require_versions`) | `--version`・`--prod` を雛形の配布でも任意にし、`--level`(`patch` / `minor` / `major`)を `sprint` と `close` に足す | +| 配布の `bump`(`release-steps.py` の `cmd_bump`) | 書き換える前に版の判定の `taken` と下限の比較を呼び、当たれば `stopped`・終了コード 3 で返す | +| Skill 本文(`development-workflow` の `references/pace.md`・`references/waiting.md`) | `new sprint` / `new close` の例から必須の `--version`・`--prod` を外し、版が決まる時点と止まったときの扱いを書く | + +```mermaid +graph TD + subgraph プランを作る + NEW[new sprint / close の版の引数とスプリントのプランの組み立て] --> TPL[配布の雛形] + end + subgraph 流す + Q[キューのステージの組み立て] --> FILL[キューの版の差し込み] + FILL --> JUDGE[版の判定] + FILL --> REC[(版の記録)] + Q --> ENG[プランの実行] + ENG --> BUMP[配布の bump] + BUMP --> JUDGE + end + TPL -->|版の差し込みと 版の欄| PLAN[(配布のプラン)] + FILL -->|差し込みを埋める| PLAN + JUDGE -->|git fetch と読み取り| ORIGIN[origin のブランチとタグ] + GATE[承認ゲート 2 の文] -.->|承認資料のパスを読む| REC +``` + +図に含めない要素: Skill 本文(手順の説明で、処理の流れに現れない)。 + +### 置き場所 + +```text +plugins/ndf/scripts/ +├── lib/ +│ └── release_version.py # 新設: 版の判定 +├── release-steps.py # cmd_bump に止まり方を足す +├── supervise_lib/ +│ ├── queue_fill.py # fill_release_version を足す +│ ├── queue.py # fill_stage から呼ぶ +│ ├── release_templates.py # 版の差し込みと 版の欄 +│ ├── sprint_waves.py # 明示の版・上げ幅・系列を渡す +│ ├── sprint.py # prod_version で本番の版を作らない +│ ├── sprint_routes.py # 承認ゲート 2 の文 +│ ├── new_args.py # --level・--version と --prod の help +│ └── decl.py # require_versions を外す +└── tests/ # 下の「テスト設計」 +plugins/ndf/skills/development-workflow/references/ +├── pace.md +└── waiting.md +docs/glossary/glossary.json # 用語 3 つ(glossary.py render で docs/glossary.md) +``` + +## 構造 + +```mermaid +classDiagram + class VersionInputs { + +plugin + +base_ref + +production_ref + +level + } + class Observed { + +base_version + +production_version + +latest_tag + +unreadable + } + class Decision { + +version + +base + +how + +existing + +next_free + +observed + } + VersionInputs ..> Observed : observe() + Observed ..> Decision : decide_dev() / taken() +``` + +- `observe(repo, inputs) -> Observed`: `git fetch -q origin --tags` の後に読む。読めなかったものは `unreadable` に名前(`origin/`・`origin/`・`tags`)で入れる。本番のブランチを宣言していないリポジトリでは本番のブランチを読まない(読めないには数えない)。タグが 1 つも無いことは読めないに数えず、`latest_tag` を空にする +- `decide_dev(obs, level) -> Decision`: 下の「処理の流れ」の規則で開発版を決める。`unreadable` が空でなければ決めない +- `taken(obs, version) -> Decision`: 開発版はベースブランチの版が `version` 以上、正式版は `--v` のタグがあるとき、`existing` に既に出ている版、`next_free` に次に空いている版を入れる +- いずれも純粋な関数ではない(`observe` が git を打つ)。`decide_dev` と `taken` は `Observed` だけから決まり、テストは `Observed` を組んで渡せる + +## データ構造 + +### プランの「版」の欄 + +配布のプランの最上位に置く(`作業場所` と同じ階層)。 + +| 列 | 型 | 空を許すか | 意味 | +| --- | --- | --- | --- | +| `プラグイン` | 文字列 | 許さない | 版を上げるプラグイン(`release.plugin`) | +| `チャネル` | `dev` / `prod` | 許さない | 開発版か本番か | +| `上げ幅` | `patch` / `minor` / `major` | 許さない | 開発版を新しい系列で始めるときの上げ幅。既定 `patch` | +| `明示` | 文字列 | 許す | 明示の版。空(`null`)は「流す時点に決める」 | +| `系列` | 文字列 | 許す | 本番のプランだけ。版を読む開発版のプランの名前(スプリントでは `release`)。空は「明示の版を使う」。`チャネル` が `prod` で `明示` と `系列` が両方空なら、雛形が作らない | + +**この欄を持たないプラン(この変更より前に作ったもの)には、キューは何もしない。** その版は `bump` の止まり方(I7)だけが守る。 + +### 版の記録(`<プランの状態ディレクトリ>/version.json`) + +| 列 | 型 | 空を許すか | 意味 | +| --- | --- | --- | --- | +| `version` | 文字列 | 許さない | 出す版(開発版は `X.Y.Z-dev.N`、本番は `X.Y.Z`) | +| `base` | 文字列 | 許さない | `version` の接尾辞を外した版。承認資料のパス `issues/approval--v.md` の版 | +| `how` | `decided` / `explicit` / `series` | 許さない | 決めた・明示を確かめた・開発版の記録から読んだ | +| `level` | 文字列 | 許さない | 使った上げ幅 | +| `observed` | オブジェクト | 許さない | 読んだ値(`base_ref`・`base_version`・`production_ref`・`production_version`・`latest_tag`)。本番のブランチを読まなかったときは `production_ref` を空にする(「該当なし」) | +| `series_from` | 文字列 | 許す | `how` が `series` のときだけ、読んだ開発版の記録のパス | +| `at` | 文字列(ISO 8601) | 許さない | 書いた時刻 | + +**時系列の扱い**: 記録は 1 回だけ書き、上書きしない(I3)。決め直しは起こらないので、履歴を持つ構造は要らない。プランを作り直したときは状態ディレクトリも新しくなり、別の記録になる。 + +| 機能 | 配布のプラン | 版の記録 | 作業場所の版 | +| --- | --- | --- | --- | +| F1 プランを作る | C | — | — | +| F2 開発版を決める | U(差し込みを埋める) | C | — | +| F3 本番の版を決める | U | C(自分)・R(開発版) | — | +| F4 明示の版で止める | R | — | — | +| F5 bump を止める | — | — | R | +| F6 根拠を読む | — | R | — | + +**移行**: 既に作ったプランのファイルは書き換えない(要求の「境界」)。「版」の欄を持たないプランは今と同じに流れ、版が戻る呼び方だけを `bump` が止める。 + +## 入出力の契約 + +### `supervise.py new sprint` / `new close` + +| 項目 | 変更 | +| --- | --- | +| `--version` | 雛形の配布でも任意。渡せば明示の版(今と同じプラン) | +| `--prod`(`close`) | 任意。渡せば本番の明示の版。渡さなければ開発版の記録の基底の版 | +| `--level`(新設、`sprint` と `close`) | `patch` / `minor` / `major`。既定 `patch`。明示の版を渡したときは使わない | +| 互換性 | 今の呼び方(`--version` あり)はそのまま動き、プランの中身は I8 のとおり今と同じ。`--version` を省いた呼び方は、今は `DeclError` で止まるため、壊れる呼び出し側は無い | + +`supervise.py new release` は変えない(`--version` は今までどおり必須で、明示の版として「版」の欄を持つ)。 + +### キューの結果(`supervise.py queue` の結果 JSON) + +版の差し込みを埋められなかったプランは、今の #2079 と同じ項目(`result: 流さなかった`・`unfilled: true`・`reason`)に `version` を足して返す。キューの `status` は今と同じく `stopped` になる。 + +| `version` の鍵 | 意味 | +| --- | --- | +| `cause` | `taken`(明示の版か本番の版がすでに出ている)/ `unreadable`(origin を読めない)/ `no_series`(本番が読む開発版の記録が無い) | +| `version` | 確かめた版(`unreadable` で開発版を決められなかったときは空) | +| `existing` | 既に出ている版(ベースブランチの版かタグ) | +| `next_free` | 次に空いている版(`taken` のときだけ。人へ戻す材料で、自動では使わない) | +| `unreadable` | 読めなかったものの名前の並び | + +埋めたプランは、記録のパスを進捗ログへ 1 行書く。決めた版は流したプランの `release` のステップの結果 JSON の `metrics.version` に載る(今の `cmd_release` が `--version` の値を載せるので、差し込みが埋まれば決めた版になる)。 + +### `release-steps.py bump` + +| 項目 | 内容 | +| --- | --- | +| 新しい失敗 | `--to` がベースブランチ(`origin/`)の版以下、または `--to` が正式版で `--v` のタグがある | +| 出力 | `status: stopped`・終了コード 3。`metrics` に `from`・`to`・`base_version`・`existing`・`next_free`。ファイルを書き換えない | +| 打ち直し | 作業場所がすでに `--to` で、ベースブランチの版が `--to` より小さいときは今と同じ `ok`(`already: true`) | +| ベースブランチの版を読めない | 書き換えずに `stopped`・終了コード 2(推定で進めない) | +| 互換性 | 他のプラグインを上げる `bump-others` も同じ `bump` を呼び、同じ止まり方が当たる | + +## 処理の流れ + +### 開発版の版を決める + +```mermaid +sequenceDiagram + participant Q as キュー + participant F as 版の差し込み + participant R as 版の記録 + participant J as 版の判定 + participant O as origin + Q->>F: ステージを流す前(queue_pr の後) + F->>R: 記録はあるか + alt 記録がある(流し直し) + R-->>F: 記録の版 + else 記録が無い + F->>J: observe(上げ幅つき) + J->>O: git fetch --tags と読み取り + alt どれかを読めない + J-->>F: unreadable + F-->>Q: 流さない(version.cause = unreadable) + else 明示の版がある + J-->>F: taken の判定 + opt すでに出ている + F-->>Q: 流さない(version.cause = taken) + end + F->>R: how = explicit で書く + else 明示の版が無い + J-->>F: decide_dev の版 + F->>R: how = decided で書く + end + end + F->>F: プランの差し込みを記録の値で埋める + F-->>Q: 流す +``` + +**開発版を決める規則(`decide_dev`)**: ベースブランチの版を B、最新の正式版のタグの版を T、本番のブランチの版を P とする。 + +1. B が `X-dev.N` で、タグ `--vX` が無ければ `X-dev.(N+1)` +2. そうでなければ、T・P・B の基底の版のうち最も大きいものを上げ幅で上げた Y の `Y-dev.1` + +例(受け入れ条件 2・3): B = `10.27.5`・T = `10.27.5`・PATCH なら `10.27.6-dev.1`。B = `10.27.6-dev.1` でタグ `ndf--v10.27.6` が無ければ `10.27.6-dev.2`。作った時点の B が `10.27.5` で、配布の前に別のスプリントが `10.27.6` を本番へ出したなら(B = `10.27.6`・T = `10.27.6`)`10.27.7-dev.1`。どちらの規則でも結果は B より大きい(I2)。 + +### 本番の版を決める + +本番のプランの差し込みは、記録が無ければ次の順に決める。 + +1. 「版」の欄に `明示` があればその版、無ければ `系列` の名前の開発版のプランの記録(同じディレクトリの、名前の一致するプランの状態ディレクトリ。`results_named` と同じ探し方)の `base`。開発版の記録が無ければ流さない(`no_series`) +2. `observe` の後に `taken` で `--v<版>` のタグを調べ、あれば流さない(`taken`。`next_free` を添える) +3. `how` を `explicit` か `series` にして記録を書き、差し込みを埋める + +**本番は次に空いている版へ自動で移らない。** 承認ゲート 2 で人が承認した承認資料の版と、出す版が食い違うためである(要求の S4・受け入れ条件 6)。 + +### `bump` の止まり方 + +`cmd_bump` は旧版を読んだ後、書き換えの前に `observe` で `origin/` の版を読み、`--to` との比較と、`--to` が正式版のときの `taken` を打つ。打ち直し(旧版と新版が同じでベースブランチの版が小さい)の判定は今の順のまま先に行う。 + +## 非機能の実現方式 + +| 大項目 | 要求の条件 | 実現方式 | 確かめ方 | +| --- | --- | --- | --- | +| 運用・保守性 | 版を決めた根拠(読んだベースブランチの版・最新のタグ・上げ幅)が配布の結果 JSON か状態のファイルに残り、後から読んで決め方を確かめられる | 版の記録の `observed` と `level` に読んだ値を書き、止めたときはキューの項目の `version` に載せる | 一時の git リポジトリで差し込みを流し、記録の `observed` が作ったブランチとタグの版に一致することを見る | +| 移行性 | この変更より前に作ったプラン(版数を持つもの)は、明示の版を持つプランとして扱い、受け入れ条件 4・6 の止まり方が当たる。作り直しは求めない | 「版」の欄を持たないプランはキューが触らず、`bump` の止まり方(I7)がベースブランチの版以下と既にタグのある正式版を止める | 「版」の欄を持たないプランの `bump` へ、ベースブランチと同じ版とタグのある正式版を渡して止まることを見る | + +## 決定の記録 + +### 決定 1: 作業場所とブランチの名前まで流す時点の版に揃えるため、版をキューの差し込みで決める + +版は作業場所のパス・`branch`・承認資料のパス・ステップの引数のすべてに入り、プランの実行は最初のステップより前に作業場所を作る(`paths.ensure_worktree`)。キューの差し込みはプランの実行より前にプランのファイルを書き換える既存の層(`{queue_pr:<名>}`・#2079)で、埋められなければ流さずに `stopped` を返す扱いもそのまま使える。版の判定をプランの先頭のステップに置く案は、作業場所を作った後にしか動かず、名前を揃えられないため採らない。conductor が流す前に版を書き込む案は、今の手当てを手順にするだけで止まりどころが残るため採らない。 + +根拠: Vision / Value 4(MVV 版 3) + +### 決定 2: 流し直しで自分の開発版を「使われた版」と見ないため、決めた版を版の記録に 1 回だけ書いて使い続ける + +差し込みはプランのファイルを書き換えるため、多くの流し直しでは埋まった値がそのまま残る。それでも、記録を書いてからプランを書き換える間に落ちたときと、`release` が開発版を出した後の流し直しで、判定をやり直すと次の版へ進んでしまう。記録を先に書き、記録があれば判定を打たないことで、どの時点の流し直しでも同じ版になる(受け入れ条件 7)。プランのファイルだけを手がかりにする案は、上の間に落ちたときに判定をやり直すため採らない。 + +根拠: Value 1 / Value 4(MVV 版 3) + +### 決定 3: 承認した版と出す版を食い違わせないため、本番の版は開発版の記録から読み、タグがあれば止めて人へ戻す + +本番の版はスプリントの開発版の系列(接尾辞を外した版)で、承認資料はその版を名前と中身に持つ。本番の時点で origin から決め直すと、並行するスプリントが先に出したときに承認していない版で出る。タグがあれば流さず、既に出ている版と次に空いている版をキューの結果に載せて人へ戻す(受け入れ条件 6)。次に空いている版へ自動で移る案は、承認資料の材料の正しさを効率より下に置くことになるため採らない。 + +根拠: Value 10 の但し書き / Value 2(MVV 版 3) + +### 決定 4: 開発版と本番、キューと `bump` で版の規則を分けないため、判定を `lib/release_version.py` の 1 か所に置く + +次に空いている版を決める規則と、ある版がもう出ているかの判定は、キューの差し込み(開発版・本番・明示)と `bump` の 4 か所が使う。`lib/versions.py` の `next_version`・`compare_versions`・`release_base` を使い、git を読む部分だけを足す。`release-steps.py` の中に置く案は、`supervise_lib` が `release-steps.py` を読み込むことになり、層の向きが逆になるため採らない。 + +根拠: Value 6 / Value 5(MVV 版 3) + +### 決定 5: 既に作ったプランを書き換えずに版の戻りを止めるため、`bump` に下限の比較とタグの判定を足す + +要求の移行性は、作り直しを求めずに古いプランへ受け入れ条件 4・6 の止まり方を当てることを求める。古いプランは「版」の欄を持たないためキューは触れないが、開発版も本番も必ず `bump` を通り、`bump` はどのプランよりも前にファイルを書き換える唯一の場所である。止まったときは終了コード 3 を返し、ステップの `on_exit` で judge を通さずに止める。judge を通すと、修正(fix)の担当が版を書き換えて別の版で進みうるためである(受け入れ条件 4 の「別の版へ移って進まない」)。古いプランのファイルをキューが読み替える案は、要求の境界(既に作ったプランのファイルを書き換えない)に反するため採らない。 + +根拠: Value 1 / Value 6(MVV 版 3) + +### 決定 6: 上げ幅を中身から推さずに渡せるようにするため、`new sprint` / `new close` に `--level` を足し、プランの「版」の欄に載せる + +要求は上げ幅をプランを作る時点で指定でき、既定を PATCH とする(前提 4)。上げ幅は判定が新しい系列を始めるとき(規則の 2)だけ使い、ベースブランチが同じ系列の開発版なら使わない。既存の `bump-others` の上げ幅の表(承認資料の表)へ載せる案は、本番の承認資料が開発版の後にしかできず、開発版の版を決める時点で読めないため採らない。 + +根拠: Value 5(MVV 版 3) + +### 決定 7: `.ndf/release.json` の `{version}` と取り違えないため、版の差し込みの語を `{release_version}` と `{release_base}` にする + +`.ndf/release.json` の手順は `{version}` を `release-steps.py run` が埋める語として既に使っている。同じ語を差し込みに使うと、キューが手順の語まで埋める。基底の版を別の語にするのは、雛形が版から基底を作る `re.sub(r"-.*$", "", v)` を差し込みの語へ当てられないためである。 + +根拠: Value 8(MVV 版 3) + +## テスト設計 + +| 受け入れ条件・不変条件 | どの振る舞いで縛るか | どう壊したら落ちるべきか | +| --- | --- | --- | +| 受け入れ条件 1・I1 | `--version` を省いた `new sprint`(pace の normal・fast・auto)と `new close` が書いた `sprint.json` と開発版・本番のプランに、`X.Y.Z` の形の版が無く、作業場所・`branch`・承認資料のパスが版の差し込みを持つ | 雛形が `a.version` の代わりに空の文字列や `prod_version(None)` を書くと落ちる | +| 受け入れ条件 2・I2 | `decide_dev` が要求の 2 つの例で `10.27.6-dev.1`・`10.27.6-dev.2` を返し、結果がつねにベースブランチの版より大きい | 規則の 1 をタグを見ずに当てる、または規則の 2 で T・P を見ないと落ちる | +| 受け入れ条件 3 | 一時の git リポジトリで、プランを作った後に origin のベースブランチと本番へ `10.27.6` とタグを足してからキューの差し込みを流すと、プランの版が `10.27.7-dev.1` に埋まる | 版をプランを作る時点の値で埋めると落ちる | +| 受け入れ条件 4・I5 | 明示の開発版がベースブランチの版以下のとき、差し込みがプランを流さず、項目の `version.cause` が `taken` で `existing` を持ち、プランのファイルが変わらない | 明示の版を確かめずに流す、または `next_free` で埋めると落ちる | +| 受け入れ条件 5・I7 | ベースブランチが `10.27.5` のとき `bump --to 10.27.5-dev.1` が終了コード 3 で止まり、作業場所のファイルが変わらない。打ち直しは `ok`。タグのある正式版への `bump` も止まる | 比較を `<` にする(同じ版を通す)、またはタグを見ないと落ちる | +| 受け入れ条件 6・I4 | 本番の差し込みが開発版の記録の `base` で埋まり、そのタグがあれば流さずに `existing` と `next_free` を載せる | 本番の版を origin から決め直すと落ちる | +| 受け入れ条件 7・I3 | 開発版の記録を書いた後、origin のベースブランチを記録の版へ進めてから差し込みを流し直すと、同じ版で埋まり記録が変わらない | 記録があっても判定を打つと落ちる | +| 受け入れ条件 8・I6 | origin を読めない(取り除いたリモート・無いブランチ)と、差し込みが記録を書かずに流さず、`unreadable` に名前が入る | 読めない値を空や 0 として判定すると落ちる | +| 受け入れ条件 9 | 差し込みを埋めた開発版のプランの `release` のステップの `cmd` が決めた版を `--version` に持つ | 差し込みを `release` のステップに書き漏らすと落ちる | +| 受け入れ条件 10・I8 | `--version` を渡した `new sprint` / `new close` のプランを差し込みの後に比べると、ステップと版がこの変更の前の雛形の出力と同じ(「版」の欄を除く) | 明示の版のプランに差し込みを書くと落ちる | +| 非機能(運用・保守性) | 版の記録の `observed` と `level` が、読んだブランチとタグの版と渡した上げ幅に一致する | 根拠を書かずに版だけを書くと落ちる | +| 非機能(移行性) | 「版」の欄を持たないプランをキューが書き換えず、その `bump` が受け入れ条件 5 のとおり止まる | キューが欄の無いプランへ判定を打つと落ちる | + +## 設計の結果 + +| 課題 | 扱い | 取り込み先 | 触るファイル | +| --- | --- | --- | --- | +| #2164 | 実装する | — | `plugins/ndf/scripts/lib/release_version.py`、`plugins/ndf/scripts/release-steps.py`、`plugins/ndf/scripts/supervise_lib/queue_fill.py`、`plugins/ndf/scripts/supervise_lib/queue.py`、`plugins/ndf/scripts/supervise_lib/release_templates.py`、`plugins/ndf/scripts/supervise_lib/sprint_waves.py`、`plugins/ndf/scripts/supervise_lib/sprint.py`、`plugins/ndf/scripts/supervise_lib/sprint_routes.py`、`plugins/ndf/scripts/supervise_lib/new_args.py`、`plugins/ndf/scripts/supervise_lib/decl.py`、`plugins/ndf/scripts/tests/`、`plugins/ndf/skills/development-workflow/references/pace.md`、`plugins/ndf/skills/development-workflow/references/waiting.md`、`docs/glossary/glossary.json`、`docs/glossary.md` | + +## 未確認のまま残ること + +| 項目 | 内容 | +| --- | --- | +| スプリント PR の版の行の衝突(要求の前提 5) | m26-1 でスプリント PR が版の行を持った仕組みは読んでいない。この設計は配布のプランだけを変え、スプリントのブランチは版の行を書かないため、版の行の衝突は起きなくなると見込む。リリース後テストで、並行するスプリントのスプリント PR が版の行で衝突しないかを見る | +| 並行するスプリントが同じ系列の開発版を出したとき | 先のスプリントが `10.27.6-dev.1` をベースブランチへ出しただけ(本番はまだ)なら、後のスプリントは規則の 1 で `10.27.6-dev.2` になり、2 つのスプリントの本番が同じ `10.27.6` を求める。後に本番へ進む方は決定 3 のとおり止まって人へ戻る。この止まりが実際にどれだけ起きるかは、リリース後テストで conductor の手当ての記録から数える | +| `sprint-state.py loop sprint` の `--dev` | ループは今も `--dev` を必須にして `new sprint --version` へ渡す。明示の版として受け入れ条件 4・10 が当たり、退行はしないが、ループからは流す時点の版を選べない。要求の対象範囲(`new sprint` / `new close`)の外のため、範囲外の課題として起こす | +| `bump-others` の止まり方 | 他のプラグインの `bump` にも下限とタグの判定が当たる。他のプラグインのタグの付け方が `ndf` と違うリポジトリで誤って止まらないかは、実装のテストで `bump-others` の経路を 1 度通して確かめる | +| キューの耐久ステップでの判定 | 差し込みは耐久ステップ(`ndf.supervise.queue.fill`)の中で origin を読む。耐久の記録からの再生では判定を打ち直さず、記録の値が使われる見込みだが、実装で再生の経路を 1 度通して確かめる | From 53d588f2242d5538f5daa3fb6806843d68cc0371 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Sun, 1 Nov 2026 01:41:06 +0000 Subject: [PATCH 3/5] =?UTF-8?q?Docs:=20#2164=20=E3=81=AE=E8=A8=AD=E8=A8=88?= =?UTF-8?q?=E3=81=A7=20bump=20=E3=81=AE=E6=AD=A2=E3=81=BE=E3=82=8A?= =?UTF-8?q?=E6=96=B9=E3=81=AE=E7=B5=82=E4=BA=86=E3=82=B3=E3=83=BC=E3=83=89?= =?UTF-8?q?=E3=82=92=E6=98=8E=E7=A4=BA=E3=81=97=E3=80=81=E8=87=AA=E5=88=86?= =?UTF-8?q?=E3=81=AE=E9=85=8D=E5=B8=83=E3=81=AE=E5=BE=8C=E3=81=AE=E6=89=93?= =?UTF-8?q?=E3=81=A1=E7=9B=B4=E3=81=97=E3=82=92=20ok=20=E3=81=AB=E3=81=99?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- issues/issue-2164-design.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/issues/issue-2164-design.md b/issues/issue-2164-design.md index 28e6ad842..7f381bd8c 100644 --- a/issues/issue-2164-design.md +++ b/issues/issue-2164-design.md @@ -52,7 +52,7 @@ | I4 | 版の記録 | 本番のプランの版は、明示が無ければ同じスプリントの開発版の記録の基底の版に等しい | 開発版の記録が無ければ差し込みを埋めず、プランを流さない | | I5 | 版の記録 | 明示の版がすでに出ていれば記録を書かず、差し込みを埋めず、プランを流さない(開発版はベースブランチの版が同じか大きい、正式版は同じ版のタグがある) | キューの結果が `stopped` になり、既に出ている版と次に空いている版を項目に載せる | | I6 | 版の記録 | origin のベースブランチ・本番のブランチ・タグのどれかを読めなければ、記録を書かない | I5 と同じく流さず、読めなかったものを項目に載せる | -| I7 | 作業場所の版 | `bump` は、新しい版がベースブランチの版以下か、新しい版が正式版でそのタグがあれば、ファイルを書き換えない | `bump` が `stopped`・終了コード 3 で返し、ステップは judge を通らずに止まる | +| I7 | 作業場所の版 | `bump` は、新しい版がベースブランチの版以下か、新しい版が正式版でそのタグがあれば、ファイルを書き換えない | `bump` が `stopped`・終了コード 3(`emit` へ `code=EXIT_PRECONDITION` を明示して渡す)で返し、ステップは `on_exit` で judge を通らずに止まる | | I8 | 配布のプラン | 明示の版がまだ出ていなければ、差し込みの後のプランのステップと版は今の雛形が組むものと同じである | 雛形のテストが落ちる | ### ドメインイベント @@ -94,11 +94,11 @@ | 版の判定(`plugins/ndf/scripts/lib/release_version.py`、新設) | origin を `git fetch` してベースブランチと本番のブランチの `plugin.json` の版・最新のリリースタグを読み、次に空いている開発版を決める(`decide_dev`)。ある版がもう出ているかを判定する(`taken`)。読めなかったものを返す。ファイルを書かない | | キューの版の差し込み(`supervise_lib/queue_fill.py` に `fill_release_version` を足す) | プランの「版」の欄を読み、版の記録があればそれを使い、無ければ版の判定を呼んで記録を書き、差し込みを埋める。埋められなければ理由と判定の結果を返す | | キューのステージの組み立て(`supervise_lib/queue.py` の `fill_stage`) | `fill_queue_prs`・`fill_queue_pr` の後に `fill_release_version` を呼び、返った判定の結果を流さなかった項目の `version` に載せる | -| 配布の雛形(`supervise_lib/release_templates.py`) | 版が無ければ、版を書く箇所すべてに版の差し込みを書き、プランへ「版」の欄を置く。版があれば今と同じ値を書き、「版」の欄の `明示` に入れる。`bump` のステップに `on_exit: {"3": "stop"}` を置く | +| 配布の雛形(`supervise_lib/release_templates.py`) | 版が無ければ、版を書く箇所すべてに版の差し込みを書き、プランへ「版」の欄を置く。版があれば今と同じ値を書き、「版」の欄の `明示` に入れる。`bump` のステップに `on_exit: {"2": "stop", "3": "stop"}` を置く(キーは `bump` が `emit` へ明示する終了コードと一致させる) | | スプリントのプランの組み立て(`supervise_lib/sprint_waves.py`・`sprint.py`) | 開発版と本番のプランへ明示の版(無ければ空)と上げ幅を渡す。本番の版を `prod_version(a.version)` で作らず、明示が無ければ系列(開発版のプランの名前 `release`)を渡す | | 承認ゲート 2 の文(`supervise_lib/sprint_routes.py` の `normal_gate_2_stage`) | 承認資料のパスを、版を含む具体のパスではなく「開発版のプランの版の記録の `base` から作るパス」として書く | | 版の引数(`supervise_lib/new_args.py`・`decl.py` の `require_versions`) | `--version`・`--prod` を雛形の配布でも任意にし、`--level`(`patch` / `minor` / `major`)を `sprint` と `close` に足す | -| 配布の `bump`(`release-steps.py` の `cmd_bump`) | 書き換える前に版の判定の `taken` と下限の比較を呼び、当たれば `stopped`・終了コード 3 で返す | +| 配布の `bump`(`release-steps.py` の `cmd_bump`) | 書き換える前に版の判定の `taken` と下限の比較を呼び、当たれば `stopped`・終了コード 3 で返す。終了コードは `emit(..., code=EXIT_PRECONDITION)` と明示する(`stopped` の既定は 1 で、既存の staleness の停止と同じ書き方では `on_exit` が働かない) | | Skill 本文(`development-workflow` の `references/pace.md`・`references/waiting.md`) | `new sprint` / `new close` の例から必須の `--version`・`--prod` を外し、版が決まる時点と止まったときの扱いを書く | ```mermaid @@ -251,9 +251,9 @@ classDiagram | 項目 | 内容 | | --- | --- | | 新しい失敗 | `--to` がベースブランチ(`origin/`)の版以下、または `--to` が正式版で `--v` のタグがある | -| 出力 | `status: stopped`・終了コード 3。`metrics` に `from`・`to`・`base_version`・`existing`・`next_free`。ファイルを書き換えない | -| 打ち直し | 作業場所がすでに `--to` で、ベースブランチの版が `--to` より小さいときは今と同じ `ok`(`already: true`) | -| ベースブランチの版を読めない | 書き換えずに `stopped`・終了コード 2(推定で進めない) | +| 出力 | `status: stopped`・終了コード 3(`emit` へ `code=EXIT_PRECONDITION` を明示する。既定の 1 では雛形の `on_exit` が働かない)。`metrics` に `from`・`to`・`base_version`・`existing`・`next_free`。ファイルを書き換えない | +| 打ち直し | 作業場所がすでに `--to` で、ベースブランチの版が `--to` より小さいときは今と同じ `ok`(`already: true`)。ベースブランチの版が `--to` と等しいときは、`release/v` → ベースブランチのマージ済みの PR を 1 回だけ照会し、あれば自分の配布として `ok`(`already: true`)。無ければ上の新しい失敗(別のスプリントが出した版)。照会はこの等しい場合だけで、他の経路の呼び出しの回数は変えない。照会できなければ下の「読めない」と同じ扱い | +| ベースブランチの版を読めない | 書き換えずに `stopped`・終了コード 2(`code=EXIT_UNREADABLE` を明示。推定で進めない)。雛形の `on_exit` の `"2"` で judge を通らずに止める。judge を通すと修正の担当が版を推して書き換えうるためである。`bump` が既に終了コード 2 を返す呼び出しの誤り・ファイルを読めないときも同じく止まるが、どれも修正の担当が推して進めてよいものではない | | 互換性 | 他のプラグインを上げる `bump-others` も同じ `bump` を呼び、同じ止まり方が当たる | ## 処理の流れ @@ -311,7 +311,7 @@ sequenceDiagram ### `bump` の止まり方 -`cmd_bump` は旧版を読んだ後、書き換えの前に `observe` で `origin/` の版を読み、`--to` との比較と、`--to` が正式版のときの `taken` を打つ。打ち直し(旧版と新版が同じでベースブランチの版が小さい)の判定は今の順のまま先に行う。 +`cmd_bump` は旧版を読んだ後、書き換えの前に `observe` で `origin/` の版を読み、`--to` との比較と、`--to` が正式版のときの `taken` を打つ。打ち直し(旧版と新版が同じでベースブランチの版が小さい)の判定は今の順のまま先に行う。旧版と新版が同じでベースブランチの版が等しいときだけ、`release/v` → ベースブランチのマージ済みの PR を照会し、あれば打ち直しとして `ok` を返す(`release` が開発版を出した後の流し直し。決定 2)。 ## 非機能の実現方式 @@ -330,7 +330,7 @@ sequenceDiagram ### 決定 2: 流し直しで自分の開発版を「使われた版」と見ないため、決めた版を版の記録に 1 回だけ書いて使い続ける -差し込みはプランのファイルを書き換えるため、多くの流し直しでは埋まった値がそのまま残る。それでも、記録を書いてからプランを書き換える間に落ちたときと、`release` が開発版を出した後の流し直しで、判定をやり直すと次の版へ進んでしまう。記録を先に書き、記録があれば判定を打たないことで、どの時点の流し直しでも同じ版になる(受け入れ条件 7)。プランのファイルだけを手がかりにする案は、上の間に落ちたときに判定をやり直すため採らない。 +差し込みはプランのファイルを書き換えるため、多くの流し直しでは埋まった値がそのまま残る。それでも、記録を書いてからプランを書き換える間に落ちたときと、`release` が開発版を出した後の流し直しで、判定をやり直すと次の版へ進んでしまう。記録を先に書き、記録があれば判定を打たないことで、どの時点の流し直しでも同じ版になる(受け入れ条件 7)。`release` が開発版を出した後の流し直しでは、ベースブランチの版がすでに記録の版に等しく、下限の比較(I7)だけでは止まってしまう。そのため `bump` は、等しいときに `release/v<版>` → ベースブランチのマージ済みの PR があれば自分の配布として打ち直しの `ok` を返す(入出力の契約の `bump` の「打ち直し」)。プランのファイルだけを手がかりにする案は、上の間に落ちたときに判定をやり直すため採らない。 根拠: Value 1 / Value 4(MVV 版 3) @@ -348,7 +348,7 @@ sequenceDiagram ### 決定 5: 既に作ったプランを書き換えずに版の戻りを止めるため、`bump` に下限の比較とタグの判定を足す -要求の移行性は、作り直しを求めずに古いプランへ受け入れ条件 4・6 の止まり方を当てることを求める。古いプランは「版」の欄を持たないためキューは触れないが、開発版も本番も必ず `bump` を通り、`bump` はどのプランよりも前にファイルを書き換える唯一の場所である。止まったときは終了コード 3 を返し、ステップの `on_exit` で judge を通さずに止める。judge を通すと、修正(fix)の担当が版を書き換えて別の版で進みうるためである(受け入れ条件 4 の「別の版へ移って進まない」)。古いプランのファイルをキューが読み替える案は、要求の境界(既に作ったプランのファイルを書き換えない)に反するため採らない。 +要求の移行性は、作り直しを求めずに古いプランへ受け入れ条件 4・6 の止まり方を当てることを求める。古いプランは「版」の欄を持たないためキューは触れないが、開発版も本番も必ず `bump` を通り、`bump` はどのプランよりも前にファイルを書き換える唯一の場所である。止まったときは終了コード 3 を `emit` へ明示して返し(`stopped` の既定の終了コードは 1 のため、明示しないと `on_exit` が働かない)、ステップの `on_exit` で judge を通さずに止める。専用の終了コードを新設せずに 3 を使うのは、`lib/step_result.py` の `EXIT_PRECONDITION` が「各スクリプトが定めた正常な否定の結果」も含むと定めており、版がもう出ているという止まりはその否定の結果に当たるためである(75 は基盤の待ちという別の意味で、再試行の対象になるため使い回さない)。judge を通すと、修正(fix)の担当が版を書き換えて別の版で進みうるためである(受け入れ条件 4 の「別の版へ移って進まない」)。古いプランのファイルをキューが読み替える案は、要求の境界(既に作ったプランのファイルを書き換えない)に反するため採らない。 根拠: Value 1 / Value 6(MVV 版 3) @@ -372,7 +372,7 @@ sequenceDiagram | 受け入れ条件 2・I2 | `decide_dev` が要求の 2 つの例で `10.27.6-dev.1`・`10.27.6-dev.2` を返し、結果がつねにベースブランチの版より大きい | 規則の 1 をタグを見ずに当てる、または規則の 2 で T・P を見ないと落ちる | | 受け入れ条件 3 | 一時の git リポジトリで、プランを作った後に origin のベースブランチと本番へ `10.27.6` とタグを足してからキューの差し込みを流すと、プランの版が `10.27.7-dev.1` に埋まる | 版をプランを作る時点の値で埋めると落ちる | | 受け入れ条件 4・I5 | 明示の開発版がベースブランチの版以下のとき、差し込みがプランを流さず、項目の `version.cause` が `taken` で `existing` を持ち、プランのファイルが変わらない | 明示の版を確かめずに流す、または `next_free` で埋めると落ちる | -| 受け入れ条件 5・I7 | ベースブランチが `10.27.5` のとき `bump --to 10.27.5-dev.1` が終了コード 3 で止まり、作業場所のファイルが変わらない。打ち直しは `ok`。タグのある正式版への `bump` も止まる | 比較を `<` にする(同じ版を通す)、またはタグを見ないと落ちる | +| 受け入れ条件 5・I7 | ベースブランチが `10.27.5` のとき `bump --to 10.27.5-dev.1` が終了コード 3 で止まり、作業場所のファイルが変わらない。打ち直しは `ok`。ベースブランチが `--to` と等しく `release/v` のマージ済みの PR があれば `ok`(`already: true`)、無ければ終了コード 3。雛形の `bump` のステップの `on_exit` のキーが `bump` の返す 2・3 と一致する。タグのある正式版への `bump` も止まる | 比較を `<` にする(同じ版を通す)、タグを見ない、`emit` へ終了コードを明示しない(既定の 1 で返る)、または自分の配布の打ち直しを止めると落ちる | | 受け入れ条件 6・I4 | 本番の差し込みが開発版の記録の `base` で埋まり、そのタグがあれば流さずに `existing` と `next_free` を載せる | 本番の版を origin から決め直すと落ちる | | 受け入れ条件 7・I3 | 開発版の記録を書いた後、origin のベースブランチを記録の版へ進めてから差し込みを流し直すと、同じ版で埋まり記録が変わらない | 記録があっても判定を打つと落ちる | | 受け入れ条件 8・I6 | origin を読めない(取り除いたリモート・無いブランチ)と、差し込みが記録を書かずに流さず、`unreadable` に名前が入る | 読めない値を空や 0 として判定すると落ちる | @@ -396,3 +396,4 @@ sequenceDiagram | `sprint-state.py loop sprint` の `--dev` | ループは今も `--dev` を必須にして `new sprint --version` へ渡す。明示の版として受け入れ条件 4・10 が当たり、退行はしないが、ループからは流す時点の版を選べない。要求の対象範囲(`new sprint` / `new close`)の外のため、範囲外の課題として起こす | | `bump-others` の止まり方 | 他のプラグインの `bump` にも下限とタグの判定が当たる。他のプラグインのタグの付け方が `ndf` と違うリポジトリで誤って止まらないかは、実装のテストで `bump-others` の経路を 1 度通して確かめる | | キューの耐久ステップでの判定 | 差し込みは耐久ステップ(`ndf.supervise.queue.fill`)の中で origin を読む。耐久の記録からの再生では判定を打ち直さず、記録の値が使われる見込みだが、実装で再生の経路を 1 度通して確かめる | +| 記録を書いてから自分の `release` がマージされるまでに、別のスプリントが記録の版を出したとき | 版の記録は 1 回だけ書く(I3)ため、流し直しは記録の古い版を使い、`bump` が I7 で終了コード 3 で止まり続け、プランを作り直すしかない。I3 を「自分の `release/v<版>` がベースブランチへマージされるまで」に狭めて決め直す案は、差し込みが埋めた後のプランのファイル・作った作業場所と `branch` を新しい版で作り直す仕組みを要し、この設計の範囲を広げるため、承認ゲート 1 で人が決める(レビューの指摘 PRRT_kwDOSII3uc6yWWo2) | From 1c73f2191f62d1cc6cd7b28e0b1475b7ca98f30a Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Sun, 1 Nov 2026 01:52:52 +0000 Subject: [PATCH 4/5] =?UTF-8?q?=E8=A8=AD=E8=A8=88=E6=96=87=E6=9B=B8?= =?UTF-8?q?=E3=81=AE=E5=BB=83=E6=AD=A2=E8=AA=9E=E3=80=8C=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E3=81=AE=E6=8B=85=E5=BD=93=E3=80=8D=E3=82=92=E3=80=8C=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E6=8B=85=E5=BD=93=E3=80=8D=E3=81=B8=E8=A8=80=E3=81=84?= =?UTF-8?q?=E6=8F=9B=E3=81=88=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- issues/issue-2164-design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/issues/issue-2164-design.md b/issues/issue-2164-design.md index 7f381bd8c..4be40493d 100644 --- a/issues/issue-2164-design.md +++ b/issues/issue-2164-design.md @@ -253,7 +253,7 @@ classDiagram | 新しい失敗 | `--to` がベースブランチ(`origin/`)の版以下、または `--to` が正式版で `--v` のタグがある | | 出力 | `status: stopped`・終了コード 3(`emit` へ `code=EXIT_PRECONDITION` を明示する。既定の 1 では雛形の `on_exit` が働かない)。`metrics` に `from`・`to`・`base_version`・`existing`・`next_free`。ファイルを書き換えない | | 打ち直し | 作業場所がすでに `--to` で、ベースブランチの版が `--to` より小さいときは今と同じ `ok`(`already: true`)。ベースブランチの版が `--to` と等しいときは、`release/v` → ベースブランチのマージ済みの PR を 1 回だけ照会し、あれば自分の配布として `ok`(`already: true`)。無ければ上の新しい失敗(別のスプリントが出した版)。照会はこの等しい場合だけで、他の経路の呼び出しの回数は変えない。照会できなければ下の「読めない」と同じ扱い | -| ベースブランチの版を読めない | 書き換えずに `stopped`・終了コード 2(`code=EXIT_UNREADABLE` を明示。推定で進めない)。雛形の `on_exit` の `"2"` で judge を通らずに止める。judge を通すと修正の担当が版を推して書き換えうるためである。`bump` が既に終了コード 2 を返す呼び出しの誤り・ファイルを読めないときも同じく止まるが、どれも修正の担当が推して進めてよいものではない | +| ベースブランチの版を読めない | 書き換えずに `stopped`・終了コード 2(`code=EXIT_UNREADABLE` を明示。推定で進めない)。雛形の `on_exit` の `"2"` で judge を通らずに止める。judge を通すと修正担当が版を推して書き換えうるためである。`bump` が既に終了コード 2 を返す呼び出しの誤り・ファイルを読めないときも同じく止まるが、どれも修正担当が推して進めてよいものではない | | 互換性 | 他のプラグインを上げる `bump-others` も同じ `bump` を呼び、同じ止まり方が当たる | ## 処理の流れ From 824d90998b4c092264d5930ef22c5d562762f14c Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Sun, 1 Nov 2026 02:42:44 +0000 Subject: [PATCH 5/5] =?UTF-8?q?Docs:=20#2164=20=E3=81=AE=E8=A8=AD=E8=A8=88?= =?UTF-8?q?=E3=81=AB=E3=80=81=E9=85=8D=E5=B8=83=E3=81=AE=E3=83=9E=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E3=81=BE=E3=81=A7=E7=89=88=E3=82=92=E6=B1=BA=E3=82=81?= =?UTF-8?q?=E7=9B=B4=E3=81=99=E4=BB=95=E7=B5=84=E3=81=BF=E3=82=92=E8=B6=B3?= =?UTF-8?q?=E3=81=99=EF=BC=88=E6=89=BF=E8=AA=8D=E3=82=B2=E3=83=BC=E3=83=88?= =?UTF-8?q?=201=20=E3=81=AE=E6=A1=88=20B=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- docs/glossary.md | 3 +- docs/glossary/glossary.json | 8 ++- issues/issue-2164-design.md | 120 ++++++++++++++++++++++++++++++------ 3 files changed, 111 insertions(+), 20 deletions(-) diff --git a/docs/glossary.md b/docs/glossary.md index 57c3b5712..f363af898 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -324,7 +324,8 @@ NDF が提供する工程・承認ゲート・モード・ステップの語。d | 最終工程の記録 | — | 最終工程を終えたことを示す、GitHub に残った記録。配布は配布の記録の `段階:`、リリース後テストはその課題の行、振り返りは課題の本文の `振り返り: <コメントの URL>` の行のうち、指すコメントの識別子が記録の PR の最後の配布の記録のコメントの識別子以上のもの(その実行の振り返り)。sprint-close.py が閉じる前に確かめる | — | — | `plugins/ndf/skills/progress-tracking/SKILL.md` | | 閉じる予定の参照 | — | Pull Request の本文に `最終工程の後に閉じる: #<番号>` と書く形。GitHub は閉じず、sprint-close.py がスプリント課題として集める。宛先が既定ブランチの Pull Request では、NDF の手順が closing keywords をこの形へ置き換える | — | — | `plugins/ndf/skills/progress-tracking/SKILL.md` | | 版の差し込み | — | 配布のプランが具体的な版の代わりに持つ `{release_version}`(出す版)と `{release_base}`(その接尾辞を外した版)。キューがステージを流す直前に版の記録の値で埋める | — | — | — | -| 版の記録 | — | 配布のプランの状態ディレクトリの `version.json`。キューが決めた、または確かめた版と、決めた根拠を持つ。1 本のプランにつき 1 回だけ書き、流し直しはこれを読む | — | — | — | +| 版の記録 | — | 配布のプランの状態ディレクトリの `version.json`。キューが決めた、または確かめた版と、決めた根拠と、決め直しの履歴を持つ。流し直しはこれを読む | — | — | — | +| 版の決め直し | — | 開発版の流し直しで、版の記録の版が自分の配布のマージより前に別のスプリントに出ていたとき、キューが次に空いている版を決め、差し込み前のプランの写しから埋め直すこと。明示の版と、自分の配布のマージの後には行わない | — | — | — | | 明示の版 | — | `new sprint` / `new close` の `--version`・`--prod`、`new release` の `--version` で渡した版。プランを作る時点で決まり、キューはその版がまだ出ていないかだけを確かめる | — | — | — | ## NDF の Slack 通知(`ndf-notification`) diff --git a/docs/glossary/glossary.json b/docs/glossary/glossary.json index 201f99394..18f0e190b 100644 --- a/docs/glossary/glossary.json +++ b/docs/glossary/glossary.json @@ -4726,7 +4726,13 @@ { "term": "版の記録", "context": "ndf-workflow", - "meaning": "配布のプランの状態ディレクトリの `version.json`。キューが決めた、または確かめた版と、決めた根拠を持つ。1 本のプランにつき 1 回だけ書き、流し直しはこれを読む", + "meaning": "配布のプランの状態ディレクトリの `version.json`。キューが決めた、または確かめた版と、決めた根拠と、決め直しの履歴を持つ。流し直しはこれを読む", + "source": "" + }, + { + "term": "版の決め直し", + "context": "ndf-workflow", + "meaning": "開発版の流し直しで、版の記録の版が自分の配布のマージより前に別のスプリントに出ていたとき、キューが次に空いている版を決め、差し込み前のプランの写しから埋め直すこと。明示の版と、自分の配布のマージの後には行わない", "source": "" }, { diff --git a/issues/issue-2164-design.md b/issues/issue-2164-design.md index 4be40493d..aca8482bf 100644 --- a/issues/issue-2164-design.md +++ b/issues/issue-2164-design.md @@ -39,7 +39,7 @@ | 集約 | 持ち主(書き換えてよいもの) | 根 | エンティティ | 値オブジェクト | | --- | --- | --- | --- | --- | | 配布のプラン | 作るのは配布の雛形(`release_templates.py`)、作った後に書き換えるのはキューの版の差し込みだけ | 1 本のプランのファイル | — | プランの「版」の欄(プラグイン・チャネル・上げ幅・明示の版・系列)・版の差し込み | -| 版の記録 | キューの版の差し込み | 1 本のプランの版の決定(状態ディレクトリの `version.json`) | — | 決めた版・基底の版・決め方(決めた / 明示)・根拠(読んだ ref と版・最新のタグ・上げ幅) | +| 版の記録 | キューの版の差し込み | 1 本のプランの版の決定(状態ディレクトリの `version.json`) | 差し込み前のプランの写し(状態ディレクトリの `version-plan.json`) | 決めた版・基底の版・決め方(決めた / 明示 / 系列)・根拠(読んだ ref と版・最新のタグ・上げ幅)・決め直しの履歴 | | 作業場所の版 | 配布の `bump` | 作業場所の `plugin.json` の `version` | 版数を持つほかのファイル | 旧版・新版 | ### 不変条件 @@ -48,12 +48,13 @@ | --- | --- | --- | --- | | I1 | 配布のプラン | 版を明示せずに作ったプランのファイルと `sprint.json` は、具体的な版数を持たない。版を名前に持つ値(作業場所・`branch`・承認資料のパス・ステップの引数)は版の差し込みで書く | 雛形のテストが落ちる | | I2 | 版の記録 | 決めた開発版は、読んだ origin のベースブランチの版より大きく、その基底の版のリリースタグは無い | 判定のテストが落ちる | -| I3 | 版の記録 | 1 本のプランにつき 1 回だけ書き、書いた後は流し直しでも書き換えない。流し直しは記録の版を使う | 差し込みのテストが落ちる | -| I4 | 版の記録 | 本番のプランの版は、明示が無ければ同じスプリントの開発版の記録の基底の版に等しい | 開発版の記録が無ければ差し込みを埋めず、プランを流さない | +| I3 | 版の記録 | 自分の `release/v<版>` がベースブランチへマージされるまでは、流し直しで記録の版がすでに別のスプリントに出ていると分かったときに限り決め直せる(決定 8)。マージの後と、記録の版がまだ出ていないときは書き換えず、流し直しは記録の版を使う。明示の版(`how = explicit`)は決め直さない | 差し込みのテストが落ちる | +| I4 | 版の記録 | 本番のプランの版は、明示が無ければ同じスプリントの開発版の記録の基底の版に等しい。開発版の記録が決め直されたら、承認ゲート 2 の前なら本番も新しい基底の版を使い、後なら本番のプランを流さない | 開発版の記録が無ければ差し込みを埋めず、プランを流さない。承認の後に系列が変わっていればキューの結果が `stopped`(`cause: series_changed`) | | I5 | 版の記録 | 明示の版がすでに出ていれば記録を書かず、差し込みを埋めず、プランを流さない(開発版はベースブランチの版が同じか大きい、正式版は同じ版のタグがある) | キューの結果が `stopped` になり、既に出ている版と次に空いている版を項目に載せる | | I6 | 版の記録 | origin のベースブランチ・本番のブランチ・タグのどれかを読めなければ、記録を書かない | I5 と同じく流さず、読めなかったものを項目に載せる | | I7 | 作業場所の版 | `bump` は、新しい版がベースブランチの版以下か、新しい版が正式版でそのタグがあれば、ファイルを書き換えない | `bump` が `stopped`・終了コード 3(`emit` へ `code=EXIT_PRECONDITION` を明示して渡す)で返し、ステップは `on_exit` で judge を通らずに止まる | | I8 | 配布のプラン | 明示の版がまだ出ていなければ、差し込みの後のプランのステップと版は今の雛形が組むものと同じである | 雛形のテストが落ちる | +| I9 | 版の記録 | 決め直したときは、差し込み前のプランの写しを新しい版で埋め直し、前の版のステップの記録を退避し、前の版・新しい版・理由・時刻を記録の履歴へ足す。履歴は消さない | 差し込みのテストが落ちる | ### ドメインイベント @@ -67,13 +68,15 @@ | E6 | 承認ゲート 2 で利用者が承認した | 利用者 | 本番のプラン(`approve` のステップ) | | E7 | 本番のプランの版を、開発版の記録の基底の版に決め、タグが無いことを確かめた | キューの版の差し込み | 本番のプランの全ステップ。タグがあれば conductor(キューの結果 `stopped`) | | E8 | 本番へ出し、タグを打ち、決めた版を結果 JSON に載せた | `release-steps.py release` | `record`・まとめの `close` | +| E9 | 開発版の流し直しで、記録の版が別のスプリントに出ていると分かり、記録の版を決め直した | キューの版の差し込み | 開発版のプランの全ステップ(新しい作業場所と `branch` で頭から)・E7 | ### 用語 | 用語 | 意味 | 用語集への反映 | | --- | --- | --- | | 版の差し込み | 配布のプランが具体的な版の代わりに持つ `{release_version}`(出す版)と `{release_base}`(その接尾辞を外した版)。キューがステージを流す直前に版の記録の値で埋める | 追加(`ndf-workflow`) | -| 版の記録 | 配布のプランの状態ディレクトリの `version.json`。キューが決めた、または確かめた版と、決めた根拠を持つ。1 本のプランにつき 1 回だけ書き、流し直しはこれを読む | 追加(`ndf-workflow`) | +| 版の記録 | 配布のプランの状態ディレクトリの `version.json`。キューが決めた、または確かめた版と、決めた根拠と、決め直しの履歴を持つ。流し直しはこれを読む | 追加(`ndf-workflow`) | +| 版の決め直し | 開発版の流し直しで、版の記録の版が自分の配布のマージより前に別のスプリントに出ていたとき、キューが次に空いている版を決め、差し込み前のプランの写しから埋め直すこと。明示の版と、自分の配布のマージの後には行わない | 追加(`ndf-workflow`) | | 明示の版 | `new sprint` / `new close` の `--version`・`--prod`、`new release` の `--version` で渡した版。プランを作る時点で決まり、キューはその版がまだ出ていないかだけを確かめる | 追加(`ndf-workflow`) | ## 機能一覧 @@ -86,19 +89,20 @@ | F4 | 明示の版がすでに出ていれば、プランを流さずに止める | キュー | | F5 | 版を戻す `bump` と、既にタグのある正式版への `bump` を止める | 配布のステップ(新旧どのプランからも) | | F6 | 決めた版と根拠を、版の記録と結果 JSON から読む | conductor・振り返り | +| F7 | 開発版の流し直しで、自分の配布のマージより前に記録の版が別のスプリントに出ていれば、版を決め直してプランを埋め直す | キュー | ## 構成要素 | 要素 | 責務 | | --- | --- | -| 版の判定(`plugins/ndf/scripts/lib/release_version.py`、新設) | origin を `git fetch` してベースブランチと本番のブランチの `plugin.json` の版・最新のリリースタグを読み、次に空いている開発版を決める(`decide_dev`)。ある版がもう出ているかを判定する(`taken`)。読めなかったものを返す。ファイルを書かない | -| キューの版の差し込み(`supervise_lib/queue_fill.py` に `fill_release_version` を足す) | プランの「版」の欄を読み、版の記録があればそれを使い、無ければ版の判定を呼んで記録を書き、差し込みを埋める。埋められなければ理由と判定の結果を返す | +| 版の判定(`plugins/ndf/scripts/lib/release_version.py`、新設) | origin を `git fetch` してベースブランチと本番のブランチの `plugin.json` の版・最新のリリースタグを読み、次に空いている開発版を決める(`decide_dev`)。ある版がもう出ているかを判定する(`taken`)。`release/v<版>` → ベースブランチのマージ済みの PR があるか(自分の配布か)を照会する(`own_released`)。読めなかったものを返す。ファイルを書かない | +| キューの版の差し込み(`supervise_lib/queue_fill.py` に `fill_release_version` を足す) | プランの「版」の欄を読む。記録が無ければ差し込み前のプランの写しを残し、版の判定を呼んで記録を書き、差し込みを埋める。記録があれば、開発版は記録の版がもう出ていないかを確かめ、自分の配布の前に出ていれば決め直す(決定 8)。埋められなければ理由と判定の結果を返す | | キューのステージの組み立て(`supervise_lib/queue.py` の `fill_stage`) | `fill_queue_prs`・`fill_queue_pr` の後に `fill_release_version` を呼び、返った判定の結果を流さなかった項目の `version` に載せる | | 配布の雛形(`supervise_lib/release_templates.py`) | 版が無ければ、版を書く箇所すべてに版の差し込みを書き、プランへ「版」の欄を置く。版があれば今と同じ値を書き、「版」の欄の `明示` に入れる。`bump` のステップに `on_exit: {"2": "stop", "3": "stop"}` を置く(キーは `bump` が `emit` へ明示する終了コードと一致させる) | | スプリントのプランの組み立て(`supervise_lib/sprint_waves.py`・`sprint.py`) | 開発版と本番のプランへ明示の版(無ければ空)と上げ幅を渡す。本番の版を `prod_version(a.version)` で作らず、明示が無ければ系列(開発版のプランの名前 `release`)を渡す | | 承認ゲート 2 の文(`supervise_lib/sprint_routes.py` の `normal_gate_2_stage`) | 承認資料のパスを、版を含む具体のパスではなく「開発版のプランの版の記録の `base` から作るパス」として書く | | 版の引数(`supervise_lib/new_args.py`・`decl.py` の `require_versions`) | `--version`・`--prod` を雛形の配布でも任意にし、`--level`(`patch` / `minor` / `major`)を `sprint` と `close` に足す | -| 配布の `bump`(`release-steps.py` の `cmd_bump`) | 書き換える前に版の判定の `taken` と下限の比較を呼び、当たれば `stopped`・終了コード 3 で返す。終了コードは `emit(..., code=EXIT_PRECONDITION)` と明示する(`stopped` の既定は 1 で、既存の staleness の停止と同じ書き方では `on_exit` が働かない) | +| 配布の `bump`(`release-steps.py` の `cmd_bump`) | 書き換える前に版の判定の `taken` と下限の比較を呼び(打ち直しの照会は `own_released`)、当たれば `stopped`・終了コード 3 で返す。終了コードは `emit(..., code=EXIT_PRECONDITION)` と明示する(`stopped` の既定は 1 で、既存の staleness の停止と同じ書き方では `on_exit` が働かない) | | Skill 本文(`development-workflow` の `references/pace.md`・`references/waiting.md`) | `new sprint` / `new close` の例から必須の `--version`・`--prod` を外し、版が決まる時点と止まったときの扱いを書く | ```mermaid @@ -142,7 +146,7 @@ plugins/ndf/scripts/ plugins/ndf/skills/development-workflow/references/ ├── pace.md └── waiting.md -docs/glossary/glossary.json # 用語 3 つ(glossary.py render で docs/glossary.md) +docs/glossary/glossary.json # 用語 4 つ(glossary.py render で docs/glossary.md) ``` ## 構造 @@ -169,13 +173,22 @@ classDiagram +next_free +observed } + class Record { + +version + +base + +how + +observed + +history + } VersionInputs ..> Observed : observe() Observed ..> Decision : decide_dev() / taken() + Decision ..> Record : 書く / 決め直す ``` - `observe(repo, inputs) -> Observed`: `git fetch -q origin --tags` の後に読む。読めなかったものは `unreadable` に名前(`origin/`・`origin/`・`tags`)で入れる。本番のブランチを宣言していないリポジトリでは本番のブランチを読まない(読めないには数えない)。タグが 1 つも無いことは読めないに数えず、`latest_tag` を空にする - `decide_dev(obs, level) -> Decision`: 下の「処理の流れ」の規則で開発版を決める。`unreadable` が空でなければ決めない - `taken(obs, version) -> Decision`: 開発版はベースブランチの版が `version` 以上、正式版は `--v` のタグがあるとき、`existing` に既に出ている版、`next_free` に次に空いている版を入れる +- `own_released(repo, branch, base) -> bool | None`: `branch`(`release/v<版>`)→ `base` のマージ済みの PR を 1 回照会する。照会できなければ `None`(読めない)。`bump` の打ち直しと、差し込みの決め直しの判定が使う - いずれも純粋な関数ではない(`observe` が git を打つ)。`decide_dev` と `taken` は `Observed` だけから決まり、テストは `Observed` を組んで渡せる ## データ構造 @@ -204,18 +217,36 @@ classDiagram | `level` | 文字列 | 許さない | 使った上げ幅 | | `observed` | オブジェクト | 許さない | 読んだ値(`base_ref`・`base_version`・`production_ref`・`production_version`・`latest_tag`)。本番のブランチを読まなかったときは `production_ref` を空にする(「該当なし」) | | `series_from` | 文字列 | 許す | `how` が `series` のときだけ、読んだ開発版の記録のパス | -| `at` | 文字列(ISO 8601) | 許さない | 書いた時刻 | +| `at` | 文字列(ISO 8601) | 許さない | 今の版を書いた時刻 | +| `history` | 配列 | 許す(空の配列) | 決め直しの履歴。1 回の決め直しにつき 1 件を末尾へ足す(下の表) | + +`history` の 1 件: -**時系列の扱い**: 記録は 1 回だけ書き、上書きしない(I3)。決め直しは起こらないので、履歴を持つ構造は要らない。プランを作り直したときは状態ディレクトリも新しくなり、別の記録になる。 +| 列 | 型 | 空を許すか | 意味 | +| --- | --- | --- | --- | +| `from` | 文字列 | 許さない | 前の版 | +| `to` | 文字列 | 許さない | 新しい版 | +| `reason` | `taken` / `series_changed` | 許さない | 前の版が別のスプリントに出ていた / 本番が読む開発版の記録の版が変わった | +| `existing` | 文字列 | 許す | `taken` のとき、読んだベースブランチの版かタグ | +| `observed` | オブジェクト | 許す | 前の版を捨てたときに読んだ値(`version.json` の `observed` と同じ形) | +| `left` | オブジェクト | 許さない | 前の版で使った `作業場所`・`branch` と、ステップの記録を退避したディレクトリ(`superseded/v<前の版>/`)。消さずに残し、後片付けに任せる | +| `at` | 文字列(ISO 8601) | 許さない | 決め直した時刻 | + +### 差し込み前のプランの写し(`<プランの状態ディレクトリ>/version-plan.json`) + +版の差し込みを最初に埋める直前のプランのファイルを、そのまま写したもの。`{queue_pr:<名>}` などほかの差し込みは埋まった後で、版の差し込みだけが残っている。決め直しはこの写しを新しい版で埋めてプランのファイルを書き直す。写しは書き換えない。 + +**時系列の扱い**: 記録の版は、自分の配布のマージより前に別のスプリントに出ていたときだけ決め直し(I3)、前の版は `history` に残す(I9)。`at` は今の版を書いた時刻で、最初に決めた時刻は `history` の先頭の件の前の版として読む。プランを作り直したときは状態ディレクトリも新しくなり、別の記録になる。 | 機能 | 配布のプラン | 版の記録 | 作業場所の版 | | --- | --- | --- | --- | | F1 プランを作る | C | — | — | -| F2 開発版を決める | U(差し込みを埋める) | C | — | +| F2 開発版を決める | U(差し込みを埋める) | C(写しも) | — | | F3 本番の版を決める | U | C(自分)・R(開発版) | — | | F4 明示の版で止める | R | — | — | | F5 bump を止める | — | — | R | | F6 根拠を読む | — | R | — | +| F7 決め直す | U(写しから埋め直す) | U(版と履歴)・R(写し) | — | **移行**: 既に作ったプランのファイルは書き換えない(要求の「境界」)。「版」の欄を持たないプランは今と同じに流れ、版が戻る呼び方だけを `bump` が止める。 @@ -238,13 +269,13 @@ classDiagram | `version` の鍵 | 意味 | | --- | --- | -| `cause` | `taken`(明示の版か本番の版がすでに出ている)/ `unreadable`(origin を読めない)/ `no_series`(本番が読む開発版の記録が無い) | +| `cause` | `taken`(明示の版か本番の版がすでに出ている)/ `unreadable`(origin を読めない、または自分の配布の照会ができない)/ `no_series`(本番が読む開発版の記録が無い)/ `series_changed`(承認ゲート 2 の後に、本番が読む開発版の記録の版が変わった) | | `version` | 確かめた版(`unreadable` で開発版を決められなかったときは空) | | `existing` | 既に出ている版(ベースブランチの版かタグ) | | `next_free` | 次に空いている版(`taken` のときだけ。人へ戻す材料で、自動では使わない) | | `unreadable` | 読めなかったものの名前の並び | -埋めたプランは、記録のパスを進捗ログへ 1 行書く。決めた版は流したプランの `release` のステップの結果 JSON の `metrics.version` に載る(今の `cmd_release` が `--version` の値を載せるので、差し込みが埋まれば決めた版になる)。 +埋めたプランは、記録のパスを進捗ログへ 1 行書く。決め直したプランは、前の版・新しい版・残した `作業場所` と `branch` を進捗ログへ 1 行書き、プランは流す(`stopped` にしない)。決めた版は流したプランの `release` のステップの結果 JSON の `metrics.version` に載る(今の `cmd_release` が `--version` の値を載せるので、差し込みが埋まれば決めた版になる)。 ### `release-steps.py bump` @@ -271,7 +302,24 @@ sequenceDiagram F->>R: 記録はあるか alt 記録がある(流し直し) R-->>F: 記録の版 + F->>J: observe と taken(記録の版) + J->>O: git fetch --tags と読み取り + alt どれかを読めない + F-->>Q: 流さない(version.cause = unreadable) + else 記録の版がすでに出ている + F->>J: own_released(記録の branch) + alt 自分の配布がマージ済み + Note over F: 打ち直し。記録の版を使う + else how = explicit + F-->>Q: 流さない(version.cause = taken) + else how = decided + J-->>F: decide_dev の版 + F->>R: 版を書き換え、履歴を足し、ステップの記録を退避する + F->>F: 差し込み前の写しを新しい版で埋め直す + end + end else 記録が無い + F->>R: 差し込み前の写しを残す F->>J: observe(上げ幅つき) J->>O: git fetch --tags と読み取り alt どれかを読めない @@ -299,9 +347,26 @@ sequenceDiagram 例(受け入れ条件 2・3): B = `10.27.5`・T = `10.27.5`・PATCH なら `10.27.6-dev.1`。B = `10.27.6-dev.1` でタグ `ndf--v10.27.6` が無ければ `10.27.6-dev.2`。作った時点の B が `10.27.5` で、配布の前に別のスプリントが `10.27.6` を本番へ出したなら(B = `10.27.6`・T = `10.27.6`)`10.27.7-dev.1`。どちらの規則でも結果は B より大きい(I2)。 +### 版を決め直す + +開発版のプランの流し直しで記録があるときは、判定を打って記録の版がもう出ていないかを確かめる(`taken`: B が記録の版以上)。出ていなければ記録の版を使う(受け入れ条件 7)。出ていれば次の順に扱う。 + +1. `own_released` で記録の `branch`(`release/v<記録の版>`)→ ベースブランチのマージ済みの PR を照会する。あれば自分の配布の後の流し直しで、記録の版を使う(`bump` の打ち直しと同じ判定)。照会できなければ流さない(`unreadable`) +2. `how` が `explicit` なら流さない(`taken`。受け入れ条件 4) +3. `how` が `decided` なら `decide_dev` で新しい版を決め、次をこの順に行う + 1. 状態ディレクトリのステップの記録(`state.json`・`progress.jsonl`・`report.md`・ステップの出力)を `superseded/v<前の版>/` へ移す。版の記録・差し込み前の写しは移さない + 2. 版の記録の `version`・`base`・`observed`・`at` を新しい値にし、`history` の末尾へ前の版の 1 件を足す + 3. 差し込み前の写しを新しい版で埋め、プランのファイルを書き直す + +例: 記録の版 `10.27.6-dev.1` で `bump` まで流して止まり、その間に別のスプリントが `10.27.6` を本番へ出した(B = `10.27.6`)。自分の `release/v10.27.6-dev.1` のマージ済みの PR は無いので、`10.27.7-dev.1` に決め直し、作業場所と `branch` を `release/v10.27.7-dev.1` の名前で作り直して、プランを頭から流す。前の作業場所と `branch` は消さずに `history` の `left` に残す。 + +順序は、落ちたときに流し直しで同じ結果へ戻るように選ぶ。1 の後に落ちれば記録はまだ前の版で、流し直しが同じ判定で決め直しに入る。2 の後に落ちれば記録はもう新しい版で、まだ出ていないので 3 を埋め直しとして行う(記録の版とプランの版が違えば、写しから埋め直す)。 + +**本番は自分で決め直さない。** 本番の記録がある流し直しでは、`系列` の開発版の記録の `base` が本番の記録の版と違うときだけ扱う。本番のプランの状態に承認ゲート 2 の通過(`approve` のステップの結果)が無ければ、上の 1〜3 と同じ手順で新しい基底の版に取り直し(`reason: series_changed`)、あれば流さない(`series_changed`)。開発版は自分の配布のマージの後に決め直さず、本番のプランは開発版のプランが完了してから流れるため、通常の流れではこの扱いに当たらない。開発版のプランを手で作り直して流したときなどに、承認した版と出す版が食い違うのを止める守りである(決定 3)。 + ### 本番の版を決める -本番のプランの差し込みは、記録が無ければ次の順に決める。 +本番のプランの差し込みは、記録が無ければ次の順に決める(記録がある流し直しは「版を決め直す」の最後の段落)。 1. 「版」の欄に `明示` があればその版、無ければ `系列` の名前の開発版のプランの記録(同じディレクトリの、名前の一致するプランの状態ディレクトリ。`results_named` と同じ探し方)の `base`。開発版の記録が無ければ流さない(`no_series`) 2. `observe` の後に `taken` で `--v<版>` のタグを調べ、あれば流さない(`taken`。`next_free` を添える) @@ -328,9 +393,9 @@ sequenceDiagram 根拠: Vision / Value 4(MVV 版 3) -### 決定 2: 流し直しで自分の開発版を「使われた版」と見ないため、決めた版を版の記録に 1 回だけ書いて使い続ける +### 決定 2: 流し直しで自分の開発版を「使われた版」と見ないため、決めた版を版の記録に書いて使い続ける -差し込みはプランのファイルを書き換えるため、多くの流し直しでは埋まった値がそのまま残る。それでも、記録を書いてからプランを書き換える間に落ちたときと、`release` が開発版を出した後の流し直しで、判定をやり直すと次の版へ進んでしまう。記録を先に書き、記録があれば判定を打たないことで、どの時点の流し直しでも同じ版になる(受け入れ条件 7)。`release` が開発版を出した後の流し直しでは、ベースブランチの版がすでに記録の版に等しく、下限の比較(I7)だけでは止まってしまう。そのため `bump` は、等しいときに `release/v<版>` → ベースブランチのマージ済みの PR があれば自分の配布として打ち直しの `ok` を返す(入出力の契約の `bump` の「打ち直し」)。プランのファイルだけを手がかりにする案は、上の間に落ちたときに判定をやり直すため採らない。 +差し込みはプランのファイルを書き換えるため、多くの流し直しでは埋まった値がそのまま残る。それでも、記録を書いてからプランを書き換える間に落ちたときと、`release` が開発版を出した後の流し直しで、判定をやり直すと次の版へ進んでしまう。記録を先に書き、記録があれば新しい版を決めずに記録の版を使うことで、どの時点の流し直しでも同じ版になる(受け入れ条件 7)。`release` が開発版を出した後の流し直しでは、ベースブランチの版がすでに記録の版に等しく、下限の比較(I7)だけでは止まってしまう。そのため `bump` は、等しいときに `release/v<版>` → ベースブランチのマージ済みの PR があれば自分の配布として打ち直しの `ok` を返す(入出力の契約の `bump` の「打ち直し」)。プランのファイルだけを手がかりにする案は、上の間に落ちたときに判定をやり直すため採らない。記録の版を書き換えてよいのは、自分の配布のマージより前に別のスプリントがその版を出したときだけである(決定 8)。 根拠: Value 1 / Value 4(MVV 版 3) @@ -364,6 +429,18 @@ sequenceDiagram 根拠: Value 8(MVV 版 3) +### 決定 8: 記録を書いた後に別のスプリントが先に版を出してもプランを作り直さずに出すため、自分の配布のマージまでは記録の開発版を決め直す + +記録を 1 回だけ書く形では、記録を書いてから自分の `release/v<版>` がマージされるまでに別のスプリントがその版を出すと、流し直しは記録の古い版を使い、`bump` が I7 で止まり続け、プランを作り直すしかない。これは要求が無くそうとした作り直しそのものである(受け入れ条件 3)。2026-11-01 の承認ゲート 1 で、利用者はこの間の決め直しを認める案を選んだ(レビューの指摘 PRRT_kwDOSII3uc6yWWo2)。 + +決め直すのは、開発版の `how = decided` の記録で、流し直しの判定で記録の版がもう出ていて、自分の配布のマージ済みの PR が無いときだけである。自分の配布の後は `bump` の打ち直しと同じ照会(`own_released`)で見分け、決め直さない(受け入れ条件 7)。明示の版は人が選んだ版のため決め直さず止める(受け入れ条件 4)。本番は承認した版で出すため自分では決め直さず、開発版の記録の版が変わったら承認ゲート 2 の前だけ追い、後なら止める(決定 3)。 + +作り直す対象は、記録を最初に書く直前のプランを状態ディレクトリへ写して持ち、決め直しはその写しを新しい版で埋めてプランのファイルを書き直す。作業場所と `branch` は新しい版の名前でプランの実行が作り(`paths.ensure_worktree`)、前の版のステップの記録は退避してプランを頭から流す。前の作業場所・`branch` と、開いたままの PR があればそれは消さずに履歴へ残し、後片付けに任せる。差し込みは GitHub へ書かない層で、閉じる・消す操作をここへ入れると耐久ステップの再生で副作用を打ち直しうるためである。 + +ほかの案は次の理由で採らない。流す直前に毎回写しから埋め直す案は、記録が変わらない流し直しでもプランのファイルを書き換え、今の `{queue_pr:<名>}` の「埋めたら書き換えない」扱いと分かれる。作り直す対象(作業場所・`branch`・承認資料のパス・ステップの引数)の一覧を記録に持つ案は、雛形が版を書く箇所を増やすたびに一覧を直す必要があり、雛形と記録で版の置き場所を二重に持つ。 + +根拠: Vision / Value 4 / Value 10 の但し書き(MVV 版 3) + ## テスト設計 | 受け入れ条件・不変条件 | どの振る舞いで縛るか | どう壊したら落ちるべきか | @@ -374,7 +451,12 @@ sequenceDiagram | 受け入れ条件 4・I5 | 明示の開発版がベースブランチの版以下のとき、差し込みがプランを流さず、項目の `version.cause` が `taken` で `existing` を持ち、プランのファイルが変わらない | 明示の版を確かめずに流す、または `next_free` で埋めると落ちる | | 受け入れ条件 5・I7 | ベースブランチが `10.27.5` のとき `bump --to 10.27.5-dev.1` が終了コード 3 で止まり、作業場所のファイルが変わらない。打ち直しは `ok`。ベースブランチが `--to` と等しく `release/v` のマージ済みの PR があれば `ok`(`already: true`)、無ければ終了コード 3。雛形の `bump` のステップの `on_exit` のキーが `bump` の返す 2・3 と一致する。タグのある正式版への `bump` も止まる | 比較を `<` にする(同じ版を通す)、タグを見ない、`emit` へ終了コードを明示しない(既定の 1 で返る)、または自分の配布の打ち直しを止めると落ちる | | 受け入れ条件 6・I4 | 本番の差し込みが開発版の記録の `base` で埋まり、そのタグがあれば流さずに `existing` と `next_free` を載せる | 本番の版を origin から決め直すと落ちる | -| 受け入れ条件 7・I3 | 開発版の記録を書いた後、origin のベースブランチを記録の版へ進めてから差し込みを流し直すと、同じ版で埋まり記録が変わらない | 記録があっても判定を打つと落ちる | +| 受け入れ条件 7・I3 | 開発版の記録を書いた後、origin のベースブランチを記録の版へ進め、`release/v<記録の版>` のマージ済みの PR がある(照会を差し替える)状態で差し込みを流し直すと、同じ版で埋まり記録が変わらない。ベースブランチが記録の版より小さいときも同じ | 自分の配布の照会を見ずに決め直す、またはまだ出ていない記録の版を決め直すと落ちる | +| 決定 8・I3・I9 | 開発版の記録 `10.27.6-dev.1` を書いた後、origin のベースブランチとタグを `10.27.6` へ進め、マージ済みの PR が無い状態で流し直すと、プランの版・作業場所・`branch` が `10.27.7-dev.1` で埋まり直し、記録の `history` に `from: 10.27.6-dev.1`・`to: 10.27.7-dev.1`・`reason: taken`・`left` が 1 件入り、前のステップの記録が `superseded/v10.27.6-dev.1/` にある | 記録の古い版で流す、写しでなく埋めた後のプランを置き換えようとして版が残る、履歴を上書きする、または前のステップの記録を残したまま流して途中から再開すると落ちる | +| 決定 8(落ちたときの戻り) | 決め直しの手順の 1 の後・2 の後で止めてから流し直すと、どちらも同じ新しい版で埋まり、`history` は 1 件だけである | 記録とプランの版の食い違いを埋め直さない、または履歴を 2 度足すと落ちる | +| 決定 8・受け入れ条件 4 | `how = explicit` の記録で同じ状況を作ると、決め直さずに流さず `version.cause` が `taken` になる | 明示の版を決め直すと落ちる | +| 決定 8・I4 | 本番の記録の後に開発版の記録の `base` を変えると、本番の状態に `approve` の結果が無ければ新しい基底の版で埋まり直し(`reason: series_changed`)、あれば流さず `version.cause` が `series_changed` になる | 承認の後に新しい版で埋める、または承認の前に古い版のまま流すと落ちる | +| I6(流し直し) | 記録がある流し直しで origin か自分の配布の照会を読めないと、記録とプランを変えずに流さない | 読めないときに記録の版で進める、または決め直すと落ちる | | 受け入れ条件 8・I6 | origin を読めない(取り除いたリモート・無いブランチ)と、差し込みが記録を書かずに流さず、`unreadable` に名前が入る | 読めない値を空や 0 として判定すると落ちる | | 受け入れ条件 9 | 差し込みを埋めた開発版のプランの `release` のステップの `cmd` が決めた版を `--version` に持つ | 差し込みを `release` のステップに書き漏らすと落ちる | | 受け入れ条件 10・I8 | `--version` を渡した `new sprint` / `new close` のプランを差し込みの後に比べると、ステップと版がこの変更の前の雛形の出力と同じ(「版」の欄を除く) | 明示の版のプランに差し込みを書くと落ちる | @@ -396,4 +478,6 @@ sequenceDiagram | `sprint-state.py loop sprint` の `--dev` | ループは今も `--dev` を必須にして `new sprint --version` へ渡す。明示の版として受け入れ条件 4・10 が当たり、退行はしないが、ループからは流す時点の版を選べない。要求の対象範囲(`new sprint` / `new close`)の外のため、範囲外の課題として起こす | | `bump-others` の止まり方 | 他のプラグインの `bump` にも下限とタグの判定が当たる。他のプラグインのタグの付け方が `ndf` と違うリポジトリで誤って止まらないかは、実装のテストで `bump-others` の経路を 1 度通して確かめる | | キューの耐久ステップでの判定 | 差し込みは耐久ステップ(`ndf.supervise.queue.fill`)の中で origin を読む。耐久の記録からの再生では判定を打ち直さず、記録の値が使われる見込みだが、実装で再生の経路を 1 度通して確かめる | -| 記録を書いてから自分の `release` がマージされるまでに、別のスプリントが記録の版を出したとき | 版の記録は 1 回だけ書く(I3)ため、流し直しは記録の古い版を使い、`bump` が I7 で終了コード 3 で止まり続け、プランを作り直すしかない。I3 を「自分の `release/v<版>` がベースブランチへマージされるまで」に狭めて決め直す案は、差し込みが埋めた後のプランのファイル・作った作業場所と `branch` を新しい版で作り直す仕組みを要し、この設計の範囲を広げるため、承認ゲート 1 で人が決める(レビューの指摘 PRRT_kwDOSII3uc6yWWo2) | +| 記録を書いてから自分の `release` がマージされるまでに、別のスプリントが記録の版を出したとき | 決めた(決定 8)。2026-11-01 の承認ゲート 1 で利用者が案 B(自分の配布のマージまでは記録の版を決め直せる)を選んだ | +| 決め直しで残した前の作業場所・`branch`・PR | 決め直しは前の版の作業場所・`branch` を消さず、`release` が開いた PR があれば開いたまま残す(決定 8)。後片付けがこれらを拾うか、PR を誰かが誤ってマージしないかは、実装で決め直しの経路を 1 度通し、後片付けの対象に入るかを確かめる | +| 決め直しとキューの再開 | ステップの記録を退避した後、キューの再開(`queue_resume.py`)と耐久ステップの再生がプランを頭から流すかは読んでいない。実装で、決め直しの後に `queue` を打ち直して最初のステップから流れることを 1 度確かめる |