From 9fd149b7f828d5e3baac1e446b0c2213f2985948 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Sun, 24 May 2026 04:01:52 +0000 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20cross-review=20TMP=5FDIR=20=E3=82=92?= =?UTF-8?q?=20worktree=20=E5=86=85=20.cross=5Freview/=20=E3=81=AB=E7=B5=B1?= =?UTF-8?q?=E4=B8=80=20(PLAN22)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gemini CLI の write_file は workspace 内のみ書き込み可能。 ~/.gemini/tmp/ は workspace 外のため result.json の書き込みが ブロックされ NO_RESULT で停止していた。 TMP_DIR を /.cross_review/ に変更し workspace 制約を根本回避。 codex の -s workspace-write 緩和は bwrap 非対応環境で不可のため維持。 Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitignore | 3 + ...view-gemini-result-workspace-constraint.md | 195 ++++++++++++++++++ plugins/ndf/skills/cross-review/SKILL.md | 2 +- .../skills/cross-review/scripts/_tmpdir.sh | 13 +- .../cross-review/scripts/launch-codex.sh | 6 +- .../skills/cross-review/scripts/monitor.py | 12 +- .../ndf/skills/cross-review/scripts/state.py | 25 +-- plugins/ndf/skills/merged/SKILL.md | 3 +- 8 files changed, 220 insertions(+), 39 deletions(-) create mode 100644 issues/PLAN22_cross-review-gemini-result-workspace-constraint.md diff --git a/.gitignore b/.gitignore index d22db56..ae076d0 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,9 @@ __pycache__/ # Plugin-generated context files # (none currently) +# cross-review worktree tmp +.cross_review/ + # Serena MCP runtime data ${SERENA_HOME}/ .serena/logs/ diff --git a/issues/PLAN22_cross-review-gemini-result-workspace-constraint.md b/issues/PLAN22_cross-review-gemini-result-workspace-constraint.md new file mode 100644 index 0000000..d95de0d --- /dev/null +++ b/issues/PLAN22_cross-review-gemini-result-workspace-constraint.md @@ -0,0 +1,195 @@ +# PLAN22: cross-review TMP_DIR を worktree 内 `.cross_review/` に統一 + +- 起票日: 2026-05-24 +- 対象 plugin: `ndf` v4.7.4 +- 対象 skill: `ndf:cross-review` +- 関連 issue: [GitHub Issue #7](https://github.com/devbasex/ai-plugins/issues/7) +- 関連 plan (先行): PLAN20 (v4.7.3, PR #4 で merge 済み) +- 報告者: takemi-ohama (`devbasex/devbase#25` の `/ndf:cross-review 25` 実行中に検出) + +## 背景・課題 + +### PLAN20 で行ったこと + +PLAN20 で `_tmpdir.sh` / `state.py _tmp_dir()` / `monitor.py _tmp_dir()` の +TMP_DIR 解決先を `/tmp/` → `~/.cross_review//` に統一した。 +これは launcher / monitor / state 側のパス統一としては正しく機能している。 + +### PLAN20 で解決できなかったこと + +gemini CLI の `write_file` ツールは **workspace ディレクトリ (= worktree) 内**にしか +書き込みできない。`~/.cross_review/` は gemini の設定ディレクトリであって workspace ではないため、 +gemini からの書き込みは依然としてブロックされる: + +``` +Error executing tool write_file: File path must be within one of the workspace directories: /work/worktrees/pr25 +``` + +検証済み: +- `--include-directories "$TMP_DIR"` → 効果なし +- `GEMINI_CLI_TRUST_WORKSPACE=true` → 効果なし (既に設定済み) + +gemini はフォールバックとして worktree 直下に `gemini-review-pr-result.json` を書くが、 +`monitor.py` は `~/.cross_review//` のみを参照するため `NO_RESULT` (exit 3) を返し、 +cross-review ループが止まる。 + +### 影響 + +- 各 round でメインセッション側の手動補完が必要 +- cross-review の「AI 直接投稿 + メイン context 節約」設計の前提が崩れる +- context 消費増・中断リスク増 + +## 修正方針 + +TMP_DIR を `~/.cross_review//` (worktree 外) から **`/.cross_review/`** (worktree 内) に変更する。 + +gemini の workspace 制約を根本的に解消でき、monitor.py の fallback コピー等の追加ロジックも不要。 + +### なぜこれで全体が連動するか + +SKILL.md (メインループ) の L171: +```bash +export CROSS_REVIEW_TMP_DIR="$TMP_DIR" +``` + +`state.py init` が出力する `TMP_DIR` を env 伝播しているため、`state.py _tmp_dir()` の +解決先を変えるだけで全後続スクリプト (launcher / monitor.py) が連動する。 +`CROSS_REVIEW_TMP_DIR` env は `_tmpdir.sh` / `monitor.py _tmp_dir()` の最優先パスなので、 +それぞれの fallback ロジックは通過しない。 + +### 変更 1: `state.py _tmp_dir()` — 解決先を worktree 内に変更 + +**ファイル**: `plugins/ndf/skills/cross-review/scripts/state.py` L60-82 + +```python +# Before +base_name = pathlib.Path(workspace or os.getcwd()).name +gemini_root = pathlib.Path.home() / ".gemini" / "tmp" +if gemini_root.is_dir() and base_name: + d = gemini_root / base_name + +# After +ws = pathlib.Path(workspace or os.getcwd()) +d = ws / ".gemini" / "tmp" +``` + +`workspace` が指定されていれば `/.cross_review/`、なければ `/.cross_review/` を返す。 + +### 変更 2: `_tmpdir.sh` — fallback を整合 + +**ファイル**: `plugins/ndf/skills/cross-review/scripts/_tmpdir.sh` + +通常フローでは `CROSS_REVIEW_TMP_DIR` env で上書きされるため到達しないが、 +直接実行時の fallback も揃える: + +```bash +# Before +gemini_root="$HOME/.gemini/tmp" +if [ -d "$gemini_root" ] && [ -n "$base" ]; then + mkdir -p "$gemini_root/$base" + echo "$gemini_root/$base" + +# After +mkdir -p "$PWD/.gemini/tmp" +echo "$PWD/.gemini/tmp" +``` + +### 変更 3: `monitor.py _tmp_dir()` — fallback を整合 + +**ファイル**: `plugins/ndf/skills/cross-review/scripts/monitor.py` L203-220 + +同様に fallback を `Path.cwd() / ".gemini" / "tmp"` に変更。 + +### 変更 4: `.gitignore` — `.cross_review/` 除外 + +**ファイル**: `.gitignore` + +``` +# cross-review gemini workspace tmp +.cross_review/ +``` + +`.gemini/` 自体は gemini CLI の設定 (settings.json) に使うため、`tmp/` サブディレクトリのみ除外。 + +### 変更 5: SKILL.md — ドキュメント整合 + +**ファイル**: `plugins/ndf/skills/cross-review/SKILL.md` L86 + +「tmp dir は `~/.cross_review//` を採用」の記述を更新。 + +## 変更対象ファイル一覧 + +| ファイル | 変更内容 | +|---|---| +| `plugins/ndf/skills/cross-review/scripts/state.py` | `_tmp_dir()` の解決先を `/.cross_review/` に変更 | +| `plugins/ndf/skills/cross-review/scripts/_tmpdir.sh` | fallback を `$PWD/.cross_review/` に変更 | +| `plugins/ndf/skills/cross-review/scripts/monitor.py` | fallback を `cwd/.cross_review/` に変更 | +| `.gitignore` | `.cross_review/` 追加 | +| `plugins/ndf/skills/cross-review/SKILL.md` | ドキュメント整合 | + +## launch-gemini.sh は変更不要 + +`launch-gemini.sh` のプロンプト内のパスは `$TMP_DIR/gemini-review-pr$STATE_PR-result.json` と +変数展開している。`$TMP_DIR` が `/.cross_review/` に変わることで、 +プロンプト上のパスも自動的に worktree 内を指す。gemini はこのパスに書き込み可能。 + +## 変更 6 (試行): `launch-codex.sh` — sandbox モード緩和 + +**ファイル**: `plugins/ndf/skills/cross-review/scripts/launch-codex.sh` L101 + +TMP_DIR が worktree 内に移ることで、codex が workspace 外に書き込む必要がなくなる。 +`--dangerously-bypass-approvals-and-sandbox` を `-s workspace-write` に切り替え可能か試行する。 + +```bash +# Before +codex exec --dangerously-bypass-approvals-and-sandbox \ + --config reasoning.effort=medium -C "$WORKTREE" \ + +# After (試行) +codex exec -s workspace-write \ + --config reasoning.effort=medium -C "$WORKTREE" \ +``` + +`codex exec` は非対話モードのため approval bypass は不要と想定。 +`-s workspace-write` で `gh api` (ネットワーク + シェル実行) が通るかが判定基準: + +- **通る場合**: `-s workspace-write` に切り替え。より安全な sandbox 設定になる +- **通らない場合**: 現行の `--dangerously-bypass-approvals-and-sandbox` を維持 + +## 変更 7: `/ndf:merged` — worktree クリーンアップ追加 + +**ファイル**: `plugins/ndf/skills/merged/SKILL.md` + +PR マージ後のクリーンアップに、cross-review で作成された worktree の削除を追加する。 + +現状の手順: +1. PR がマージ済みか確認 +2. stash → main checkout → pull +3. feature ブランチ削除 → stash 復元 + +追加する手順 (3 の前に挿入): +- `git worktree list` で当該 PR 番号に対応する worktree を探す (例: `pr`) +- 見つかれば `git worktree remove ` で削除 +- worktree 内の `.cross_review/` も worktree ごと消えるため個別削除は不要 + +## 単一 PR 判定 + +- 変更ファイル 6〜7 個、差分 ~40〜50 行 +- 全変更は TMP_DIR パスの統一 + それに伴う sandbox 緩和 + クリーンアップという 1 つの関心事 +- release ブランチ不要。通常の feature branch + PR フローで進行 + +## ブランチ + +``` +feature/PLAN22-gemini-result-workspace +``` + +## テスト計画 + +- [ ] worktree 上で `/ndf:cross-review` を実行し、gemini が `/.cross_review/` に result.json を書くことを確認 +- [ ] `monitor.py` が `/.cross_review/` の result.json を検出して OK 判定することを確認 +- [ ] `state.py read-result` が正常に result.json を読めることを確認 +- [ ] `.cross_review/` が `git status` に表示されないことを確認 +- [ ] codex 側のフローに影響がないことを確認 (codex は workspace 制約がないが、パスが変わっても動作すること) +- [ ] codex で `-s workspace-write` に切り替え、`gh api` によるレビュー投稿が通ることを確認。通らなければ `--dangerously-bypass-approvals-and-sandbox` を維持 +- [ ] `/ndf:merged ` 実行後、対応する worktree (`pr`) が削除されていることを確認 diff --git a/plugins/ndf/skills/cross-review/SKILL.md b/plugins/ndf/skills/cross-review/SKILL.md index 90dfd0f..066ef50 100644 --- a/plugins/ndf/skills/cross-review/SKILL.md +++ b/plugins/ndf/skills/cross-review/SKILL.md @@ -83,7 +83,7 @@ state.json の読み書きや AI launcher 起動・完了待ちは全て委譲 |---|---|---| | 1 | 自分の PR 判定(422 回避) | `gh api user` と `gh pr view --json author` を比較し `is_own_pr` / `event_downgrade` を state.json に書く | | 2 | worktree 分離 | `git worktree add /pr ` を冪等実行(`` は `NDF_WORKTREE_BASE` env > `/work/worktrees` > `$HOME/work/worktrees` の優先順で解決) | -| 3 | gemini trusted directory | `launch-gemini.sh` が `GEMINI_CLI_TRUST_WORKSPACE=true` + `--skip-trust` を必ず併用。さらに **tmp dir は `~/.gemini/tmp//`** を採用し、gemini の workspace 制約 (workspace 外の `read_file` / `write_file` がブロックされる) を回避 | +| 3 | gemini trusted directory | `launch-gemini.sh` が `GEMINI_CLI_TRUST_WORKSPACE=true` + `--skip-trust` を必ず併用。**tmp dir は `/.cross_review/`** を採用し、gemini の workspace 制約 (workspace 外の `write_file` がブロックされる) を根本回避 | | 4 | 既存コメント差分 | `gh api .../comments --paginate` を `$TMP_DIR/cross-review-pr-existing-comments.txt` に保存し、gemini プロンプトには **内容をインライン埋め込み**、codex プロンプトには path を渡す | ### `` の解決順 diff --git a/plugins/ndf/skills/cross-review/scripts/_tmpdir.sh b/plugins/ndf/skills/cross-review/scripts/_tmpdir.sh index 8601615..09716b3 100755 --- a/plugins/ndf/skills/cross-review/scripts/_tmpdir.sh +++ b/plugins/ndf/skills/cross-review/scripts/_tmpdir.sh @@ -7,7 +7,7 @@ # # 優先順位: # 1. 環境変数 CROSS_REVIEW_TMP_DIR (明示) -# 2. ~/.gemini/tmp// (gemini の workspace 制約を回避する公式 path) +# 2. $PWD/.cross_review/ (worktree 内。gemini の workspace 制約を根本回避) # 3. /tmp/ (フォールバック) tmpdir() { @@ -16,13 +16,6 @@ tmpdir() { echo "$CROSS_REVIEW_TMP_DIR" return fi - local base - base=$(basename "$PWD") - local gemini_root="$HOME/.gemini/tmp" - if [ -d "$gemini_root" ] && [ -n "$base" ]; then - mkdir -p "$gemini_root/$base" - echo "$gemini_root/$base" - return - fi - echo "/tmp" + mkdir -p "$PWD/.cross_review" + echo "$PWD/.cross_review" } diff --git a/plugins/ndf/skills/cross-review/scripts/launch-codex.sh b/plugins/ndf/skills/cross-review/scripts/launch-codex.sh index 810ef28..22b4261 100755 --- a/plugins/ndf/skills/cross-review/scripts/launch-codex.sh +++ b/plugins/ndf/skills/cross-review/scripts/launch-codex.sh @@ -8,8 +8,10 @@ # gh コマンドに使う「現在のレビュー対象 PR」は state.json の `current_pr` を読む。 # # tmp ディレクトリは `_tmpdir.sh` の `tmpdir()` 関数で決定: -# CROSS_REVIEW_TMP_DIR env → ~/.gemini/tmp// → /tmp/ -# gemini の workspace 制約を回避するため、`~/.gemini/tmp/...` を優先採用する。 +# CROSS_REVIEW_TMP_DIR env → $PWD/.cross_review/ +# worktree 内に配置することで gemini の workspace 制約を根本回避。 +# codex は --dangerously-bypass-approvals-and-sandbox を維持 +# (-s workspace-write は bwrap 非対応環境で失敗するため)。 # # 状態ファイル: $TMP_DIR/codex-review-pr-{result,err,stdout,pid}.json # (パスは STATE_PR ベースで固定 — monitor.py / state.py と一致させる。) diff --git a/plugins/ndf/skills/cross-review/scripts/monitor.py b/plugins/ndf/skills/cross-review/scripts/monitor.py index ae57bb3..1ece63e 100755 --- a/plugins/ndf/skills/cross-review/scripts/monitor.py +++ b/plugins/ndf/skills/cross-review/scripts/monitor.py @@ -205,7 +205,7 @@ def _tmp_dir() -> pathlib.Path: state.py の `_tmp_dir()` と同じロジック。優先: 1. `CROSS_REVIEW_TMP_DIR` env - 2. `~/.gemini/tmp//` (`~/.gemini/tmp/` が存在するとき) + 2. `/.cross_review/` (worktree 内。gemini の workspace 制約を根本回避) 3. `/tmp/` (フォールバック) """ env = os.environ.get("CROSS_REVIEW_TMP_DIR") @@ -213,13 +213,9 @@ def _tmp_dir() -> pathlib.Path: d = pathlib.Path(env) d.mkdir(parents=True, exist_ok=True) return d - base_name = pathlib.Path(os.getcwd()).name - gemini_root = pathlib.Path.home() / ".gemini" / "tmp" - if gemini_root.is_dir() and base_name: - d = gemini_root / base_name - d.mkdir(parents=True, exist_ok=True) - return d - return pathlib.Path("/tmp") + d = pathlib.Path.cwd() / ".cross_review" + d.mkdir(parents=True, exist_ok=True) + return d # ---------- データ型 ---------- diff --git a/plugins/ndf/skills/cross-review/scripts/state.py b/plugins/ndf/skills/cross-review/scripts/state.py index 3a3df80..129d918 100755 --- a/plugins/ndf/skills/cross-review/scripts/state.py +++ b/plugins/ndf/skills/cross-review/scripts/state.py @@ -5,7 +5,7 @@ # /// """cross-review state.json 操作 CLI。 -`/tmp/cross-review-pr-state.json` の初期化 / 読み書きと、 +`/.cross_review/cross-review-pr-state.json` の初期化 / 読み書きと、 ループ判定(round 開始 / 収束 / 振動 / PR ローテーション要否 / fix 結果マージ / deferred nit レポート)を 1 つの CLI に集約する。 @@ -62,24 +62,20 @@ def _tmp_dir(workspace: str | None = None) -> pathlib.Path: 優先順位: 1. 環境変数 `CROSS_REVIEW_TMP_DIR` (明示) - 2. `~/.gemini/tmp//` (gemini workspace 制約を回避するため、 - `~/.gemini/tmp/` が存在するなら自動使用) + 2. `/.cross_review/` (worktree 内。gemini の workspace 制約を根本回避) 3. `/tmp/` (フォールバック) - `workspace` 未指定なら `os.getcwd()` の basename を使う。 + `workspace` 未指定なら `os.getcwd()` を使う。 """ env = os.environ.get("CROSS_REVIEW_TMP_DIR") if env: d = pathlib.Path(env) d.mkdir(parents=True, exist_ok=True) return d - base_name = pathlib.Path(workspace or os.getcwd()).name - gemini_root = pathlib.Path.home() / ".gemini" / "tmp" - if gemini_root.is_dir() and base_name: - d = gemini_root / base_name - d.mkdir(parents=True, exist_ok=True) - return d - return pathlib.Path("/tmp") + ws = pathlib.Path(workspace or os.getcwd()) + d = ws / ".cross_review" + d.mkdir(parents=True, exist_ok=True) + return d def _state_path(pr: int) -> pathlib.Path: @@ -244,12 +240,7 @@ def cmd_init(args: argparse.Namespace) -> None: """Step 0 — state 初期化 or 既存 state 引き継ぎ + プリチェック。""" pr = args.pr # worktree path を先に解決してから tmp_dir を決定する。 - # gemini の workspace 制約 (~/.gemini/tmp/) と - # 一致させるため、worktree basename ベースで tmp_dir を計算する必要がある。 - # 旧実装は _tmp_dir(args.worktree) を args.worktree=None のまま呼び、 - # os.getcwd() の basename (= 親リポジトリ名) を採用していたため、 - # launch-gemini.sh で `cd $WORKTREE` した後の gemini が - # `~/.gemini/tmp/` への write をブロックして hard timeout していた。 + # tmp_dir は /.cross_review/ に配置し、gemini の workspace 制約を根本回避。 worktree = args.worktree or str(_default_worktree_base() / f"pr{pr}") tmp_dir = _tmp_dir(worktree) state_file = tmp_dir / f"cross-review-pr{pr}-state.json" diff --git a/plugins/ndf/skills/merged/SKILL.md b/plugins/ndf/skills/merged/SKILL.md index 5ceebb7..202450c 100644 --- a/plugins/ndf/skills/merged/SKILL.md +++ b/plugins/ndf/skills/merged/SKILL.md @@ -17,7 +17,8 @@ PRマージ後のクリーンアップを実行。 0. **事前確認**: github mcpで引数の(引数が無ければ自身が作成した最新の)PRがmainにmergeされていることを確認。mergeされていなければ終了 1. **事前確認**: `git status`→変更あればstash 2. **main更新**: `git checkout main`→`git pull` -3. **ブランチ削除**: `git branch -d ` → stash復元 +3. **worktreeクリーンアップ**: `git worktree list` で当該PR番号に対応する worktree (`pr`) を探し、あれば `git worktree remove ` で削除(worktree 内の `.cross_review/` も一緒に消える) +4. **ブランチ削除**: `git branch -d ` → stash復元 **注意**: 冪等性保証・エラー時中断・削除済み無視 From 80776ea2cf50e9598b610343652c9a38146e770c Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Sun, 24 May 2026 04:16:10 +0000 Subject: [PATCH 2/5] =?UTF-8?q?Fix:=20cross-review=20=E3=83=AC=E3=83=93?= =?UTF-8?q?=E3=83=A5=E3=83=BC=E6=8C=87=E6=91=98=204=E4=BB=B6=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20(PR=20#8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - state.py: _tmp_dir() 呼び出しを worktree 存在チェック・作成の後に移動。 _tmp_dir() が mkdir で worktree ディレクトリを副作用で作成し、 exists() チェックが常に true になる順序問題を解消。 - _tmpdir.sh: $PWD 依存を git rev-parse --show-toplevel に変更し、 サブディレクトリから実行しても worktree root を正しく特定。 - PLAN22 ドキュメント: After スニペットの .gemini/tmp を .cross_review に統一。 Co-Authored-By: Claude Opus 4.7 (1M context) --- ...view-gemini-result-workspace-constraint.md | 11 +++++----- .../skills/cross-review/scripts/_tmpdir.sh | 7 +++++-- .../ndf/skills/cross-review/scripts/state.py | 20 +++++++++++++------ 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/issues/PLAN22_cross-review-gemini-result-workspace-constraint.md b/issues/PLAN22_cross-review-gemini-result-workspace-constraint.md index d95de0d..55c2da9 100644 --- a/issues/PLAN22_cross-review-gemini-result-workspace-constraint.md +++ b/issues/PLAN22_cross-review-gemini-result-workspace-constraint.md @@ -70,7 +70,7 @@ if gemini_root.is_dir() and base_name: # After ws = pathlib.Path(workspace or os.getcwd()) -d = ws / ".gemini" / "tmp" +d = ws / ".cross_review" ``` `workspace` が指定されていれば `/.cross_review/`、なければ `/.cross_review/` を返す。 @@ -90,15 +90,16 @@ if [ -d "$gemini_root" ] && [ -n "$base" ]; then echo "$gemini_root/$base" # After -mkdir -p "$PWD/.gemini/tmp" -echo "$PWD/.gemini/tmp" +root="$(git rev-parse --show-toplevel 2>/dev/null)" || root="$PWD" +mkdir -p "$root/.cross_review" +echo "$root/.cross_review" ``` ### 変更 3: `monitor.py _tmp_dir()` — fallback を整合 **ファイル**: `plugins/ndf/skills/cross-review/scripts/monitor.py` L203-220 -同様に fallback を `Path.cwd() / ".gemini" / "tmp"` に変更。 +同様に fallback を `Path.cwd() / ".cross_review"` に変更。 ### 変更 4: `.gitignore` — `.cross_review/` 除外 @@ -109,7 +110,7 @@ echo "$PWD/.gemini/tmp" .cross_review/ ``` -`.gemini/` 自体は gemini CLI の設定 (settings.json) に使うため、`tmp/` サブディレクトリのみ除外。 +cross-review の一時ファイルディレクトリを除外。 ### 変更 5: SKILL.md — ドキュメント整合 diff --git a/plugins/ndf/skills/cross-review/scripts/_tmpdir.sh b/plugins/ndf/skills/cross-review/scripts/_tmpdir.sh index 09716b3..b37cd7e 100755 --- a/plugins/ndf/skills/cross-review/scripts/_tmpdir.sh +++ b/plugins/ndf/skills/cross-review/scripts/_tmpdir.sh @@ -16,6 +16,9 @@ tmpdir() { echo "$CROSS_REVIEW_TMP_DIR" return fi - mkdir -p "$PWD/.cross_review" - echo "$PWD/.cross_review" + # サブディレクトリから呼ばれた場合でも worktree root を正しく特定する + local root + root="$(git rev-parse --show-toplevel 2>/dev/null)" || root="$PWD" + mkdir -p "$root/.cross_review" + echo "$root/.cross_review" } diff --git a/plugins/ndf/skills/cross-review/scripts/state.py b/plugins/ndf/skills/cross-review/scripts/state.py index 129d918..f8a6690 100755 --- a/plugins/ndf/skills/cross-review/scripts/state.py +++ b/plugins/ndf/skills/cross-review/scripts/state.py @@ -242,13 +242,17 @@ def cmd_init(args: argparse.Namespace) -> None: # worktree path を先に解決してから tmp_dir を決定する。 # tmp_dir は /.cross_review/ に配置し、gemini の workspace 制約を根本回避。 worktree = args.worktree or str(_default_worktree_base() / f"pr{pr}") - tmp_dir = _tmp_dir(worktree) - state_file = tmp_dir / f"cross-review-pr{pr}-state.json" - # 再開 - if state_file.exists(): - st = json.loads(state_file.read_text()) + # worktree 存在チェック用: _tmp_dir() は mkdir するため、先に呼ぶと + # worktree ディレクトリが副作用で作成され exists() が常に true になる。 + # そのため _tmp_dir() 呼び出しは worktree 作成/確認の後に行う。 + + # 再開チェック: state ファイルの存在確認は _tmp_dir() を使わず直接パスを組む + resume_state_file = pathlib.Path(worktree) / ".cross_review" / f"cross-review-pr{pr}-state.json" + if resume_state_file.exists(): + st = json.loads(resume_state_file.read_text()) if st.get("final") is None: + tmp_dir = _tmp_dir(worktree) wt = st.get("worktree_path") or "" info(f"↻ 前回中断 state から再開(round={len(st.get('rounds', []))})") print(f"PR={st['current_pr']}") @@ -265,7 +269,7 @@ def cmd_init(args: argparse.Namespace) -> None: if is_own: info(f"⚠ 自分の PR (author={me}) — REQUEST_CHANGES → COMMENT 強制ダウングレード") - # worktree 分離 + # worktree 分離 — _tmp_dir() より先に worktree を作成/確認する head_branch = _sh(["gh", "pr", "view", str(pr), "--json", "headRefName", "--jq", ".headRefName"]) base_branch = _sh(["gh", "pr", "view", str(pr), "--json", "baseRefName", "--jq", ".baseRefName"]) if not pathlib.Path(worktree).exists(): @@ -280,6 +284,10 @@ def cmd_init(args: argparse.Namespace) -> None: else: info(f"↻ 既存 worktree 流用: {worktree}") + # worktree 作成/確認後に _tmp_dir() を呼ぶ (ここで .cross_review/ が作られる) + tmp_dir = _tmp_dir(worktree) + state_file = tmp_dir / f"cross-review-pr{pr}-state.json" + # 既存コメントスナップショット(重複指摘防止)。 # NOTE: `gh api --paginate` は REST のページごとに **JSON 配列が連続して** stdout に出る # ため、`json.loads(r.stdout)` は複数ページで JSONDecodeError になり、コメントが空に From 2b38de987d699ab37c04114dab3e31541659e870 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Sun, 24 May 2026 04:28:54 +0000 Subject: [PATCH 3/5] =?UTF-8?q?Fix:=20cross-review=20Round=203=20=E6=8C=87?= =?UTF-8?q?=E6=91=98=206=20=E4=BB=B6=E4=BF=AE=E6=AD=A3=20(PLAN22)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PLAN22 ドキュメントの "After" コード例を .gemini/tmp → .cross_review に修正 - state.py / monitor.py の docstring から /tmp/ フォールバック記述を削除 - state.py の _tmp_dir() で pathlib.Path に .resolve() を追加し絶対パス化 - monitor.py の _tmp_dir() フォールバックを git rev-parse --show-toplevel ベースに変更 - monitor.py のモジュール docstring の result.json パス記述を更新 Co-Authored-By: Claude Opus 4.7 (1M context) --- ...view-gemini-result-workspace-constraint.md | 10 ++++----- .../skills/cross-review/scripts/monitor.py | 22 ++++++++++++++----- .../ndf/skills/cross-review/scripts/state.py | 5 ++--- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/issues/PLAN22_cross-review-gemini-result-workspace-constraint.md b/issues/PLAN22_cross-review-gemini-result-workspace-constraint.md index 55c2da9..f296657 100644 --- a/issues/PLAN22_cross-review-gemini-result-workspace-constraint.md +++ b/issues/PLAN22_cross-review-gemini-result-workspace-constraint.md @@ -69,7 +69,7 @@ if gemini_root.is_dir() and base_name: d = gemini_root / base_name # After -ws = pathlib.Path(workspace or os.getcwd()) +ws = pathlib.Path(workspace or os.getcwd()).resolve() d = ws / ".cross_review" ``` @@ -90,16 +90,16 @@ if [ -d "$gemini_root" ] && [ -n "$base" ]; then echo "$gemini_root/$base" # After -root="$(git rev-parse --show-toplevel 2>/dev/null)" || root="$PWD" -mkdir -p "$root/.cross_review" -echo "$root/.cross_review" +_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" +mkdir -p "$_root/.cross_review" +echo "$_root/.cross_review" ``` ### 変更 3: `monitor.py _tmp_dir()` — fallback を整合 **ファイル**: `plugins/ndf/skills/cross-review/scripts/monitor.py` L203-220 -同様に fallback を `Path.cwd() / ".cross_review"` に変更。 +同様に fallback を `git rev-parse --show-toplevel` ベースの `/.cross_review/` に変更。 ### 変更 4: `.gitignore` — `.cross_review/` 除外 diff --git a/plugins/ndf/skills/cross-review/scripts/monitor.py b/plugins/ndf/skills/cross-review/scripts/monitor.py index 1ece63e..8a1be70 100755 --- a/plugins/ndf/skills/cross-review/scripts/monitor.py +++ b/plugins/ndf/skills/cross-review/scripts/monitor.py @@ -17,7 +17,7 @@ - **FATAL** (auth/quota/sandbox 等の明確な致命): 検知時に kill - **WARN** (生の `Error:` / `Traceback` 等の曖昧パターン): 警告ログのみ、kill せず通常判定を継続 - `--no-early-error` / `MONITOR_NO_EARLY_ERROR=1` で検知自体を無効化可 - 4. **result.json**: プロセス終了後に `/tmp/-review-pr-result.json` が + 4. **result.json**: プロセス終了後に `/.cross_review/-review-pr-result.json` が 生成されていなければ失敗扱い 5. **hard timeout**: 既定 7 分。`--timeout` または `MONITOR_TIMEOUT` で上書き可 6. **stall timeout**: err.log + stdout.log の合計サイズが一定時間変化しなければ @@ -205,15 +205,27 @@ def _tmp_dir() -> pathlib.Path: state.py の `_tmp_dir()` と同じロジック。優先: 1. `CROSS_REVIEW_TMP_DIR` env - 2. `/.cross_review/` (worktree 内。gemini の workspace 制約を根本回避) - 3. `/tmp/` (フォールバック) + 2. `/.cross_review/` (worktree 内。gemini の workspace 制約を根本回避) + + worktree root は `git rev-parse --show-toplevel` で取得する。 + サブディレクトリから起動した場合でも一貫したパスを返す。 """ env = os.environ.get("CROSS_REVIEW_TMP_DIR") if env: - d = pathlib.Path(env) + d = pathlib.Path(env).resolve() d.mkdir(parents=True, exist_ok=True) return d - d = pathlib.Path.cwd() / ".cross_review" + # git worktree root を取得。サブディレクトリから起動しても一貫したパスにする。 + import subprocess as _sp + r = _sp.run( + ["git", "rev-parse", "--show-toplevel"], + capture_output=True, text=True, + ) + if r.returncode == 0 and r.stdout.strip(): + root = pathlib.Path(r.stdout.strip()).resolve() + else: + root = pathlib.Path.cwd().resolve() + d = root / ".cross_review" d.mkdir(parents=True, exist_ok=True) return d diff --git a/plugins/ndf/skills/cross-review/scripts/state.py b/plugins/ndf/skills/cross-review/scripts/state.py index f8a6690..a9e77cd 100755 --- a/plugins/ndf/skills/cross-review/scripts/state.py +++ b/plugins/ndf/skills/cross-review/scripts/state.py @@ -63,16 +63,15 @@ def _tmp_dir(workspace: str | None = None) -> pathlib.Path: 優先順位: 1. 環境変数 `CROSS_REVIEW_TMP_DIR` (明示) 2. `/.cross_review/` (worktree 内。gemini の workspace 制約を根本回避) - 3. `/tmp/` (フォールバック) `workspace` 未指定なら `os.getcwd()` を使う。 """ env = os.environ.get("CROSS_REVIEW_TMP_DIR") if env: - d = pathlib.Path(env) + d = pathlib.Path(env).resolve() d.mkdir(parents=True, exist_ok=True) return d - ws = pathlib.Path(workspace or os.getcwd()) + ws = pathlib.Path(workspace or os.getcwd()).resolve() d = ws / ".cross_review" d.mkdir(parents=True, exist_ok=True) return d From 21f5b3c3aa7c7cacdb5f96a87e854e34df50f903 Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Sun, 24 May 2026 04:41:27 +0000 Subject: [PATCH 4/5] =?UTF-8?q?Fix:=20=5Ftmp=5Fdir()=20workspace=20?= =?UTF-8?q?=E6=9C=AA=E6=8C=87=E5=AE=9A=E6=99=82=E3=81=AB=20git=20rev-parse?= =?UTF-8?q?=20--show-toplevel=20=E3=81=A7=E3=83=95=E3=82=A9=E3=83=BC?= =?UTF-8?q?=E3=83=AB=E3=83=90=E3=83=83=E3=82=AF=20(PLAN22=20Round=204)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit サブディレクトリから実行した場合に os.getcwd() だとパス不一致が発生する問題を修正。 git コマンド失敗時のみ os.getcwd() にフォールバックする。 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ndf/skills/cross-review/scripts/state.py | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/plugins/ndf/skills/cross-review/scripts/state.py b/plugins/ndf/skills/cross-review/scripts/state.py index a9e77cd..f1c6301 100755 --- a/plugins/ndf/skills/cross-review/scripts/state.py +++ b/plugins/ndf/skills/cross-review/scripts/state.py @@ -57,6 +57,20 @@ def _default_worktree_base() -> pathlib.Path: return pathlib.Path.home() / "work" / "worktrees" +def _git_toplevel() -> str | None: + """git worktree root を取得する。失敗時は None を返す。""" + try: + r = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], + capture_output=True, text=True, + ) + if r.returncode == 0 and r.stdout.strip(): + return r.stdout.strip() + except OSError: + pass + return None + + def _tmp_dir(workspace: str | None = None) -> pathlib.Path: """cross-review 用 tmp ディレクトリを決定する。 @@ -64,14 +78,16 @@ def _tmp_dir(workspace: str | None = None) -> pathlib.Path: 1. 環境変数 `CROSS_REVIEW_TMP_DIR` (明示) 2. `/.cross_review/` (worktree 内。gemini の workspace 制約を根本回避) - `workspace` 未指定なら `os.getcwd()` を使う。 + `workspace` 未指定なら `git rev-parse --show-toplevel` で worktree root を + 取得する。サブディレクトリから実行してもパス不一致が発生しない。 + git コマンドが失敗した場合のみ `os.getcwd()` にフォールバックする。 """ env = os.environ.get("CROSS_REVIEW_TMP_DIR") if env: d = pathlib.Path(env).resolve() d.mkdir(parents=True, exist_ok=True) return d - ws = pathlib.Path(workspace or os.getcwd()).resolve() + ws = pathlib.Path(workspace or _git_toplevel() or os.getcwd()).resolve() d = ws / ".cross_review" d.mkdir(parents=True, exist_ok=True) return d From c81ddd44b7db084cb3efe333c3e705100ccb88ad Mon Sep 17 00:00:00 2001 From: "takemi.ohama" Date: Sun, 24 May 2026 04:54:29 +0000 Subject: [PATCH 5/5] =?UTF-8?q?Docs:=20cross-review=20=E3=83=89=E3=82=AD?= =?UTF-8?q?=E3=83=A5=E3=83=A1=E3=83=B3=E3=83=88=E3=83=BB=E3=82=B3=E3=83=A1?= =?UTF-8?q?=E3=83=B3=E3=83=88=E3=81=AE=E6=97=A7=E3=83=91=E3=82=B9=E8=A8=98?= =?UTF-8?q?=E8=BF=B0=E3=82=92=20.cross=5Freview=20=E4=BD=93=E7=B3=BB?= =?UTF-8?q?=E3=81=AB=E7=B5=B1=E4=B8=80=20(PLAN22=20round=205=20fix)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SKILL.md: 状態ファイルパスを /tmp/ → /.cross_review/ に更新 - docs/02-fix-and-rotation.md: TMP_DIR 解決順の説明を実装に合わせて更新 - scripts/_tmpdir.sh: コメントの優先順位から削除済みの /tmp/ fallback を除去 - scripts/launch-gemini.sh: コメント内の旧パス ~/.gemini/tmp/ を .cross_review/ に更新 Co-Authored-By: Claude Opus 4.7 (1M context) --- plugins/ndf/skills/cross-review/SKILL.md | 2 +- plugins/ndf/skills/cross-review/docs/02-fix-and-rotation.md | 4 ++-- plugins/ndf/skills/cross-review/scripts/_tmpdir.sh | 3 +-- plugins/ndf/skills/cross-review/scripts/launch-gemini.sh | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/plugins/ndf/skills/cross-review/SKILL.md b/plugins/ndf/skills/cross-review/SKILL.md index 066ef50..fe0d786 100644 --- a/plugins/ndf/skills/cross-review/SKILL.md +++ b/plugins/ndf/skills/cross-review/SKILL.md @@ -39,7 +39,7 @@ state.json の読み書きや AI launcher 起動・完了待ちは全て委譲 | レビュー投稿 | **AI 自身が `gh api` で PR に直接投稿**。メインはペイロードを保持しない | | 修正 | **必ずサブエージェント (`general-purpose`) で実行**。メイン context に diff は載せない | | ユーザ問い合わせ | 自動判断を最大化(`critical`/`major`/`minor` は自動修正、`nit` は最後にまとめて 1 回だけ問い合わせ) | -| 状態の永続化 | `/tmp/cross-review-pr<番号>-state.json` に集約。中断・再開可能 | +| 状態の永続化 | `/.cross_review/cross-review-pr<番号>-state.json` に集約。中断・再開可能 | | 長尺PR対策 | **`--rotate-after` ラウンドで PR をローテーション**(default=light: 同ブランチで PR 巻き直し / squash: 新ブランチ + squash 統合) | | 振動検知 | 同じ指摘が 2 round で 50%以上重複したら中断 | diff --git a/plugins/ndf/skills/cross-review/docs/02-fix-and-rotation.md b/plugins/ndf/skills/cross-review/docs/02-fix-and-rotation.md index 40e9239..f198476 100644 --- a/plugins/ndf/skills/cross-review/docs/02-fix-and-rotation.md +++ b/plugins/ndf/skills/cross-review/docs/02-fix-and-rotation.md @@ -26,8 +26,8 @@ 4. nit / 判断が割れる minor は **修正せず deferred 記録**(reply は `[deferred / nit]` ラベル付き、Resolve しない) 5. **PR レベルの Summary コメントを `gh pr comment` で投稿**(対応件数 / 重要度別 / deferred 件数 / rejected 件数 / commit SHA を含む) 6. 戻り値ファイル `$TMP_DIR/fix-pr-result.json` を必ず書き出す - (`$TMP_DIR` は env `CROSS_REVIEW_TMP_DIR` > `~/.gemini/tmp//` > `/tmp/` の順で解決。 - 詳細は `scripts/state.py _tmp_dir()` 参照。`/tmp/` 直書きでも `state.py merge-fix` は fallback で拾う) + (`$TMP_DIR` は env `CROSS_REVIEW_TMP_DIR` > `/.cross_review/` の順で解決。 + 詳細は `scripts/state.py _tmp_dir()` 参照。`/tmp/` 直書きでも `state.py merge-fix` は legacy fallback で拾う) > ⚠ inline thread への reply + Resolve **だけでは不十分**。PR ページの > conversation タブに表示される **PR レベルコメント** がレビュアーへの diff --git a/plugins/ndf/skills/cross-review/scripts/_tmpdir.sh b/plugins/ndf/skills/cross-review/scripts/_tmpdir.sh index b37cd7e..8df7371 100755 --- a/plugins/ndf/skills/cross-review/scripts/_tmpdir.sh +++ b/plugins/ndf/skills/cross-review/scripts/_tmpdir.sh @@ -7,8 +7,7 @@ # # 優先順位: # 1. 環境変数 CROSS_REVIEW_TMP_DIR (明示) -# 2. $PWD/.cross_review/ (worktree 内。gemini の workspace 制約を根本回避) -# 3. /tmp/ (フォールバック) +# 2. /.cross_review/ (worktree 内。gemini の workspace 制約を根本回避) tmpdir() { if [ -n "${CROSS_REVIEW_TMP_DIR:-}" ]; then diff --git a/plugins/ndf/skills/cross-review/scripts/launch-gemini.sh b/plugins/ndf/skills/cross-review/scripts/launch-gemini.sh index 9e24c9e..b1ccc72 100755 --- a/plugins/ndf/skills/cross-review/scripts/launch-gemini.sh +++ b/plugins/ndf/skills/cross-review/scripts/launch-gemini.sh @@ -34,7 +34,7 @@ SHA=$(gh pr view "$PR" --json headRefOid -q .headRefOid) PROMPT=$TMP_DIR/gemini-review-pr$STATE_PR-prompt.md # 既存コメントは **プロンプトにインライン埋め込み** する。 -# tmp dir は `~/.gemini/tmp//` を使うようになったが、念のため +# tmp dir は `/.cross_review/` を使うが、念のため # プロンプト埋め込み方式も維持 (gemini が read_file を呼ばずに済むので確実)。 EXISTING_FILE=$TMP_DIR/cross-review-pr$STATE_PR-existing-comments.txt if [ -s "$EXISTING_FILE" ]; then