Skip to content
Merged
2 changes: 1 addition & 1 deletion plugins/playbooks/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "playbooks",
"version": "0.14.4",
"version": "0.14.5",
"description": "Doctrine and knowledge playbooks as on-demand skills, repo-sweep for running a catalog of hygiene skills through a repository one commit per step, plus a maintainer-facing update skill. boris carries Boris Cherny's Claude Code workflow tips (howborisusesclaudecode.com), skill-authoring carries Anthropic's internal skill-authoring playbook, and fable-5 carries Claude Fable 5's operating doctrine (self-authored, no upstream). The boris and skill-authoring packs vendor a verbatim upstream baseline; /playbooks:update drift-checks and syncs those baselines centrally (maintainers).",
"author": {
"name": "Melodic Software",
Expand Down
9 changes: 9 additions & 0 deletions plugins/playbooks/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ All notable changes to the `playbooks` plugin are recorded here. The `version` i
`.claude-plugin/plugin.json` is the delivery vehicle. A consumer receives a change
only after that version increases.

## [0.14.5] - 2026-09-29

### Fixed

- **`repo-sweep next` stops when the sweep PR conflicts with its base** ([#5206](https://github.com/melodic-software/claude-code-plugins/issues/5206)).
GitHub runs no `pull_request` workflows on a conflicting PR, so step commits pushed to it got no
CI. `state.sh` now prints `mergeable CONFLICTING`, and `next` stops before any step and asks the
user to merge the base branch into the sweep branch and push.

## [0.14.4] - 2026-09-29

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion plugins/playbooks/skills/repo-sweep/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ error everywhere; codes 10 and up carry the meanings below; any other non-zero c
| `history.sh <catalog>` | TSV id, recommendation (`run`, `rerun`, `rerun-optional`), reason, from merged sweep PRs then `Playbook-Step` trailers | 1 catalog error |
| `render.sh --checklist <catalog> <selection-line> [<recs-tsv>]` | The PR checklist block plus `Not run:` | 1 bad id, selection, or TSV |
| `render.sh --page <catalog> <recs-tsv>` | The filled selection page on stdout | 1 as above, or template missing |
| `state.sh` | `key value` lines: `pr`, `branch`, `pr-state`, `playbook`, `dirty`, `untick-committed <id> <sha> <skill@version>...`, `done-unverified <id>`, `next <id> in-progress\|pending`, `sweep <n> <branch>` | 0 next step found; 1 no markers; 10 no sweep PR; 11 PR merged or closed; 12 dirty tree, step pending; 13 all done; 14 one open sweep on another branch; 15 several open sweeps |
| `state.sh` | `key value` lines: `pr`, `branch`, `pr-state`, `mergeable CONFLICTING`, `playbook`, `dirty`, `untick-committed <id> <sha> <skill@version>...`, `done-unverified <id>`, `next <id> in-progress\|pending`, `sweep <n> <branch>` | 0 next step found; 1 no markers; 10 no sweep PR; 11 PR merged or closed; 12 dirty tree, step pending; 13 all done; 14 one open sweep on another branch; 15 several open sweeps |
| `tick.sh <id> in-progress` / `[--partial <detail>] committed <sha> <skill@version>...` / `no-findings <skill@version>...` / `partial <detail> <skill@version>...` / `not-applicable <evidence> <skill@version>...` / `[--partial <detail>] report-only <n> <skill@version>...` / `[--partial <detail>] declined <n> <skill@version>...` | Sets that checklist line, re-reads the body to confirm | 1 line missing, already done, or edit did not land |
| `guard.sh <base-sha> <pr-snapshot-file>` | Checks a step stayed on the branch and opened no PR | 10 stop (prints `branch-changed`, `base-not-ancestor`, `new-pr` lines); 11 prints `squash git reset --soft <base-sha>` |

Expand Down
12 changes: 12 additions & 0 deletions plugins/playbooks/skills/repo-sweep/evals/evals.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@
"Invokes /plugin-quality:audit for the skill defect",
"Files nothing with gh issue create until the user approves each draft"
]
},
{
"id": 7,
"name": "next-stops-on-conflicting-pr",
"prompt": "/playbooks:repo-sweep next (state.sh exits 0 with a pending step and prints mergeable CONFLICTING)",
"expected_output": "The skill stops before running any step, says GitHub runs no pull_request workflows on a conflicting PR so pushed commits get no CI, and asks the user to merge the base branch into the sweep branch and push, then rerun next.",
"files": [],
"expectations": [
"Stops on the mergeable CONFLICTING line without running the pending step",
"Names merging the base branch into the sweep branch as the fix",
"Does not stop when mergeable is UNKNOWN"
]
}
]
}
9 changes: 8 additions & 1 deletion plugins/playbooks/skills/repo-sweep/reference/next.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@ never combined with `&&` or other commands in one call. The record for these sha
1. On a `chore/repo-sweep-*` branch, bring it current first: `git fetch origin '<branch>'`, then
`git merge --ff-only 'origin/<branch>'`. A step committed on another machine is only reconciled
when its commit is local.
2. Run `S/state.sh` and act on its exit code:
2. Run `S/state.sh`. If it printed `mergeable CONFLICTING` (any exit code), stop before the exit
code handling below and before any step. GitHub runs no `pull_request` workflows on a
conflicting PR, so pushed step commits get no CI. Ask the user to merge the base branch into
the sweep branch and push, then rerun `next`. `UNKNOWN` never stops. Basis: the `pull_request`
section of
<https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows>
says workflows do not run on pull_request activity while the pull request has a merge conflict.
As of: 2026-09-29. Recheck: that section changes. Otherwise act on its exit code:
- 10: no sweep PR. Point to `/playbooks:repo-sweep plan`. Stop.
- 11: the sweep PR is merged or closed. Refuse to continue and point to `plan`. Stop.
- 12: the tree is dirty and no step is in progress. Show `git status --short`, ask the user
Expand Down
4 changes: 3 additions & 1 deletion plugins/playbooks/skills/repo-sweep/scripts/state.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# pr <number>
# branch <head-branch>
# pr-state OPEN|MERGED|CLOSED
# mergeable CONFLICTING a warning: printed only when the open PR conflicts with its base
# playbook <name>
# dirty yes|no any change, tracked or untracked, outside .work/
# untick-committed <id> <sha> <skill@version>...
Expand All @@ -35,7 +36,7 @@
set -euo pipefail

prefix=chore/repo-sweep-
fields=number,state,headRefName,baseRefName,body,isCrossRepository
fields=number,state,mergeable,headRefName,baseRefName,body,isCrossRepository
# Branch names, playbook names, and step ids reach commands the agent builds, so all three are
# restricted to safe characters, and fork PRs never count as a sweep.
safe_branch='^chore/repo-sweep-[A-Za-z0-9._-]+$'
Expand Down Expand Up @@ -71,6 +72,7 @@ fi

jq -r '"pr \(.number)\nbranch \(.headRefName)\npr-state \(.state)"' "$tmp/pr"
[[ $(jq -r .state "$tmp/pr") == OPEN ]] || exit 11
jq -r 'select(.mergeable == "CONFLICTING") | "mergeable CONFLICTING"' "$tmp/pr"
jq -r .body "$tmp/pr" | tr -d '\r' | awk '
/^<!-- repo-sweep:end -->/ { if (inb) exit; next }
inb { print; next }
Expand Down
23 changes: 20 additions & 3 deletions plugins/playbooks/skills/repo-sweep/scripts/state.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ body() { # <eol> <checklist lines...>
printf "%s$eol\n" "$@"
printf '<!-- repo-sweep:end -->%s\n\nNot run:%s\n- tidy: not selected%s\n' "$eol" "$eol" "$eol"
}
pr() { # <state> <body> [<number>] [<branch>]
jq -n --arg s "$1" --arg b "$2" --argjson n "${3:-7}" --arg h "${4:-$sweep}" \
'{number: $n, state: $s, headRefName: $h, baseRefName: "main", body: $b}'
pr() { # <state> <body> [<number>] [<branch>] [<mergeable>]
jq -n --arg s "$1" --arg b "$2" --argjson n "${3:-7}" --arg h "${4:-$sweep}" --arg m "${5:-}" \
'{number: $n, state: $s, headRefName: $h, baseRefName: "main", body: $b} + (if $m == "" then {} else {mergeable: $m} end)'
}
serve() { jq -s . >"$TMP/gh/$1.json"; }
run() { # sets out, rc
Expand Down Expand Up @@ -132,6 +132,23 @@ pr MERGED "$(body '' '- [ ] a: p:a')" | serve head
run
assert_eq "merged PR: exit 11" "11 pr-state MERGED" "$rc $(grep '^pr-state' <<<"$out")"

pr OPEN "$(body '' '- [ ] a: p:a')" 7 "$sweep" CONFLICTING | serve head
run
assert_eq "CONFLICTING open PR: warning line, normal exit" "0 mergeable CONFLICTING" "$rc $(grep '^mergeable' <<<"$out")"
assert_eq "CONFLICTING: line sits between pr-state and playbook" "pr-state OPEN
mergeable CONFLICTING
playbook fixture" "$(sed -n 3,5p <<<"$out")"

for m in MERGEABLE UNKNOWN; do
pr OPEN "$(body '' '- [ ] a: p:a')" 7 "$sweep" "$m" | serve head
run
assert_eq "$m open PR: no mergeable line" "0 " "$rc $(grep '^mergeable' <<<"$out")"
done

pr MERGED "$(body '' '- [ ] a: p:a')" 7 "$sweep" CONFLICTING | serve head
run
assert_eq "CONFLICTING but merged PR: exit 11, no mergeable line" "11 " "$rc $(grep '^mergeable' <<<"$out")"

serve head </dev/null
run
assert_eq "no PR for the sweep branch: exit 10" "10" "$rc"
Expand Down
Loading