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
166 changes: 159 additions & 7 deletions .agents/skills/drive/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ review skill の観点別 `exit_criteria.drive_loop` を集約して終了判定
`--merge` 指定時に実行する。

1. **Auto-merge の有効化:** `gh pr merge --auto --squash --delete-branch` を実行する
- **subagent として単一モードを実行する場合は `--delete-branch` を省略する:** 自 worktree が当該 branch を握っているため `fatal: '<branch>' is already used by worktree at ...` エラーになる。ローカル branch / worktree の整理は親側 Phase Final で一括処理する([Issue #69](https://github.com/ozzy-labs/skills/issues/69))
2. **成否の確認:**
- 成功(Auto-merge がセットされた、または即時マージされた)→ 次へ
- 失敗(Auto-merge がリポジトリで無効など)→ ユーザーに通知し、手動マージを促す(状態を `merge-ready` にする)
Expand Down Expand Up @@ -169,6 +170,8 @@ wave を順に実行する。
- **隔離:** worktree 隔離で起動する(必須。並列実行時の作業ディレクトリ衝突防止)
- **委譲粒度:** subagent には `.agents/skills/drive/SKILL.md` を Read させ、target #N について単一モードのワークフロー(Phase 1-5)を実行するよう指示する。slash command は subagent からは呼べないため、SKILL.md を直接実行する
- **main への checkout 禁止:** subagent は自 worktree branch で完結する。`git checkout main` / `git switch main` / `git checkout HEAD~` 等で worktree の HEAD を移動させない。worktree は親の Phase Final で削除されるため、main へ戻す必要はない。共有 git directory 経由で親 worktree の `HEAD` / `index` が汚染されるリスクを避けるため、自 branch 以外を触らないこと
- **`--delete-branch` 禁止:** subagent が `gh pr merge` を呼ぶ際、`--delete-branch` フラグは使わない。自 worktree が当該 branch を握っているため `fatal: '<branch>' is already used by worktree at ...` エラーになり、リモート merge は成功するがローカル branch が中途半端に残る。ローカル branch / worktree の整理は親の Phase Final で一括処理する([Issue #69](https://github.com/ozzy-labs/skills/issues/69))
- **scope 外波及の最低限チェック:** 自 issue で schema enum / field / CLI flag を追加した場合、リポ全体で対応する help 文字列・エラーメッセージ・サンプル/docs を grep し、同期されているか確認する(例: `rg -n '<old-enum-list>' src/ docs/`)。同期されていなければ **可能なら自 PR に含める**(自 scope の自然な拡張として)。判断に迷う / 自 scope を明確に超える場合は修正せず、戻り値 `cross_cutting_gaps` に `<file>:<line> — <symbol> not synced` 形式で記録し、親の Phase Final-3 audit に集約する([Issue #70](https://github.com/ozzy-labs/skills/issues/70))
- **ベースブランチ:**
- 依存元 wave がない target → main からブランチを作る
- 依存元 wave がある target → 依存元 PR の `headRefName` をベースにブランチを作る(stacked PR)。`--merge` 指定時は依存元がマージ済みのため main をベースにできるが、未指定時はこの stacked 構造が必須
Expand All @@ -189,10 +192,16 @@ wave を順に実行する。
"total": {"critical": 0, "warning": 0, "info": 0},
"iterations": <N>
},
"cross_cutting_gaps": [
"src/cli/foo.ts:213 — help text missing new kind 'html-js'",
"src/cli/foo.ts:299 — validation error message lists old enum set"
],
"error": "<message if failed>"
}
```

`cross_cutting_gaps` は subagent が「scope 外波及の最低限チェック」で気付いたが自 PR では修正しなかった項目を記録する任意フィールド(空配列でも可)。フィールドが欠落している戻り値も後方互換のためエラーにせず、`[]` として扱う。親は Phase Final-3 post-merge audit でこれを集約し、独自検出した gap と統合して warning として list-up する。

#### 観測性

- `Agent` tool は subagent 完了時に最終結果のみを返すため、ストリーム的な中間報告は不可
Expand Down Expand Up @@ -220,7 +229,11 @@ wave を順に実行する。

### Phase Final: 集約レポート

集約レポートを出力する前に、**親 worktree の整合性を確認する**。subagent が共有 git directory 経由で親の `HEAD` / `index` を汚染するケースに備えるための fail-safe([Issue #66](https://github.com/ozzy-labs/skills/issues/66) 由来)。
Phase Final は次の 3 ステップで構成する。順に実行する。

#### Phase Final-1: 親 worktree 整合性チェック

subagent が共有 git directory 経由で親の `HEAD` / `index` を汚染するケースに備えるための fail-safe([Issue #66](https://github.com/ozzy-labs/skills/issues/66) 由来)。

1. `git rev-parse HEAD` と `git rev-parse $(git symbolic-ref HEAD)` が一致するか(HEAD が detached でないこと)
2. `git diff HEAD --stat` が空か(index が HEAD と乖離していないか)
Expand All @@ -241,7 +254,142 @@ wave を順に実行する。
git reset --hard origin/main
```

整合性チェックが通った場合は、通常どおり集約レポートを出力する:
#### Phase Final-2: subagent worktree cleanup

**今回の drive 実行で起動した subagent** の worktree と関連 local branch をクリーンアップする([Issue #69](https://github.com/ozzy-labs/skills/issues/69) 由来)。今回の実行外の orphan worktree (前回の異常終了で残ったもの等) は対象外。orphan の検出・整理は `/health` 領域 #7 に委譲する([Issue #71](https://github.com/ozzy-labs/skills/issues/71))。

1. 今回起動した subagent のリストを保持する。各 subagent の worktree パス(`.claude/worktrees/agent-<id>/`)と戻り値 `status` をひとまず控える
2. 各 subagent について `status` を参照して扱いを分岐:
- **`merged`**: cleanup 対象(リモート merge 完了済み、ローカル iterate 不要)
- `git worktree list --porcelain` で当該 worktree が握っている branch を取得する(パターンマッチに頼らない)
- `git worktree remove -f -f <path>` を実行する(`-f -f` の二重 force は Claude Code harness の `lock` 解除のため必須)
- 取得した branch を `git branch -D <branch>` で削除する
- **`auto-merge enabled`**: cleanup **しない**(後で実マージされるまで状態保留、マージ後にユーザーが手動 / `/health` で整理)
- **`merge-ready`**: cleanup **しない**(ユーザーが手動マージ前にローカル iterate する余地を残す)
- **`failed`**: cleanup **しない**(再実行で resume できるよう残置)
3. 補助的に `worktree-agent-<id>` 形式の synthetic branch が残っていれば併せて `git branch -D` する(Claude Code harness 実装由来のパターン依存。検出失敗時は warning に留め fail しない)
4. cleanup 結果を集計し、Phase Final-3 の集約レポートに含める

`merged` 以外で残置された worktree がある場合、または cleanup 自体に失敗した worktree がある場合、集約レポート末尾に warning を出す:

```text
⚠️ Stale worktrees / branches detected:
preserved (not yet merged):
.claude/worktrees/agent-<id> [<branch>] ← #<N> auto-merge enabled; マージ後に手動削除
.claude/worktrees/agent-<id> [<branch>] ← #<N> merge-ready; iterate 用に残置
preserved (failed):
.claude/worktrees/agent-<id> [<branch>] ← #<N> failed; resume 可能
cleanup failed:
.claude/worktrees/agent-<id> [<branch>] reason: <error>
Manual cleanup:
git worktree remove -f -f <path>
git branch -D <branch>
```

#### Phase Final-3: post-merge audit (cross-cutting)

複数 subagent が自 sub-issue scope に閉じて並列実行する結果、**scope を跨ぐ波及 (cross-cutting) が構造的に漏れる**ことがある(enum/field/CLI flag の help・エラーメッセージ・サンプルへの未反映、ステータス系文言の取り残し、lockfile drift 等)。集約レポート出力前に best-effort で検出する([Issue #70](https://github.com/ozzy-labs/skills/issues/70) 由来)。

検出された gap は **すべて warning 扱い**で集約レポート末尾に list-up し、ユーザーに follow-up PR の要否を問う。critical / info への severity 分類はしない(best-effort 性質に合わせシンプルに保つ)。

**前提**: Phase Final-2 cleanup 後の main ブランチで実施する(merged PR の変更が main に取り込まれている状態)。`merged` 以外(`auto-merge enabled` / `merge-ready` / `failed`)の subagent は対象外(main に未反映のため)。

各検査項目内の `gh pr diff` 呼び出しは独立しているため、複数 PR の diff 取得は並列実行してよい。

##### 0. subagent からの報告を集約

検査の起点として、各 subagent 戻り値の `cross_cutting_gaps` フィールドをすべて集約する。subagent が自前で気付き、自 PR では修正しなかった gap がここに含まれる([Issue #70](https://github.com/ozzy-labs/skills/issues/70) B2/B3)。

```text
subagent #N の cross_cutting_gaps:
- src/cli/foo.ts:213 — help text missing new kind 'html-js'
- ...
```

集約した gap は後続の独自検出と重複排除して最終的な warning list を構成する。重複判定キーは `file:line` を基本とし、同一 `file:line` で複数の異なる message がある場合は両方併記する(情報を捨てない)。

##### 1. cross-cutting symbol の同期確認 (heuristic)

各 subagent の戻り値 `pr_number` に対し `gh pr diff <N>` で差分を取得し、新規追加された enum 値・field 名・CLI flag らしき symbol を heuristic に抽出する。例:

```bash
# 追加された enum 値 / case 文 / object literal の値 を抽出
gh pr diff <N> | grep -E '^\+' | grep -oE '(case\s+["'\'']\w[\w-]+["'\''])|(--[a-z][a-z0-9-]+)|(["'\''][a-z][a-z0-9-]+["'\''])' | sort -u
```

抽出した symbol を repo 全体で grep し、help 文字列・エラーメッセージ・サンプル・docs に同期されているか確認する:

```bash
rg -n --no-heading '<symbol>' src/ docs/
```

抽出に偽陽性は許容する(grep は AI 判断のサポートツール)。AI は抽出結果を見て「この symbol は CLI 層の help にも追加されるべきか」を判断し、未同期と思われるものを gap として list-up する。

##### 2. 古い文言の残骸検出

ステータス系 keyword (`alpha`, `beta`, `Phase \d+`, `pending`, `TODO`, `FIXME` 等) が merged PR 群で削除されている場合、同じ文字列が他ファイルに残骸として残っていないか確認する:

```bash
# 各 merged PR で削除された status keyword 行を抽出
for PR in <pr_numbers>; do
gh pr diff $PR | grep -E '^-' | grep -iE '(alpha|beta|phase\s+[0-9]+|pending)'
done

# 残骸を repo 全体で grep
rg -n --no-heading -iE '(alpha|beta|phase\s+[0-9]+|pending)' src/ docs/ README.md
```

固有名詞として正当な使用 (例: `alpha` がリリースチャネル名として残るべき) は AI が判断して除外する。

##### 3. lockfile drift

`pnpm-lock.yaml` / `package-lock.json` / `yarn.lock` / `uv.lock` 等が merged PR で変更されているが対応する manifest (`package.json` / `pyproject.toml` 等) が変更されていない、またはその逆を検出する:

```bash
# 今 drive 実行で merged になった PR の数だけ遡って変更ファイルを取得
# (1 PR = squash merge で 1 commit のため、merged_pr_count == commit 数)
git diff --name-only origin/main~<merged_pr_count>..origin/main
```

manifest と lockfile の対応関係を確認し、不整合があれば gap として list-up する。

##### 4. docs ⇄ code grep 整合 (スコープ縮小版)

docs 系の merged PR (タイトルが `docs:` / `docs(<scope>):` 等) の diff から追加された CLI 呼び出し文字列を抽出し、code 側に対応する文字列が存在するか grep で確認する。

```bash
# docs PR の追加された code block 内 CLI 文字列を抽出
gh pr diff <docs_pr_number> | grep -E '^\+' | grep -oE '`[a-z][a-z0-9-]+\s+[a-z][^`]*`|--[a-z][a-z0-9-]+'

# code 側で対応する文字列を grep
rg -n --no-heading '<extracted_cli_string>' src/
```

「実行ベース」検証(実際に `<cmd> --help` を実行して確認)は行わない(リポによって CLI 構成が違うため汎用化困難)。grep ベースの整合確認のみ。

##### 検出結果の出力

gap が検出されたら、Phase Final-4 集約レポート末尾に warning として追記する。gap が 0 件なら warning ブロックは出力せず、集計行 `cross-cutting:` のみ `none` 表示にする:

```text
⚠️ Cross-cutting gaps detected:
enum/field/flag sync:
src/cli/foo.ts:213 — help text missing new kind 'html-js' (PR #N で追加)
src/cli/foo.ts:299 — validation error message lists old enum set (PR #N で追加)
stale status text:
src/cli/index.ts:52 — outdated "Status: alpha" (PR #M で他から削除済み)
lockfile drift:
pnpm-lock.yaml changed but package.json not changed in this drive run
docs/code mismatch:
docs/user-guide.md:42 references `--flag-x` but not found in src/cli/
Recommended: follow-up PR(s) to fix
```

gap が 0 件の場合は warning ブロックを省略し、Phase Final-4 集計行のみ `cross-cutting: none` と表示する(ノイズ抑制)。

#### Phase Final-4: 集約レポート

整合性チェック・worktree cleanup・post-merge audit の結果を踏まえ、集約レポートを出力する:

```text
drive 完了 (3/5 merged, 1 merge-ready, 1 skipped):
Expand All @@ -252,13 +400,17 @@ drive 完了 (3/5 merged, 1 merge-ready, 1 skipped):
#5 refactor: ... | failed (test loop)

集計:
merged: 2
merge-ready: 1
skipped: 1
failed: 1
総レビュー反復: 5 回
merged: 2
merge-ready: 1
skipped: 1
failed: 1
総レビュー反復: 5 回
worktree cleanup: 2/5 removed (3 preserved: 1 merge-ready, 1 failed, 1 skipped)
cross-cutting: 2 gaps detected (warning)
```

`cross-cutting:` 行は Phase Final-3 で gap が検出された場合は `<N> gaps detected (warning)` を表示し、詳細は前述の warning ブロックを参照する。gap が 0 件なら `cross-cutting: none` と表示し warning ブロックは出力しない。

## 失敗 semantics

| 状況 | 扱い | downstream への影響 |
Expand Down
35 changes: 34 additions & 1 deletion .agents/skills/health/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ description: リポジトリ改修中に意図せず残る状態(working tree,
- コマンド: `git branch -vv` および `git for-each-ref --sort=committerdate --format='%(refname:short) %(upstream:track) %(committerdate:relative)' refs/heads/`(古い順)
- PR 検出(**1 度だけ batch 取得**): `gh pr list --state all --json number,state,mergedAt,headRefName --limit 100` を 1 回実行し、client side で local branch 名と `headRefName` を join する(branch ごとに gh を呼ばない)
- 各 branch について:
- `worktree-agent-<id>` パターン(Claude Code harness が作る合成 branch):
- 対応する `.claude/worktrees/agent-<id>/` worktree が **存在する** → 推奨なし(使用中)
- 対応する worktree が **存在しない** → タグ `drive synthetic` を付与し、`prune`(`git branch -D <branch>`。親 worktree が削除済みの orphan synthetic branch。[Issue #71](https://github.com/ozzy-labs/skills/issues/71))
- merged 済みの PR が存在し、かつ merge base 以降に追加 commit が **ない** → `delete`(PR 番号を表示)
- merged 済みの PR が存在し、かつ merge base 以降に追加 commit が **ある** → `要確認`(PR 番号と追加 commit 数を表示。merge 後に作業継続したケース)
- upstream なし、かつ最終 commit から 14 日以上 → `要確認`
Expand All @@ -89,6 +92,22 @@ description: リポジトリ改修中に意図せず残る状態(working tree,

「追加 commit の有無」の判定: PR の merge commit と local branch の `git rev-list --count <merge-commit>..<branch>` を比較し、結果が 0 なら追加なし、1 以上なら追加あり。

`worktree-agent-<id>` の id 部分は `.claude/worktrees/agent-<id>/` の id と 1:1 対応する。判定手順:

1. #7 worktree チェックで取得した `git worktree list --porcelain` 結果から、`.claude/worktrees/agent-<id>/` パスの `<id>` を抽出して集合を作る(例: `{a33c59a3504b6dc3e, a6658497dd970cd6d}`)
2. 各 local branch 名のうち `worktree-agent-` で始まるものから prefix を除いた残り部分を id 候補として抽出
3. id 候補が手順 1 の集合に **含まれない** branch を orphan synthetic と判定する

branch ごとに `git worktree list` を呼ばない(#7 の結果を使い回す)。

表示例:

```text
#5 local branch:
worktree-agent-a33c59... drive synthetic, parent worktree missing → prune (git branch -D)
ci/playwright-... merged (PR #117) → delete
```

#### 6. remote tracking

- コマンド: `git remote prune origin --dry-run`
Expand All @@ -97,7 +116,21 @@ description: リポジトリ改修中に意図せず残る状態(working tree,
#### 7. worktree

- コマンド: `git worktree list --porcelain`
- main worktree 以外を列挙する。関連 branch が merged または存在しない → `prune`、それ以外 → 推奨なし
- main worktree 以外を列挙する。`locked` 状態の worktree も漏らさず表示する(`git worktree list --porcelain` は `locked` も含めて出力するため通常通り扱えばよい)
- 各 worktree について判定:
- パスが `.claude/worktrees/agent-*` パターン → タグ `drive orphan` を付与し、`prune` 推奨(`/drive` skill の Phase Final-2 cleanup を通らなかった残骸の可能性。異常終了 / interrupt / 手動 kill 等)
- `locked` 状態 → タグ `locked` を併記。`prune` 推奨コマンドは `git worktree remove -f -f <path>` を提示する(`-f -f` の二重 force は Claude Code harness の `lock` 解除のため必須。[Issue #71](https://github.com/ozzy-labs/skills/issues/71))
- 関連 branch が merged または存在しない → `prune`
- それ以外 → 推奨なし

タグは推奨アクションとは別軸の **分類ラベル** として表示する(推奨アクション語彙は固定のまま)。表示例:

```text
#7 worktree:
/home/.../repo/.claude/worktrees/agent-a33c59... [ci/playwright-...] drive orphan, locked → prune (git worktree remove -f -f)
/home/.../repo/.claude/worktrees/agent-a6658... [docs/html-js-...] drive orphan, locked → prune (git worktree remove -f -f)
/home/.../repo/../old-feature [feat/old] → prune
```

#### 8. submodule

Expand Down
Loading