basic BMAD setup

This commit is contained in:
Ebbe Baß
2026-10-15 08:57:23 +02:00
parent 44029127dc
commit 5e747654b9
438 changed files with 41818 additions and 0 deletions
@@ -0,0 +1,81 @@
# 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 <repo>` on every call when it is set. Needs gh 2.94+; check `--help` when a flag is doubted.
"""
fields = """
risk is a label risk:<value>; 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:<value> (`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 <name>` 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 <t> --body-file <b> --label <type>,<[tickets.status].<state>>[,hitl,risk:<r>,P<n>]
[--milestone <initiative>]`, and mirror tracker_id and remote from the url it prints before anything else. Then add the
relations with `gh issue edit <n> --parent <n> --add-blocked-by <n,n>`, leaving out any that `gh issue view <n> --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 <n>` (--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 <n>`;
dropped: the same with `--reason "not planned"`. An initiative is a
milestone (`gh api repos/{owner}/{repo}/milestones -f title=<name>`) holding its tree's issues.
"""
query = """
One issue: `gh issue view <n> --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 <n>
--json subIssues`. Words: `gh issue list --search "<words>" --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
@@ -0,0 +1,83 @@
# Store starter: Jira Cloud. `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: acli, Atlassian's official CLI (`acli jira workitem ...`): create with --parent, transition,
# links, comments, assign, JQL. The Atlassian Rovo MCP server (https://mcp.atlassian.com/v1/mcp/authv2)
# is the alternative for an org that prefers MCP; it has no link-creation tool.
description = "Jira Cloud through acli: work types, transitions, links, JQL. For teams already on Jira, usually with an admin who can adjust the project workflow."
[tickets]
store = "jira"
key = "" # the default Jira project key, e.g. "SHOP"; Jira assigns the number. A container's own `key` overrides it for everything under it
site = "" # https://yourorg.atlassian.net
access = """
Use acli against `site`. Custom fields go through `--from-json`; check a subcommand's spelling
with `acli jira workitem --help` before first use.
"""
fields = """
severity is Jira's priority: P0 = Highest, P1 = High, P2 = Medium, P3 = Low; set it at create
(--priority, or --from-json when the flag is absent) and change it the same way. risk is a label
risk-low, risk-medium, or risk-high. estimate is the Story Points field on a story and a label est:<value> on an epic, through --from-json. Empty field: default priority, no label.
"""
reference = """
acli creates no web links; the description line is the reference.
"""
[verbs]
setup = """
Connect, unless acli is already installed and signed in to `site`: install acli (on macOS,
`brew tap atlassian/homebrew-acli && brew install acli`; elsewhere, per Atlassian's acli install
guide), then `acli jira auth login --web --site <site>`.
acli cannot create work types or statuses; those live in the project's settings. At first publish,
read an existing work item (`acli jira workitem view <key> --json --fields issuetype,status`) and
the project's work types, compare with [tickets.types] and [tickets.status], and tell the user
exactly what to add in Jira or which map entry to set in the project's store config. Jira's defaults (To Do, In
Progress, Done; resolution Won't Do) need no setup; [tickets.status] also expects an In Review status.
Do not ask for a level above Epic: by default the initiative stays local and everything below it
publishes. A team-managed project's admin adds a status as a board column under
Project settings, Board; a company-managed project needs a Jira admin to add it to the workflow.
"""
write = """
The project is the ticket's own `key`, else that of the nearest container above it, else `key` here.
tracker_id is the work item's key and remote its url.
Create:
`acli jira workitem create --project <key> --type <per [tickets.types]> --summary <t>
--description-file <b> --parent <key> --label <type>[,hitl,risk-<risk>]`, priority per `fields` on
a bug. Changes: `edit` (body, labels), `assign`, `transition --status <[tickets.status].<state>>` —
status never moves by edit — `link create --out <blocker> --in <blocked> --type "Blocks"`,
`comment create`. No parent: omit --parent — the item sits loose in the project. Custom fields go through --from-json; when dropped shares a status with done,
set the resolution the same way so the two stay distinct.
Epics sit loose in the project while [tickets.types].initiative is empty. Set that type only when the
project has a level above Epic; then epics take it as --parent.
"""
query = """
One work item: `acli jira workitem view <key> --json --fields
key,issuetype,summary,status,assignee,parent,labels,issuelinks,resolution,description`. after is the
"is blocked by" links; Done with resolution Won't Do is dropped; the description's covers holds the source requirement ids. Children:
`search --jql "parent = <key>"`. Words: `search --jql 'project = <key> AND text ~ "<words>"'`,
narrowed by issuetype or status per the maps.
"""
# BMad type -> this store's type.
[tickets.types]
initiative = "" # empty: the initiative stays local. Jira's default hierarchy is Epic > Story > Subtask; a level above Epic needs Plans (Premium or Enterprise). Set to "Initiative" only when the project has that level.
epic = "Epic"
story = "Story"
spike = "Task" # plus label spike
bug = "Bug"
# BMad state -> this store's status.
[tickets.status]
backlog = "To Do"
in-progress = "In Progress"
review = "In Review" # not a default Jira status; `setup` asks for it. With no review status, set it to the in-progress one
done = "Done"
dropped = "Done" # Jira default has no separate status; resolution Won't Do distinguishes it
@@ -0,0 +1,75 @@
# Store starter: Linear. `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: Linear's official MCP server. Initiatives hold projects, parent issues are epics, labels carry
# type, relations carry blocking. Linear ships no CLI; community CLIs are not used here.
description = "Linear through its MCP server: projects, parent issues, relations, labels. For product teams on Linear."
[tickets]
store = "linear"
key = "" # Linear team key, e.g. "SHOP"; Linear assigns the number
team = "" # the default team, key or name. A container's own `key` overrides it for everything under it
access = """
Use the Linear MCP (create_issue, update_issue, create_project, update_project, get_issue,
list_issues, list_issue_statuses, create_issue_label, create_comment) against `team`. Confirm from the tool schema whether parent
and blockedBy take identifiers (SHOP-12) or ids, and the exact tool names against the server's list at first use.
"""
fields = """
severity is Linear's priority, set on the issue: P0 = Urgent, P1 = High, P2 = Medium, P3 = Low.
risk is a label risk:<value>; `setup` creates the three. estimate is Linear's estimate field on a story; a label est:<value> on an epic. Empty field: no priority, no label.
"""
reference = """
Also pass the url in the issue's links so it shows in the sidebar.
"""
[verbs]
setup = """
Connect, unless the Linear tools already answer: add the MCP server https://mcp.linear.app/mcp to
the agent tool in use (in Claude Code, `claude mcp add --transport http linear-server https://mcp.linear.app/mcp`),
then sign in over OAuth. Do not use the deprecated /sse endpoint.
`create_issue_label` for every non-empty value in [tickets.types], `hitl`, and risk:low, risk:medium,
risk:high that the team lacks. States
cannot be created from the MCP: `list_issue_statuses`, compare with [tickets.status], and tell the
user what to add under Settings, Teams, Issue statuses, or which map entry to set in the project's
store config. Linear's defaults (Backlog, Todo, In Progress, Done, Canceled) need no setup; [tickets.status]
also expects an In Review state in the Started group.
"""
write = """
The team is the ticket's own `key`, else that of the nearest container above it, else `team` here.
tracker_id is the issue's identifier and remote its url.
`create_issue` creates, `update_issue` changes — title, description, state, assignee, parent,
blockedBy, labels, priority, project. The issue's state is [tickets.status].<state>. Labels carry type, hitl, and
risk:<value>; priority carries severity per `fields`. A standalone ticket: omit parent and project.
Epics are parent issues. Set [tickets.types].initiative to map onto a Linear Initiative only when the
server exposes initiative tools.
"""
query = """
One issue: `get_issue` with includeRelations — labels, priority, state, assignee, parent,
project, relations, description; after is the "blocked by" relations, and the description's
covers holds the source requirement ids. Many: `list_issues` by project, parent, label, state, or
query words.
"""
# BMad type -> this store's type.
[tickets.types]
initiative = "" # empty: the initiative stays local. Linear has no Epic object and Projects cannot nest; set to "initiative" only to map onto Linear Initiatives, which nest five deep and allow several parents, so the tree cannot round-trip.
epic = "epic" # label on a parent issue
story = "story"
spike = "spike"
bug = "bug"
# BMad state -> this store's status.
[tickets.status]
backlog = "Backlog" # or "Todo" — the team's choice
in-progress = "In Progress"
review = "In Review" # not a default Linear state; `setup` asks for it. With no review state, set it to the in-progress one
done = "Done"
dropped = "Canceled"
@@ -0,0 +1,78 @@
# Store starter: Notion. `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: Notion's official MCP server. One database holds every ticket; properties carry type,
# status, assignee, and hitl; two self-relations carry hierarchy (Parent) and blocking (Blocked by);
# a board view grouped by Status is the kanban. `setup` creates all of it.
description = "Notion through its MCP server: one tickets database with a board view, relations for hierarchy and blocking. For teams that plan in Notion and want tickets beside their docs."
[tickets]
store = "notion"
key = "" # prefix of the database's ID property, e.g. "SHOP" gives SHOP-12; empty = page ids only
database = "" # url or id of the tickets database; empty = `setup` creates it
access = """
Use the Notion MCP (notion-fetch, notion-create-pages, notion-update-page,
notion-query-data-sources, notion-create-comment, notion-get-users) against `database`. Confirm
from the tool schema how relation and person properties are passed; `notion-get-users` resolves a name.
An empty `database` means `setup` has not run.
"""
fields = """
risk and severity are select properties: Risk (low, medium, high) and Severity (P0-P3); `setup`
creates them, publish and update set them. estimate is a text property Estimate, created with the rest. Empty field: property left empty.
"""
reference = """
Notion renders urls as links; a Notion page url becomes a mention.
"""
[verbs]
setup = """
Connect, unless the Notion tools already answer: add the MCP server https://mcp.notion.com/mcp to
the agent tool in use (in Claude Code, `claude mcp add --transport http notion https://mcp.notion.com/mcp`),
then sign in over OAuth. Either way, share the database, or the page it will be created under, with
the connection.
When `database` is empty: ask which page to create under, then `notion-create-database` named
Tickets with properties Title (title), Type (select, options per [tickets.types]), Status (status,
options per [tickets.status]), Parent (relation to this database), Blocked by (relation to this
database), Assignee (person), Covers (text), Risk (select: low, medium, high), Severity (select:
P0, P1, P2, P3), Estimate (text), hitl (checkbox), and, when `key` is set, ID (unique id
with that prefix). Then `notion-create-view`: a board grouped by Status. Give the user the url and
put it in `database` in the project's store config.
When `database` is set: `notion-fetch` it and compare its properties and options with the maps;
add a missing option with `notion-update-data-source`, and tell the user about anything the
schema needs a person to change.
"""
write = """
tracker_id is the ID property when `key` is set, else the page id; remote is the page url.
`notion-create-pages` into `database` creates; `notion-update-page` changes any property or the
content — Status, Assignee, Parent, Blocked by, Covers, Risk, Severity, hitl. Status is [tickets.status].<state>. No parent: leave the Parent relation empty. An initiative
is a page of Type initiative with no Parent. The body is the page content.
"""
query = """
One page: `notion-fetch` — Title, Type, Status, Assignee, Parent, Blocked by, Covers, Risk,
Severity, hitl, and the content; after is Blocked by. Many: `notion-query-data-sources` on `database` filtered by any
property; `notion-search` scoped to `database` for a looser match.
"""
# BMad type -> this store's type (Type select options; `setup` creates them).
[tickets.types]
initiative = "initiative"
epic = "epic"
story = "story"
spike = "spike"
bug = "bug"
# BMad state -> this store's status (Status property options; `setup` creates them).
[tickets.status]
backlog = "Backlog"
in-progress = "In Progress"
review = "Review"
done = "Done"
dropped = "Dropped"
@@ -0,0 +1,57 @@
# Store starter: tickets as files in the repo, the default. `scripts/read_store.py` reads this file as the
# base layer; the project's _bmad/custom/ticketing-store-config.toml names the store and replaces any key
# here, key by key. With no project file this starter is the whole config.
# 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.
#
# Tickets and artifacts live together as markdown files under `output_folder` (layout in SKILL.md,
# The ticket tree), committed in the monorepo or in a repo of their own. Status and assignee change by commit, and the skill never pushes,
# so others see them only after a push and pull.
description = "Quick setup, and the default when nothing else is chosen. Tickets are markdown files under version control: in the monorepo, or in their own repo (recommended for mono and poly repos alike). No tracker or account needed. The simplest store — right for a solo developer or a very small team with no other tracking."
[tickets]
store = "repo"
access = """
Read and write the files under `output_folder`.
"""
fields = """
risk and severity live in the frontmatter (risk: low|medium|high on any ticket; severity: P0-P3
on bugs); estimate the same way. Nothing else carries them.
"""
[verbs]
setup = """
Tickets live in `output_folder` beside the documents. To keep them outside the project, set `output_folder`
under `[core]` in `_bmad/custom/config.toml` to a folder beside it — the session then launches one level
up — or in the workspace that already holds the repos. `git init` a new folder so tickets have
history; skip that when the user picks a folder already under git, the monorepo included. Then
create `backlog/` and the active initiative's folder there when they do not exist.
"""
write = """
Every change is an edit to the ticket's file, its `tickets.toml` entry, or, for a leaf's status and assignee, its plan through `tickets.py mark`.
Commit only the files you wrote on the current branch. Root not in a git repo: skip the commit and say so.
"""
query = """
The files are the store: `tickets.py find`, `next`, and `status` answer from them. By a frontmatter field: `grep -rl "^type: bug" <folder>`.
"""
# BMad type -> this store's type.
[tickets.types]
initiative = "initiative"
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 = "done"
dropped = "dropped"
@@ -0,0 +1,76 @@
# Store starter: Trello. `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 official Trello MCP server. Trello is flat: lists carry status, labels carry type, card
# urls in descriptions carry hierarchy and blocking. Using the defaults below: the board needs the lists
# Backlog, In Progress, Review, Done, Initiatives, Epics and a label per type plus hitl; `setup` says how. Members, comments, attachments, and label creation
# are not in the MCP yet. Weakest fit of the shipped configs; expect to tune it.
description = "Trello through its MCP server: lists as status, labels as type, card links as hierarchy. For small teams that run a Trello board and want to keep it."
[tickets]
store = "trello"
key = "" # ids are card short links (from the card url); leave empty
board = "" # the board url
access = """
Use the Trello MCP (trelloReadBoard, trelloReadCard, trelloWriteCard, trelloWriteChecklist,
trelloSearch) against `board`. Write tools take the server's ids, not urls: resolve a url with
`trelloReadCard get` first.
"""
fields = """
risk is a label risk:<value>; severity a label P0-P3. The MCP cannot create labels: `setup` asks
the user to add them with the type labels. estimate is an `estimate:` line in the description. Empty field: no label.
"""
reference = """
Attachments are not in the MCP yet; the description line is the reference.
"""
[verbs]
setup = """
Connect, unless the Trello tools already answer: add the MCP server https://mcp.trello.com/v1 to
the agent tool in use (in Claude Code, `claude mcp add --transport http trello https://mcp.trello.com/v1`),
then sign in over OAuth with write permission.
The MCP can create a board but not lists or labels. When `board` is empty, offer to create one;
then ask the user to add the lists in [tickets.status] (Backlog, In Progress, Review, Done) plus
Initiatives and Epics, and labels for every value in [tickets.types] plus hitl, risk:low, risk:medium, risk:high, and
P0-P3. Check with
`trelloReadBoard` before first publish and name what is still missing.
"""
write = """
tracker_id is the card's short link and remote its url.
`trelloWriteCard create` makes a card, `update` changes description or labels, `move` changes
list, `archive` is dropped. Status is the list [tickets.status].<state>; initiative cards live in the Initiatives list,
epics in Epics, both carrying a `status:` line at the top of the description. Labels carry type,
hitl, risk, severity. Trello has no fields for the rest — conventions in the description:
`parent: <card url>` as its first line, an `assignee:` line (no member tools yet), after as
card urls; the parent card keeps a checklist with each child's url. A standalone ticket: no parent line.
The body is the description, and the `assignee:` line stays in it.
"""
query = """
One card: `trelloReadCard get` by url — name, list, labels, description (assignee, parent,
covers, after live there), checklists; a container's children are its checklist items. Many:
`trelloSearch` scoped to `board`, or `trelloReadBoard` filtered by the maps.
"""
# BMad type -> this store's type.
[tickets.types]
initiative = "initiative"
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 = "Done"
dropped = "archived"