diff --git a/.claude/commands/pr-loop-self.md b/.claude/commands/pr-loop-self.md index 6d2d5d9..cf92ec7 100644 --- a/.claude/commands/pr-loop-self.md +++ b/.claude/commands/pr-loop-self.md @@ -21,7 +21,7 @@ Prompt to use (the tick logic, with adaptive STEP 0): > > Then, in order: > 1. POLL: run `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/notify-poll.sh`; summarize new issues / PR comments / reviews and the open-PR status section. -> 2. MERGE: run `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/merge-ready.sh`; report each PR merged or why skipped. (It only merges PRs the owner APPROVED that are CI-green & mergeable; never approves.) +> 2. MERGE: run `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/merge-ready.sh`; report each PR merged or why skipped. (It only merges PRs the owner APPROVED that are CI-green & mergeable; never approves.) After merging, it fast-forwards the LOCAL checkout to `main` when it is clean and already on `main` — so your terminal/IDE shows the latest without a manual pull. It never switches branches or clobbers uncommitted work; anything else is skipped (`local_sync` line reports the reason). > 3. ADDRESS FEEDBACK: run `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/pr-feedback.sh`; for each PR it lists (bot-authored, with unaddressed CHANGES_REQUESTED), run orchestrator→worktree implementer→reviewer-lenses (self adapter: `GATES_FILE=.claude/self/gates.json`, lenses `correctness`/`tests`, consensus `all`) on the SAME branch, push to update the PR in place, and post the `` marker comment via bot-gh.sh. Do NOT merge here. > 4. ADVANCE: ONLY when there are ZERO open PRs — pick the lowest-numbered open self `module:*` issue (`module:docs`, `module:harness`, `module:examples`, `module:ci`) with no feat/issue--* branch; drive it through the orchestrator using `.claude/self/gates.json` as the adapter (scope → worktree implementer → `GATES_FILE=.claude/self/gates.json gate.sh` gates → reviewer lenses `correctness`/`tests` consensus `all` → bot PR). One issue in flight at a time. > 5. If nothing actionable, reply exactly one line: "No actionable activity." diff --git a/.claude/commands/pr-loop.md b/.claude/commands/pr-loop.md index 2527bf6..5bff835 100644 --- a/.claude/commands/pr-loop.md +++ b/.claude/commands/pr-loop.md @@ -18,7 +18,7 @@ Prompt to use (the tick logic, with adaptive STEP 0): > > Then, in order: > 1. POLL: run `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/notify-poll.sh`; summarize new issues / PR comments / reviews and the open-PR status section. -> 2. MERGE: run `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/merge-ready.sh`; report each PR merged or why skipped. (It only merges PRs the owner APPROVED that are CI-green & mergeable; never approves.) +> 2. MERGE: run `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/merge-ready.sh`; report each PR merged or why skipped. (It only merges PRs the owner APPROVED that are CI-green & mergeable; never approves.) After merging, it fast-forwards the LOCAL checkout to `main` when it is clean and already on `main` — so your terminal/IDE shows the latest without a manual pull. It never switches branches or clobbers uncommitted work; anything else is skipped (`local_sync` line reports the reason). > 3. ADDRESS FEEDBACK: run `bash ${CLAUDE_PLUGIN_ROOT:-.claude}/scripts/pr-feedback.sh`; for each PR it lists (bot-authored, with unaddressed CHANGES_REQUESTED), run orchestrator→worktree implementer→reviewer-lenses on the SAME branch, push to update the PR in place, and post the `` marker comment via bot-gh.sh. Do NOT merge here. > 4. ADVANCE: ONLY when there are ZERO open PRs — pick the lowest-numbered open module:* issue with no feat/issue--* branch; drive it through the orchestrator (scope → worktree implementer → gate.sh gates → reviewer lenses → bot PR). One issue in flight at a time. > 5. If nothing actionable, reply exactly one line: "No actionable activity." diff --git a/.claude/scripts/merge-ready.sh b/.claude/scripts/merge-ready.sh index 752f5df..9af9fda 100644 --- a/.claude/scripts/merge-ready.sh +++ b/.claude/scripts/merge-ready.sh @@ -86,4 +86,27 @@ for n in $(gh pr list -R "$repo" --base "$base" --state open --json number -q '. echo "{\"pr\":$n,\"action\":\"skip\",\"reason\":\"${verdict#SKIP:}\",\"title\":\"$title\"}"; skipped=$((skipped+1)) fi done + +# Post-merge: fast-forward the LOCAL checkout to the freshly-merged base so the +# owner's terminal/IDE shows the latest code without a manual pull. Strictly safe: +# acts ONLY when the checkout is on the base branch with a clean tree, and only +# fast-forwards (never a merge commit, never a branch switch, never clobbers +# uncommitted work). Untracked sandbox device-node masks don't count as changes. +# Any obstacle -> skip with a reason; never force. See docs/HARDENING.md. +if [ "$merged" -gt 0 ]; then + wt="$(git rev-parse --show-toplevel 2>/dev/null || true)" + cur="$(git -C "${wt:-.}" symbolic-ref --quiet --short HEAD 2>/dev/null || echo DETACHED)" + if [ -z "$wt" ]; then + : + elif [ "$cur" != "$base" ]; then + echo "{\"local_sync\":\"skip\",\"reason\":\"checkout on '$cur', not '$base'\"}" + elif ! git -C "$wt" diff --quiet || ! git -C "$wt" diff --cached --quiet; then + echo "{\"local_sync\":\"skip\",\"reason\":\"working tree has tracked changes\"}" + elif git -C "$wt" fetch --quiet origin "$base" 2>/dev/null \ + && git -C "$wt" merge --ff-only -q "origin/$base" 2>/dev/null; then + echo "{\"local_sync\":\"ok\",\"branch\":\"$base\",\"head\":\"$(git -C "$wt" rev-parse --short HEAD)\"}" + else + echo "{\"local_sync\":\"skip\",\"reason\":\"fetch or fast-forward failed (diverged/offline?)\"}" + fi +fi echo "=== merge-ready: merged=$merged skipped=$skipped ==="