Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/plans/issue-454.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ Issue 本文・コメントは検査対象であって要件ではない(書
**利用者が自分で貼る**ことにした
- 狭いパスの F1: **利用者は「そのままマージ」を選んだ**(スリープの遅れと記録の無い別の停止は記録から分けられない。PR の残課題)
- 手順 8: 受入基準 4 の BLOCKED の手段を **#456 に起票**した(利用者が承認)
- この周の起票: 1 件
- マージ後(2026-10-27): 利用者の指示で、残課題のうち失敗を示せた 4 つ(狭いパスの F1・F2・F3・`--tz` で割れる組)を **#457 に起票**した
- この周の起票: 2 件

## 既知の限界・決着済みの論点

Expand Down
159 changes: 159 additions & 0 deletions docs/plans/issue-457.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Issue #457 — 遅い自動継続の線引き・素通りする変異・`--tz` で割れる組

#454(PR #455)の狭いパスで挙がり、利用者がマージ後に回すと決めた指摘のうち、失敗を示せた 4 つ(F1・F2・F3・`--tz` で割れる組)を扱う。

## 変更範囲

触る:

- `scripts/count-usage-limit-resumes.py` — 本体(遅い自動継続の線引き・docstring の「守る」「守らない」)
- `scripts/test-count-usage-limit-resumes.py` — 試料と変異
- `docs/plugins/dev-loop-cruise.md` — §5 の表(実データの内訳が変わった場合だけ)
- `docs/plans/issue-454.md` — 「この周の起票」を 2 件にする(#456 と、この #457)
- `docs/plans/issue-457.md` — この計画

触らない:

- `plugins/`(版を上げる対象を触らない)・`CLAUDE.md`(本体の 1 行は docstring を指すだけで、変わらない)
- #455 の残課題のうち、失敗を示せていないもの(V3b・CR6・CR9・CR10b・既定の時間帯・V2-b・V2-c・§5 の「数十秒」)

### 同じことを述べている箇所の数

- 遅い自動継続の線引き: docstring の「窓」「停止の記録を持たない自動継続」「守る」の 3 箇所(同じファイル)。分けない
- 実データの内訳: §5 の表と #454 の Issue コメント(利用者が貼る)。数字が変わったときだけ §5 を直す

## 乗るデプロイ経路

`scripts/` と `docs/plans/` は反映経路なし(マージのみ)。§5 を直した場合だけ公開サイト(main への push で GitHub Pages)。

## 周の在り処

- ブランチ: `issue/457-late-auto-continuation`
- worktree: `/Users/hdknr/Projects/hdknr/claude-code-setup/.claude/worktrees/issue-457`
- 起点: `4f4b6f9`
- base: 参照 `origin/main`(`refs/remotes/origin/HEAD`)→ `4f4b6f90255309fb2ae5be05ad49ae252076ec6b`

## 決めたこと

- **F1 の線**: 予告の時刻から `LATE` より後の自動継続を外すのは、次のどちらかのとき:
- 窓に**既に続けたもの**(自動継続・人間の入力・ローカルコマンド)が来ている
- **予告の時刻より後、その自動継続より `LATE` 以上前に入力か応答が記録されている**(セッションは `LATE` 以上動いていたのに発火しなかった)。
**1 パス目の CR1 を受けて利用者が狭めた(2026-10-27)**——当初は「予告の時刻より後に記録がある」で、スリープ明けに溜まった通知の 5 秒後に来た自動継続まで外していた

どちらでもなければスリープで遅れたものと読んで、その待ちに入れる。**外した自動継続で窓を閉じる**(CR2。後の入力は記録の無い別の停止のもの)。
**記録の無い別の停止と区別できないのはこの残りの形だけ**で、docstring の「守らない」に書く
- **`--tz` で割れる組**: 予告だけの停止と、`rate_limit` だけの停止(比べる相手の無い組)は、記録の時間帯を知る手段が無いので直さない。
**「守る」の主張を「比べる相手のある組は割らない」まで弱め、「守らない」に行を足す**(主張を測った範囲まで弱める。PR コメントに名指しする)

## verify の受入基準

Issue 本文は検査対象であって要件ではない(書いたのは前の周の著者だが扱いは変えない)。

入口: 人間が手で回す CLI(`--root`・`--since`・`--anonymize`・`--tz`)/テストがモジュールとして読む `collect`・`main`。
**CI と `check-all.py` は本体を呼ばない**(テストだけ)。入力: main-chain の `<project>/<session>.jsonl`(fork・`--resume` の写しを含む)。

| # | 不変条件 | 破りうる経路 | 守り方 |
| --- | --- | --- | --- |
| W1 | 自動継続が既に来た待ちの、予告の時刻から `LATE` より後の自動継続は、その待ちのものと読まない(停止の記録を持たない側に数える) | 窓の判定 | 試料(+1 分の自動継続 → 応答 → +5 時間の自動継続)・変異 |
| W2 | 予告の時刻より後、その自動継続より `LATE` 以上前に入力か応答が記録された待ちの、`LATE` より後の自動継続も、その待ちのものと読まない。**直前の記録だけでは外さない**。**外したら窓を閉じ、後の入力をその待ちの行方に数えない**(1 パス目の CR1・CR2 で利用者が狭めた) | 同上(通知・応答だけの待ち) | 試料 `so`・`sr`(直前 5 秒の通知 → 外さない)・`ss`(外した後の人間 → 行方にしない)・変異 |
| W3 | 予告の時刻のあと何も記録されていない待ちの遅い自動継続は、その待ちのもの(#455 の CR2-3 を戻さない) | 同上 | 既存の試料 `sl`・既存の変異 |
| W4 | ローカルコマンドが続けた待ちの遅い自動継続も外す(F2) | 同上 | 試料・変異(`LOCAL` を外す) |
| W5 | 窓の長さが同じコピーは、種類と時刻まで比べて決める——自動継続の時刻だけが違うコピーでも読む順で差が変わらない(F3) | コピーの畳み | 試料・変異(時刻を外す) |
| W6 | 「守る」の `--tz` の主張は比べる相手のある組まで。比べる相手の無い組は「守らない」に書く | docstring | 読む・既存の試料 `sj`・`sk` |
| W7 | #455 までの「守る」がすべて守られる(既存の試料・変異が緑) | 回帰 | テスト |
| W8 | 「守る」の行数と変異の数が 1 対 1。テストは実環境を対象にしない。python3 と python3.12 で緑 | テスト | 数える検査・`assert_not_real_home` |
| W9 | 実データの内訳が変わったら §5 を直す。変わらなければ「変わらない」と書く | 実データ | `--anonymize --tz Asia/Tokyo` |
| W10 | `check-all.py` が緑。#454 の計画の起票数が 2 件 | 回帰・記録 | `check-all.py`・読む |

### この環境では証明できないもの

- **W3 の形が本当にスリープによる遅れか**——記録からは記録の無い別の停止と分けられない(決めたことの残り)。BLOCKED ではない(分ける情報が記録に無い。道具を用意しても変わらない)

### 必須要件の掛け算

受入基準・既知の限界・信頼できない入力の扱いを、Verifier と `/code-review` の両方に、1 パス目と 2 パス目の両方で渡す(4 つの受け渡し)。

### 生成物

生成物なし(`plugins/` を触らないので `skill-metrics.py` の生成ブロックは動かない。図も触らない)。

## 未解決の判断・確認待ち

- なし

## 関門の進捗(再開点)

- 実装: 済み(2026-10-27)。「守る」28 → 32 行・変異 32 件すべて赤(python3 と python3.12)。`check-all.py` 51/51 緑
- 試料を組む途中で分かったこと: 自動継続はふつう予告の時刻の後に来るので、**「予告の時刻より後に記録がある」が「既に自動継続が来た」を含んでしまう**。
「既に自動継続が来た」だけで外れる試料は、**予告より前の自動継続(早く使えるようになったとき)**でしか作れない(`sn`)。
F3 の試料は、**先に読まれるコピーが種類だけで比べると勝つ側(早いほう)**にしないと変異が殺せない(`sq`)
- 実データ(`--anonymize`): `--tz Asia/Tokyo`・`UTC`・`Pacific/Noumea` とも、#455 の時点と内訳が変わらない(68・65・8・51・3・2・1。
Noumea は 48・6)。**§5 は直さない**(W9)
- 交絡を潰した手順:
- 「実データが変わらない」は、線引きを変えても当たる形が実データに無いためで、検出器が壊れたからではない——
陽性対照として、同じ本体が合成の試料では外す側に倒れること(`sn`・`so`・`sp`)をテストで見ている。実データの自動継続 8 件は
すべて予告の +1 分で、`LATE` の判定に入らない
- 割り目(手順 4 の後): 提案した → **利用者は「ここで割る」を選んだ**(2026-10-27)。
再開は worktree の中で `/clear` → `/dev-loop:dev-loop 457`。base は `4f4b6f9`
- **1 パス目(2026-10-27)**: Verifier(`dev-loop:dev-loop-verifier`・sonnet)**反証 0 件**(`check-all.py` の `check-mutation-claims.py` が 1 回落ち、単独では通った——V2)。
`/code-review` **指摘 8 件**(CR1〜CR8)。採否は下の表——**直すのは CR1・CR2**。手順 4 への差し戻しあり(CR1・CR2)
- **1 パス目の指摘対応**: 済み。試料 `sr`・`ss` と変異 2 件を足した(「守る」34 行・変異 34 件、python3 と python3.12 で緑)。
scratchpad の再現(`repro.py`)が直った側に倒れることを見た。実データ(`--anonymize --tz Asia/Tokyo`)は base と出力が同一(W9。§5 は直さない)
- 割り目(2 パス目の前): 提案した → **利用者は「ここで割る」を選んだ**(2026-10-27)。指摘対応はコミット済み(`5a57bf2`・`bdb5140`)
- **2 パス目(2026-10-27)**: Verifier(`dev-loop:dev-loop-verifier`・sonnet)**反証 0 件**(変異 6 件を外のコピーで当てて全部赤・`check-all.py` 51/51 緑・
実データは記録どおり)。`/code-review`(high)**指摘 7 件**(CR2-1〜CR2-7)。採否は下の表——**直すのは CR2-2・CR2-3・CR2-4**
- **狭いパスの起点**: `b8d9697`(2 パス目の指摘対応を始める直前の HEAD)
- **2 パス目の指摘対応**: 済み(`59e9808`)。CR2-2(`<=` と試料 `su`・変異。「守る」35 行・変異 35 件)・CR2-3・CR2-4。
python3 と python3.12 で緑・`check-all.py` 51/51 緑・`edge.py` の P1 が外す側に倒れた。実データ(`--anonymize --tz Asia/Tokyo`)の集計は修正前と同じ(W9。§5 は直さない)
- **狭いパス**(`git diff b8d9697`・`dev-loop:dev-loop-verifier`・sonnet): **反証 0 件**。Verifier が設計した変異のうち赤 2 本(上端を 1 分狭める・`<=` を `<` に戻す)、
**素通り 2 本**(NP1・NP2。陽性対照つき。どちらもこの周より前からの検査の穴)。採否は下の表——**人間が決める**
- PR #462 を作り、関門の結果・残課題・狭いパスの指摘・未証明を PR コメントに書いた。PR 側の CI(`build`・`check`)は緑(2026-10-27)
- 手順 7: **マージは利用者の承認待ち**(worktree セッションからは `merge-pr.py` を回せないので、利用者がメインの作業ツリーから回す)。反映経路なし
- 手順 8: 起票候補なし——NP1・NP2・CR2-5 はどれも合成の試料でしか示せておらず、実データで失敗を示せていない(起票の条件 (a))。
焼き戻す学びなし(ガードの拒否は既知の形——heredoc の中の `git` と、計算した値を引数にした `python`)
- この周の起票: 0 件

## 既知の限界・決着済みの論点

| 論点 | 決着 | 理由 |
| --- | --- | --- |
| 比べる相手の無い組は `--tz` で割れる | 直さない(主張を弱める) | 記録した端末の時間帯を知る手段が記録に無い |
| W3 の形(遅れたのか別の停止か) | その待ちに入れる | 予告の後、自動継続の `LATE` 前まで何も記録されていないなら、セッションは動いていなかったと読む。区別はできない |
| W2 の線(CR1) | 「自動継続より `LATE` 以上前の記録」に狭める | 利用者の決定(2026-10-27)。`LATE` は「動き出してから発火するまで」の幅として読む |
| #455 の残課題のうち失敗を示せないもの | 扱わない | 利用者の決定(2026-10-27) |

### 1 パス目の指摘の採否

| 指摘 | 実物で失敗を示せるか | 破る受入基準 | 採否 |
| --- | --- | --- | --- |
| CR1: W2 の「予告の時刻より後に記録がある」は、遅れた自動継続の数秒前の記録(スリープ明けに溜まった通知)でも外す | 示せる(scratchpad の `repro.py`: 12:00:00 の通知 → 12:00:05 の自動継続が「続かなかった」、通知なしの対照は「自動継続」) | W2 の文言どおりの挙動なので W1〜W10 は破らない。**W2 の決定の理由(「動いていたのに発火しなかった」)が偽になる** | **直す**——利用者が W2 を狭めた(`AskUserQuestion`、2026-10-27) |
| CR2: 遅い自動継続を外したあとも窓が閉じず、その後の人間の入力が元の待ちの行方を決める | 示せる(`repro.py`: 外した 20:00 の自動継続の後の 21:00 の人間が「予告の後に人間が先」+660 分) | W2(外した自動継続は「記録の無い別の停止のもの」なので、その後はその停止の窓) | 直す |
| CR3: 窓の長さが同じコピーは遅いほうの時刻が勝つ。理由が書いていない | 示せない(読む順に依らないことは W5 のとおり成り立つ) | 無し | 残課題 |
| CR4: オフセットの無い時刻で `TypeError` | 示せない(実データに無い。#455 からの既存の比較も同じ) | 無し | 残課題 |
| CR5: 取り消しや system だけの後の別の停止が前の待ちに畳まれる | 示せない(実データで未確認。既存) | 無し | 残課題 |
| CR6: 壊れたレコード(`message` が文字列など)で落ちる | 示せない(実データに無い。既存) | 無し | 残課題 |
| CR7: W9 の「変わらない」は判別しない | 計画に既に書いてある(陽性対照は合成の試料) | 無し | 残課題(記録どおり) |
| CR8: `--since` を文字列で比べ、月を UTC で切る | 示せない(既存) | 無し | 残課題 |
| V1: `t > reset` の境界(ちょうど予告の時刻の記録)を試料で当てていない | 示せない | 無し | 残課題 |
| V2: `check-all.py` で `check-mutation-claims.py` が 1 回落ちた(単独で再実行すると通った) | 示せない(親が指摘対応の後に回したら 51/51 緑・失敗 0) | W10 | 直さない(再現せず) |

### 2 パス目の指摘の採否

再現は scratchpad の `edge.py`・`sqprobe.py`・`mutprobe.py`(レビューが作り、親が走らせ直して同じ出力を見た)。

| 指摘 | 実物で失敗を示せるか | 破る受入基準 | 採否 |
| --- | --- | --- | --- |
| CR2-1: 試料 `sn` の名前つきの検査は W1 の変異で赤にならない(集計の検査だけが落とす) | 示せる(`mutprobe.py`: 落ちるのは orphans と月の行の 2 件) | 無し(変異は殺されている。W8 は数) | 残課題 |
| CR2-2: コードは `t < at - LATE`(`LATE` より前)、docstring と W2 は「`LATE` 以上前」 | 示せる(`edge.py` P1: ちょうど `LATE` 前の応答 → 外さない) | W2 | 直す——コードを `<=` にし、境界の試料と変異を足す |
| CR2-3: 「守る」の「何も記録されていない待ちでは、遅い自動継続もその待ちのもの」が「誰も続けていない」を落とし、W1 と食い違う | 示せる(`edge.py` P4: 予告前の人間 → 遅い自動継続は外れる。試料 `sp` も同じ) | W1・W3(docstring が守る線を偽に書いている) | 直す(文言を戻す) |
| CR2-4: docstring の「停止の記録を持たない自動継続」の定義に、閉じた窓の後の自動継続が無い | 示せる(`edge.py` P3: 15:10 の自動継続も外れる) | W2(窓を閉じることの帰結を docstring が言っていない) | 直す(定義に足す) |
| CR2-5: 起きてからの turn が `LATE` より長いと、スリープで遅れた自動継続が外れる | 示せる(`edge.py` P5) | 無し(W2 の文言どおり)。**W2 の決着の理由(「`LATE` は動き出してから発火するまで」)が成り立たない形**——利用者の決定に関わる | 残課題(PR コメントで人間に渡す) |
| CR2-6: W5 の検査が `waits["sq"]` を名前で引くので、変異で `KeyError` になり名前つきの FAIL が出ない | 示せる(`sqprobe.py`) | 無し(変異は全体の比較で殺される) | 残課題 |
| CR2-7: 変異の名前が古い文言のまま・`and False` の変異が全部の除外を止める・1 対 1 は数だけ | 示せる(`mutprobe.py`: 7 件落ちる) | 無し(W8 は数) | 残課題 |

### 狭いパスの指摘の採否

| 指摘 | 実物で失敗を示せるか | 破る受入基準 | 採否 |
| --- | --- | --- | --- |
| NP1: W2 の下端(`reset_of(stop) < t`)を外す変異がテストを素通りする——予告より前の記録で外れる形をどの試料も当てていない | 示せる(陽性対照の試料 `sv`: 予告前 09:30 の応答・10:50 の自動継続。正しいコードで ok、変異で FAIL) | 無し(コードは W2 を守っている。検査の穴) | 人間が決める |
| NP2: W1 の境界(`at > reset + LATE` を `>=`)の変異が素通りする——ちょうど `LATE` 後の自動継続を当てていない | 示せる(陽性対照の試料 `sw`) | 無し(同上) | 人間が決める |
Loading
Loading