Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b761c0e
Test: pytest が止められたときと終わったときに、テストが起こした子孫のプロセスを止める(#2291)
takemi-ohama Oct 31, 2026
0418b2c
Feat: 全体テストを同じ条件で測り、着手前後のテストの集合を照らす実験版を足す(#2291)
takemi-ohama Oct 31, 2026
d0aa9ba
Docs: テストを削る基準と速くする手段を開発ガイドに置く(#2204・#2291)
takemi-ohama Oct 31, 2026
768126b
CI: Python tests で同じ PR の古い実行を取り消し、uv をキャッシュつきで入れる(#2154・#2291)
takemi-ohama Oct 31, 2026
5f3a96a
Test: 用語集のテストの分岐をプロセスの中で確かめ、重複と総当たりのテストを絞る(#2291・#2204)
takemi-ohama Oct 31, 2026
c70815e
Test: supervise・リリース・宣言などのテストの分岐をプロセスの中で確かめる(#2291)
takemi-ohama Oct 31, 2026
6eff32b
Test: 中継のテストの固定の待ちを、子が端末を raw にした印と既存の待ちの環境変数で縮める(#2291)
takemi-ohama Oct 31, 2026
c2e02bb
Test: 指示書の検査・スプリントの終わり・PR の手順などのテストの分岐をプロセスの中で確かめる(#2291)
takemi-ohama Oct 31, 2026
93b48c1
Test: cross-refactoring の init のテストの git の準備をプロセスごとに 1 度作って複製する(#2291)
takemi-ohama Oct 31, 2026
af94ad7
Fix: 計測の実験版の関数の名前を lib の同名の関数と分ける(#2291)
takemi-ohama Oct 31, 2026
a80669b
Docs: 根の conftest の説明に子孫のプロセスの後始末を足す(#2291)
takemi-ohama Oct 31, 2026
5b81ff6
feat(cross-refactoring): 想定最大時間を省いたら下限まで上げ、時間の枠の不足は飛ばした結果で返す(#2047)
takemi-ohama Oct 31, 2026
9fab0e5
feat(supervise): 飛ばしたリファクタリングをコードレビューへ進め、駆動の見込みでステップの締め切りを延ばす(#2047)
takemi-ohama Oct 31, 2026
ec2bae9
fix: script-structure の行数の例外リストを #2047 で増えた分へ更新する(cross-refactoring の…
takemi-ohama Oct 31, 2026
d739146
docs(#2047): 確定仕様と締め切りの式の正本を、着手前のテストの見込み・想定最大時間の引き上げ・ステップの延長に合わせる
takemi-ohama Oct 31, 2026
23344df
docs: 仕様の文から課題番号の由来を外す(doc-lint)
takemi-ohama Oct 31, 2026
f6ed838
Merge pull request #2307 from devbasex/feat/issue-2047-l20261031-5
takemi-ohama Oct 31, 2026
8d57043
Docs: 全体テストの着手前後の実測と遅いテストの表を台帳に書く(#2291)
takemi-ohama Oct 31, 2026
66df2ca
Docs: 打ち切りの後にテストのプロセスが残らないことを確かめた結果を台帳に書く(#2291)
takemi-ohama Oct 31, 2026
899a2e1
Merge remote-tracking branch 'origin/sprint/l20261031-5' into feat/is…
takemi-ohama Oct 31, 2026
50040c4
Merge pull request #2311 from devbasex/feat/issue-2291-l20261031-5
takemi-ohama Oct 31, 2026
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
16 changes: 14 additions & 2 deletions .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ on:
- "pytest.ini"
- "pyproject.toml"

# 同じ Pull Request で新しい push が来たら、古い実行を取り消す(#2154・#2291 の決定 5)。
# push(main / develop)は実行ごとに別の組へ入れて最後まで回す。同じ組に新しい実行が入ると、
# 待っている前の実行は cancel-in-progress が false でも取り消され、コミットごとの記録が欠けるため。
# 取り消した実行でも集約のジョブ pytest は always() で走り、古いコミットに失敗を付ける。
# head のコミットのチェックには新しい実行の結果だけが付く
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
# 配布の PR(版数と説明だけの差分、または develop で通ったコミットを進めるだけの main 宛の
# PR)では重いジョブを省く(#1055)。判定は scripts/ci-heavy-skip.py が差分と develop の
Expand Down Expand Up @@ -75,8 +84,11 @@ jobs:
- uses: actions/setup-node@v7
with:
node-version: "22"
- name: Install uv
run: python3 -m pip install --upgrade uv
# uv と依存の取得を uv.lock をキーにしたキャッシュで縮める(#2291 の決定 6)
- uses: astral-sh/setup-uv@v10.3.3
with:
enable-cache: true
cache-dependency-glob: uv.lock
# 起点はリポジトリの根に置く。環境は根の pyproject.toml と uv.lock が固定する(#1142 の決定 22)。
# playwright_kit・plugins/ndf の全グループ(--all-extras)・pytest と pytest-xdist が入り、
# 包みを呼ぶ側のテストも同じプロセスで外部パッケージを import できる。
Expand Down
8 changes: 6 additions & 2 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
シェルから起動しても、既定値を前提にするテストが同じ結果になる(#678)
6. `SHARD_TOTAL` と `SHARD_INDEX` が与えられたら、収集した項目をファイル単位で分け、
自分の分だけを残す。継続的統合がジョブを分けて回すために使う(#882)
7. pytest が SIGTERM で止められたとき・終わったときに、テストが起こした子孫のプロセスを止める
(`scripts/pytest_child_cleanup.py`。#2291)

どの束のディレクトリを起点にしても読まれるよう、テストの基準のディレクトリ(rootdir)は
根の設定ファイル(`pytest.ini`)がリポジトリの根へ固定する。
Expand All @@ -37,9 +39,11 @@

# `pytester` は playwright-kit の pytest plugin の自己テストが使う。pytest はこの宣言を
# 最上位の conftest.py でしか受け付けない。
pytest_plugins = ["pytester"]

# `pytest_child_cleanup` は、テストが起こした子孫のプロセスを、pytest が SIGTERM で止められたとき・
# 終わったときに止める(#2291)。親と xdist の各 worker で働く。
ROOT = Path(__file__).resolve().parent
sys.path.insert(0, str(ROOT / "scripts"))
pytest_plugins = ["pytester", "pytest_child_cleanup"]

# `plugins/<family>/dev.agy/skills/` は配布 Skill への symlink である。実体は
# `plugins/<family>/skills/` にあり、そちらを収集する。両方をたどると同じテストが 2 度
Expand Down
1 change: 1 addition & 0 deletions docs/ndf-experiments.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
| `runner-trial.py` | #1142 の不足 i の設計の前に、プランの実行とキューを置き換える候補(LangGraph の SqliteSaver・Burr の SQLitePersister・DBOS Transact の SQLite)で、`supervise.py new impl` の雛形(ステップを sleep と決めた終了コードの偽物にしたもの)・キュー・kill -9 からの再開・進捗ログと `supervise.py wait` を保てるかを確かめる | 2026-09-26・`exp/runner-trial` | 2026-09-26: 3 候補ともサーバーを立てず(プロセスの待ち受けのポート 0)SQLite のファイルだけで動き、確かめた 5 つが成り立った(`check <候補>` の結果がすべて ok)。表せるか: `on_fail`・judge の選択・上限 20・承認ゲート(止まり、承認の無い打ち直しは同じ所で止まったまま、承認で続く)を 5 つのシナリオで supervise.py の規則どおりの経路で流した。LangGraph の `interrupt()` は続けるときノードを頭から流し直すため、止める前の副作用はノードの外に置く。`recursion_limit` は invoke ごとに数え直すため、上限は状態で数える。Burr は遷移を `when(cur=...)` で行き先ごとに並べ、承認ゲートは `halt_before` で止める。DBOS は承認ゲートで `DBOS.recv` の待ちのままプロセスを抜け(`os._exit`)、打ち直しの `DBOS.launch()` が記録から続ける(落ちた後は状態が ENQUEUED に見える)。キュー: 6 本(ステージ 4 本 → 2 本)を同時 2 本・GraphQL の枠 1 本で流し、同時は最大 2 本・GraphQL は最大 1 本(枠の待ちが 1〜8 回起きた)で、後のステージは前のステージがすべて終わってから流れ、前のステージの 1 本が止まると後のステージは流れなかった。キューを持つのは DBOS だけ(`register_queue` の `worker_concurrency`。資源のタグは別のキューの `concurrency`)。LangGraph と Burr はキューを持たないため、子プロセスと flock の枠で自前にした(約 60 行)。6 本の所要は LangGraph 6.9 秒・Burr 6.0 秒・DBOS 12.4 秒(DBOS はキューの問い合わせの間隔 0.1 秒の分だけ遅い)。再開: 3 候補とも kill -9 のときに流れていたステップ(test-limited)から流し直し、済んだステップは流し直さなかった。費用(uv 0.12.19・キャッシュの冷えた状態。LangGraph / Burr / DBOS の順): 解決 0.28 / 0.45 / 0.21 秒、導入 0.32 / 0.20 / 0.45 秒(キャッシュが温まって 0.03 / 0.01 / 0.01 秒)、大きさ 54.9 / 20.2 / 60.6 MB、依存 41 / 2 / 11 件(LangGraph は langchain-core・langsmith・pydantic-core を、DBOS は SQLite でも SQLAlchemy・psycopg-binary を入れる)、import 冷えて 0.82 / 0.06 / 0.78 秒・温まって 0.26 / 0.04 / 0.24 秒、起動し直しを含むエントリポイント(温まって)0.31 / 0.08 / 0.31 秒。進捗ログと wait: 進捗ログの行(step・attention)はライブラリでなく NDF の部品がステップの中で書き、今の `supervise.py wait` は手を入れずに attention で 20、done で 0 を返した | 本体へ移した(DBOS Transact を SQLite で `lib/durable.py` の包みから使う。#1142 のスプリント 2c: 依存と包み #1538・プランの実行 #1548・投稿キュー #1545・cross-review の drive #1540・cross-refactoring の drive #1541・キューと重なりと資源の枠 #1551)。試行は Q6 で消した |
| `hook-trial.py` | #1142 の決定 20(hook を 1 本の Python のエントリポイントへまとめる)の前に、成り立ちと退行を確かめる。今の hook のテストの入力のすべてを tree-sitter-bash(py-tree-sitter)で判定して今の判定と比べる・hook 1 回の所要・環境が無いときに判定をせずに通るか・claude-agent-sdk が `supervise_lib/claude.py` の `claude_cmd`・`call_claude` の契約を満たすか・bump-my-version が `release-steps.py` の `cmd_bump` の 15 箇所の書き換えを満たすか | 2026-09-26・`exp/hook-trial` | 2026-09-26: 5 つのうち 3 つが成り立ち、2 つが成り立たなかった。判定: hook のテスト(3121 件)が渡した入力を集めた。書き込み先の推定 552 件・コマンド 546 件のうち、前景の sleep とプランを起こす副命令は全件で一致した。書き込み先は 2 件が食い違った。`! case … esac` は tree-sitter-bash が読めず(否定した複合コマンドは ERROR になる)、worktree の側と取り違える。`cp a.txt $(basename b).txt` は今の実装が `b).txt` を出し、テストがそれを期待として固定している(tree-sitter の側は展開を含む語として出さない)。構文木の癖が 5 つあり、包みの側で直した: 並びとパイプの末尾のリダイレクトが並び全体に付く・リダイレクトの後ろの語が destination として並ぶ・`<>` が ERROR になる・`time` がコマンド名になる・ヒアドキュメントの本文の `$((…))` をコマンド置換と読み、バッククォートは読まない。所要(40 回ずつの中央値。宣言のあるリポジトリ): 今の `worktree-guard.sh` は 26.8〜76.1 ms、1 本のエントリポイント(用意済みの環境の `bin/python` を直に起動)は 20.1〜21.6 ms で、どの入力でも速かった。内訳は python の起動 6.9 ms・tree-sitter の import 込み 16.9 ms。環境が無いとき: hook の command を `sh -c '[ -x "$0" ] || exit 0; exec "$0" "$1"'` の形にすれば、出力なし・終了コード 0 で通る。python を直に指す形は終了コード 127 で終わる。tree-sitter を import できない python でも終了コード 0 で通る。claude-agent-sdk 0.2.160: 3 つの形(最小構成・work の Tool と Serena・Skill を回す形と `--resume`)の引数を今の引数と同じに組めた。打ち切ると子のプロセスは残らない。結果の値(本文・使用量・modelUsage・費用・往復・セッション)の形も同じだった。ただし 2 つが成り立たない。1 つ目は、今のテストの偽物の CLI(JSON を 1 つ書く)が SDK の下で動かないこと。2 つ目は、同じ依頼(haiku)で入力が 281 → 1039 トークン、費用が 2.73 倍になり、`usage` の入力(281)が modelUsage(1039)と合わないこと。同梱の CLI でも手元の CLI(どちらも 2.1.283)でも同じだった。bump-my-version 1.5.1: `10.17.30-dev.1` → `-dev.2` と → `10.17.31` の両方で、`cmd_bump` と同じ差分(7 と 8 ファイル)を書いた。手で直す箇所の報告と `check-doc-staleness.py` の実行は NDF の側に残る。2026-09-26(T2b): SDK の 2 点はどちらも SDK の設定で揃えられた。費用: SDK は子の CLI へ `CLAUDE_AGENT_SDK_VERSION` を必ず立て、CLI はこれを見て会話のタイトルを作る呼び出し(haiku・`source=generate_session_title`)を本体の前に 1 回足す。`usage` は本体の呼び出しだけを、modelUsage と費用は両方を数えるため合わない。`claude -p` の直の起動(`CLAUDE_CODE_ENTRYPOINT=sdk-cli`)では足さない。引数と環境を 1 つずつ入れ替えて測ると、`--input-format stream-json`・`CLAUDECODE` を外すこと・`CLAUDE_CODE_SDK_READS_SESSION_STATE` は入力を変えず、`CLAUDE_AGENT_SDK_VERSION` か `CLAUDE_CODE_ENTRYPOINT=sdk-py` を立てたときだけ 275 → 1033 トークンになった。`ClaudeAgentOptions(env={"CLAUDE_CODE_DISABLE_TERMINAL_TITLE": "1"})` で呼び出しが止まり、`hook-trial.py sdk --live --runs 3` で入力 280 トークン(usage と modelUsage が一致)・費用 1.01 倍(止めない形は 1038 トークン・2.77 倍)になった。`CLAUDE_CODE_ENTRYPOINT` を `env` で上書きしても `CLAUDE_AGENT_SDK_VERSION` が残るため止まらない。偽物の CLI: SDK は起動の直後に stream-json の `control_request`(`initialize`)を書いて `control_response` を待つ。今の偽物は標準入力の EOF を待つため双方が待ち合って止まる。制御の要求へ答えて `user` を 1 つ読み `result` を 1 つ書く偽物(`cli_path`・約 15 行)と、プロセスを起こさない `query(transport=...)` の差し替えは、どちらも ResultMessage を返した | 本体へ: tree-sitter-bash と hook の 1 本化はスプリント 2b の D5、bump-my-version は D7。claude-agent-sdk は採らない(`issues/issue-1142-design-libraries.md` の決定 24) |
| `training-optout.py` | LLM へ入力を渡す前に、そのアカウントが入力を学習に使わない設定かを 1 行の JSON で返す。claude は OAuth の `account/settings` の `grove_enabled`、他は `unsupported` | 2026-10-02・`exp/training-optout` | 2026-10-02: Claude Max の実物のアカウントで `training: false`(`grove_updated_at` 2026-08-13)・終了コード 0。`--runtime kiro` を足すと `unsupported` で 3 | 2026-10-02: 本体へ昇格(`plugins/ndf/scripts/training-optout.py`。`check` に codex の読み取り、`session-start` で SessionStart の書き換えを足した。#1597) |
| `whole-test-timing.py` | 全体テストを宣言の順に同じ条件(開始時の load average・junit の集計)で測り、件数・各テストの所要の合計・遅いテストの上位 30 件・ファイルの上位 10 本を出す。着手前後のテストの集合を台帳と照らし、台帳に無い消えたテストと skip に変わったテストを出す | 2026-10-31・`feat/issue-2291-l20261031-5` | 2026-10-31: #2291 の着手前後の計測と台帳の照合に使った(結果は `issues/issue-2291-test-ledger.md`) | 未定 |

## 実験版の仕組みそのものの直し

Expand Down
23 changes: 23 additions & 0 deletions docs/plugin-development-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,29 @@ uv run --with pytest --with pytest-xdist pytest scripts/tests -q -n auto
対象プロジェクト(`pyproject.toml`)が無く、`pytest` が環境にも入っていないためです。
`plugins/ndf/skills/*/tests/` の既存のテストも同じ形で実行します。

### テストを削る基準

全体テスト(`.ndf/project.json` の `test.suites` の全部)は手元で 180 秒以内に終わる大きさに保つ。テストの数がそのまま全体テストの長さと保守の費用になるため、守る振る舞いを別のテストが確かめているテストは残さない。次のどれかに当たるテストは消すか、1 件にまとめる。

| # | 基準 | 例 |
| --- | --- | --- |
| 1 | 重複: 同じ経路を、別の名前で同じ入力から確かめている | 同じ CLI を同じ引数で起動し、同じ終了コードを見るテストが 2 件ある |
| 2 | 消えた機能の残骸: 廃止した機能・オプションを確かめている | 外した副命令が「無い」ことだけを確かめる |
| 3 | `.md` の文言の固定 | Skill の本文に決まった文があるかを照合する |
| 4 | 実装の細部の固定: 内部の関数名・呼び出しの回数・中間の値を固定し、利用者に見える振る舞いを確かめていない | 判定は NDF の `tdd-cycle` の `references/test-quality.md` の「判定の基準」に従う |
| 5 | 総当たり: 1 つの振る舞いを、全テンプレート・全組み合わせで確かめている | 代表は「分岐ごとに 1 つ・境界の両側」で選び、残りを消す |
| 6 | suite・CI のジョブが既に走らせる検査を、テストの中でもう一度走らせる | `script-structure` の suite と同じ検査を、テストがリポジトリ全体へ流す |

消す・まとめるときは、1 件ごとに、当たった基準の番号と、守っていた振る舞いを今後どのテストが守るか(守らなくてよいならその理由)を PR から追える形で残す。落ちるから消すのは基準に当たらない。

遅いテストを速くするときは、確かめる振る舞いを弱めずに次の 3 つの手段を使う。テストを速くするためにテストの対象のコードを変えない。

- 待ちの値を、対象のコードが既に持つ環境変数で縮める
- 1 つの CLI の配線(引数・終了コード・結果 JSON)は代表の数件の起動で確かめ、分岐はプロセスの中で関数を呼んで確かめる。スクリプトの起動だけで 1 件 0.2〜0.4 秒かかる
- git のリポジトリのような重い準備は、プロセスごとに 1 度作って複製する

テストが起こした子孫のプロセスは、pytest が SIGTERM で止められたときと終わったときに、根の `conftest.py` が読み込む `scripts/pytest_child_cleanup.py` が止める。テストの中で止め忘れても次のテストへ持ち越さないが、テストは自分で起こしたプロセスを自分で止める。

ローカル hook は任意で導入できます。

```bash
Expand Down
Loading
Loading