# Store starter: GitHub Issues. `scripts/read_store.py` reads this file as the base layer; the project's # _bmad/custom/ticketing-store-config.toml names the store, fills the empty fields below, and replaces # any other key here, key by key. # It updates with the skill and holds only what is specific to this store: the rules every store shares # are in references/board.md. Comments and `description` are never loaded; `description` is for choosing a store. # # One tool: the gh CLI, 2.94 or later (sub-issues and dependencies). Labels carry type and status, # sub-issues carry hierarchy, blocked-by relations carry blocking, milestones put initiatives on the roadmap. # Using the defaults below: the repo needs the type, status, and hitl labels; `setup` creates them. description = "GitHub Issues through the gh CLI: labels, sub-issues, blocked-by, milestones. For teams whose code and planning already live on GitHub." [tickets] store = "github" key = "" # ids are the repo's issue numbers; leave empty repo = "" # OWNER/REPO; empty = the repo of the current checkout (origin) access = """ Use the gh CLI against `repo`, passing `-R ` on every call when it is set. Needs gh 2.94+; check `--help` when a flag is doubted. """ fields = """ risk is a label risk:; severity is a label P0-P3. `setup` creates them with the other labels; publish and update set them like any label. estimate is a label est: (`setup` creates none; add on first use). Empty field: no label. """ reference = """ GitHub renders urls as links; nothing more to do. """ [verbs] setup = """ Connect, unless `gh auth status` already shows a login: `gh auth login`. Labels must exist before they are used. List the repo's labels (`gh label list --limit 1000 --json name`) and run `gh label create ` for each of these it lacks, comparing names ignoring case: the [tickets.types] values except initiative (a milestone), the [tickets.status] values except done and dropped (closed states), `hitl`, risk:low, risk:medium, risk:high, and P0-P3. A create that fails with "already exists" found the label after all: leave it. Never `--force`: it recolours a label the repo already has. """ write = """ One issue per new ticket. tracker_id is the issue number and remote its url. Create with content only: `gh issue create --title --body-file --label ,<[tickets.status].>[,hitl,risk:,P] [--milestone ]`, and mirror tracker_id and remote from the url it prints before anything else. Then add the relations with `gh issue edit --parent --add-blocked-by `, leaving out any that `gh issue view --json parent,blockedBy` already shows, since adding one twice fails. Keep --parent and --blocked-by off the create: gh adds them after the issue exists, and when that step fails it exits non-zero without printing the url, so a rerun files the ticket twice. A create that fails with no url may still have made the issue: before creating again, list the newest issues (`gh issue list --state all --limit 100 --json number,title,body,createdAt`) twice, a few seconds apart since the list can trail a create, and take the one whose title and body are what was sent. None matches and the list does not reach back to the failed create: ask before creating again. Changes go through `gh issue edit ` (--body-file, --add-assignee, --add-blocked-by, label swaps; --add-sub-issue on the parent). No parent: omit --parent; no initiative: omit --milestone. Status is a label swap on an open issue. done: remove the status label and `gh issue close `; dropped: the same with `--reason "not planned"`. An initiative is a milestone (`gh api repos/{owner}/{repo}/milestones -f title=`) holding its tree's issues. """ query = """ One issue: `gh issue view --json number,title,labels,assignees,state,stateReason,milestone,parent,blockedBy,subIssuesSummary,body`. Type, hitl, risk, severity, and status are labels — except done and dropped, the closed states per [tickets.status]; after is blockedBy; the body's covers holds the source requirement ids. Children: `gh issue view --json subIssues`. Words: `gh issue list --search "" --state all`, narrowed by --label. An initiative's issues are the ones in its milestone; a standalone ticket has no milestone. """ # BMad type -> this store's type. [tickets.types] initiative = "milestone" # an initiative is a milestone, not an issue; its epics carry it epic = "epic" story = "story" spike = "spike" bug = "bug" # BMad state -> this store's status. [tickets.status] backlog = "backlog" in-progress = "in-progress" review = "review" done = "closed" # state, not a label dropped = "closed: not planned" # state, not a label