feat(bmad-github): worktree-aware story-dev, no commit-on-main prereq#12
Merged
choucrifahed merged 1 commit intoMay 17, 2026
Conversation
…#11) Make /story-dev compatible with orchestrators like Conductor that spawn each agent in a pre-created worktree. The blocker was Phase 2's "edit sprint-status.yaml → commit → push on main" prelude before worktree creation, which can't run from inside a worktree. Changes: - /story-dev: detect current context via branch name (main vs story/<key> worktree vs unrecognized). Pick the next story from GitHub status:ready labels (or branch name in the worktree case). Atomically flip the GitHub label to status:in-progress as the cross-agent lock. Skip worktree creation when already inside one. - /story-create: dependency check now queries GitHub labels (with sprint-status.yaml fallback if gh is unreachable). Worktree-tolerant. - /story-init: sprint-status.yaml made optional in pre-flight (the sync script already handles its absence). - /story-sync: writes preserved — primary writer on main for sprint-status.yaml, story files, GH labels, epic promotion. CI reframed as a redundant safety net rather than the canonical writer. - references/sync-first.md: on main, runs the full /story-sync reconciliation so BMAD's downstream workflows see current sprint-status.yaml. In a worktree, git fetch + warn (can't write main from here). The full reconciliation is required for correctness because BMAD's own create-story, dev-story, retrospective, etc. read sprint-status.yaml from disk to decide what's next. Source-of-truth model: - GitHub status:* labels — cross-agent coordination (atomic, always current). - sprint-status.yaml in active worktree — BMAD's own dev-story writes this at Step 4 (in-progress) and Step 9 (review); rides into main with the PR merge. - sprint-status.yaml on main — kept current by /story-sync and/or the bmad-story-sync CI workflow on PR merge. Both writers are idempotent. Bumped module.yaml, plugin.json, and marketplace.json from 1.0.3 → 1.1.0. Behavior is additive; no public API changed. Existing users running the standard sequential flow from main see the same outcomes (worktree per story, PR per story, labels tracked). Backwards-compatible. Tests: 65/65 passing. Biome clean. Closes #11 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
choucrifahed
added a commit
that referenced
this pull request
May 18, 2026
The BMAD Story Sync workflow used `gh pr list --search "#N"` to find the PR that closed an issue. This had two compounding bugs: 1. `gh pr list --search` hits GitHub's `/search/issues` endpoint, which is eventually-consistent Elasticsearch. The `issues.closed` webhook fires the instant the merge auto-closes the issue, well inside the 5-30 s window in which freshly-merged PRs are still missing from the search index. Cold-cache misses left stories stuck at `review`. 2. `--search "#5"` is free-text search, not a structured "PR closes #5" lookup — `#` is not a search operator. Matches were incidental (PR bodies contain `Closes #N`) and also matched unrelated PRs whose text contained the substring (e.g. a dependabot bump to `46.0.5`). `.[0]` then silently attributed the sync to the wrong PR. Replace with a GraphQL query on `issue.timelineItems[CLOSED_EVENT].closer`, which reads from canonical event data — no search index, no race, no ambiguity. The jq `select(__typename == "PullRequest" and state == "MERGED")` guard keeps the existing "closed without a merged PR" fallthrough intact for manual closes and revert-then-reclose scenarios. Verified against three real cases in this repo (#11 closed by #12, #5 closed by #6, and #13 still open) — all return the canonical closer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
choucrifahed
added a commit
that referenced
this pull request
May 18, 2026
The BMAD Story Sync workflow used `gh pr list --search "#N"` to find the PR that closed an issue. This had two compounding bugs: 1. `gh pr list --search` hits GitHub's `/search/issues` endpoint, which is eventually-consistent Elasticsearch. The `issues.closed` webhook fires the instant the merge auto-closes the issue, well inside the 5-30 s window in which freshly-merged PRs are still missing from the search index. Cold-cache misses left stories stuck at `review`. 2. `--search "#5"` is free-text search, not a structured "PR closes #5" lookup — `#` is not a search operator. Matches were incidental (PR bodies contain `Closes #N`) and also matched unrelated PRs whose text contained the substring (e.g. a dependabot bump to `46.0.5`). `.[0]` then silently attributed the sync to the wrong PR. Replace with a GraphQL query on `issue.timelineItems[CLOSED_EVENT].closer`, which reads from canonical event data — no search index, no race, no ambiguity. The jq `select(__typename == "PullRequest" and state == "MERGED")` guard keeps the existing "closed without a merged PR" fallthrough intact for manual closes and revert-then-reclose scenarios. Verified against three real cases in this repo (#11 closed by #12, #5 closed by #6, and #13 still open) — all return the canonical closer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
/story-devcompatible with orchestrators like Conductor that spawn each agent in a pre-created worktree, by removing the "editsprint-status.yaml→ commit → push onmain" prelude that previously blocked branching.status:*labels as the cross-agent coordination layer (atomic viagh issue edit), whilesprint-status.yamlcontinues to be written by BMAD's owndev-storyfrom inside the worktree (Step 4 →in-progress, Step 9 →review) and by/story-sync/bmad-story-syncCI onmain.Issue #11 has the full problem statement, root cause, and solution design.
Source-of-truth model
status:*labels/story-dev,/story-create/story-init,/story-create,/story-dev,/story-syncsprint-status.yaml(in current workspace)create-story,dev-story,retrospective, etc.dev-story(Steps 4 + 9)mainsprint-status.yaml(onmain)/story-syncand/orbmad-story-syncCIWorktree detection
/story-devnow classifies its starting context:main(or repo's default)mainstory/<key>matching plugin conventionworktreeunrecognizedFiles changed
bmad-github/skills/bmad-github-story-dev/SKILL.md— rewritten with worktree detection, atomic GH label flip, no commit-on-main, race handling forgit worktree add -b.bmad-github/skills/bmad-github-story-sync/SKILL.md— writes preserved (primary writer onmain); reframed as such, with CI as redundant safety net.bmad-github/skills/bmad-github-story-create/SKILL.md— GH-label-based dependency check, worktree-tolerant.bmad-github/skills/bmad-github-story-init/SKILL.md—sprint-status.yamloptional in pre-flight.bmad-github/references/sync-first.md— onmain, full/story-syncreconciliation before proceeding (required for BMAD correctness since BMAD readssprint-status.yamlfrom disk). In a worktree,git fetch+ warn.bmad-github/module-help.csv,README.md— descriptions updated; README adds a Branch column to the Skills table.bmad-github/module.yaml,bmad-github/.claude-plugin/plugin.json,.claude-plugin/marketplace.json— version 1.0.3 → 1.1.0.Test plan
npm test— 65/65 passing (no test changes; onlysync-stories-to-github.mjshas unit tests and its API is unchanged)npm run check— Biome clean/story-devfrommain(standard flow) — verify worktree created, label flipped, BMAD dev-story invoked, PR opened/story-devfrom inside an existingstory/<key>worktree (Conductor simulation) — verify worktree reused, no creation attempted, label flipped, BMAD dev-story invoked/story-createwith an explicit story key from a worktree — verify it does not requiremain/story-syncfrommainafter a PR merge — verifysprint-status.yamlupdated, story fileStatus:updated, worktree cleaned up, commit pushed/story-setup-ciinstalled, merge a PR and confirm CI also writes (no-op since/story-syncalready did, but verifies idempotency)Closes #11
🤖 Generated with Claude Code