Crowi Orchestrate (per-tick: ready worktree 取り込み + spec groom + main review + dependabot + 停滞検知 + flaky-test 検知)
main セッションで /loop /crowi-orchestrate として 1 tick ごとに呼ばれる 前提の
skill。単発 (/crowi-orchestrate) でも動く。6 系統 (A〜F) を順に実行する。
鉄則 (毎 tick 守る):
- push しない (常にユーザー指示待ち)。
- spec を自動削除しない (削除は提案のみ、user 承認を待つ)。
- main が dirty なら integrate しない / dirty な main に勝手に commit しない (skip して報告)。
- 依存の自動 bump をしない (D 系統。影響範囲が広く、lint/test 通る保証もなく、 判断系。報告のみで user の判断を待つ)。
- 不可逆 / 判断系で詰まったら強行せず ping して待つ。
運用モード: watch(推奨・event-driven)と /loop(polling)
watch モード(推奨): .claude/scripts/orchestrate-watch.sh を persistent Monitor で
常駐させる。bash がトークンゼロで監視し続け、モデルは event が来たときだけ起きる
(/loop は変化のない tick にもトークンを使い、セッションが寝ている間の event は
拾えない — その逆)。
起動(main セッションで 1 回。TaskList に同名 Monitor が既にあれば張り直さない):
Monitor({ command: 'bash .claude/scripts/orchestrate-watch.sh',
description: 'orchestrate watch (A/C/D/E/F lanes)', persistent: true })
event → 対応(各 lane の実行手順・鉄則は下記の従来定義のまま):
| event | lane | action |
|---|---|---|
READY_TO_INTEGRATE: <id> |
A | A の裏取り → /integrate-worktree <id> |
STALLED: <id> (...) |
E | 報告のみ(割り込まない) |
REVIEW_THRESHOLD: <n> impl commits since <sha> |
C | /crowi-review <sha>..main → 完了後 lastReviewedMainSha を更新 |
NEW_DEPENDABOT: #<n> <sev> <pkg> |
D | 報告(fix は /crowi-deps)。knownDependabotAlerts の更新は act 時 |
NEW_FLAKY_ISSUE: #<n> <title> / UPDATED_FLAKY_ISSUE: #<n> <title> |
F | 報告のみ(fix は manager 判断で /crowi-fix)。knownFlakyTestIssues の更新は act 時 |
B(spec groom)は分析仕事なので watch に含めない — 単発 /crowi-orchestrate で
on-demand 実行する。
注意: watcher の dedup はプロセス寿命(= セッション)内のみ。張り直し直後は現況を 1 回再発火しうるが、act 前の裏取りが冪等性を担保する。script は state ファイルを 読むだけ(書き込みはモデルが act するときに従来どおり行う)。/loop モードも 従来どおり使える(watch が張れない環境の fallback)。
A. integrate watcher (行動系)
ready for merge な worktree を取り込む。
.feature-state/tasks/*.jsonを読み、status === "READY_TO_INTEGRATE"の task を探す。なければ A は skip。- 各 ready task について、
git worktree listから worktree を特定 (worktree 名 = task id の運用)。worktree が見つからなければ報告して skip。 - 裏取り (signal を鵜呑みにしない):
- worktree が clean (
git -C <wt> status --porcelain空) git log main..<branch>が非空tasks/{id}.jsonのreadyForMerge.headShaが現在の branch HEAD と一致 (古い signal でない。ズレていたら「signal が stale」と報告して skip)
- worktree が clean (
- main の作業ツリーが clean か確認。dirty なら integrate せず その旨を報告 (この tick では取り込まない)。
- 揃ったら
/integrate-worktree <id>を起動 (Skill 経由)。- integrate-worktree は merge → conflict 解消 → check → gw end → simplify を行う。
- conflict 解消に設計判断が要る / check が落ちる で詰まったら、強行せず中断し、
PushNotificationで「worktree の取り込みが <理由> で詰まりました」と ping。
- 取り込めた / 詰まった結果を簡潔に記録。
複数 ready があっても 1 tick で 1 worktree にする (integrate-worktree の範囲外 ポリシーに合わせる)。残りは次 tick で。
B. spec groomer (分析中心)
.feature-state/specs/*.md を 1 つずつ評価する (read-only 中心)。
各 spec について、repo root で bash .claude/skills/_shared/validate-implementation-spec.sh <spec> を実行する。ready 条件の正本は .claude/skills/_shared/spec-contract.md + validator であり、lane B 独自の簡易判定を持たない。依存(他 spec / 他機能)の解決状況だけは machine-readable でない場合があるため、validator green 後に追加確認する。exit 0 は WARN: が stderr に出ていても ready(symbol 粒度の freshness 判定が共有ファイルへの無関係な変更を soft に落としたもの)。1 回の scan の間、各 spec の validator 実行について stdout・stderr・exit status を当該 spec id と対応づけて保持し(spec をまたいで混ざらないようにするため)、走査した spec id ごとにその WARN: 行を保持しておく。
分類して報告:
- 着手 ready: validator green (exit 0) かつ依存解決済み → 「
<id>は implementation-ready」と報告。WARN:があれば同じ報告に spec id ごとグループ化して rawWARN:行を原文のまま添える(要約・書き換えしない)。必要なら「/crowi-kickoff <id>で worktree を切って実装に着手できます」と提案する (worktree 作成・実装着手は user 判断、ここでは提案まで)。 - not ready: validator の
ERROR:(exit 1)と未解決依存を列挙する。legacy contract / path-symbol map 欠落 / AC-test 対応欠落 / stale(symbol line hard stale、または path-only・5 条件不成立の file-level hard stale)も not ready。自動補完・安価なモデルでの再設計はしない。 - stale 削除候補 (提案のみ・自動削除しない): 対応機能が main に integrate 済みに
見える spec。判定根拠の例: 同名 task が
COMMITTEDかつ worktree が既に無い、かつ 機能コードが main に存在。→ 「<id>は削除候補 (根拠: …)」と提示し、削除は user 承認を待つ (絶対に自分で消さない)。 - 化石 task (アーカイブ候補・提案のみ):
tasks/*.jsonのうち以下すべてを満たすもの: (1) status がCOMMITTED/INTEGRATED相当の終端状態、(2)git worktree listに 同名 worktree が無い、(3)specs/に同名 spec が無い (= 進行の気配ゼロ)。 → 一覧で提示し、user 承認後にまとめてrm(gitignore 配下なので commit 不要)。 自動削除しない。初回は歴史的化石が大量に出る想定なので、一括承認の UX (「全部消す / 個別に選ぶ / 何もしない」) を許容する。
C. main 直作業の review watcher (品質系)
main に 直接 積まれた作業 (worktree 経由でない) が 意味のある塊 になったら、
その範囲をレビューする。手段は crowi-review を既定とする (codex preflight OK 時。
wrapper が落ちれば skill 側が Claude subagent に自動 fallback)。codex が使えない環境
でのみ組み込み /code-review (Claude subagent) を使う。worktree 経由の取り込みは
integrate-worktree の Step 7 (simplify) で既にレビュー済みなので、ここでは
二重レビューしない。
状態
.feature-state/orchestrate-state.json の lastReviewedMainSha を基準にする。
- 無ければ初回は 現在の main HEAD で初期化し (過去履歴は遡って review しない)、 この tick の C は skip。
- 書き込みは tmp+rename で atomic に。
直作業の抽出
git log --first-parent --no-merges <lastReviewedMainSha>..main
--first-parent: integrate-worktree の merge 経由で入った worktree の個別 commit を 除外 (本流のみ歩く)。--no-merges: merge commit 自体も除外。- → 残るのは main の本流に直接積まれた非マージ commit = 「main で直接作業されたもの」。
「意味のある塊」判定 (発火条件)
上記のうち housekeeping を除いた実装 commit が 2 つ以上あるとき発火する。
- housekeeping (除外):
chore(.claude…)/ format-only / orchestrate の 状態ファイル更新など、packages/**の source を触らない commit。 - 実装 commit:
packages/**の source を触るfeat/fix/refactor/perf/test等。 - 閾値に満たない (0〜1 個) なら
lastReviewedMainShaを進めず 次 tick へ持ち越し (溜まってからまとめて review)。
レビュー実行
発火したら <lastReviewedMainSha>..main の直作業差分をレビュー (correctness bug +
reuse / simplify / efficiency)。手段は crowi-review が既定 (そのスコープを渡す。
command -v codex が通らない環境でのみ組み込み /code-review)。
- low-risk な指摘: main が clean なら直接修正して別 commit (例
refactor(review): …)。 - main が dirty (= user が作業中): 勝手に commit しない。指摘を報告に留め、適用は main clean 時 or user 承認後。
- 大きい / 判断系: 報告に留めユーザー判断を仰ぐ(直す指示が出れば直す、出なければ捨てる)。
どこにも書き残さない(fix or drop — 全 skill 共通方針)。重要なら
PushNotificationで ping。
後始末
レビューが終わったら lastReviewedMainSha = 現在の main HEAD に更新 (atomic)。
発火しなかった tick では更新しない (閾値到達まで蓄積)。
D. Dependabot security alerts watcher (品質系)
GitHub Dependabot の open security alerts を定期チェックし、前回の tick 時点から
新しく出てきた advisory のみ を簡潔に報告する watcher。自動 bump はしない
(deps 更新は影響範囲が広く、lint/test の検証と判断を伴う行動)。実際の fix
(direct bump / parent bump / per-major override / major-upgrade 待ちは報告のみ +
検証 + commit)は /crowi-deps skill に集約してあるので、新規が出たら user が
/crowi-deps を打つ(D は検知・報告に徹する)。
取得
gh api repos/crowi/crowi/dependabot/alerts --paginate -X GET -f state=open \
--jq '.[] | {number, ghsa: .security_advisory.ghsa_id,
severity: .security_advisory.severity,
package: .dependency.package.name,
scope: .dependency.scope,
first_patched: .security_vulnerability.first_patched_version.identifier}'
gh が無い / 認証されていない環境では D 系統を skip(エラーは出さず、報告に
「D: gh 未認証で skip」とのみ記す)。
状態
.feature-state/orchestrate-state.json に knownDependabotAlerts: [<number>, ...]
の配列で保存する (alert number は GH 内で安定)。
- 無ければ初回は 現在 open な全 alert で初期化し (= 既存はサイレント受理)、 この tick の D の報告は skip。
- 書き込みは tmp+rename で atomic に。
報告条件
open_now - known の差集合 = 新規 alert のみを報告する。
- 新規 0 件: D は黙る。
- 新規あり: severity / package / first_patched をテーブルで列挙。
- direct dep か transitive かを
grep -E '"<pkg>"\s*:' packages/*/package.json apps/*/package.json package.jsonで簡易判定 (見つかれば direct)。 - 直し方は
/crowi-depsに集約(direct は version bump / transitive は親 bump → 不可なら per-major override / major upgrade 待ちは報告のみ)。報告には 「/crowi-depsで対応可」と一言添える。 - high / critical が混じってる、または prod scope だけで 3件以上溜まったら
PushNotificationで ping。
- direct dep か transitive かを
自動更新ルール
- 対応した GHSA を含む commit が main に入った(commit message に
GHSA-<id>を 含む、もしくはchore(deps)系でpnpm-lock.yamlが変更されている)場合、次 tick で GH 側の alert がstate: fixedに変わる →open_nowから自然に消えるので、knownDependabotAlertsのメンテは何もしなくて良い (差集合計算の自然な縮退)。 - 手動で dismiss された alert も
open_nowから消えるので同じ扱い。
後始末
knownDependabotAlerts = open_now の number 配列 で常に上書き (atomic)。
- これにより「次回までに新たに出た / 消えた」が正しく差分管理される。
- 報告したかどうかは別管理せず、open ⇄ known の集合差で判定する。
E. worktree 停滞 watcher (検知系)
orchestrate A は READY_TO_INTEGRATE signal を待つだけなので、complete-feature を 打ち忘れた worktree は永遠に不可視になる (過去の実害: editor-preview-reliability 39 commit・ci-automation 12 commit の長期滞留)。E はこれを検知して報告だけする watcher。
閾値: STALL_THRESHOLD_DAYS = 3 (仮置き。運用で調整)。ただし対応する
.feature-state/tasks/<id>.json の longLived が true の worktree は
ORCH_STALL_DAYS_LONG(既定 14。orchestrate-watch.sh の env)が閾値になる —
release ready まで数フェーズを跨ぐような長期 worktree(例: umbrella spec の実装)を
通常閾値で誤検知しないための marker。event 名・報告形式は不変(閾値だけが変わる —
完全放置はこれまでどおり検知される)。longLived の STALLED は「統合漏れ」より
「作業中断」の可能性が高い読み筋になる — ただし E 自身の行動原則(下記「行動しない」)
は longLived でも変わらない。integrate を急がず、状況確認は報告を受けた側の判断で
行う(E が能動的に agmsg で割り込むわけではない)。
検知ロジック
git worktree list --porcelain # main 以外の各 worktree を列挙
# 各 worktree <wt> (id = dir basename から crowi- を除去) について:
git -C <wt> log main..HEAD --oneline | wc -l # 積んだ commit 数
git -C <wt> log -1 --format=%ct # 最終 commit の epoch
git -C <wt> status --porcelain | head -1 # dirty か (作業中の気配)
# .feature-state/tasks/<id>.json の status / readyForMerge.headSha
停滞判定 (すべて成立):
main..HEADの commit 数 > 0readyForMergeが無い、またはreadyForMerge.headSha≠ 現在の HEAD (stale signal)- 最終 commit から閾値以上経過
dirty な worktree は「セッション作業中の可能性が高い」ので併記するが、判定からは 除外しない (dirty のまま放置も負債)。
状態管理
.feature-state/orchestrate-state.json に worktreeWatch を追加 (既存キーと同居):
"worktreeWatch": {
"<id>": { "stalledSince": "<ISO>", "lastReportedHead": "<sha>" }
}
- 報告は変化のみ (D と同じ思想): 新規に停滞入りした worktree / head が進んで (or signal が立って) 解消した worktree だけを報告。毎 tick の全列挙はしない。
- 解消したら entry を削除。書き込みは tmp+rename で atomic (既存パターン)。
行動しない
E は報告のみ。integrate しない・worktree セッションに agmsg で割り込まない・
complete-feature を代行しない(longLived task でも同じ — 上記の「作業中断の可能性
が高い」という読み筋は判断材料であって、E 自身の行動を変えるものではない)。報告
文面の例:
「<id> が停滞候補 (N commits ahead, 最終 commit M 日前, signal 無し)。worktree
セッションで /crowi-complete-feature か /crowi-handoff の実行を検討してください」。
longLived task の場合は「(longLived task — 統合漏れというより作業中断の可能性が
高い。状況確認は worktree セッション/impl への agmsg で)」を文面に添えてよい。
F. flaky-test issue watcher (検知系)
CI flake-report job (scripts/test-flake-report-issue.mjs) は FLAKY≥1 の分類ごとに
flaky-test label 付き GitHub issue を起票/occurrence コメント追記するが、これ自体は
non-blocking job の一部で誰も見ていない可能性がある。F はこの起票/更新を検知して
報告だけする watcher(D・E と同じく行動しない)。
取得
watcher event 起点(通常はこちら)。手動 tick(単発 /crowi-orchestrate)では同じ
コマンドを直接叩く:
gh issue list --repo crowi/crowi --label flaky-test --state open --json number,title,updatedAt --limit 200
状態
.feature-state/orchestrate-state.json の knownFlakyTestIssues: [{number, updatedAt}]
を読みのみ(D と同じ契約。書き込みは act 時にモデルが行う)。
- 無ければ初回は現況(現在 open な全 flaky-test issue)で silent 初期化し(D と同じ 思想 — 過去に溜まっていた分をまとめて new 扱いしない)、この tick の F の報告は skip。
- 書き込みは tmp+rename で atomic に。
報告条件
known に無い issue number → 新規(NEW_FLAKY_ISSUE)、known にあるが updatedAt が
進んだ issue → 新しい occurrence が追記された(UPDATED_FLAKY_ISSUE)。それぞれ
manager 向けに 1 行で要約する: issue の title(= flake: <path>)・直近 occurrence の
run URL / ref・(分かれば)これまでの発生回数目安。
対応判断
F(および watcher・orchestrate 全体)は検知報告のみ。issue のクローズ/再オープン・
テストの修正は一切代行しない — 優先度判断は manager が行い、修正が必要なら
/crowi-fix へ回す。flake の root-cause は早合点しやすく誤診断のコストが高いので、
F はここでも断定せず事実(issue 番号・title・occurrence)の伝達に徹する。
後始末
act 後(報告した後)に knownFlakyTestIssues を 現況の open flaky-test issue 一覧
([{number, updatedAt}])で常に上書きする(D の後始末と同じ、open ⇄ known の集合差で
次回の新規/更新を判定する)。
出力
- A で何かが起きた (integrate した / 詰まった / stale signal)、B で新規に ready / stale / 化石 task が出た、C で review を実行した (指摘あり)、D で新規 advisory が出た、 E で停滞の出入りがあった、または F で flaky-test issue の新規/更新があった場合のみ、 簡潔に報告。
- staleness warnings(B の
WARN:)はそれ単独では出力条件にしない。WARN:は再 ground されるまで毎回の validation で出続けるので、条件に含めると orchestrate が毎 tick 報告し続け、「新規イベントのみ / 変化なし」の契約を壊す。B が ready / stale を新規報告するときに、その spec id のWARN:があれば添えて出す(spec id ごとにグループ化した rawWARN:行)。 - どれも変化なしなら「変化なし」一言で終える (毎 tick の冗長な列挙はしない)。
loop との組み合わせ
/loop /crowi-orchestrate
で自律ループに乗せる。各 tick でこの skill が走り、loop 側が pacing と
「3 連続変化なしなら ping して縮退」等を司る。停止は /loop stop。