Skip to content

feat(bmad-github): worktree-aware story-dev, no commit-on-main prereq#12

Merged
choucrifahed merged 1 commit into
mainfrom
11-make-story-dev-compatible-with-conductor-worktree-aware-no-commit-on-main-prerequisite
May 17, 2026
Merged

feat(bmad-github): worktree-aware story-dev, no commit-on-main prereq#12
choucrifahed merged 1 commit into
mainfrom
11-make-story-dev-compatible-with-conductor-worktree-aware-no-commit-on-main-prerequisite

Conversation

@choucrifahed

Copy link
Copy Markdown
Owner

Summary

  • Make /story-dev compatible with orchestrators like Conductor that spawn each agent in a pre-created worktree, by removing the "edit sprint-status.yaml → commit → push on main" prelude that previously blocked branching.
  • Use GitHub status:* labels as the cross-agent coordination layer (atomic via gh issue edit), while sprint-status.yaml continues to be written by BMAD's own dev-story from inside the worktree (Step 4 → in-progress, Step 9 → review) and by /story-sync / bmad-story-sync CI on main.
  • Bump plugin version 1.0.3 → 1.1.0.

Issue #11 has the full problem statement, root cause, and solution design.

Source-of-truth model

Concern Source of truth Read by Written by
Cross-agent coordination ("is this story claimed?") GitHub status:* labels /story-dev, /story-create /story-init, /story-create, /story-dev, /story-sync
Story execution state in a workspace sprint-status.yaml (in current workspace) BMAD's create-story, dev-story, retrospective, etc. BMAD's own dev-story (Steps 4 + 9)
Long-term truth on main sprint-status.yaml (on main) Anyone who clones the repo /story-sync and/or bmad-story-sync CI

Worktree detection

/story-dev now classifies its starting context:

Branch Classification Worktree creation
main (or repo's default) main Create new worktree
story/<key> matching plugin convention worktree Skip — reuse the current one
anything else unrecognized STOP and ask the user

Files changed

  • bmad-github/skills/bmad-github-story-dev/SKILL.md — rewritten with worktree detection, atomic GH label flip, no commit-on-main, race handling for git worktree add -b.
  • bmad-github/skills/bmad-github-story-sync/SKILL.md — writes preserved (primary writer on main); 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.mdsprint-status.yaml optional in pre-flight.
  • bmad-github/references/sync-first.md — on main, full /story-sync reconciliation before proceeding (required for BMAD correctness since BMAD reads sprint-status.yaml from 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; only sync-stories-to-github.mjs has unit tests and its API is unchanged)
  • npm run check — Biome clean
  • Manual: run /story-dev from main (standard flow) — verify worktree created, label flipped, BMAD dev-story invoked, PR opened
  • Manual: run /story-dev from inside an existing story/<key> worktree (Conductor simulation) — verify worktree reused, no creation attempted, label flipped, BMAD dev-story invoked
  • Manual: run /story-create with an explicit story key from a worktree — verify it does not require main
  • Manual: run /story-sync from main after a PR merge — verify sprint-status.yaml updated, story file Status: updated, worktree cleaned up, commit pushed
  • Manual: with /story-setup-ci installed, merge a PR and confirm CI also writes (no-op since /story-sync already did, but verifies idempotency)

Closes #11

🤖 Generated with Claude Code

…#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 choucrifahed merged commit fe8f55f into main May 17, 2026
2 checks passed
@choucrifahed choucrifahed deleted the 11-make-story-dev-compatible-with-conductor-worktree-aware-no-commit-on-main-prerequisite branch May 17, 2026 16:35
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make /story-dev compatible with Conductor (worktree-aware; no commit-on-main prerequisite)

1 participant