ワンクリックで
ratchet-watch
Watch active PRs for merge conflicts, CI failures, and review comments
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Watch active PRs for merge conflicts, CI failures, and review comments
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Run agent pairs through phase-gated debates — guided by epic roadmap and current focus
Analyze project and generate tailored agent pairs through codebase analysis and human interview
Tighten the ratchet — analyze all improvement signals and sharpen the system
View or continue an ongoing debate
Manage guards — list, add, run, and override deterministic checks at phase boundaries
Add a new agent pair to an existing Ratchet configuration
| name | ratchet:watch |
| description | Watch active PRs for merge conflicts, CI failures, and review comments |
Watch active Ratchet PRs for merge conflicts, CI failures, and review comments. When problems detected, automatically creates discoveries in plan.yaml so they surface in /ratchet:run and /ratchet:status. When actionable review comments detected, spawns response agents to address feedback directly on the PR branch. Uses Claude Code's /loop feature to poll every 10 minutes.
Auto-started by /ratchet:run — run orchestrator starts this loop when PRs exist and stops it on completion. Use /ratchet:watch manually only if you want monitoring outside of a run session.
/ratchet:watch # Start watching PRs (includes review comment detection)
/ratchet:watch --no-respond # Watch but don't auto-spawn response agents for reviews
/ratchet:watch --stop # Stop watching
.ratchet/plan.yaml must exist with issues that have pr fields populatedgh CLI must be installed and authenticatedIf no PRs exist in plan.yaml, inform user: "No PRs found in plan.yaml. PRs are created by /ratchet:run when issues complete."
gh authentication before any GitHub API calls:
gh auth status >/dev/null 2>&1
If check fails (exit code != 0), inform user via AskUserQuestion:
"I've authenticated — retry", "Cancel"
If "retry", re-run gh auth status check. If "Cancel", exit skill./ratchet:run Step 1a)pr fields.ratchet/watch-state.json:
if [ ! -f .ratchet/watch-state.json ]; then
echo '{"seen_comment_ids": [], "seen_review_ids": []}' > .ratchet/watch-state.json
fi
Watch state file tracks which review comments and reviews have been processed to avoid re-processing on subsequent poll cycles.--no-respond: Disable auto-spawning response agents for review comments. When set, actionable comments are logged as feedback entries but no agent is spawned. Default behavior is to auto-respond when github-issues progress adapter is configured in workflow.yaml.--stop: Cancel loop and exit./loop 10m check Ratchet PRs for conflicts, CI failures, and review comments
For each PR URL found in plan.yaml (epic.milestones[].issues[] | select(.pr != null) | .pr):
pr_num=$(echo "$pr_url" | grep -oP 'pull/\K\d+')
# Check merge conflict status
mergeable=$(gh pr view "$pr_num" --json mergeable -q .mergeable)
# Check CI status
ci_status=$(gh pr view "$pr_num" --json statusCheckRollup -q '.statusCheckRollup[] | select(.conclusion != "SUCCESS") | .name')
Merge conflict detected (mergeable == "CONFLICTING"):
epic.discoveries[] | select(.source == "pr-conflict-<pr_num>")issue_ref=$(yq eval ".epic.milestones[].issues[] | select(.pr == \"$pr_url\") | .ref" .ratchet/plan.yaml)
milestone_id=$(yq eval ".epic.milestones[] | select(.issues[] | .pr == \"$pr_url\") | .id" .ratchet/plan.yaml | head -1)
yq eval -i ".epic.discoveries += [{
\"ref\": \"discovery-conflict-$(date +%s)\",
\"title\": \"Merge conflict in PR #$pr_num\",
\"description\": \"PR #$pr_num for issue $issue_ref has merge conflicts with main.\",
\"category\": \"bug\",
\"severity\": \"critical\",
\"source\": \"pr-conflict-$pr_num\",
\"status\": \"pending\",
\"issue_ref\": \"$issue_ref\",
\"context\": {\"milestone\": $milestone_id, \"issue\": \"$issue_ref\", \"debate\": null},
\"pairs\": [],
\"affected_scope\": null,
\"retro_type\": null,
\"created_at\": \"$(date -Iseconds)\"
}]" .ratchet/plan.yaml
CI failure detected (non-empty ci_status):
epic.discoveries[] | select(.source == "pr-ci-failure-<pr_num>")issue_ref=$(yq eval ".epic.milestones[].issues[] | select(.pr == \"$pr_url\") | .ref" .ratchet/plan.yaml)
milestone_id=$(yq eval ".epic.milestones[] | select(.issues[] | .pr == \"$pr_url\") | .id" .ratchet/plan.yaml | head -1)
milestone_id_value=$([ -n "$milestone_id" ] && echo "$milestone_id" || echo "null")
yq eval -i ".epic.discoveries += [{
\"ref\": \"discovery-ci-$(date +%s)\",
\"title\": \"CI failure in PR #$pr_num\",
\"description\": \"PR #$pr_num for issue $issue_ref failed: $ci_status\",
\"category\": \"tech-debt\",
\"severity\": \"major\",
\"source\": \"pr-ci-failure-$pr_num\",
\"status\": \"pending\",
\"issue_ref\": \"$issue_ref\",
\"context\": {\"milestone\": $milestone_id_value, \"issue\": \"$issue_ref\", \"debate\": null},
\"pairs\": [],
\"affected_scope\": null,
\"retro_type\": \"ci-failure\",
\"created_at\": \"$(date -Iseconds)\"
}]" .ratchet/plan.yaml
Review comments detected — fetch and classify new review activity:
# Get the repo owner/name from gh
repo_nwo=$(gh repo view --json nameWithOwner -q .nameWithOwner)
# Fetch PR review comments (inline code comments)
comments_json=$(gh api "repos/${repo_nwo}/pulls/${pr_num}/comments" \
--jq '.[] | {id: .id, user: .user.login, body: .body, path: .path, created_at: .created_at, in_reply_to_id: .in_reply_to_id}' 2>/dev/null) || true
# Fetch PR reviews (top-level review verdicts: APPROVED, CHANGES_REQUESTED, COMMENTED)
reviews_json=$(gh pr view "$pr_num" --json reviews \
-q '.reviews[] | {id: .id, author: .author.login, state: .state, body: .body, submittedAt: .submittedAt}' 2>/dev/null) || true
Filter already-seen comments — compare against .ratchet/watch-state.json:
seen_comment_ids=$(jq -r '.seen_comment_ids[]' .ratchet/watch-state.json 2>/dev/null)
seen_review_ids=$(jq -r '.seen_review_ids[]' .ratchet/watch-state.json 2>/dev/null)
Skip any comment or review whose ID appears in seen lists. For each new comment or review, classify as actionable or informational:
| Category | Signal | Action |
|---|---|---|
| Actionable: Requested change | Review state CHANGES_REQUESTED, or comment contains specific code suggestion, fix request, or "please change/update/fix" | Create feedback entry |
| Actionable: Question | Comment ends with ? or contains "why", "how", "could you explain" | Create feedback entry |
| Actionable: Code suggestion | Comment contains a fenced code block with suggested replacement | Create feedback entry |
| Informational: Approval | Review state APPROVED | Log only, no action |
| Informational: Acknowledgment | Comment body matches LGTM, "looks good", "nice", thumbs up | Log only, no action |
Filter bot comments — exclude comments from authenticated gh user to avoid self-referential feedback loops:
bot_user=$(gh api user --jq .login 2>/dev/null) || bot_user=""
# Skip any comment where .user == "$bot_user"
# Skip any review where .author == "$bot_user"
If bot_user cannot be determined (API failure), proceed without filtering but log a warning:
Warning: Could not determine authenticated GitHub user — bot comment filtering disabled
For each actionable comment (after bot filtering), create a structured feedback entry in plan.yaml:
# Encode review metadata in description since the schema's additionalProperties: false
# prohibits extra top-level fields. Use source field for deduplication.
# Note: use null (no quotes) for empty affected_scope to produce YAML null, not string "null"
affected_scope_value=$([ -n "$comment_path" ] && echo "\"$comment_path\"" || echo "null")
milestone_id=$(yq eval ".epic.milestones[] | select(.issues[] | .pr == \"$pr_url\") | .id" .ratchet/plan.yaml | head -1)
milestone_id_value=$([ -n "$milestone_id" ] && echo "$milestone_id" || echo "null")
yq eval -i ".epic.discoveries += [{
\"ref\": \"feedback-review-${pr_num}-$(date +%s)\",
\"title\": \"Review feedback on PR #${pr_num}: $(echo "$comment_body" | head -c 60)\",
\"description\": \"Author: $comment_author | File: ${comment_path:-top-level} | Category: $category\n\n$comment_body\",
\"category\": \"tech-debt\",
\"severity\": \"major\",
\"source\": \"pr-review-${pr_num}-${comment_id}\",
\"status\": \"pending\",
\"retro_type\": \"review-feedback\",
\"issue_ref\": \"$issue_ref\",
\"context\": {
\"milestone\": $milestone_id_value,
\"issue\": \"$issue_ref\",
\"debate\": null
},
\"pairs\": [],
\"affected_scope\": $affected_scope_value,
\"created_at\": \"$(date -Iseconds)\"
}]" .ratchet/plan.yaml
Review metadata (author, file path, comment category) encoded in description and source fields since discovery schema (additionalProperties: false) prohibits extra top-level fields. Use source: "pr-review-<pr_num>-<comment_id>" for deduplication — comment ID uniquely identifies each feedback entry.
Update watch state — append all newly processed IDs (both actionable and informational) to .ratchet/watch-state.json:
jq --argjson new_comments "$new_comment_ids_json" \
--argjson new_reviews "$new_review_ids_json" \
'.seen_comment_ids += $new_comments | .seen_review_ids += $new_reviews' \
.ratchet/watch-state.json > .ratchet/watch-state.json.tmp \
&& mv .ratchet/watch-state.json.tmp .ratchet/watch-state.json
Auto-respond behavior (when --no-respond is NOT set and github-issues adapter is configured): for each actionable feedback entry, watcher notes the feedback for the response agent (see m4-response-agent). Response agent is a separate concern — this issue only detects and classifies review comments. When --no-respond IS set: feedback entries are created in plan.yaml but no response agent is spawned. Feedback surfaces in /ratchet:status for manual handling.
All clear: No output (silent when nothing is wrong — no conflicts, no CI failures, no new review comments).
/ratchet:watch --stop — cancel the loop. No cleanup needed beyond stopping the loop itself.
Watcher maintains .ratchet/watch-state.json to track which review comments and reviews have been processed. Structure:
{
"seen_comment_ids": [12345, 12346],
"seen_review_ids": [67890]
}
File created automatically on first run if it does not exist. Persists across poll cycles within a session and across sessions (since it lives on disk). To force re-processing of all comments (e.g., after a reset), delete this file:
rm .ratchet/watch-state.json
Error handling: If .ratchet/watch-state.json exists but contains invalid JSON, watcher recreates it with empty arrays and logs a warning:
Warning: .ratchet/watch-state.json was malformed — reset to empty state
gh auth: uses GitHub CLI for PR status and review comment fetchinggh user (typically Ratchet bot) excluded to avoid self-referential feedback loops