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
2 changes: 1 addition & 1 deletion plugins/source-control/.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": "source-control",
"version": "0.16.1",
"version": "0.16.2",
"description": "Git and GitHub delivery workflow: /commit (Conventional Commits + Co-Authored-By trailer via safe heredoc mechanics), /pull-request (prep, create, CI monitoring, review-comment triage, merge, CI-log fetch), /babysit-prs (self-pacing fleet loop — safe by default; opt-in worker/autopilot tiers add gate-checked merge and thread resolution behind a deterministic Python engine), /worktree (create, status, cleanup, audit for parallel-session isolation), /setup (check the effective commit-subject / PR-title convention merged across its config layers and the babysit-prs config, or apply — interview the repo and write the convention config to a chosen layer), and /resolve-conflicts (intent-first merge/rebase conflict resolution with a semantic-conflict sweep — never --abort). The commit-subject / PR-title convention is configurable via a source-control.md config written by a re-runnable setup skill, layered across a ~/.claude user-global file, the tracked team file, and a gitignored .claude/source-control.local.md personal overlay merged per key; Conventional Commits is the default when no convention is declared.",
"author": {
"name": "Melodic Software",
Expand Down
34 changes: 34 additions & 0 deletions plugins/source-control/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,40 @@
All notable changes to the `source-control` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.16.2]

### Fixed

- **Babysit worker-worktree head-safety + merge-only freshness (`#548`).** A babysit worker can be
assigned a worktree in detached HEAD (its PR branch locked in a sibling/foreign worktree) or on a
stale local branch tip behind `origin`; the checkout/freshness mechanics then merged and pushed
from that tip, so a stale-tip integration could silently revert the newest branch commit — a
near-miss where safety depended on the assigned `HEAD` happening to match, not a guard.
- `reference/safety.md` Checkout And Push Invariants now require asserting the assigned worktree's
`HEAD` equals the true PR head (`gh pr view --json headRefOid`) before any merge/edit/push (stop
on a stale/detached mismatch) and pushing via an explicit refspec (`git push "$PUSH_REMOTE"
HEAD:<headRefName>`) to a **fail-closed** destination — `origin` for a same-repo head; for a
write-allowed cross-repo head, the fork destination validated by **host + owner/repo** identity,
not by remote name: canonicalize the URL `git push` will actually use (`git remote get-url
--push`, which honors a `pushurl` that can differ from the fetch URL) and require it to equal the
head repo's own URL (`gh api repos/<nameWithOwner> --jq .html_url`), else read-only — fast-forward
by construction, never `--force` — so a branch locked by a sibling worktree is not a `git
checkout` dead-end.
- The worker mechanics are reconciled to that contract: `reference/loop.md` §5.1.2 acquires the head
via `gh pr checkout` and asserts `HEAD == the live headRefOid` in every checkout path (already-at-
head, sibling-locked `--detach` reuse, and heal-via-checkout), degrading to read-only on mismatch;
`SKILL.md` Step 0.2 + cross-tier invariants and `reference/orchestration.md`'s conflict-worker
follow the same assertion + upstream refspec push.
- **Freshness is now merge-only.** The prior `loop.md` path rebased-and-`--force-with-lease`d
linear-history branches, which both violated the skill's own never-force-push invariant
(`safety.md` "Never Do Automatically", `orchestration.md`) and was the silent-revert vector.
Behind-default branches now always integrate via `git merge` + a fast-forward refspec push (the
final squash merge still flattens interim history). **Behavior change:** linear-history branches
now carry an interim merge commit during freshness instead of being rebased.

Enforcement remains agent discipline; whether the head assertion belongs in a deterministic helper
is tracked in `#885`.

## [0.16.1]

### Fixed
Expand Down
14 changes: 7 additions & 7 deletions plugins/source-control/skills/babysit-prs/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ the safe tier the table's `no — report` still governs: report the blocker exac
and do not spawn the conflict worker. Only `worker` and `autopilot` read that same string as
license to act.

**Cross-tier invariants** — hold in every tier including autopilot: never an unprotected
force-push (a rebase integration pushes only `--force-with-lease` per
[reference/loop.md](reference/loop.md)); never `--admin`; never delete a branch or worktree
**Cross-tier invariants** — hold in every tier including autopilot: never a force-push (freshness is
merge-only, refspec-pushed fast-forward — [reference/loop.md](reference/loop.md)); never `--admin`;
never delete a branch or worktree
that is dirty or unmerged; never change GitHub settings, secrets, branch protection, or
billing; never act on a repository outside the watched owners; never resolve a thread whose
finding is not actually addressed. A merge always requires the deterministic gate — autopilot
Expand Down Expand Up @@ -337,10 +337,10 @@ Execute for EACH PR discovered, oldest first. Detailed mechanics:
`${CLAUDE_PLUGIN_ROOT}/scripts/fetch-all-pr-comments.sh` (derives owner/repo from the current directory; from a cwd that is not a checkout of the target repo, export `FETCH_COMMENTS_OWNER`/`FETCH_COMMENTS_REPO` first — also unblocks the readiness gate's exit 4), filter own prior replies, classify
addressed/unaddressed from GitHub evidence (§5.0.3). GitHub is the source of truth, not model
memory
- [ ] **Step 0.2 — Branch checkout:** `gh pr checkout <N>` with worktree/dirty-tree pre-checks;
read-only mode when the branch is owned elsewhere (§5.1.2)
- [ ] **Step 0.2 — Branch checkout:** put this worktree's HEAD at the true PR head (`gh pr view --json headRefOid`) — `gh pr checkout <N>`, or `--detach` when the branch is locked in a sibling worktree (never `git checkout` the locked branch);
assert HEAD == that head before any mutate, read-only on mismatch or dirty tree (§5.1.2)
- [ ] **Step 0.3 — Branch freshness:** fetch + `git merge-base --is-ancestor`; integrate
(merge vs rebase per the branch's own history), graduated conflict handling (§5.1.2)
merge-only (never rebase — rebasing a PR branch needs a forbidden force-push), graduated conflict handling (§5.1.2)
- [ ] **Step 1 — Event-delivery gate:** cloud poll / push channel / Monitor watch, re-armed
per PR (§5.1.1)
- [ ] **Steps A–F — Per-PR iteration checklist** (§5.1.3): terminal check, CI classification,
Expand All @@ -350,7 +350,7 @@ Execute for EACH PR discovered, oldest first. Detailed mechanics:
the configured extra self identities are `${user_config.babysit_self_logins}` — when that value
is non-empty and not a literal unexpanded token, append `--extra-self "<value>"`),
report
- [ ] **Step 5 — Commit + push** fixes on the PR branch; clean working tree; follow-up replies
- [ ] **Step 5 — Commit + push** fixes to the PR branch (refspec; works from a detached HEAD); clean working tree; follow-up replies
cite commit SHAs
- [ ] **Step 6 — PR transition:** next-oldest PR needing attention (§5.1.6)
- [ ] **Step 7 — Self-pace:** schedule the next wake per the cadence contract (§5.3)
Expand Down
12 changes: 6 additions & 6 deletions plugins/source-control/skills/babysit-prs/evals/evals.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,14 @@
},
{
"id": 6,
"name": "babysit-preserves-merge-workflow",
"prompt": "/source-control:babysit-prs — PR #482's checked-out branch is behind main and `git log --merges origin/main..HEAD` returns a merge commit.",
"expected_output": "The skill integrates `origin/main` with `git merge`, preserving the branch's merge workflow, and uses a plain `git push`. It does not rebase or force-push the merge-commit branch.",
"name": "babysit-freshness-is-merge-only",
"prompt": "/source-control:babysit-prs — PR #482's checked-out branch is behind the default branch and needs freshening before CI.",
"expected_output": "The skill integrates the default branch with `git merge` (never a rebase) and pushes the merge commit by refspec to the branch's configured upstream (`git push \"$PUSH_REMOTE\" HEAD:<headRefName>`, where PUSH_REMOTE is `origin` for a same-repo head or the fork's remote for a write-allowed cross-repo head). It never rebases or force-pushes a PR branch as freshness maintenance.",
"files": [],
"expectations": [
"It checks for merge commits before choosing an integration mode",
"It uses git merge origin/main for a merge-commit branch",
"It uses a plain push, not a force-with-lease push, after the merge"
"It integrates the default branch with git merge, never a rebase",
"It pushes by refspec to the branch's configured upstream, not a hardcoded origin and not a plain push that assumes a tracking branch",
"It never force-pushes or force-with-lease-pushes a PR branch as freshness maintenance"
]
},
{
Expand Down
Loading
Loading