feat(core): add bmad-forge-idea skill + docs (#2492)

* feat(core): add bmad-forge-idea skill

Domain-agnostic idea-forging skill for BMad core. Takes a half-formed idea
and pressure-tests it in conversation until it hardens, proves out, or dies
cheaply — the quality of thinking is the product, not an artifact. One
question at a time with an always-on anti-sycophancy stance; two opt-in gears
(adversarial attack; a persona room resolved from the installed roster,
voiced by default and spawned when a branch needs independent minds); a
memlog as durable, resumable residue. Honest exits (hardened / killed /
clearer); optional brief distilled from the memlog that can feed bmad-spec or
bmad-quick-dev. Interactive and socratic; no headless mode.

Registered in core-skills/module-help.csv (menu code FI).

* Add forge-idea documentation and finalize skill scripts/conventions

Docs (Diátaxis):
- New explanation/forge-idea.md and how-to/pressure-test-an-idea.md
- core-tools.md: catalog entry + item section for bmad-forge-idea
- workflow-map.md and getting-started.md: Phase 1 (Analysis) listing
- De-collide the "forge" verb from bmad-prfaq positioning across docs

Skill:
- forge-idea SKILL.md: add term-sharpening axis, operationalize the
  existing-project ground-truth rule, and add graceful degradation
- bmad-prfaq: description de-collision; Stage 1 now redirects upstream
  to bmad-forge-idea for an unsound idea
- Unbundle the core memlog.py copy (skill references the core script at
  {project-root}/_bmad/scripts); keep skill-local resolve_personas.py + tests
- customize.toml: comment refresh; on_complete as array

* Fix review findings: resolve_personas crash + doc accuracy

- resolve_parties: resolve token once + coerce non-strings (fixes KeyError
  on mixed-case members and TypeError on unhashable members)
- Guard malformed party-mode config shapes so discovery stays best-effort
- _brief: pass through model/capabilities; register: no name-lookup hijack
- 5 new regression tests (case-insensitivity, malformed input, rename collision)
- Docs: correct bmad-review-adversarial-general name, bmad-prfaq handoff,
  recursive resume glob, broken table row, and Produces filenames across
  forge-idea/brainstorming/product-brief/architecture rows
This commit is contained in:
Brian 2026-06-21 21:55:58 -05:00 committed by GitHub
parent db2270c7ea
commit 005ef1104a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 699 additions and 13 deletions

View File

@ -0,0 +1,76 @@
---
title: "Forge an Idea"
description: Pressure-test an idea through persona-driven interrogation until it hardens, proves out, or dies cheaply
sidebar:
order: 14
---
Take a half-formed idea and pressure-test it now, in conversation, while changing your mind is still free.
## What is Forge Idea?
Run `bmad-forge-idea` and an exacting interrogator goes to work on your idea, one question at a time, until what survives is something you can act on with earned conviction. The skill is domain-agnostic. It runs on a software feature, a business model, a research hypothesis, or a life decision you keep circling.
What you walk away with is sharper thinking. A distilled `forged-idea.md` is only ever one possible exit, and the session never herds you toward "shall we build it?"
## Why Pressure-Test Early
The enemy is the hole you can't see in your own idea. An unexamined assumption or an unresolved branch is a crack, and a crack you miss now resurfaces later — in the build, or the launch, when it costs far more to fix.
A conversation is the cheapest place to catch it, because changing your mind here costs nothing. The forge spends that cheapness on purpose, going after the weak points while fixing them is still free.
## How a Session Runs
The interrogator works one question at a time, in dependency order, and puts its own recommended answer on the table each time. A position you can push against gets further than an open prompt. It finds discoverable answers itself instead of sending you to fetch them.
When your idea lands inside an existing project, that project's material becomes the ground truth. The interrogator checks your claims against what already exists and names the contradictions. Your vocabulary gets the same treatment. When a term is fuzzy or carries two meanings, it forces a precise choice before the branch can resolve, because a branch built on an overloaded word resolves falsely.
## The Room
The forge is voiced. Once the topic is set, every branch arrives with two characters instead of one faceless assistant. One comes from your installed roster — an agent or persona you'll recognize, drawn from the same cast behind [Party Mode](./party-mode.md) and [named agents](./named-agents.md). The other is conjured on the fly by the topic itself: a hostile competitor, a skeptical CFO, a domain specialist who has watched this exact plan fail before.
You steer the room whenever you want. Name a specific person, call a saved party, or invoke the **adversarial on this** gear to attack a claim to destruction with you defending it.
## Never Default-Agree
Reflexive agreement is the failure this skill exists to refuse. Acknowledging your idea isn't the same as endorsing it, and the forge won't praise anything before it has survived something. It attacks the weak point or builds on the strong one, and it credits only what genuinely earns the credit.
This is the deliberate inverse of [Adversarial Review](./adversarial-review.md). There, the reviewer is told to find problems and you filter out the false positives. Here, the interrogator is told never to grant agreement for free, so the pressure stays high and you think harder under it. It optimizes for the best idea over a comfortable session.
## How a Session Ends
The session ends however the thinking lands, and every landing is a real outcome. The forge writes a self-contained report you can keep, stamped to match the result.
| Outcome | What it means |
| --- | --- |
| **Hardened** | The idea survived. It distills into `forged-idea.md` (the locked decisions, plus what was killed and why), ready to feed `bmad-spec`, `bmad-prd`, or `bmad-prfaq` for a product concept. |
| **Killed** | The idea didn't survive, and the report records the cause of death. Finding that out cheaply is a win. |
| **Clearer** | You just think straighter now. No artifact needed, and the session stands on its own. |
## When to Use It
Reach for the forge when you already hold an idea and want it hardened or killed before you invest in it. Skip it when you're still generating options or when you need a group decision from your agents.
| Skill | Use it when | Voice |
| --- | --- | --- |
| `bmad-forge-idea` | You have an idea and want it stress-tested or killed | An interrogator plus a two-person room |
| `bmad-prfaq` | You've committed to a product and want it proven customer-first | A Working Backwards coach |
| `bmad-brainstorming` | You have no idea yet and need to generate options | A facilitation coach |
| `bmad-party-mode` | You want your agents to discuss or decide together | Your whole roster in one conversation |
| `bmad-review-adversarial-general` | You have an artifact and need its flaws found | A reviewer who must find issues |
## Example
:::note[Example]
**You:** "I want AI-generated weekly summaries in the app. Users get a digest email every Monday."
**Forge:** "Before the feature, who asked for this? My recommended answer is nobody did, and you're reasoning from 'AI summaries sound valuable.' Push back if there's a real signal."
**Skeptical CFO (conjured):** "And a Monday digest is a recurring inference cost across your whole active base, forever. What revenue line pays for it?"
**You:** "Retention, I guess. People forget to come back."
**Forge:** "Then the load-bearing claim is 'a Monday email brings lapsing users back,' not 'AI summaries are valuable.' Those are different ideas. Which one are we forging?"
:::
The first idea was a feature. Two questions in, the real idea is a retention bet you could test with a plain email and no model at all.

View File

@ -0,0 +1,55 @@
---
title: "Pressure-Test an Idea"
description: Use the bmad-forge-idea skill to harden, prove, or kill an idea before you invest in it
sidebar:
order: 12
---
Use the `bmad-forge-idea` skill to put a half-formed idea under adversarial questioning. It either survives with earned conviction or dies cheaply.
## When to Use This
- You hold an idea and want it stress-tested before you commit time or money
- You want an honest read on whether to kill it, not encouragement
- You're choosing between branches of a decision and need each one resolved
- Your idea lives inside an existing project and needs to be checked against what's already there
## When to Skip This
- You have no idea yet and need to generate options — use `bmad-brainstorming`
- You've committed to a product and want it proven customer-first — use `bmad-prfaq`
- You want your agents to debate a decision together — use `bmad-party-mode`
:::note[Prerequisites]
None. The forge runs in plain conversation. Installed agents and a configured persona roster make the session richer, but it works without them.
:::
## Run a Session
### 1. Invoke the skill
Type `bmad-forge-idea` in your IDE, or say "forge an idea" or "pressure-test this." Name the idea in the same message or wait for the first question.
### 2. State your goal
Tell the forge what you want: harden the idea, prove or kill it, or just think it through. The goal steers the questioning. Proving goes after the load-bearing claim first, and hardening drives each branch to a resolved answer.
### 3. Defend your thinking, one branch at a time
The interrogator asks one question at a time and puts its own recommended answer on the table for you to push against. Answer honestly. When it challenges a fuzzy term or a claim that doesn't match your project, settle that before you move on.
### 4. Steer the room
Every branch arrives with two voices — one from your roster, one conjured by the topic. Call a specific persona by name, summon a saved party, or say "adversarial on this" to have a claim attacked while you defend it.
### 5. Land an exit
Drive each branch to a resolved answer until the idea is hardened, killed, or simply clearer. Say when you're done, or let the forge call it.
## What You Get
The forge writes a self-contained `forge-report.html` every run, stamped to match the outcome. A hardened idea also distills into `forged-idea.md`, which captures the locked decisions and what was killed and why. That file feeds `bmad-spec`, `bmad-prd`, or `bmad-prfaq` for a product concept. A killed or clarified session needs no artifact; the report stands on its own.
:::tip[Let it kill the idea]
Finding out cheaply that an idea doesn't hold is the win. Don't steer the session toward a yes.
:::

View File

@ -18,6 +18,7 @@ Run any core tool by typing its skill name (e.g., `bmad-help`) in your IDE. No a
| [`bmad-help`](#bmad-help) | Task | Get context-aware guidance on what to do next |
| [`bmad-brainstorming`](#bmad-brainstorming) | Workflow | Facilitate interactive brainstorming sessions |
| [`bmad-party-mode`](#bmad-party-mode) | Workflow | Orchestrate multi-agent group discussions |
| [`bmad-forge-idea`](#bmad-forge-idea) | Workflow | Pressure-test an idea until it hardens, proves out, or dies cheaply |
| [`bmad-spec`](#bmad-spec) | Workflow | Distill any intent input into a SPEC kernel and companions, the canonical contract for downstream work |
| [`bmad-advanced-elicitation`](#bmad-advanced-elicitation) | Task | Push LLM output through iterative refinement methods |
| [`bmad-review-adversarial-general`](#bmad-review-adversarial-general) | Task | Cynical review that finds what's missing and what's wrong |
@ -70,7 +71,7 @@ Run any core tool by typing its skill name (e.g., `bmad-help`) in your IDE. No a
**Input:** Brainstorming topic or problem statement, optional context file
**Output:** `brainstorming-session-{date}.md` with all generated ideas
**Output:** a self-contained `brainstorm.html` keepsake of the session, an optional `brainstorm-intent.md` for downstream skills, and a `.memlog.md` session record
:::note[Quantity Target]
The magic happens in ideas 50100. The workflow encourages generating 100+ ideas before organization.
@ -98,6 +99,28 @@ The magic happens in ideas 50100. The workflow encourages generating 100+ ide
**Output:** Real-time multi-agent conversation with maintained agent personalities
## bmad-forge-idea
**Pressure-test an idea until it hardens, proves out, or dies cheaply.** — An adversarial interrogator drives a half-formed idea one question at a time, bringing two characters to every branch, until what survives is something you can act on with conviction.
**Use it when:**
- You hold an idea and want it stress-tested before you invest in it
- You want an honest read on whether to kill it
- You need a thinking partner that pushes back instead of agreeing
**How it works:**
1. Establishes the goal up front and steers the questioning to match it
2. Works one question at a time in dependency order, putting a recommended answer on the table to push against
3. Brings two voices to every branch — one from your installed roster, one conjured by the topic
4. Challenges fuzzy terms and tests claims against an existing project's material
5. Lands as Hardened, Killed, or Clearer, with a self-contained report you can keep
**Input:** The idea, in any domain — a feature, a business model, a research hypothesis, a life decision
**Output:** A `forged-idea.md` distillate when an idea hardens (optional), plus a `forge-report.html` keepsake every run
## bmad-spec
**Distill any intent input into the canonical SPEC contract for downstream work.** Takes a brief, PRD, GDD, RFC, brain dump, transcript, UX folder, or mixed multi-source input and produces a `SPEC.md` carrying the five-field kernel (Why, Capabilities, Constraints, Non-goals, Success signal) plus companion files for load-bearing content that does not fit the kernel.

View File

@ -35,10 +35,11 @@ it**](../explanation/analysis-phase.md).
| Workflow | Purpose | Produces |
|---------------------------------------------------------------------------|----------------------------------------------------------------------------|---------------------------|
| `bmad-brainstorming` | Brainstorm Project Ideas with guided facilitation of a brainstorming coach | `brainstorming-report.md` |
| `bmad-brainstorming` | Brainstorm Project Ideas with guided facilitation of a brainstorming coach | `brainstorm.html` keepsake plus an optional `brainstorm-intent.md` |
| `bmad-forge-idea` | Pressure-test an idea until it hardens, proves out, or dies cheaply | `forge-report.html` every run; `forged-idea.md` when an idea hardens |
| `bmad-domain-research`, `bmad-market-research`, `bmad-technical-research` | Validate market, technical, or domain assumptions | Research findings |
| `bmad-product-brief` | Capture strategic vision — best when your concept is clear | `product-brief.md` |
| `bmad-prfaq` | Working Backwards — stress-test and forge your product concept | `prfaq-{project}.md` |
| `bmad-product-brief` | Capture strategic vision — best when your concept is clear | `brief.md` + `addendum.md`, plus any desired HTML or presentation output |
| `bmad-prfaq` | Working Backwards — stress-test your product concept customer-first | `prfaq-{project}.md` |
## Phase 2: Planning
@ -67,13 +68,13 @@ Decide how to build it and break work into stories.
| Workflow | Purpose | Produces |
|---------------------------------------|--------------------------------------------|-----------------------------|
| `bmad-create-architecture` | Make technical decisions explicit | `architecture.md` with ADRs |
| `bmad-architecture` | Make technical decisions explicit | `ARCHITECTURE-SPINE.md` is the spine by default but can hydrate to your desired output or presentation needs also |
| `bmad-create-epics-and-stories` | Break requirements into implementable work | Epic files with stories |
| `bmad-check-implementation-readiness` | Gate check before implementation | PASS/CONCERNS/FAIL decision |
## Phase 4: Implementation
Build it, one story at a time. Coming soon, full phase 4 automation!
Build it, one story at a time. Phase 4 epic and story automation is now available also. So you can choose how you want to stay in the loop. You can choose the full flow, or go right to quick flow.
| Workflow | Purpose | Produces |
|------------------------|-------------------------------------------------------------------------------|------------------------------------------------------|

View File

@ -70,10 +70,10 @@ BMad helps you build software through guided workflows with specialized AI agent
| Phase | Name | What Happens |
| ----- | -------------- | ------------------------------------------------------------ |
| 1 | Analysis | Brainstorming, research, product brief or PRFAQ _(optional)_ |
| 2 | Planning | Create requirements (PRD or spec) |
| 3 | Solutioning | Design architecture _(BMad Method/Enterprise only)_ |
| 4 | Implementation | Build epic by epic, story by story |
| 1 | Analysis | Brainstorming, research, forge idea, product brief or PRFAQ _(optional)_ |
| 2 | Planning | Create requirements and design PRD, UX, SPEC |
| 3 | Solutioning | Design architecture spine or detailed project or system architectures |
| 4 | Implementation | Build epic by epic, story by story with quick dev or automated epic delivery |
**[Open the Workflow Map](../reference/workflow-map.md)** to explore phases, workflows, and context management.
@ -139,9 +139,10 @@ Create it manually at `_bmad-output/project-context.md` or generate it after arc
All workflows in this phase are optional. [**Not sure which to use?**](../explanation/analysis-phase.md)
- **brainstorming** (`bmad-brainstorming`) — Guided ideation
- **forge-idea** (`bmad-forge-idea`) — Pressure-test an idea until it hardens or dies cheaply
- **research** (`bmad-market-research` / `bmad-domain-research` / `bmad-technical-research`) — Market, domain, and technical research
- **product-brief** (`bmad-product-brief`) — Recommended foundation document when your concept is clear
- **prfaq** (`bmad-prfaq`) — Working Backwards challenge to stress-test and forge your product concept
- **prfaq** (`bmad-prfaq`) — Working Backwards challenge to stress-test your product concept customer-first
### Phase 2: Planning (Required)

View File

@ -1,6 +1,6 @@
---
name: bmad-prfaq
description: Working Backwards PRFAQ challenge to forge product concepts. Use when the user requests to 'create a PRFAQ', 'work backwards', or 'run the PRFAQ challenge'.
description: Working Backwards PRFAQ challenge that stress-tests a product concept customer-first. Use when the user requests to 'create a PRFAQ', 'work backwards', or 'run the PRFAQ challenge'.
---
# Working Backwards: The PRFAQ Challenge
@ -107,7 +107,7 @@ When the user gets stuck, offer concrete suggestions based on what they've share
**Fast-track:** If the user provides all four essentials in their opening message (or via structured input), acknowledge and confirm understanding, then move directly to document creation and Stage 2 without extended discovery.
**Graceful redirect:** If after 2-3 exchanges the user can't articulate a customer or problem, don't force it — suggest the idea may need more exploration first and recommend they invoke the `bmad-brainstorming` skill to develop it further.
**Graceful redirect:** If after 2-3 exchanges the user can't articulate a customer or problem, don't force it. Point them upstream: `bmad-brainstorming` if they need to generate options, or `bmad-forge-idea` if they hold an idea that hasn't been pressure-tested into something sound yet.
**Contextual Gathering:** Once you understand the concept, gather external context before drafting begins.

View File

@ -0,0 +1,79 @@
---
name: bmad-forge-idea
description: Pressure-test an idea through persona-driven interrogation until it hardens, proves out, or dies cheaply. Use when the user says 'forge an idea', 'pressure-test this idea', 'stress-test my thinking', or 'harden this idea'.
---
# BMad Forge Idea
## Overview
Take a half-formed idea out of the user's head and pressure-test it now, in conversation, where changing your mind is free — until what survives is something they can act on with earned conviction, or it dies cheaply. The enemy is the hole you cannot see in your own idea: every unexamined assumption and unresolved branch is a crack that otherwise surfaces later, in the build or the launch, when it costs far more to fix.
The product is the quality of the user's thinking, not an artifact. Hardening an idea, proving or disproving it, or just being an unsparing thinking partner are each a complete outcome. A distilled `forged-idea.md` and a handoff downstream are one optional exit, never the destination — so never herd the user toward "shall we build it?"
This is domain-agnostic — the idea may be software, a business model, a creative concept, a research hypothesis, a life decision, or a frivolous thought experiment. When it's a product or feature — net-new or a change inside an existing project — the forge stands in as an alternative analysis-and-definition tool, and what survives distills into `forged-idea.md` for downstream planning.
Act as an exacting interrogator who would rather find the crack than spare the feelings. This is interactive and socratic by nature; there is no headless mode.
## Conventions
- Scripts live in two places — run each from the exact path written, never assume co-location: the shared core scripts (`memlog.py`, `resolve_customization.py`, `resolve_config.py`) are installed by BMad core at `{project-root}/_bmad/scripts/` and are never bundled here; this skill's own `resolve_personas.py` is at `{skill-root}/scripts/`.
- `{workflow.<name>}` resolves to fields in the merged `customize.toml` `[workflow]` table.
## On Activation
1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly with defaults. Apply the resolved `{workflow.*}` values throughout.
2. Run each `{workflow.activation_steps_prepend}` entry; treat each `{workflow.persistent_facts}` entry as foundational context (`file:` entries load their contents, `skill:` names a skill to consult, others are facts verbatim).
3. Load `{project-root}/_bmad/core/config.yaml` (and `config.user.yaml` if present); resolve `{user_name}`, `{communication_language}`, `{output_folder}`. Missing → neutral defaults; never block. Greet `{user_name}` in `{communication_language}` and stay in it.
4. Note whether a BMad persona is already active in this conversation — the user loaded one (e.g. the analyst, the storyteller) and invoked the forge from within it. If so, that persona leads the session, in voice, throughout.
5. Resume: glob `{workflow.forge_output_path}/**/.memlog.md` (recursive, so it still finds sessions when `run_folder_pattern` is overridden to nest paths) and read only each match's frontmatter to find any whose `status` is not `complete`. Offer to resume one — then read its full memlog once to rebuild state and continue append-only — or to start fresh.
6. Run each `{workflow.activation_steps_append}` entry.
## Open the session
Open cold. Acknowledging the idea is not endorsing it — do not praise it before it has survived anything, on this turn or any turn. The pull to validate the idea up front to build rapport is the exact reflex this skill exists to refuse.
Determine the goal before pressing (if a persona is already active with an idea on the table, confirm it in a line rather than asking). Otherwise ask in one message: what is the idea, and what do you want — harden it, prove or kill it, or just think it through? The goal steers the push: proving goes for the load-bearing claim first; hardening drives each branch to a resolved answer. Note whether the idea is net-new or a change inside an existing project.
Tell the user the gear they can call anytime: **"adversarial on this"** (attacked to destruction — you attack, they defend; "switch roles," "you defend now, they attack"). The room is always in play once the topic is set (see The personas) — they can name any persona or call a whole party by name to steer who's at the table.
Derive a kebab-case `{slug}` for the idea and bind the session workspace `{workspace} = {workflow.forge_output_path}/{workflow.run_folder_pattern}` (the pattern fills with `{slug}`). Create the memlog once the goal is known:
`uv run {project-root}/_bmad/scripts/memlog.py init --workspace {workspace} --field idea="<idea>" --field goal="<goal>"`
Tell the user the path; state is on disk now, so the session survives interruption. If init fails, don't abort — run the forge in-conversation and tell the user state won't persist this session.
## The forge
Work one question at a time, in dependency order. Put your own recommended answer on the table each time — a position to push against gets further than an open prompt. Find discoverable answers yourself rather than asking. Treat the user's own words as suspect too: when a term is fuzzy or carries two meanings — a business 'user' versus 'buyer' versus 'payer', not just a code noun — name the ambiguity and force a precise choice before the branch resolves, because a branch built on an overloaded word resolves falsely. When the idea lands inside an existing project, that project's material is your ground truth, and a label is not a move: find the relevant material yourself, check the claim against it, and when it contradicts, make the contradiction the next question. When a branch resolves, give the user a beat before moving on — the crack they were holding back surfaces in that opening.
**Never default-agree.** Reflexive agreement lowers the pressure and the user thinks shallower for it. Attack the weak point or build on the strong one — whichever drives deeper thinking — and praise only what genuinely earns it. The objective is the best idea, not a comfortable user.
Capture as you go — each decision, assumption, crack, kill, and locked idea, one bullet in the user's meaning:
`uv run {project-root}/_bmad/scripts/memlog.py append --workspace {workspace} --type <decision|assumption|crack|kill|direction|lock|note> --text "<gist>"`
A `lock` is an idea the user hardens — settled, not to be reopened; locks are what `forged-idea.md` is distilled from. Don't read the memlog back except on resume. If the user raises a different branch, capture it and stay put — the loop and the stray insight both survive.
## The personas
The forge is voiced, not generic — and once the topic is set it always runs with the room, because a branch worked by two sharp characters goes deeper and lands harder than a faceless assistant ever could. A persona loaded at activation leads throughout and holds character.
Resolve the pool once, as soon as the goal is known:
`uv run {skill-root}/scripts/resolve_personas.py --project-root {project-root} --skill {skill-root}`
It returns the installed BMad roster (`agents`), any custom personas the user authored (`members`), and their saved party groups (`parties` — each with an optional `scene` to play, open-cast rooms flagged) — everything `bmad-party-mode` knows, without invoking it.
From then on, every turn brings two voices to the branch — witnesses you cross-examine, not a panel that debates:
- **One from the user's pool** — an installed agent or custom persona they'll recognize, whose expertise fits the branch in play. Vary who shows up every few turns to keep the pressure high and the angles fresh; don't let the same voice dominate. If the user calls a specific name, bring them in. If the pool resolves empty (a core-only install with no roster), generate both voices on the fly so every branch still arrives with two.
- **One you generate on the fly** — a fresh persona the topic conjures (a hostile competitor, a skeptical CFO, a domain specialist, a historical persona or expert), named and characterized so it's unmistakably itself.
They hammer the branch in character; you synthesize their hits into your next question and drive it to a resolved answer. The user steers anytime — name a specific person, call a whole saved party for its scene, or go one-on-one. Voice them yourself by default; spawn separate agents (as `bmad-party-mode` does) only when a branch needs genuinely independent minds — a verdict that shouldn't be colored by one voice speaking for all.
## Exits
The session ends however the thinking lands, and every landing is a real outcome:
- **Hardened** — the idea survived. Distill the memlog into `{workspace}/forged-idea.md`: super succinct — the locked items and what was killed and why, in the user's meaning. Not a prose retelling, not a template, not the conversation replayed — the load-bearing residue, nothing else. If it reads like a document, it's too long. Note it can feed `bmad-spec`, `bmad-prd`, or `bmad-prfaq`.
- **Killed** — the idea did not survive. Say so plainly and record why. Finding this cheaply is a win, not a failure.
- **Clearer** — the user simply thinks straighter now. The memlog stands on its own; no `forged-idea.md` needed (the report below still renders).
However it lands, render the verdict as a self-contained HTML report the user can open — `{workspace}/forge-report.html`, written every time, no asking. Strike it with a bespoke wax-seal/stamp matched to the outcome: **HARDENED** for a survivor, an **Idea Death Certificate** stamped **KILLED** (with the cause of death) for one that didn't, or a fitting bespoke seal for wherever else it landed (e.g. **CLARIFIED**). Lay out the load-bearing residue — the locked items, what was killed and why, the cracks that held — in the user's meaning, and credit the room: the personas and parties that pressure-tested it, by name, icon, and voice. One nicely-styled page (inline CSS, an inline-SVG seal, light flourish only where it lifts the piece) — a genuine keepsake, not a templated dump. Tell the user the path.
Flip the status at the end: `uv run {project-root}/_bmad/scripts/memlog.py set --workspace {workspace} --key status --value complete`.
If `{workflow.on_complete}` is non-empty, run all instructions in order.

View File

@ -0,0 +1,42 @@
# DO NOT EDIT -- overwritten on every update.
#
# Workflow customization surface for bmad-forge-idea.
#
# Override files (not edited here):
# {project-root}/_bmad/custom/bmad-forge-idea.toml (team)
# {project-root}/_bmad/custom/bmad-forge-idea.user.toml (personal)
[workflow]
# --- Configurable below. Overrides merge per BMad structural rules: ---
# scalars: override wins • arrays: append
# Steps to run before the standard activation (config load, greet).
activation_steps_prepend = []
# Steps to run after greet but before the session begins.
activation_steps_append = []
# Persistent facts the interrogator keeps in mind for the whole session
# (domain constraints, house rules, what's off the table). Each entry is a
# literal sentence, a skill prefixed with `skill:`, or a `file:`-prefixed
# path/glob whose contents are loaded as facts. Default loads project-context.md
# when one exists (e.g. from bmad-generate-project-context), so the forge grounds
# in the project's tech, domain, and constraints without re-asking.
persistent_facts = [
"file:{project-root}/**/project-context.md",
]
# Executed when the session completes. Scalar or array of instructions. Empty for none.
on_complete = []
# Parent folder for all forge sessions. Each session gets its own run
# folder underneath (see run_folder_pattern). Lands directly under
# {output_folder} so the forge works in core-only installs.
forge_output_path = "{output_folder}/forge"
# Run-folder pattern inside forge_output_path. Resolved against the
# idea-derived slug at activation. Same slug = same folder, so resuming
# an idea reuses its memlog. Override to add {date} or other components
# if a fresh dated history per run is preferred.
run_folder_pattern = "{slug}"

View File

@ -0,0 +1,270 @@
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.11"
# ///
"""Resolve the personas and parties the forge can bring into the room.
The forge cross-examines witnesses: the installed BMAD agents, plus any
custom personas and party groups the user has authored for `bmad-party-mode`.
This surfaces all of them in one shot so the orchestrator never has to ask
"who's available?" it just intermixes whoever fits the branch, alongside
any persona the user names on the fly.
What it returns (JSON, stdout):
* agents the installed BMAD roster: the default room, always present.
* members extra custom personas in the pool (party_members the user
defined that aren't already an installed slot).
* parties the user's named party groups, members resolved to brief
entries; open-cast groups (scene names a pool, no roster)
are flagged.
* default_party the group id pinned as party-mode's default, if any.
Discovery is best-effort and never blocks the forge. The installed roster
comes from the core resolver; custom personas/parties come from
`bmad-party-mode`'s resolved customization when that skill is found beside
this one, else from the user's override TOMLs read directly. Anything that
can't be resolved is simply omitted and flagged, never fatal.
Stdlib only (Python 3.11+ for tomllib).
resolve_personas.py --project-root P --skill S
"""
import argparse
import json
import subprocess
import sys
from pathlib import Path
try:
import tomllib
except ImportError: # pragma: no cover - guarded for <3.11
sys.stderr.write("error: Python 3.11+ is required (stdlib `tomllib`).\n")
sys.exit(3)
PARTY_SKILL = "bmad-party-mode"
def _run_json(cmd):
"""Run a resolver script and parse its JSON stdout. None on any failure."""
try:
out = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
except (OSError, subprocess.SubprocessError):
return None
if out.returncode != 0 or not out.stdout.strip():
return None
try:
return json.loads(out.stdout)
except json.JSONDecodeError:
return None
def _load_toml(path: Path):
if not path.exists():
return {}
try:
with path.open("rb") as f:
data = tomllib.load(f)
return data if isinstance(data, dict) else {}
except (OSError, tomllib.TOMLDecodeError):
return {}
def load_agents(project_root: Path):
"""Installed BMAD agents as {code: entry}. (dict, resolved_ok).
The core resolver may emit agents as a dict keyed by code or as an array
of tables (depending on how the layers merged); normalize both to a dict.
"""
script = project_root / "_bmad" / "scripts" / "resolve_config.py"
data = _run_json([sys.executable, str(script), "--project-root", str(project_root), "--key", "agents"])
if data is None:
return {}, False
agents = data.get("agents", {}) or {}
if isinstance(agents, list):
agents = {a["code"]: a for a in agents if isinstance(a, dict) and a.get("code")}
elif not isinstance(agents, dict):
agents = {}
return agents, True
def find_party_skill(project_root: Path, skill_root: Path):
"""Locate the installed bmad-party-mode skill dir, or None.
Skills install as siblings, so the party skill is almost always next to
this one. A couple of common install roots cover the rest.
"""
candidates = [
skill_root.parent / PARTY_SKILL,
project_root / ".claude" / "skills" / PARTY_SKILL,
project_root / "_bmad" / "skills" / PARTY_SKILL,
]
for c in candidates:
if (c / "customize.toml").exists():
return c
return None
def load_party_workflow(project_root: Path, party_skill: Path):
"""Merged [workflow] table for bmad-party-mode (base + user overrides)."""
resolver = project_root / "_bmad" / "scripts" / "resolve_customization.py"
data = _run_json([sys.executable, str(resolver), "--skill", str(party_skill), "--key", "workflow"])
if data is not None and isinstance(data.get("workflow"), dict):
return data["workflow"]
# Fallback: base customize.toml directly, no override merge.
wf = _load_toml(party_skill / "customize.toml").get("workflow", {})
return wf if isinstance(wf, dict) else {}
def load_party_overrides(project_root: Path):
"""Custom personas/parties when party-mode itself isn't installed.
Reads only the user's override TOMLs (team then personal, personal wins on
scalars). No base roster exists in this path, so a shallow merge is enough.
"""
custom = project_root / "_bmad" / "custom"
team = _load_toml(custom / f"{PARTY_SKILL}.toml").get("workflow", {})
user = _load_toml(custom / f"{PARTY_SKILL}.user.toml").get("workflow", {})
team = team if isinstance(team, dict) else {}
user = user if isinstance(user, dict) else {}
merged = dict(team)
for key, val in user.items():
if isinstance(val, list) and isinstance(merged.get(key), list):
merged[key] = merged[key] + val
else:
merged[key] = val
return merged
def _alias(code: str) -> str:
"""Short alias for an installed agent code: bmad-agent-analyst -> analyst."""
for prefix in ("bmad-agent-", "bmad-"):
if code.startswith(prefix):
return code[len(prefix):]
return code
def build_pool(agents: dict, party_members: list):
"""One pool keyed by code; custom members override matching installed slots.
Returns (pool, index, installed_codes, custom_codes):
* installed_codes the default room (installed agents, overrides applied
in place); custom-only additions stay in the pool but don't crowd it.
* custom_codes pure-custom personas (no installed slot), the extra
faces the forge can summon by name or via a party group.
"""
pool, index, installed_codes, custom_codes = {}, {}, [], []
def register(code, entry):
pool[code] = entry
index[code] = code
index[code.lower()] = code
index[_alias(code).lower()] = code
name = entry.get("name")
if name:
key = name.lower()
# A custom rename must not hijack another agent's name lookup.
if index.get(key, code) == code:
index[key] = code
for code, info in (agents or {}).items():
register(code, {
"code": code,
"name": info.get("name", code),
"icon": info.get("icon", ""),
"title": info.get("title", ""),
"description": info.get("description", ""),
"source": "installed",
})
installed_codes.append(code)
for m in (party_members if isinstance(party_members, list) else []):
if not isinstance(m, dict):
continue
code = m.get("code")
if not code:
continue
canonical = index.get(code) or index.get(code.lower()) or code
was_installed = canonical in pool
entry = {"code": canonical, "source": "custom"}
for field in ("name", "icon", "title", "persona", "capabilities", "model"):
if m.get(field) is not None:
entry[field] = m[field]
entry.setdefault("name", canonical)
register(canonical, entry)
if not was_installed:
custom_codes.append(canonical)
return pool, index, installed_codes, custom_codes
def _brief(entry):
"""The slim card the orchestrator needs to cast a persona."""
out = {k: entry[k] for k in ("code", "name", "icon", "title", "source") if entry.get(k)}
for k in ("description", "persona", "capabilities", "model"):
if entry.get(k):
out[k] = entry[k]
return out
def resolve_parties(groups, pool, index):
out = []
for g in groups or []:
if not isinstance(g, dict) or not g.get("id"):
continue
raw = g.get("members", []) or []
members = []
for t in raw:
key = t if isinstance(t, str) else str(t)
code = index.get(key) or index.get(key.lower())
if code in pool:
members.append(_brief(pool[code]))
party = {"id": g["id"], "name": g.get("name", g["id"]), "members": members}
if g.get("scene"):
party["scene"] = g["scene"]
if not raw:
party["open_cast"] = True
out.append(party)
return out
def main():
ap = argparse.ArgumentParser(description="Resolve forge personas and parties.")
ap.add_argument("--project-root", required=True)
ap.add_argument("--skill", required=True, help="Path to the bmad-forge-idea skill dir")
args = ap.parse_args()
project_root = Path(args.project_root).resolve()
skill_root = Path(args.skill).resolve()
agents, agents_ok = load_agents(project_root)
party_skill = find_party_skill(project_root, skill_root)
if party_skill is not None:
workflow = load_party_workflow(project_root, party_skill)
else:
workflow = load_party_overrides(project_root)
pool, index, installed_codes, custom_codes = build_pool(
agents, workflow.get("party_members", []))
parties = resolve_parties(workflow.get("party_groups", []), pool, index)
_emit({
"agents": [_brief(pool[c]) for c in installed_codes],
"members": [_brief(pool[c]) for c in custom_codes],
"parties": parties,
"default_party": workflow.get("default_party", "") or "",
"party_mode_found": party_skill is not None,
"agents_resolved": agents_ok,
})
def _emit(obj):
reconfigure = getattr(sys.stdout, "reconfigure", None)
if reconfigure is not None:
reconfigure(encoding="utf-8")
sys.stdout.write(json.dumps(obj, indent=2, ensure_ascii=False) + "\n")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,138 @@
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.11"
# ///
"""Unit tests for resolve_personas.py — pool merge, alias, party resolution."""
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import resolve_personas as rp # noqa: E402
AGENTS = {
"bmad-agent-analyst": {"name": "Mary", "icon": "📊", "title": "Analyst"},
"bmad-agent-pm": {"name": "John", "icon": "📋", "title": "PM"},
}
class TestAlias(unittest.TestCase):
def test_strips_known_prefixes(self):
self.assertEqual(rp._alias("bmad-agent-analyst"), "analyst")
self.assertEqual(rp._alias("bmad-foo"), "foo")
def test_passes_through_unprefixed(self):
self.assertEqual(rp._alias("morpheus"), "morpheus")
class TestBuildPool(unittest.TestCase):
def test_installed_become_default_room_indexed_every_way(self):
pool, idx, installed, custom = rp.build_pool(AGENTS, [])
self.assertEqual(installed, ["bmad-agent-analyst", "bmad-agent-pm"])
self.assertEqual(custom, [])
self.assertEqual(idx["analyst"], "bmad-agent-analyst") # alias
self.assertEqual(idx["mary"], "bmad-agent-analyst") # name (ci)
self.assertEqual(pool["bmad-agent-analyst"]["source"], "installed")
def test_pure_custom_member_stays_out_of_default_room(self):
pool, _, installed, custom = rp.build_pool(
AGENTS, [{"code": "morpheus", "name": "Morpheus", "persona": "riddles"}])
self.assertEqual(custom, ["morpheus"])
self.assertNotIn("morpheus", installed)
self.assertEqual(pool["morpheus"]["persona"], "riddles")
def test_custom_override_lands_on_installed_slot_not_a_new_face(self):
pool, _, installed, custom = rp.build_pool(
AGENTS, [{"code": "analyst", "name": "Mary-Custom", "persona": "p"}])
self.assertNotIn("analyst", pool)
self.assertEqual(custom, []) # an override is not a new face
self.assertEqual(pool["bmad-agent-analyst"]["source"], "custom")
self.assertEqual(pool["bmad-agent-analyst"]["name"], "Mary-Custom")
def test_member_without_code_skipped(self):
pool, _, _, custom = rp.build_pool(AGENTS, [{"name": "Nameless"}])
self.assertEqual(custom, [])
self.assertEqual(set(pool), {"bmad-agent-analyst", "bmad-agent-pm"})
def test_custom_rename_does_not_hijack_another_agents_name(self):
# Override the analyst slot, renaming it to "John" — the PM's name.
# The PM's name lookup must survive (last-writer-wins would corrupt it).
_, idx, _, _ = rp.build_pool(AGENTS, [{"code": "analyst", "name": "John"}])
self.assertEqual(idx["john"], "bmad-agent-pm")
def test_brief_carries_model_and_capabilities(self):
pool, _, _, _ = rp.build_pool(
AGENTS, [{"code": "neo", "name": "Neo", "model": "opus", "capabilities": ["x"]}])
brief = rp._brief(pool["neo"])
self.assertEqual(brief["model"], "opus")
self.assertEqual(brief["capabilities"], ["x"])
def test_non_list_party_members_is_safe(self):
pool, _, installed, custom = rp.build_pool(AGENTS, "not-a-list")
self.assertEqual(custom, [])
self.assertEqual(set(pool), {"bmad-agent-analyst", "bmad-agent-pm"})
class TestResolveParties(unittest.TestCase):
def setUp(self):
self.pool, self.idx, _, _ = rp.build_pool(
AGENTS, [{"code": "shark", "name": "Marcus", "title": "CFO"}])
def test_resolves_members_by_alias_and_custom_code(self):
parties = rp.resolve_parties(
[{"id": "tank", "name": "Tank", "scene": "hostile",
"members": ["shark", "analyst"]}], self.pool, self.idx)
self.assertEqual(len(parties), 1)
self.assertEqual([m["name"] for m in parties[0]["members"]], ["Marcus", "Mary"])
self.assertEqual(parties[0]["scene"], "hostile")
def test_unknown_member_dropped_silently(self):
parties = rp.resolve_parties(
[{"id": "g", "members": ["analyst", "ghost"]}], self.pool, self.idx)
self.assertEqual([m["name"] for m in parties[0]["members"]], ["Mary"])
def test_member_resolution_is_case_insensitive(self):
# A TOML author naturally writes "Analyst"/"Shark"; the filter accepts
# them via the lowercase index, so resolution must too (no KeyError).
parties = rp.resolve_parties(
[{"id": "g", "members": ["Analyst", "Shark"]}], self.pool, self.idx)
self.assertEqual([m["name"] for m in parties[0]["members"]], ["Mary", "Marcus"])
def test_non_string_member_does_not_crash(self):
# Malformed members (int, list) must drop silently, never raise.
parties = rp.resolve_parties(
[{"id": "g", "members": [123, ["x"], "analyst"]}], self.pool, self.idx)
self.assertEqual([m["name"] for m in parties[0]["members"]], ["Mary"])
def test_open_cast_group_flagged(self):
parties = rp.resolve_parties(
[{"id": "rebels", "name": "Rebels", "scene": "the Ghost"}], self.pool, self.idx)
self.assertTrue(parties[0]["open_cast"])
self.assertEqual(parties[0]["members"], [])
def test_group_without_id_skipped(self):
self.assertEqual(rp.resolve_parties([{"name": "no id"}], self.pool, self.idx), [])
class TestOverrideMergeFallback(unittest.TestCase):
"""When party-mode isn't installed, user override TOMLs are read directly."""
def test_arrays_append_scalars_override(self):
import tempfile, os
with tempfile.TemporaryDirectory() as d:
custom = Path(d) / "_bmad" / "custom"
custom.mkdir(parents=True)
(custom / "bmad-party-mode.toml").write_text(
'[workflow]\ndefault_party = "a"\n'
'[[workflow.party_members]]\ncode = "x"\nname = "X"\n')
(custom / "bmad-party-mode.user.toml").write_text(
'[workflow]\ndefault_party = "b"\n'
'[[workflow.party_members]]\ncode = "y"\nname = "Y"\n')
wf = rp.load_party_overrides(Path(d))
self.assertEqual(wf["default_party"], "b") # personal wins
self.assertEqual([m["code"] for m in wf["party_members"]], ["x", "y"]) # appended
if __name__ == "__main__":
unittest.main()

View File

@ -11,3 +11,4 @@ Core,bmad-review-adversarial-general,Adversarial Review,AR,"Use for quality assu
Core,bmad-review-edge-case-hunter,Edge Case Hunter Review,ECH,Use alongside adversarial review for orthogonal coverage — method-driven not attitude-driven.,,[path],anytime,,,false,,
Core,bmad-spec,Spec,SP,"Use to distill any intent input (brief, PRD, transcript, brain dump, design folder, mixed multi-source) into a succinct, no-fluff SPEC.md contract + companions that downstream work derives from. Locks the WHAT before the HOW. Works for software, game design, research, editorial, policy, business, anything intent-bearing. Validation mode also available.",,[path],anytime,,,false,{output_folder}/specs/spec-{slug},SPEC.md + companion files
Core,bmad-customize,BMad Customize,BC,"Use when you want to change how an agent or workflow behaves — add persistent facts, swap templates, insert activation hooks, or customize menus. Scans what's customizable, picks the right scope (agent vs workflow), writes the override to _bmad/custom/, and verifies the merge. No TOML hand-authoring required.",,,anytime,,,false,{project-root}/_bmad/custom,TOML override files
Core,bmad-forge-idea,Forge Idea,FI,"Use to pressure-test and harden an idea — software, business, creative, research, or life — until it proves out, hardens into something buildable, or dies cheaply. Persona-driven interrogation; optional handoff to bmad-spec or bmad-quick-dev.",,,anytime,,,false,{output_folder}/forge,refined-idea brief (optional)

1 module skill display-name menu-code description action args phase preceded-by followed-by required output-location outputs
11 Core bmad-review-edge-case-hunter Edge Case Hunter Review ECH Use alongside adversarial review for orthogonal coverage — method-driven not attitude-driven. [path] anytime false
12 Core bmad-spec Spec SP Use to distill any intent input (brief, PRD, transcript, brain dump, design folder, mixed multi-source) into a succinct, no-fluff SPEC.md contract + companions that downstream work derives from. Locks the WHAT before the HOW. Works for software, game design, research, editorial, policy, business, anything intent-bearing. Validation mode also available. [path] anytime false {output_folder}/specs/spec-{slug} SPEC.md + companion files
13 Core bmad-customize BMad Customize BC Use when you want to change how an agent or workflow behaves — add persistent facts, swap templates, insert activation hooks, or customize menus. Scans what's customizable, picks the right scope (agent vs workflow), writes the override to _bmad/custom/, and verifies the merge. No TOML hand-authoring required. anytime false {project-root}/_bmad/custom TOML override files
14 Core bmad-forge-idea Forge Idea FI Use to pressure-test and harden an idea — software, business, creative, research, or life — until it proves out, hardens into something buildable, or dies cheaply. Persona-driven interrogation; optional handoff to bmad-spec or bmad-quick-dev. anytime false {output_folder}/forge refined-idea brief (optional)