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
40 changes: 33 additions & 7 deletions .github/workflows/initiative-planner.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ on:
required: true
type: string
target_repo:
description: 'Target repo (owner/repo) to plan FOR — read the Discussion, create the epic + DAG, post the reply there. Empty => this repo (the dogfood/self path).'
description: 'Target repo (owner/repo) to plan FOR — create the epic + DAG there and read its _bmad-output/planning-artifacts/. Empty => this repo (the dogfood/self path).'
required: false
default: ''
type: string
discussion_repo:
description: 'Repo (owner/repo) holding the Discussion when it differs from target_repo (incubator idea -> product repo epic). The plan reply goes there. Empty => target_repo.'
required: false
default: ''
type: string
Expand Down Expand Up @@ -66,8 +71,13 @@ permissions: {}
concurrency:
# One plan per (target repo, discussion) at a time; never cancel an in-flight
# planning run. Keying on the target repo as well as the discussion number stops
# two fleet repos that happen to share a discussion number from colliding.
group: initiative-planner-${{ github.event.inputs.target_repo || github.repository }}-${{ github.event.inputs.discussion || github.event.discussion.number }}
# two fleet repos that happen to share a discussion number from colliding; the
# discussion repo is keyed too, since a graduated idea's number is the
# incubator's, not the target's.
group: >-
initiative-planner-${{ github.event.inputs.target_repo || github.repository
}}-${{ github.event.inputs.discussion_repo || github.event.inputs.target_repo || github.repository
}}-${{ github.event.inputs.discussion || github.event.discussion.number }}
cancel-in-progress: false

jobs:
Expand Down Expand Up @@ -139,14 +149,22 @@ jobs:
# here via the `redispatch` job), so inputs are always present.
# REPO is the TARGET repo to plan for; empty target_repo => this repo (self).
REPO: ${{ github.event.inputs.target_repo || github.repository }}
# DISCUSSION_REPO holds the approved idea's Discussion; empty => REPO. It
# differs from REPO when an incubator idea graduates into a product repo.
DISCUSSION_REPO: ${{ github.event.inputs.discussion_repo || github.event.inputs.target_repo || github.repository }}
# Token for every WRITE to the TARGET repo (gather-context, the Bob
# materialization, apply-reviewed-plan, and the plan reply). GITHUB_TOKEN is
# scoped only to .github-private, so it is valid for the self path only;
# Cross-repo runs use GH_PAT_WORKFLOWS (same pattern as actions-fleet-monitor.yml);
# S1 (#818) will replace this with a minted GitHub App token for tighter scoping.
# The workflow_dispatch re-invoke keeps GH_PAT_WORKFLOWS (redispatch job) — that
# role is loop-prevention, not content writes, and is unaffected.
TARGET_GH_TOKEN: ${{ (github.event.inputs.target_repo == '' || github.event.inputs.target_repo == github.repository) && secrets.GITHUB_TOKEN || secrets.GH_PAT_DON_PETRY || secrets.GH_PAT_WORKFLOWS }}
# GITHUB_TOKEN only when BOTH the target and the discussion repo are self;
# a cross-repo Discussion read/reply needs the PAT too.
TARGET_GH_TOKEN: >-
${{ (github.event.inputs.target_repo == '' || github.event.inputs.target_repo == github.repository)
&& (github.event.inputs.discussion_repo == '' || github.event.inputs.discussion_repo == github.repository)
&& secrets.GITHUB_TOKEN || secrets.GH_PAT_DON_PETRY || secrets.GH_PAT_WORKFLOWS }}
DISCUSSION_NUMBER: ${{ github.event.inputs.discussion }}
DRY_RUN: ${{ inputs.dry_run == true && '1' || '0' }}
FORCE_REPLAN: ${{ inputs.force_replan == true && '1' || '0' }}
Expand Down Expand Up @@ -203,7 +221,8 @@ jobs:

- name: Gather planning context
env:
# Reads the TARGET repo's Discussion + open epics.
# Reads the Discussion from DISCUSSION_REPO, and the open epics +
# _bmad-output/planning-artifacts/ from the TARGET repo.
GH_TOKEN: ${{ env.TARGET_GH_TOKEN }}
run: bash scripts/initiative-planner/gather-context.sh

Expand Down Expand Up @@ -281,8 +300,14 @@ jobs:
## Your input

The approved idea + repo context is at `$CONTEXT_PATH` (JSON): the
Discussion title/body/comments, the open `initiative` epics, and the
issues the idea references (likely prerequisites). Read it, then ground
Discussion title/body/comments (from `discussion_repo`, which may be
an incubator repo rather than `repo`), the open `initiative` epics,
the issues the idea references (likely prerequisites), and
`planning_artifacts` — the target repo's
`_bmad-output/planning-artifacts/` (PRD in full, other docs
truncated), or `null` when it has none. When a PRD is present it is
your PRIMARY planning input; see "Planning inputs" in
`prompts/bmad/scrum-master.md`. Read it, then ground
stories in the checked-out repo with Read/Grep/Glob (`AGENTS.md`,
`CLAUDE.md`, `scripts/`, `.github/workflows/`, `docs/initiatives/`):

Expand Down Expand Up @@ -373,6 +398,7 @@ jobs:

```bash
REPO="$REPO" \
DISCUSSION_REPO="$DISCUSSION_REPO" \
PLAN_PATH="$PLAN_PATH" \
DISCUSSION_NUMBER="$DISCUSSION_NUMBER" \
DISCUSSION_NODE_ID="$DISCUSSION_NODE_ID" \
Expand Down
58 changes: 58 additions & 0 deletions docs/initiatives/idea-to-initiative-pipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,66 @@ inputs.
flow is unchanged. The handoff is artifact-download-by-run-ID; the artifact
is re-fetched from GitHub Actions and not taken from a local file. This plan-review gate is **distinct** from the `initiative:auto` gate: the
former binds the plan *contents*, the latter activates auto-*implementation*.
- **Planning inputs.** `gather-context.sh` also reads the target repo's
`_bmad-output/planning-artifacts/` (the planning source of truth named in org
`AGENTS.md`) into `planning_artifacts`: `prd.md` in full, every other
top-level `.md` truncated (12,000 chars by default). When a PRD is present Bob
plans from its functional requirements and success metrics and treats the
Discussion as context; with no folder (404) or no markdown in it,
`planning_artifacts` is `null` and Bob plans from the Discussion as before.
- **`discussion_repo`** (optional) names the repo the Discussion lives in when it
differs from `target_repo` — see [Graduation](#graduation-incubator-idea--product-repo).
- Tooling: `scripts/initiative-planner/` (persona: `prompts/bmad/scrum-master.md`).

## Graduation: incubator idea → product repo

An idea is approved in `combsmith/incubator`, but its epic belongs in the product
repo it graduates into. The planner reads the Discussion from one repo and plans
into another:

```
combsmith/incubator: ★ Ideas Discussion #N approved (decision brief merged)
│
▼
product repo (e.g. combsmith/worldchest): seed _bmad-output/planning-artifacts/
│ prd.md (+ brief.md, architecture.md, ...) committed on the default branch
▼
combsmith/.github-private initiative-planner.yml
-f discussion=N -f discussion_repo=combsmith/incubator -f target_repo=<product repo>
│ reads Discussion #N from the incubator, the PRD + open epics from the
│ product repo; Bob plans from the PRD's FRs and success metrics
▼
inert epic + story DAG in the product repo; the plan reply is posted on the
incubator Discussion, with links qualified as <product repo>#n
```

1. **Approve in the incubator.** The decision brief for Discussion #N is merged and
the owner decides to build. The incubator has no planner stub, so this does not
dispatch anything by itself.
2. **Seed the artifacts in the product repo.** Copy the PRD and its companions
into `_bmad-output/planning-artifacts/` on the product repo's default branch
(the planner reads it through the contents API, so it must be merged, not on
a branch).
3. **Dry-run the planner** with both repos set:

```bash
gh workflow run initiative-planner.yml --repo combsmith/.github-private \
-f discussion=N -f discussion_repo=combsmith/incubator \
-f target_repo=combsmith/<product> -f dry_run=true
```

4. **Review and apply.** Download the `initiative-plan-dry-run` artifact, review
`plan.json`, then re-dispatch the same command with
`-f plan_artifact_run_id=<dry-run run id> -f dry_run=false`.
5. **Arm it.** Add `initiative:auto` to the new epic in the product repo.

The epic's back-reference is `Planned from idea discussion combsmith/incubator#N`,
which is also the idempotency key, so re-dispatching (or `force_replan` /
`reconcile`) binds to the same epic. Same-repo runs keep the bare `#N` key, so
epics planned before this change still bind. A cross-repo run uses the PAT for
both repos (`GITHUB_TOKEN` only covers `.github-private`). No mirror Discussion in
the product repo is needed.

## Labels the pipeline relies on

| Label | On | Meaning |
Expand Down
36 changes: 33 additions & 3 deletions prompts/bmad/scrum-master.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,35 @@ You are **Bob** 🏃 — Technical Scrum Master, crisp and checklist-driven, zer
tolerance for ambiguity, whose job is to set the `dev-lead` developer agent up to
succeed.

## Planning inputs — PRD first, Discussion as context

`$CONTEXT_PATH` carries two kinds of input. Weigh them in this order:

1. **`planning_artifacts`** — the target repo's BMAD planning output
(`_bmad-output/planning-artifacts/`), which org `AGENTS.md` names as the
source of truth for planning. `planning_artifacts.prd` is the PRD's path and
its `files[]` entry carries it in full; other files (brief, architecture, UX,
research) are truncated (`truncated: true`; read the full file from the
target repo if you need more). When a PRD is present:
- Its **functional requirements** are the scope. Every story traces to one or
more FRs (cite them, e.g. `FR3`, in `references` and the AC wording), and
every in-scope FR is covered by a story or explicitly deferred in
`open_questions`. Do not add scope the PRD does not ask for.
- Its **success metrics** become the epic's initiative-level success metric
(rubric item 2) — quote them; do not invent new ones.
- Its non-functional requirements and out-of-scope list bound the stories'
AC and Dev Notes.
2. **`discussion`** — the approved idea and its thread. With a PRD present it is
**context**: the why, the history, and decisions taken in comments. Where the
Discussion and the PRD disagree, follow the PRD and record the conflict as an
`open_questions` entry. With `planning_artifacts: null` (no PRD), the
Discussion is the primary input, as before.

When `discussion_repo` differs from `repo`, the idea graduated from another repo
(usually the incubator). Its `referenced_issues` live in `discussion_repo` (each
entry carries `repo`); they are context only and must **not** become
`blocked_by_existing_issues`, which are issue numbers in `repo`.

## Step 2 — Sprint planning (sequence the work)

Read and follow the sprint-planning skill, applied to our input (the approved
Expand All @@ -27,11 +56,12 @@ cat frameworks/bmad-method/src/bmm-skills/4-implementation/bmad-sprint-planning/
cat frameworks/bmad-method/src/bmm-skills/4-implementation/bmad-sprint-planning/checklist.md
```

Decompose into the **smallest set of stories** (typically 3–8), each a single
Plan from the PRD's functional requirements when one exists (see "Planning
inputs" above). Decompose into the **smallest set of stories** (typically 3–8), each a single
PR-sized unit for `dev-lead`, sequenced by a minimal `blocked_by` DAG (true
ordering only; acyclic, with an entry point). Prerequisites already tracked as
repo issues (see `referenced_issues`) become `blocked_by_existing_issues`, not
new stories.
issues in the target repo (see `referenced_issues` whose `repo` is the target)
become `blocked_by_existing_issues`, not new stories.

## Step 3 — Create each story (BMAD create-story method)

Expand Down
2 changes: 1 addition & 1 deletion scripts/initiative-planner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ create-story template, which `plan.schema.json` mirrors field-for-field.
| File | Role |
|------|------|
| `redispatch.sh` | Bridge the `discussion [labeled]` trigger to `workflow_dispatch` (claude-code-action rejects `discussion` event contexts). Fired with a PAT so the dispatch actually starts a run. Forwards `force_replan=true` when the firing label is `initiative:replan`, or `reconcile=true` when it is `initiative:reconcile` (vs the default `idea:approved` plan path). |
| `gather-context.sh` | Fetch the approved Discussion + repo context → `$CONTEXT_PATH`; export `DISCUSSION_NODE_ID`. Under `RECONCILE=1` also harvests the bound epic's sub-issues (state, labels, comments) into a `reconcile` block so Bob can pick up comment-surfaced follow-ups (#708). |
| `gather-context.sh` | Fetch the approved Discussion (from `DISCUSSION_REPO`, default `REPO`) + the target repo's context and `_bmad-output/planning-artifacts/` (`planning_artifacts`; PRD in full, other `.md` truncated, `null` when absent) → `$CONTEXT_PATH`; export `DISCUSSION_NODE_ID`. Under `RECONCILE=1` also harvests the bound epic's sub-issues (state, labels, comments) into a `reconcile` block so Bob can pick up comment-surfaced follow-ups (#708). |
| `plan.schema.json` | The plan contract Bob must emit (epic + stories + `blocked_by`). |
| `validate-plan.py` | Schema + semantic checks: unique ids, acyclic DAG, an entry point, no dangling edges. |
| `lib/mutations.sh` | DRY_RUN-aware GitHub helpers (create issue, link sub-issue, add `blocked_by`, comment on Discussion, find/close issues for the idempotency guard, list existing reconcile-keys for the reconcile diff). |
Expand Down
Loading
Loading