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
11 changes: 6 additions & 5 deletions .claude/commands/pr-loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,19 @@ Prompt to use (the tick logic, with adaptive STEP 0):

> Run one tick of the autonomous PR loop. ALL `gh` interaction (yours and every agent's) MUST run as the bot via `.claude/scripts/bot-gh.sh` — never bare `gh`; only `git` commits/pushes stay as the owner.
>
> STEP 0 — run the tick. Invoke, as a REAL bash tool call, exactly: `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/loop-tick.sh`. This one script runs census, then notify-poll.sh, merge-ready.sh, pr-feedback.sh, pr-comment-fix.sh, and pr-ci-fix.sh, IN ORDER, with their full output, and ends with exactly one machine-readable verdict line as the LAST line of output: `action=none`, `action=advance issue=N`, `action=feedback pr=N`, `action=comment-fix pr=N`, or `action=ci-fix pr=N`. That verdict line is the SOLE source of truth for what to do next: never hand-count open PRs, planned issues, feedback PRs, comment-fix PRs, or CI-red PRs yourself, and never skip this invocation because the tick "looks quiet" — it must run, and its output must be read, on every single tick with no exceptions.
> STEP 0 — run the tick. Invoke, as a REAL bash tool call, exactly: `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/loop-tick.sh`. This one script runs census, then notify-poll.sh, merge-ready.sh, pr-feedback.sh, pr-comment-fix.sh, pr-ci-fix.sh, and pr-rebase.sh, IN ORDER, with their full output, and ends with exactly one machine-readable verdict line as the LAST line of output: `action=none`, `action=advance issue=N`, `action=feedback pr=N`, `action=comment-fix pr=N`, `action=ci-fix pr=N`, or `action=rebase pr=N`. That verdict line is the SOLE source of truth for what to do next: never hand-count open PRs, planned issues, feedback PRs, comment-fix PRs, CI-red PRs, or unmergeable PRs yourself, and never skip this invocation because the tick "looks quiet" — it must run, and its output must be read, on every single tick with no exceptions.
>
> CADENCE: the script's `=== 1/6 loop-census.sh ===` section includes a line `cadence=FAST|WATCH|IDLE cron=<expr>` — this is the desired cadence; consume it as-is, do NOT re-derive it from counts. If this cron job's current schedule differs from that `cron=<expr>`, CronDelete this job and CronCreate a durable replacement with this SAME prompt at the desired schedule.
> CADENCE: the script's `=== 1/7 loop-census.sh ===` section includes a line `cadence=FAST|WATCH|IDLE cron=<expr>` — this is the desired cadence; consume it as-is, do NOT re-derive it from counts. If this cron job's current schedule differs from that `cron=<expr>`, CronDelete this job and CronCreate a durable replacement with this SAME prompt at the desired schedule.
>
> Then obey the verdict line (the tick already ran poll/merge/feedback-detection/comment-fix-detection/ci-fix-detection above — do not re-run those scripts). Precedence when more than one is ready: `feedback` > `comment-fix` > `ci-fix` > `advance` (a PR with unaddressed feedback outranks everything; a PR with an unresolved qualifying review-comment thread outranks a CI fix or a fresh advance):
> Then obey the verdict line (the tick already ran poll/merge/feedback-detection/comment-fix-detection/ci-fix-detection/rebase-detection above — do not re-run those scripts). Precedence when more than one is ready: `feedback` > `comment-fix` > `ci-fix` > `rebase` > `advance` (a PR with unaddressed feedback outranks everything; a PR with an unresolved qualifying review-comment thread outranks a CI fix, a rebase, or a fresh advance; a CI fix outranks a rebase or a fresh advance; an unmergeable PR needing a rebase outranks only a fresh advance):
> - `action=feedback pr=N` → address PR N's feedback: run orchestrator → worktree implementer → reviewer lenses (per .claude/gates.json) on the SAME branch, push to update the PR in place, and post the `<!-- claude-addressed -->` marker comment via bot-gh.sh. Do NOT merge.
> - `action=comment-fix pr=N` → address PR N's unresolved review-comment thread(s): label the PR `claude-comment-fixing` via bot-gh.sh first (in-flight guard), then run orchestrator → worktree implementer on the SAME branch (checkout the PR's existing branch, do NOT create a new one) → reviewer lenses (per .claude/gates.json), push to update the PR in place. For each thread you actually addressed, resolve it on GitHub and post a bot comment containing `<!-- claude-comment-addressed:<thread-id>:<attempt> -->` for each (the real thread id and attempt number from the `5/6 pr-comment-fix.sh` section above) so pr-comment-fix.sh's cursor recognizes it as addressed. Do NOT merge, and do NOT force-push.
> - `action=comment-fix pr=N` → address PR N's unresolved review-comment thread(s): label the PR `claude-comment-fixing` via bot-gh.sh first (in-flight guard), then run orchestrator → worktree implementer on the SAME branch (checkout the PR's existing branch, do NOT create a new one) → reviewer lenses (per .claude/gates.json), push to update the PR in place. For each thread you actually addressed, resolve it on GitHub and post a bot comment containing `<!-- claude-comment-addressed:<thread-id>:<attempt> -->` for each (the real thread id and attempt number from the `5/7 pr-comment-fix.sh` section above) so pr-comment-fix.sh's cursor recognizes it as addressed. Do NOT merge, and do NOT force-push.
> - `action=ci-fix pr=N` → fix PR N's failing CI: label the PR `claude-ci-fixing` via bot-gh.sh first (in-flight guard), then run orchestrator → worktree implementer on the SAME branch (checkout the PR's existing branch, do NOT create a new one) → reviewer lenses (per .claude/gates.json) to fix the failure, push to update the PR in place. After pushing, query the PR's current head SHA (`bot-gh.sh pr view N --json headRefOid`) and post a bot comment containing exactly `<!-- claude-ci-addressed:<head-sha> -->` (the real SHA substituted in) so pr-ci-fix.sh's cursor recognizes this head as already addressed. Do NOT merge, and do NOT force-push.
> - `action=rebase pr=N` → rebase PR N onto base: label the PR `claude-rebasing` via bot-gh.sh first (in-flight guard), checkout the PR's EXISTING branch/worktree (do NOT create a new one), `git fetch` then `git rebase origin/<baseBranch>`. On a CLEAN rebase: `git push --force-with-lease`, rerun the adapter's gates, and post a bot comment containing `<!-- claude-rebase-attempted:<base-sha>:<attempt> -->` (the real base SHA and attempt number from the `7/7 pr-rebase.sh` section above) so pr-rebase.sh's cursor recognizes this attempt. On a CONFLICT: `git rebase --abort` (never leave the worktree mid-rebase), label the PR `needs-human`, and post a comment explaining the conflict (also containing the same marker). NEVER merge, and NEVER force-push anything but this bot-owned branch.
> - `action=advance issue=N` → advance issue N through the orchestrator (scope → worktree implementer → gate.sh gates → reviewer lenses → bot PR). One issue in flight at a time. `backlog` issues are owner-unapproved: never pick them, and if you file an issue yourself, label it `backlog` — NEVER `planned` (that label is the owner's formal approval and is assigned by the owner alone; see docs/USAGE.md → "Autonomous loop & the issue queue").
> - `action=none` → reply exactly one line: "No actionable activity." This is the ONLY path to that phrase — never reply it without loop-tick.sh having actually been invoked (and its output read) earlier in this same turn.
>
> Token discipline: only read docs/USAGE.md and .claude/agents/* when the verdict actually requires orchestrating agents (advance/feedback/ci-fix); an `action=none` tick needs only the script's own output. Keep the tick report to a few lines — it is telemetry, not documentation.
> Token discipline: only read docs/USAGE.md and .claude/agents/* when the verdict actually requires orchestrating agents (advance/feedback/ci-fix/rebase); an `action=none` tick needs only the script's own output. Keep the tick report to a few lines — it is telemetry, not documentation.

## 2. Run one tick now
Execute the tick logic above immediately so the loop doesn't wait for the next cron fire. Report what happened (polled items, merges, feedback addressed, issue advanced — or "No actionable activity").
Expand Down
12 changes: 10 additions & 2 deletions .claude/scripts/loop-ceilings.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@ check() {
# issue #96 -- defaults to empty, i.e. no ci-fix candidates, so every existing
# 3/4-arg call site keeps working unchanged). $6=fake_commentfix (TSV body,
# issue #96 part 2 -- defaults to empty, i.e. no comment-fix candidates, so
# every existing 3/4/5-arg call site keeps working unchanged).
# every existing 3/4/5-arg call site keeps working unchanged). $7=fake_rebase
# (TSV body, issue #96 part 3 -- defaults to empty, i.e. no rebase candidates,
# so every existing 3/4/5/6-arg call site keeps working unchanged).
new_fixture() {
local name="$1" fake_census="$2" fake_feedback="$3" with_gh="${4:-0}" fake_cifix="${5:-}" fake_commentfix="${6:-}"
local name="$1" fake_census="$2" fake_feedback="$3" with_gh="${4:-0}" fake_cifix="${5:-}" fake_commentfix="${6:-}" fake_rebase="${7:-}"
local dir="$work/$name/.claude/scripts"
mkdir -p "$dir" "$work/$name/.claude/state" 2>/dev/null
rm -rf "$work/$name/.claude/state" # loop-tick.sh must mkdir -p it itself
Expand Down Expand Up @@ -89,6 +91,12 @@ EOF
cat <<'COMMENTFIX'
$fake_commentfix
COMMENTFIX
EOF
cat > "$dir/pr-rebase.sh" <<EOF
#!/usr/bin/env bash
cat <<'REBASE'
$fake_rebase
REBASE
EOF
chmod +x "$dir"/*.sh

Expand Down
10 changes: 9 additions & 1 deletion .claude/scripts/loop-census.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
# already a feedback candidate, not already
# addressed for its current state
# (pr-comment-fix.sh, issue #96 part 2)
# rebase_prs=N bot PRs that went unmergeable (mergeable=
# CONFLICTING) against base, not already a
# feedback/comment-fix/ci-fix candidate, not
# already rebased for the current base commit
# (pr-rebase.sh, issue #96 part 3)
# planned_issues=N open issues labelled `planned` AND one of the
# adapter's module:* labels, one detail line each:
# issue=<n> branch=<feat/issue-n-* or none> title=<title>
Expand Down Expand Up @@ -318,6 +323,9 @@ echo "ci_fix_prs=$ci_fix_prs"
comment_fix_prs=$(bash "$script_dir/pr-comment-fix.sh" "$repo" | grep -c . || true)
echo "comment_fix_prs=$comment_fix_prs"

rebase_prs=$(bash "$script_dir/pr-rebase.sh" "$repo" | grep -c . || true)
echo "rebase_prs=$rebase_prs"

# Open `planned` issues carrying any of the adapter's module labels, ascending.
planned=$(gh issue list -R "$repo" --state open --label planned --json number,title,labels \
--jq '.[] | [.number, ([.labels[].name]|join(",")), .title] | @tsv' | sort -n)
Expand Down Expand Up @@ -469,7 +477,7 @@ fi
echo "advance_ready=$advance_ready"

# Desired cadence per the loop policy: FAST only when the loop can ACT now.
if [ "$feedback_prs" -ge 1 ] || [ "$comment_fix_prs" -ge 1 ] || [ "$ci_fix_prs" -ge 1 ] || { [ "$open_prs" -eq 0 ] && [ "$planned_count" -ge 1 ]; }; then
if [ "$feedback_prs" -ge 1 ] || [ "$comment_fix_prs" -ge 1 ] || [ "$ci_fix_prs" -ge 1 ] || [ "$rebase_prs" -ge 1 ] || { [ "$open_prs" -eq 0 ] && [ "$planned_count" -ge 1 ]; }; then
echo 'cadence=FAST cron=* * * * *'
elif [ "$open_prs" -ge 1 ]; then
echo 'cadence=WATCH cron=*/5 * * * *'
Expand Down
32 changes: 31 additions & 1 deletion .claude/scripts/loop-census.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ cat > "$scripts_dir/pr-comment-fix.sh" <<'EOF'
#!/usr/bin/env bash
exit 0
EOF
cat > "$scripts_dir/pr-rebase.sh" <<'EOF'
#!/usr/bin/env bash
exit 0
EOF

# Fake bot-gh.sh: no network, no real `gh` — dispatches on the subcommand and
# a `--json` marker to canned, fixture-appropriate output.
Expand Down Expand Up @@ -209,6 +213,7 @@ check "planned_issues=4 counted" bash -c 'printf "%s\n" "$1" | grep -qx "planned
check "issue=42 branch line shows the origin-prefixed remote-tracking name" bash -c 'printf "%s\n" "$1" | grep -q "^issue=42 branch=origin/feat/issue-42-y"' _ "$out"
check "ci_fix_prs=0 counted (no-op pr-ci-fix.sh stub, issue #96)" bash -c 'printf "%s\n" "$1" | grep -qx "ci_fix_prs=0"' _ "$out"
check "comment_fix_prs=0 counted (no-op pr-comment-fix.sh stub, issue #96 part 2)" bash -c 'printf "%s\n" "$1" | grep -qx "comment_fix_prs=0"' _ "$out"
check "rebase_prs=0 counted (no-op pr-rebase.sh stub, issue #96 part 3)" bash -c 'printf "%s\n" "$1" | grep -qx "rebase_prs=0"' _ "$out"

# ---------------------------------------------------------------------------
# ci_fix_prs (issue #96): loop-census.sh must surface pr-ci-fix.sh's own
Expand Down Expand Up @@ -247,6 +252,19 @@ git -C "$fixture" -c [email protected] -c user.name=t commit -q -m "swap in comm
outCommentFix="$(env -u GATES_FILE bash "$scripts_dir/loop-census.sh" "acme/repo")"
check "comment_fix_prs=1 counted when pr-comment-fix.sh reports one candidate" bash -c 'printf "%s\n" "$1" | grep -qx "comment_fix_prs=1"' _ "$outCommentFix"

# ---------------------------------------------------------------------------
# rebase_prs (issue #96 part 3): same wrapping contract as ci_fix_prs/
# comment_fix_prs above, exercised against pr-rebase.sh instead.
# ---------------------------------------------------------------------------
cat > "$scripts_dir/pr-rebase.sh" <<'EOF'
#!/usr/bin/env bash
printf '30\tfeat/issue-30-a\tsha30\tbase30\t1\n'
EOF
git -C "$fixture" add .claude/scripts/pr-rebase.sh
git -C "$fixture" -c [email protected] -c user.name=t commit -q -m "swap in rebase_prs stub (test fixture)"
outRebase="$(env -u GATES_FILE bash "$scripts_dir/loop-census.sh" "acme/repo")"
check "rebase_prs=1 counted when pr-rebase.sh reports one candidate" bash -c 'printf "%s\n" "$1" | grep -qx "rebase_prs=1"' _ "$outRebase"

# ---------------------------------------------------------------------------
# driver_unit_active guard (issue #119 post-review finding #5): loop-census.sh
# must never report advance_ready for an issue whose transient driver unit
Expand Down Expand Up @@ -285,6 +303,10 @@ EOF
cat > "$scripts/pr-comment-fix.sh" <<'EOF'
#!/usr/bin/env bash
exit 0
EOF
cat > "$scripts/pr-rebase.sh" <<'EOF'
#!/usr/bin/env bash
exit 0
EOF
cat > "$scripts/bot-gh.sh" <<'EOF'
#!/usr/bin/env bash
Expand Down Expand Up @@ -380,7 +402,11 @@ EOF
#!/usr/bin/env bash
exit 0
EOF
chmod +x "$scripts/pr-feedback.sh" "$scripts/pr-ci-fix.sh" "$scripts/pr-comment-fix.sh" "$scripts/cockpit.sh" "$scripts/loop-census.sh"
cat > "$scripts/pr-rebase.sh" <<'EOF'
#!/usr/bin/env bash
exit 0
EOF
chmod +x "$scripts/pr-feedback.sh" "$scripts/pr-ci-fix.sh" "$scripts/pr-comment-fix.sh" "$scripts/pr-rebase.sh" "$scripts/cockpit.sh" "$scripts/loop-census.sh"
git -C "$dir" init -q -b main
git -C "$dir" -c [email protected] -c user.name=t commit -q --allow-empty -m init
}
Expand Down Expand Up @@ -628,6 +654,10 @@ cat > "$scriptsStall/pr-comment-fix.sh" <<'EOF'
#!/usr/bin/env bash
exit 0
EOF
cat > "$scriptsStall/pr-rebase.sh" <<'EOF'
#!/usr/bin/env bash
exit 0
EOF
cat > "$scriptsStall/bot-gh.sh" <<'EOF'
#!/usr/bin/env bash
case "$1" in
Expand Down
Loading
Loading