Skip to content

スプリント m8a - #462

Merged
takemi-ohama merged 15 commits into
mainfrom
sprint/m8a
Oct 12, 2026
Merged

takemi-ohama merged 15 commits into
mainfrom
sprint/m8a

Conversation

@takemi-ohama

@takemi-ohama takemi-ohama commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

スプリント m8a の課題を main へ取り込む。

利用者向けの変化

移行の手順

  • 無し

課題と設計

コミット

変更の統計

 docs/user/cli-reference/05-snapshot.md         |   7 +
 docs/user/snapshot-guide.md                    |  68 +++++-
 lib/devbase/snapshot/exclude.py                | 124 +++++++++++
 lib/devbase/snapshot/manager.py                | 107 ++++++++-
 tests/ci/test_ci_workflow.py                   |  19 ++
 tests/cli/test_completion.py                   |   3 +-
 tests/cli/test_zsh_completion_run.py           | 273 +++++++++++++++++++++++
 tests/cli/zsh_completion_harness.py            | 234 ++++++++++++++++++++
 tests/snapshot/test_archive_layout.py          |   5 +-
 tests/snapshot/test_backup_real_tar.py         | 114 ++++++++++
 tests/snapshot/test_exclude.py                 | 171 +++++++++++++++
 tests/snapshot/test_exclude_generations.py     | 291 +++++++++++++++++++++++++
 tests/snapshot/test_manager_series.py          |   2 +
 tests/snapshot/test_restore_incremental.py     |  12 +-
 20 files changed, 1526 insertions(+), 27 deletions(-)

テスト(supervise.py の run のステップ)

ステップ exit 最後の行
collect 0

集めた実装の PR

閉じる課題

Closes #368
Closes #455

手動確認

モード: standard / 通した工程: 実装 → Pull Request

🤖 Generated with Claude Code

takemi-ohama and others added 9 commits October 12, 2026 17:46
zsh/zpty で対話の zsh を起こし compadd を包んで、[name] と --context の
集合のすべての道で etc/_devbase が出す候補を期待と比べる。zsh か zsh/zpty が
無ければ、DEVBASE_TEST_REQUIRE_ZSH=1 のときだけ落とし、ほかは飛ばす。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
zsh を起動する検査が無いことを前提にした記述(決定 9・決定 13・テスト観点)を改め、
補完の実行テストの小節と決定 14〜17 を足す。CI の確定仕様に pytest のジョブの zsh と印を足す。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#455 を実装する(スプリント m8a)
除外の一覧を exclude.py で組み立て、フル・差分・復元前バックアップの tar に
--anchored --no-wildcards-match-slash でメンバーより前に渡す。世代の meta.yml に
作ったときの除外の一覧を残し、違う一覧の世代へは差分を積まない(自動は新しい世代、
名前の指定は止める)。設定が読めない間は世代の記録の一覧で控える。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#368 を実装する(スプリント m8a)
@takemi-ohama

Copy link
Copy Markdown
Contributor Author

#368 の受け入れ条件 9 の確認(conductor、2026-10-12)

合格。 既定の除外のパスを消したホームで dev コンテナを起動し、4 つの CLI はどれもエラーで止まらずに起動した。

項目 値
イメージ devbase-base:latest(macOS・Docker Desktop)
ホーム 共通とグループのボリュームから、CLI の設定と状態だけを使い捨てのボリュームへ写した(会話の履歴・.codex/packages は容量のため写していない)。既定の除外のパスは写さず、コンテナの中で無いことを確かめた(.codex/logs_2.sqlite・.codex/.tmp・.codex/cache・.claude/plugins/cache・.claude/plugins/marketplaces・.serena/logs・.gemini/tmp/*/logs.json・.gemini/tmp/*/logs)
CLI 結果
Claude Code 2.1.288 claude -p が応答した。セッションを始める前の claude plugin list は 4 件が failed to load: cache-miss。最初のセッションの起動でマーケットプレイスとプラグインを自分で取り直し、その後は失敗が 0 件、有効にしてあった 7 件がすべて読み込まれた(installed_plugins.json の一覧のまま)
Codex 0.160.0 codex exec が応答した。消した logs_2.sqlite と cache を自分で作り直した
Gemini CLI 0.62.0 起動して認証の段階まで進む。グループ with の .gemini で、logs.json・logs/ が無いときと戻したときで --list-sessions の出力が同じだった(除外による違いが無い)。応答まで行かないのは、GCP のプロジェクトの指定とクォータという環境の側の理由で、同じグループの実コンテナでも同じように止まる
Serena(serena-agent==1.7.0、プラグイン mcp-serena) claude mcp list で ✔ Connected

知っておくこと: 復元の直後、Claude Code の最初のセッションが始まるまでは、claude plugin list がプラグインを cache-miss と出す。最初のセッションで戻る。
後片付け: 使い捨てのコンテナ 2 つとボリューム 3 つを消した。

@takemi-ohama

takemi-ohama commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor Author

改修計画 — devbasex/devbase #462

/ndf:cross-refactoring が提案し、改修計画し、適用した改善項目の記録である。
理由と手順は提案の時点でしか残らないため、公開の直前に書き出している。

  • 件数: 採用 6・未確認 0・取り消し 0・見送り 0(最終ゲート: passed)
  • 公開: ef7a59a を push した
  • 書き直した時刻: 2026-10-12T20:35:47
  • 対象範囲: .github/workflows/ci.yml, CHANGELOG.md, docs/developer/contributing.md, docs/specifications/ci-checks.md, docs/specifications/cli-argument-resolution.md, docs/specifications/snapshot-series.md, docs/user/cli-reference/05-snapshot.md, docs/user/snapshot-guide.md, lib/devbase/snapshot/exclude.py, lib/devbase/snapshot/manager.py, tests
  • テストの戦略: local-full(根拠 test.strategy)
  • 範囲テストの雛形: env -u DEVBASE_ROOT uv run --locked pytest -q -o junit_family=xunit1 --junitxml=build/ndf/junit.xml {paths}
  • 着手前のテスト: env -u DEVBASE_ROOT uv run --locked pytest tests/ -q -o junit_family=xunit1 --junitxml=build/ndf/junit.xml(whole)
  • 着手前のテストの結果: 通過(128.8 秒 / HEAD 1bed59f)
  • 既存失敗の件数: 0
  • 想定最大時間: 30 分 / 改修計画の時点で使えた時間: 8.42 分
  • 実装担当: claude

改善項目

I-001 — lib/devbase/snapshot/manager.py#SnapshotManager._excludes_for

兆候 手法 重要度 等級 提案元 状態 コミット
deep_nesting extract_method minor high claude / kiro 採用 1

なぜ: 設定を読めないときの 3 段のフォールバック(作り直す世代の記録 → 系列の最新の世代の記録 → 今の一覧)のうち、2 段目が series_latest・_safe_snap_dir の例外の握り・is_dir・_recorded_excludes を 3 段の入れ子で同居させ、関数の主筋(どの順で記録を選ぶか)が読みにくい。認知的複雑度 11 は本体の上位 5 番目

手順: 1. 2 段目を _latest_recorded_excludes(self, volumes) -> Optional[tuple] として抽出する(series_latest が None・_safe_snap_dir が SnapshotError・ディレクトリでない・記録が無い、のいずれでも None を返す)
2. _excludes_for を「読めたら今の一覧 → 作り直す世代の記録 → _latest_recorded_excludes → 今の一覧」の平らな順に書き換える(return self._recorded_excludes(snap_dir) or ... にはせず、空のタプルの記録を落とさないよう is not None の判定を残す)
3. tests/snapshot/test_exclude_generations.py の unreadable_config 系(limit 後の full・snar の fallback・pre-restore・記録無し)を流して振る舞いが変わらないことを確かめる

I-002 — lib/devbase/snapshot/manager.py#SnapshotManager.auto_snapshot_target

兆候 手法 重要度 等級 提案元 状態 コミット
duplication extract_method major high kiro 採用 1

なぜ: 差分を積める世代かの判定(対象ボリュームの一致・archive_root が members・除外の一覧が今と同じ)が auto_snapshot_target と _create_incremental の 2 か所に同じ条件で書かれている。auto_snapshot_target は 3 条件を INFO ログ + None で、_create_incremental は同じ 3 条件を SnapshotError で表す。片方だけ条件を直すと「積み先に選ぶが、積もうとすると拒否される」食い違いが起きる。auto_snapshot_target は認知的複雑度 10・52 行・return 7 本。

手順: 1. 3 条件のうち失敗した 1 件を表す結果を返す純粋な判定を 1 つ抽出する(例: _incremental_block_reason(snap_dir, volumes) が「不一致」「源が古い」「除外が違う」「積める」の区別を返す。副作用なし)
2. auto_snapshot_target は抽出した判定の戻り値で分岐し、理由ごとの INFO ログと None を出す(ログ文言と戻り値は現状維持)
3. _create_incremental は同じ判定の戻り値で分岐し、理由ごとの SnapshotError を送出する(例外メッセージは現状維持)
4. tests/snapshot/test_auto_snapshot_series.py と test_exclude_generations.py を実行し、ログ文言・例外 match・積み先の挙動が不変であることを確認する

I-003 — lib/devbase/snapshot/exclude.py#_load_user_excludes

兆候 手法 重要度 等級 提案元 状態 コミット
long_method extract_method minor medium claude / kiro 採用 1

なぜ: 1 関数に「ファイルの存在確認」「読み込みと YAML 解釈」「最上位が辞書か・exclude がリストかの構造検証」「各パターンの正規化と警告」が同居する。認知的複雑度 9・CC 9・8 分岐・22 文。構造検証と正規化が同じ try に混ざり、どの段で失敗したかが読みにくい。

手順: 1. try の中身(open → safe_load → None の補い → 辞書/リストの検証)を _read_exclude_values(path) -> list として抽出し、形が違えば ValueError を投げる形はそのまま保つ
2. _load_user_excludes は「無ければ ([], True) → _read_exclude_values を同じ例外の組で受けて警告し ([], False) → 値ごとに normalize」の 3 段にする
3. tests/snapshot/test_exclude.py(壊れた YAML・最上位が辞書でない・exclude がリストでない・個別の値の警告)を流す

I-004 — lib/devbase/snapshot/exclude.py#resolve_excludes

兆候 手法 重要度 等級 提案元 状態 コミット
duplication consolidate_duplication minor medium claude 採用 1

なぜ: 除外の一覧を比べられる形(重複を除いて並べ替える)にする知識が exclude.py の resolve_excludes の tuple(sorted(set(...))) と manager.py の _recorded_excludes の tuple(sorted(set(value))) に 2 か所あり、さらに _excludes_differ が set へ戻して比べ、_create_full が sorted(excludes) で記録する。正規の形の定義が分散していて、片方だけ変えると記録と今の一覧の比較が食い違う

手順: 1. exclude.py に canonical(patterns) -> tuple(重複を除いて並べ替えたタプル)を置く
2. resolve_excludes と SnapshotManager._recorded_excludes をこの関数で書き換える
3. 両方が正規の形になったので、_excludes_differ の比較を recorded != self.excludes にする(set への変換を外す)
4. tests/snapshot/test_exclude.py と test_exclude_generations.py を流す

I-005 — lib/devbase/snapshot/manager.py#SnapshotManager.excludes

兆候 手法 重要度 等級 提案元 状態 コミット
data_clump inline minor medium claude 採用 1

なぜ: _excludes と _exclude_config_readable の 2 つの属性が常に組で作られ、遅延の初期化を _resolve_excludes と 2 つのプロパティが手で分担している(None を未計算の印に使う隠れた状態)。resolve_excludes の戻り値の組を functools.cached_property 1 つに持てば、初期化の印と 3 つのメソッドが要らなくなる

手順: 1. @cached_property def _exclude_resolution(self) -> tuple: return resolve_excludes(self.backups_dir) を置く(警告を 1 実行 1 回にする理由のコメントを移す)
2. excludes と exclude_config_readable はそれぞれ self._exclude_resolution[0] / [1] を返すだけにする
3. init の _excludes・_exclude_config_readable と _resolve_excludes を消す
4. test_warnings_are_emitted_once_per_manager を含む tests/snapshot を流す

I-006 — lib/devbase/snapshot/manager.py#SnapshotManager._create_incremental

兆候 手法 重要度 等級 提案元 状態 コミット
duplication consolidate_duplication major low codex 採用 1

なぜ: auto_snapshot_target と _create_incremental が、世代のボリューム構成 → archive_root → 除外一覧の同じ互換性判定を別々に持つ。除外の条件を変える際に両方を追う必要があり、自動選択と名前指定で差分を積める条件がずれる余地がある。ログで新世代へ進むか例外で停止するかは別の責務なので残す。

手順: 1. test_manager_volumes.py・test_auto_snapshot_series.py・test_exclude_generations.py の既存ケースを確認し、複数の不一致が同時にある場合の判定優先順位を公開 API 経由の現状固定テストで補う。
2. manager.py 内に、現行と同じ順番・遅延評価で最初の不一致と必要な記録済みボリュームを返す非公開ヘルパーを抽出する。判定のためのメタデータ読み込み順や例外伝播を変えない。
3. 2 つの呼び手を共通判定へ置き換え、それぞれのログ・例外の文言を保つ。自動選択だけのディレクトリ存在条件、差分数上限、snar 不在時のフォールバックは呼び手に残す。
4. snapshot 関連テストで返り値・警告・失敗時のファイル不変を確認する。

レビューで確かめるテストの差分: tests/snapshot/test_exclude_generations.py(機械で期待値が変わったか決まらなかった)

指標の測定

  • 状態: written / 言語とツールの対応: 既定(.ndf/code-metrics.json は無い) / 所要: 10.0 秒(使えた時間 90 秒 / 上限 90 秒)
  • 指標のファイル: /var/folders/qz/qxt0p_y15xv5jg4x77zmtxj80000gp/T/ndf-worktrees/devbasex--devbase/rf462/work/.cross_refactoring/code-metrics-rf462.md
言語 ツール 版 ランナー ファイル 秒 結果
python ruff-complexipy ruff 0.16.9 / complexipy 8.0.1 uvx 203 2.8 測った
重複: python symilar pylint 4.0.9 uvx 203 6.8 測った(3 箇所)

時間の上限

値 中身
提案の枠の終わり(監視の上限 = 残り + 余裕) 2026-10-12T20:26:58+09:00
改修計画の枠の終わり(同上) 2026-10-12T20:29:58+09:00
テストの追加の終わり = 実装の終わり − Σ(採っていて未検証の項目の実装の見積り) 2026-10-12T20:27:20+09:00
実装の終わり = 最終ゲートの修正の打ち切り − バッファ − Σ(採っていて未検証の項目の検証の見積り) 2026-10-12T20:32:32+09:00
直しの試行の打ち切り 2026-10-12T20:41:11+09:00
最終ゲートの修正の打ち切り(想定最大時間の終わり) 2026-10-12T20:48:50+09:00
打ち切りの後の取り消し(案 A)の締め切り = 最終ゲートの修正の打ち切り + 0.20·B(工程の 1 つとして起動したとき) 2026-10-12T20:54:50+09:00
最終ゲートの修正の 1 回目に必ず渡す長さ(秒。予備時間の final_fix) 330
指標の測定の上限(秒。提案の枠の中から割く) 90
着手前のテスト 1 回の上限(秒) 375
範囲テスト 1 回の上限(秒) 387
手元の全体テスト 1 回の上限(秒) 375
CI の待ちの上限(秒) 243
余裕(秒。手順の上限と CLI の上限に足す) 90

入力: 予算 30 分 / 全体テストの所要 w 124.8(ndf-record) / 着手前の実測 x 128.8 / CI の壁時計 c 81.0 / 範囲テストの所要 s None(—) / 戦略 local-full

無音の打ち切りは手順の監視の上限と同じ値である。項目ごとの期限は持たない(実装を止めるのは実装の終わりだけ)。

見送った提案

対象 兆候 理由 根拠(MVV) 補足
tests/snapshot/test_exclude_generations.py#_broken_config duplication budget Vision 見積り 1.4 分が残りに入らない
tests/snapshot/test_manager_series.py#write_state divergent_change budget Vision / Value 3 見積り 1.4 分が残りに入らない
tests/cli/zsh_completion_harness.py#run_completions long_method budget Vision / Mission 見積り 1.6 分が残りに入らない
lib/devbase/snapshot/manager.py#SnapshotManager._apply_rotation_plan long_method budget Value 2 見積り 1.6 分が残りに入らない
lib/devbase/snapshot/manager.py#SnapshotManager._warn_about_lost_renames long_method budget Value 2 見積り 1.6 分が残りに入らない
lib/devbase/snapshot/exclude.py#tar_exclude_args primitive_obsession budget Vision 見積り 1.3 分が残りに入らない
lib/devbase/snapshot/manager.py#SnapshotManager._extract_archive data_clump budget Value 2 / Value 3 見積り 1.3 分が残りに入らない
tests/containers/test_base_dockerfile_fonts.py#_run_blocks duplication budget Vision 見積り 1.4 分が残りに入らない
tests/containers/test_base_image_font_matching.py#_docker_unavailable duplication budget Vision 見積り 1.4 分が残りに入らない
tests/containers/test_entrypoint_ai_settings_concurrent.py#run_concurrent divergent_change budget Vision / Value 2 見積り 1.4 分が残りに入らない
tests/snapshot/test_exclude_generations.py#RecordingManager duplication budget Vision 見積り 1.4 分が残りに入らない
lib/devbase/snapshot/manager.py#SnapshotManager._load_metadata duplication budget Value 2 見積り 1.6 分が残りに入らない
lib/devbase/snapshot/manager.py#SnapshotManager.list divergent_change budget Value 2 見積り 1.6 分が残りに入らない
tests/containers/test_base_shellcheck_ci.py#_shellcheck_commands deep_nesting budget Vision 見積り 1.6 分が残りに入らない

@takemi-ohama takemi-ohama left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 cross-review | round 1 | codex | APPROVE

指定コミットを静的レビューし、修正を要する critical / major の指摘はありません(判断の根拠: Vision・Value 2・Value 3)。テストは指示に従い未実行。

@takemi-ohama takemi-ohama left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 cross-review | round 1 | kiro | APPROVE

スナップショットの除外機能 (#368) をレビューした。除外一覧の組み立て・検査・tar 引数のアンカー、世代ごとに同じ除外一覧で控える制約 (auto_snapshot_target 順 4a / _create_incremental のガード)、読めない設定での世代記録の継承 (読めない設定を理由に新しい世代を作ってローテーションで古い世代を消さない) は、仕様 (snapshot-series.md 順 4a・データ/設定表) と実装が一致している。_validate_volumes のボリューム制限は不変で、復元前の消去が devbase 以外のボリュームへ及ぶ経路は増えていない。tests/snapshot/test_backup_real_tar.py が実 GNU tar で除外の効き目を縛り、test_exclude_generations.py が読めない設定の各経路 (差分上限の新フル・snar フォールバック・復元前バックアップ・記録なし) を網羅する。test_restore_incremental.py の入れ替え場所を除外外 (data/cache) へ移した変更も、除外内だと検証が無効化される問題を正しく避けている。critical / major の基準 (利用者経路の誤動作・データ破壊・秘密/認可/戻せない操作・設計と実装の食い違い) に当たる指摘は見つからなかった。

@takemi-ohama

Copy link
Copy Markdown
Contributor Author

🔧 /ndf:fix サマリ | commit (なし)

対応件数: critical=0 / major=0 / minor=0(合計 0 件)
決着: 0 件 / 見送り: 0 件 / 却下: 0 件
CI: NONE

@takemi-ohama
takemi-ohama marked this pull request as ready for review October 12, 2026 11:17
@takemi-ohama

Copy link
Copy Markdown
Contributor Author

#455 のマージ前の手動確認(conductor、2026-10-12、sprint/m8a の 1bed59f、macOS の zsh 5.9)

受け入れ条件 6: 合格。 今の etc/_devbase では tests/cli/test_zsh_completion_run.py が 17 passed。etc/_devbase を git show 54ed5bf^:etc/_devbase に差し替えると 4 failed, 13 passed になった。[name] の行は 14 行が落ち、行ごとに打った行・期待・実際の候補が並ぶ:

E       AssertionError: etc/_devbase の補完が期待の候補を出さない(14 行):
E           打った行: 'devbase up '
E             期待: proj-a, proj-b を含む
E             実際: --context

差し替えは git checkout で戻した(差分 0 行)。

受け入れ条件 7: 合格。 zsh を PATH から外して流した(uv・git などだけを置いたディレクトリを PATH にした)。

印 DEVBASE_TEST_REQUIRE_ZSH 結果
無し 8 passed, 9 skipped。飛ばした理由は zsh が無いため、zsh の補完の実行テストを飛ばす。-q でも warnings summary が出る。ほかのテストは走る
1 8 passed, 1 skipped, 8 errors。理由は DEVBASE_TEST_REQUIRE_ZSH=1 なのに zsh が無い。zsh の補完の実行テストを流せない

@takemi-ohama

Copy link
Copy Markdown
Contributor Author

#368 の受け入れ条件 1〜4・8 の確認(conductor、2026-10-12、sprint/m8a の 1bed59f)

合格。 実の devbase-snapshot:latest イメージで、作業用のボリューム(共通側 m8a-snap-ai、グループ側 devbase_home_m8atest。利用者のボリュームではない)に対して作成・差分・復元を行った。SnapshotManager を一時のルートで組み、共通ボリュームの名前の定数(devbase_home_ubuntu に限る検証がある)はこの試しのプロセスの中でだけ作業用の名前へ差し替えた。

置いたパス: 既定の除外 9 つ(.gemini/tmp/p1/logs.json・.gemini/tmp/p1/logs/x・.codex/logs_2.sqlite・-wal・.codex/.tmp/x・.codex/cache/x・.claude/plugins/cache/x・.claude/plugins/marketplaces/x・.serena/logs/x)と、外さないもの 9 つ(.claude/projects/x・.codex/sessions/x・.codex/state_5.sqlite・.gemini/tmp/p1/chats/s.jsonl・.claude/plugins/installed_plugins.json・.claude/plugins/data/x・.serena/language_servers/x・深い場所の同じ名前 work/.codex/cache/x・work/.claude/plugins/cache/x)。両方のボリュームに置いた。

受け入れ条件 操作 結果
1・2 フル(full.tar.zst、56 エントリ) 除外の 9 つは 1 つも無い。外さないもの 9 つはすべてある(深い場所の同じ名前も残る)
14 meta.yml の exclude 既定の除外の 10 項目が並べ替えて残る
3 .codex/logs_2.sqlite と .codex/sessions/x を書き換えて差分(incr-001.tar.zst) 書き換えた logs_2.sqlite は無い。書き換えた sessions/x はある
8 両ボリュームにファイルを足してから復元 除外したパスは無い。足したファイルは消えた。.codex/sessions/x は差分の時点の中身(v2)
4 復元前バックアップ(pre-restore-* の full.tar.zst、58 エントリ) 除外の 9 つは無い

後片付け: 作業用のボリューム 2 つと一時のルートを消した。

takemi-ohama and others added 6 commits October 12, 2026 20:28
…nager._excludes_for

設定を読めないときの 2 段目 (系列の最新の世代の記録) を
_latest_recorded_excludes へ抽出し、_excludes_for を
今の一覧 → 作り直す世代の記録 → 最新の世代の記録 → 今の一覧 の平らな順にした。
空のタプルの記録を落とさないよう is not None の判定は残す。

Item-Id: I-001
Impl-Runtime: claude
Impl-Model: default
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nager.auto_snapshot_target

差分を積める世代かの 3 条件 (対象ボリューム・控えの起点・除外の一覧) を
_incremental_block_reason へ抽出した。auto_snapshot_target は理由ごとに
INFO と None、_create_incremental は理由ごとに SnapshotError を出す。
調べる順・メタデータの読み方・ログと例外の文言は変えない。

Item-Id: I-002
Impl-Runtime: claude
Impl-Model: default
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…_excludes

読み込み・YAML の解釈・形の検証を _read_exclude_values へ抽出した。
_load_user_excludes は「無ければ読めたとみなす → 読めなければ警告して空 →
値ごとに正規化」の 3 段になる。受ける例外の組と警告の文言は変えない。

Item-Id: I-003
Impl-Runtime: claude
Impl-Model: default
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…esolve_excludes

除外の一覧を比べられる形 (重複を除いて並べ替えたタプル) にする知識を
exclude.canonical の 1 か所に寄せ、resolve_excludes と
SnapshotManager._recorded_excludes から使う。両側が同じ形になったので
_excludes_differ は set へ戻さずタプルのまま比べる。

Item-Id: I-004
Impl-Runtime: claude
Impl-Model: default
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cludes

常に組で作られる _excludes・_exclude_config_readable と、None を未計算の印に
使う _resolve_excludes を、resolve_excludes の戻り値を持つ cached_property
_exclude_resolution 1 つにまとめた。excludes・exclude_config_readable は
その組の要素を返すだけになる。警告は今までどおり 1 つのマネージャーで 1 回。

Item-Id: I-005
Impl-Runtime: claude
Impl-Model: default
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…napshotManager._create_incremental

自動の積み先と名前を指定した差分が同じ判定 (_incremental_block_reason、I-002 で
抽出済み) を通ることを縛る現状固定テストを足した。積めない理由が重なるときは
対象ボリューム → 控えの起点 → 除外の一覧の順に最初の 1 件だけを理由にする。
足したテストはリファクタリング前のコード (1bed59f) でも通る。

Item-Id: I-006
Impl-Runtime: claude
Impl-Model: default
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@takemi-ohama takemi-ohama left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 cross-review | round 2 | claude | APPROVE

critical / major の指摘なし。round 1 の後のリファクタリング (0912344..ef7a59a) は、積めない理由の判定順 (対象ボリューム → 控えの起点 → 除外の一覧)・ログと例外の文言・設定を読めないときの記録のフォールバック・除外の一覧の正規の形を変えておらず、重なる理由の優先順位は現状固定テストで縛られている(根拠: Vision・Value 2)。

@takemi-ohama takemi-ohama left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 cross-review | round 2 | kiro | APPROVE

rf462 の 6 件のリファクタリング (manager.py / exclude.py) を round 2 の観点でレビューした。合流元 1bed59f からの新規差分は exclude.py・manager.py・test_exclude_generations.py の 3 ファイルのみで、docs の変更は既にマージ済みの #460/#461 由来のため本 PR の新規範囲ではない。canonical() は旧 tuple(sorted(set(...))) と等価、_incremental_block_reason は 2 呼び出し元の短絡順序と SnapshotError の伝播を保存、_latest_recorded_excludes は旧インライン実装と同一、cached_property は『manager あたり警告 1 回』を保存、_excludes_differ の tuple 比較は両辺が canonical なので妥当。削除した _resolve_excludes/_excludes/_exclude_config_readable への残参照なし・slots 衝突なし。新テストが積めない理由の優先順位を 2 経路で現状固定している。critical / major の基準に当たる指摘は無し。

@takemi-ohama

Copy link
Copy Markdown
Contributor Author

リリース後テスト(#368、conductor、2026-10-12、main c516806、macOS・Docker Desktop)

利用者の承認を得て、利用者の端末の実ボリュームで devbase snapshot create --full --group team-a を 1 回打った(共通側 devbase_home_ubuntu 11,474MB、グループ側 devbase_home_team-a 8,024MB。du -sm)。所要 66 秒。コンテナは止まらない。

確認 結果
新しいフルの世代に既定の除外のパスが入らない 合格。 full.tar.zst の一覧で既定の除外 10 項目に当たるエントリは 0 件
meta.yml に除外の一覧が残る(受け入れ条件 14) 合格。 exclude: に 10 項目が並ぶ
フルの控えの対象が除外の分だけ減る(性能の要求) 合格(見込みより小さい)。 今のボリュームで除外のパスの合計は 1,265MB(共通 1,262MB・グループ 3MB)で、これが控えの対象から外れた。要求の見込み(約 1.9GB、2026-10-02 の測定)より小さいのは、.codex/logs_2.sqlite などの大きさが測った時点と違うため

前の世代との比較(同じ系列 devbase_home_ubuntu + devbase_home_team-a):

世代 作成 full.tar.zst 展開後の合計(tar の一覧の大きさの和) うち除外の対象
20261002-191730(変更前) 2026-10-02 3.3GB 16,955MB 1,440MB(73,682 件)
20261012-211605(変更後) 2026-10-12 3.7GB 17,810MB 0

新しいフルのほうが大きいのは、10 日でボリュームそのものが伸びたため(除外を除いた分で 15,515MB → 17,810MB)。除外が効いていなければ、今のボリュームのフルの展開後は約 19,075MB になっていた。

作った世代 20261012-211605 は残した(利用者の判断)。

@takemi-ohama

Copy link
Copy Markdown
Contributor Author

振り返り(2026-10-12)

対象: issue #455 / issue #368 / PR #462(実装 #460・#461、確定仕様化 #464)

#455 は zsh を zsh/zpty で起動して補完の実際の候補を比べるテストを足し、CI の pytest のジョブで必須にした。#368 はスナップショットの控えから既定の除外と backups/exclude.yml の除外を外し、除外の一覧が前の世代と違う系列では新しいフルの世代を作るようにした。リリース後テストでは、利用者の端末の実ボリュームで snapshot create --full を打ち、除外のエントリが 0 件、控えの対象が 1,265MB 減ったことを確かめた。

何が起きたか

観点 事実
受け入れ条件 途中で変えた条件は無い。#461 はテスト設計の 1 行(test_restore_incremental.py を変えない)と違えた。入れ替える場所が既定の除外に入ったためで、受け入れ条件 10 そのものは変わらない。設計の決定と違えたところは両課題とも無い
手戻り スプリント PR の cross-review は 1 回で指摘なし。設計 #459 のレビューで 2 件を直した
確かめ方(良かった点) #368 の受け入れ条件 1〜4・8 は、実の devbase-snapshot イメージを使い、作業用の名前のボリュームと一時のルートで確かめた。利用者のボリュームには触れていない(この PR のコメント)。この形はリリース後テストの結果と食い違わなかった
事故 受け入れ条件 9 の確認でホームのボリュームを丸ごと写した(約 7GB)。VM の中の空きだけを見ていたため、ホストのディスクが満杯になった。Docker Desktop が止まり、動いていた 12 のコンテナが落ち、裏で回していた検査の refactor も書けなくなって 1980 秒で打ち切られた。利用者の承認でキャッシュと古い世代などを消して 115GB を空け、コンテナは docker start と devbase project post-start で戻した。確認は小さいボリュームでやり直して合格
工程 検査の refactor を supervise.py run --from refactor で再開すると、前回の時間枠を引き継いだ。「使える時間 -95.3 分 → 採用 0 件 / 時間で見送り 9 件」のまま、要約は「見送り 0」・status: ok で review へ進んだ。耐久ワークフローの状態と作業ツリーを退避し、drive を打ち直して採用 6 件
PR 本文 #461 の担当が work/pr-notes.md に書いた覚え書き(設計との違い: 無し・受け入れ条件 9 の扱い・手元で飛んだ GNU tar のテスト)は本文に入らず、conductor が転記した。実装のプランの pr のステップが append を持たないためで、m10a・m10b(#440・#448・#449)に続く再発。#460 の担当は同じ用途のファイルを work/pr-body.md と名付けていた
見落とし #455 の利用者の端末での実の Tab の確認は保留のまま(zsh/zpty の機械の確認は合格)
範囲 #368 の設計で見つけた既存の不具合(既存の世代の名前で --full)を #458 として起票した。ディスクを空ける作業で見つけた VS Code Server のボリュームの溜まりを #463 として起票した

context window の大きさ

この会話(仕上げ)の conductor の記録だけを測った。スプリントの前半の会話は含まない。

層 フェーズ モデル 件数 固定費の中央値 実作業の中央値 実作業 < 固定費 最大充填の最大 目印
conductor - claude-opus-5-5 1 65222 43239 1 108461
supervisor 仕上げ claude-opus-5-5 2 41110 97987 0 158598
層 件数 固定費の合計 実作業の合計 総消費
conductor 1 65222 43239 108461
supervisor 2 82219 195974 278193
合計 3 147441 239213 386654

目印の付いた行は無い。

承認ゲートの差し戻し

gate-rejections.py count: 関門 1 が 0 件、関門 2 が 0 件。

MVV を見直すか

project-mvv.py signals(版 1)の結果は、覆し 0 件(閾値 3)、判定できない 0 件(閾値 5、続けて 0 回 / 閾値 3)、流出不具合 0 件(閾値 3)。兆候は無いため、改訂しない(2026-10-06 の利用者の判断のとおり)。

次に変えること

変えること 落とし先 状態
確かめのためにボリュームを作る・写す前に、ホストの df を見る。写すのは要るファイルだけにする。落ちたコンテナは docker start と post-start で戻す プロジェクトの取り決め(AGENTS.md の「落とし穴」) #465 で反映
計画の前で落ちた cross-refactoring を再開したとき、止まっていた時間を数えない。全候補を時間で見送ったら ok で返さない 上流(NDF の cross-refactoring) devbasex/ai-plugins へ起票する(利用者の同意待ち)
実装のプランの pr のステップに append を置き、担当の覚え書きを本文へ載せる 上流(devbasex/ai-plugins#1742)。上流が直るまでは、次のスプリントでプランを流す前に pr のステップへ append を手で足す(#1742 の「利用側の回避」) 再発のコメントは利用者の同意待ち。手での回避は次のスプリントで試す
実のボリュームに触れず、作業用の名前のボリュームと一時のルートで実イメージを確かめる形を続ける 変えない(今回うまくいった形) —

途中で起票した課題

番号 何を見つけたか 見つけた場面
#458 既存の世代の名前で snapshot create --full を打つと、full.tar.zst が変更分だけになり、復元でファイルを失う 設計(#368 の --full の経路の調査)
#463 VS Code Server のボリュームがコンテナごとに旧版を溜める(手元で約 40GB) マージ前の確認(ディスクを空ける作業)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant