Compare commits

..

No commits in common. "b7bc837ff4c704f4685129c6a54c6c299bc4e5e0" and "3970ace79310bb09b255d0fefe999e9bde0595be" have entirely different histories.

103 changed files with 3508 additions and 3452 deletions

View File

@ -13,14 +13,13 @@
"name": "bmad-pro-skills", "name": "bmad-pro-skills",
"source": "./", "source": "./",
"description": "Next level skills for power users — advanced prompting techniques, agent management, and more.", "description": "Next level skills for power users — advanced prompting techniques, agent management, and more.",
"version": "6.8.0", "version": "6.6.0",
"author": { "author": {
"name": "Brian (BMad) Madison" "name": "Brian (BMad) Madison"
}, },
"skills": [ "skills": [
"./src/core-skills/bmad-help", "./src/core-skills/bmad-help",
"./src/core-skills/bmad-brainstorming", "./src/core-skills/bmad-brainstorming",
"./src/core-skills/bmad-customize",
"./src/core-skills/bmad-spec", "./src/core-skills/bmad-spec",
"./src/core-skills/bmad-party-mode", "./src/core-skills/bmad-party-mode",
"./src/core-skills/bmad-shard-doc", "./src/core-skills/bmad-shard-doc",
@ -36,13 +35,12 @@
"name": "bmad-method-lifecycle", "name": "bmad-method-lifecycle",
"source": "./", "source": "./",
"description": "Full-lifecycle AI development framework — agents and workflows for product analysis, planning, architecture, and implementation.", "description": "Full-lifecycle AI development framework — agents and workflows for product analysis, planning, architecture, and implementation.",
"version": "6.8.0", "version": "6.6.0",
"author": { "author": {
"name": "Brian (BMad) Madison" "name": "Brian (BMad) Madison"
}, },
"skills": [ "skills": [
"./src/bmm-skills/1-analysis/bmad-product-brief", "./src/bmm-skills/1-analysis/bmad-product-brief",
"./src/bmm-skills/1-analysis/bmad-prfaq",
"./src/bmm-skills/1-analysis/bmad-agent-analyst", "./src/bmm-skills/1-analysis/bmad-agent-analyst",
"./src/bmm-skills/1-analysis/bmad-agent-tech-writer", "./src/bmm-skills/1-analysis/bmad-agent-tech-writer",
"./src/bmm-skills/1-analysis/bmad-document-project", "./src/bmm-skills/1-analysis/bmad-document-project",
@ -51,22 +49,18 @@
"./src/bmm-skills/1-analysis/research/bmad-technical-research", "./src/bmm-skills/1-analysis/research/bmad-technical-research",
"./src/bmm-skills/2-plan-workflows/bmad-agent-pm", "./src/bmm-skills/2-plan-workflows/bmad-agent-pm",
"./src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer", "./src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer",
"./src/bmm-skills/2-plan-workflows/bmad-prd",
"./src/bmm-skills/2-plan-workflows/bmad-create-prd", "./src/bmm-skills/2-plan-workflows/bmad-create-prd",
"./src/bmm-skills/2-plan-workflows/bmad-edit-prd", "./src/bmm-skills/2-plan-workflows/bmad-edit-prd",
"./src/bmm-skills/2-plan-workflows/bmad-validate-prd", "./src/bmm-skills/2-plan-workflows/bmad-validate-prd",
"./src/bmm-skills/2-plan-workflows/bmad-ux", "./src/bmm-skills/2-plan-workflows/bmad-create-ux-design",
"./src/bmm-skills/3-solutioning/bmad-agent-architect", "./src/bmm-skills/3-solutioning/bmad-agent-architect",
"./src/bmm-skills/3-solutioning/bmad-architecture",
"./src/bmm-skills/3-solutioning/bmad-create-architecture", "./src/bmm-skills/3-solutioning/bmad-create-architecture",
"./src/bmm-skills/3-solutioning/bmad-check-implementation-readiness", "./src/bmm-skills/3-solutioning/bmad-check-implementation-readiness",
"./src/bmm-skills/3-solutioning/bmad-create-epics-and-stories", "./src/bmm-skills/3-solutioning/bmad-create-epics-and-stories",
"./src/bmm-skills/3-solutioning/bmad-generate-project-context", "./src/bmm-skills/3-solutioning/bmad-generate-project-context",
"./src/bmm-skills/4-implementation/bmad-agent-dev", "./src/bmm-skills/4-implementation/bmad-agent-dev",
"./src/bmm-skills/4-implementation/bmad-investigate",
"./src/bmm-skills/4-implementation/bmad-dev-story", "./src/bmm-skills/4-implementation/bmad-dev-story",
"./src/bmm-skills/4-implementation/bmad-quick-dev", "./src/bmm-skills/4-implementation/bmad-quick-dev",
"./src/bmm-skills/4-implementation/bmad-checkpoint-preview",
"./src/bmm-skills/4-implementation/bmad-sprint-planning", "./src/bmm-skills/4-implementation/bmad-sprint-planning",
"./src/bmm-skills/4-implementation/bmad-sprint-status", "./src/bmm-skills/4-implementation/bmad-sprint-status",
"./src/bmm-skills/4-implementation/bmad-code-review", "./src/bmm-skills/4-implementation/bmad-code-review",

2
.gitignore vendored
View File

@ -47,8 +47,6 @@ CLAUDE.local.md
.claude/settings.local.json .claude/settings.local.json
.junie/ .junie/
.agents/ .agents/
.analysis/
z*/ z*/
!docs/zh-cn/ !docs/zh-cn/

View File

@ -1,46 +1,5 @@
# Changelog # Changelog
## v6.9.0 - 2026-06-21
### ✨ Headline
**Reasoning skills get sharper and orchestration gets a memory.**
**bmad-forge-idea** is a new core skill that takes a half-formed idea and pressure-tests it one Socratic question at a time — with an adversarial attack mode and optional persona rooms — until the idea hardens, proves out, or dies cheaply.
**bmad-architecture** lands as a ground-up rewrite of the old multi-step create-architecture flow: a lean spine (`ARCHITECTURE-SPINE.md`) that is the source of truth, intent-based routing (Create/Update/Validate), a breadth-coverage rubric so no dimension is silently skipped, and an opt-in reviewer gate.
**party-mode** is reborn with creatable, savable custom parties, optional party memory, and many pacing and dynamics improvements.
**Under the hood:** a canonical shared **memlog** (`_bmad/scripts/memlog.py`) replaces per-skill decision logs and is now the standard working-memory primitive across the suite. The installer now checks for **uv** and reframes it as the standard way to run BMAD's Python scripts (`uv run`). Plus an **Astro 6** security upgrade clearing 8+ Dependabot advisories and two new platform targets.
### ⚠️ Upcoming Breaking Change (in v7) — standardizing on `uv`
The industry is converging on [**uv**](https://docs.astral.sh/uv/) for running Python, and BMAD is following. Today our skills use a **mix** of `uv run` and direct `python3` invocation. In the **v7 release, every skill that runs a Python script will standardize on `uv run`** instead of calling `python3` directly — `uv` provisions the interpreter and manages dependencies, so scripts run consistently regardless of what's on your PATH.
**What to do now:** install and set up `uv` ([docs](https://docs.astral.sh/uv/)) — or just ask your AI agent to "install and set up uv for me." Starting this release the installer checks for it and points you to setup if it's missing. `uv` is **not yet required** but without it some skills may have degraded performance or a shim AGENTS.md (or similar) or rule will need to be added to your environment to tell the agent when it sees uv run to use python3 instead. The best course of action though at this time is to install uv. A missing `uv` still warns rather than blocks, but it will be the assumed default in v7. Custom skills and overrides that shell out to `python3` should plan to migrate to `uv run`.
### 🎁 Features
* **bmad-forge-idea — new core skill** (#2492). Domain-agnostic idea pressure-testing for the analysis phase: Socratic, one-question-at-a-time interrogation with an adversarial attack mode and optional persona rooms resolved from the installed roster. Hardens or kills an idea cheaply; emits memlog residue and an optional brief that feeds bmad-spec or bmad-quick-dev. Interactive only (menu code FI).
* **bmad-architecture — lean spine rewrite** (#2467, #2475). Replaces the fixed-step `bmad-create-architecture` (retained as a forwarding shim, removed in v7) with intent-based routing across five entry shapes (raw idea, large doc, codebase, feature slice, existing spine). The spine (`ARCHITECTURE-SPINE.md`) is the source of truth and SPEC.md is derived from it. Adds a breadth-coverage rubric (every altitude-owned dimension decided/deferred/open), an opt-in reviewer gate that scales lenses to rigor, and a full non-interactive headless mode. `lint_spine.py` hardened with fence-blanking, robust column detection, and 28 regression tests.
* **party-mode: configurable parties + persistent memory** (#2479, #2484). Custom personas (`party_members`) and named rooms (`party_groups`, with optional scenes), four run modes (auto/session/subagent/agent-team), and a preloaded "Code Review Crew" of five adversarial lenses. Each party keeps append-only session memory under `{memory_dir}/<party_id>/` so sessions resume with prior context; ad-hoc casts stay ephemeral.
* **bmad-brainstorming: facilitation modes + visual composer** (#2445). Three modes (Facilitator / Creative Partner / Ideate for me), append-only memlog with optional `--by` authorship attribution, and a self-contained `brain-selector.html` composer (technique strategy, category chips, filter, copy-to-clipboard, dark mode). Catalog grows to 108 techniques (8 new classics: HMW, JTBD, Empathy Map, Backcasting, TRIZ, Fishbone, Build on What Works, Scenario Cross) plus a convergence phase.
* **Canonical shared memlog script** (#2462). New `src/scripts/memlog.py` — append-only chronological working memory with init/append/set ops, no lifecycle-status design, Python 3.8+ support, 30 tests. Any skill can call it at runtime.
* **Retrospective action items tracked in sprint-status** (#2465). The retrospective step appends an `action_items` section to `sprint-status.yaml`; sprint-status validates and surfaces open items, and sprint-planning preserves them on regenerate.
* **Installer checks for `uv` and reframes it as the standard** (#2495). Replaces the old python3 probe with a `uv` check, adds a heads-up to the install intro and a tip to the "BMAD is ready" summary, and updates docs/script docstrings (en/fr/vi-vn) to frame `uv run` as the standard and `python3` as the transition fallback. Migration-friendly: a missing `uv` warns and points you to setup, never blocks. See the Upcoming Breaking Change note above.
* **New installer platform targets: hermes-agent and CodeWhale** (#2489, #2459). hermes-agent added as a tool target; CodeWhale uses `.codewhale/skills/` (project) and `~/.codewhale/skills/` (global), both with test coverage.
### 🐛 Fixes
* **Astro 6 security upgrade clears Dependabot alerts** (#2493). Astro 5.18.1 → 6.4.6 and Starlight 0.37.5 → 0.40.0 (8 XSS/SSRF advisories), esbuild pinned to 0.28.1 (Windows dev-server file read), markdown-it 14.2.0 (smartquotes ReDoS), brace-expansion 5.0.6 (range DoS). Docs content config migrated to `src/content.config.ts`; page output verified identical to baseline.
* **Guard WSL installs from Windows Node** (#2470). Detects and prevents a Windows `node.exe` being used inside WSL, where it would silently fail.
* **Remove empty skill-group dirs after install** (#2461). Prunes empty parent dirs (e.g. `_bmad/bmm/1-analysis`) left after skill cleanup, with a path-boundary check to avoid sibling-dir collisions.
* **bmad-create-epics-and-stories discovers bmad-ux spine outputs** (#2446). Prerequisites now recognize `DESIGN.md` / `EXPERIENCE.md` alongside the legacy `ux-spec.md`.
* **Pass diff inline to the blind-hunter reviewer** (#2463). Diff output is passed inline in the subagent prompt rather than via a file the reviewer can't read, preventing context-starved hallucination.
* **Website: nav height for dual announcement banners** (#2473). Fixes layout crowding when two banners show at once.
* **Workflow clarity & numbering** — clarify quick-dev subagent use across code-review/create-story/quick-dev (#2450), renumber retrospective steps (#2448).
## v6.8.0 - 2026-05-25 ## v6.8.0 - 2026-05-25
### ✨ Headline ### ✨ Headline

View File

@ -1,76 +0,0 @@
---
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

@ -1,150 +1,59 @@
--- ---
title: "Party Mode" title: "Party Mode"
description: Get your AI agents in one conversation — run them, build your own cast, and choose how independently they think description: Multi-agent collaboration - get all your AI agents in one conversation
sidebar: sidebar:
order: 11 order: 11
--- ---
Party mode puts your AI agents in one room and lets them talk, to each other and to you. This page explains what a party is, the four ways it can run, how to build your own cast of personas instead of using the installed agents, and how a party remembers you between sessions. Get all your AI agents in one conversation.
## What is Party Mode? ## What is Party Mode?
Run `bmad-party-mode` and the BMad agents you already have installed gather in one conversation: the PM, Architect, Dev, UX Designer, and whoever else your selected modules bring. That installed lineup is your default party, ready with no setup. They answer in character, agree, disagree, and build on each other. You steer the room. Ask a follow-up, push back, pull one voice forward, or change the subject. The conversation runs until you end it. Run `bmad-party-mode` and you've got your whole AI team in one room - PM, Architect, Dev, UX Designer, whoever you need. Party Mode orchestrates the discussion, picking relevant installed agents per message. Agents respond in character, agree, disagree, and build on each other's ideas.
It works because the personas hold different priorities. The Architect guards the design, the PM guards scope, the Dev guards what's actually buildable. Put them in the same room and the tradeoff surfaces now, in the conversation, instead of three weeks into the sprint. The conversation continues as long as you want. Ask follow-ups, push back on answers, redirect the discussion - it's a real back-and-forth with your agents until you're done.
**Good for:** **Good for:**
- Decisions with real tradeoffs - Big decisions with tradeoffs
- Brainstorming and "what are we missing?" - Brainstorming sessions
- Post-mortems and retrospectives - Post-mortems when things go wrong
- Pressure-testing a plan before you commit - Sprint retrospectives and planning
Party mode is also a fast and genuinely fun way to brainstorm, since the personas have opinions and they clash. And you can start a party from inside any other workflow: mid-brainstorm, mid-PRD, while coding, working a sales angle, or shaping a creative piece. Any time you want more perspectives on what's in front of you, pull in a room without dropping what you were doing. ## Examples
:::note[Example] ### Calling Out Bad Architecture
**You:** Monolith or microservices for the MVP?
**Architect:** Start monolith. Microservices add operating cost you don't need at a thousand users. **You:** "Okay team, our last sprint was a disaster. The authentication service went down three times. Who screwed this up?"
**PM:** Agreed. Time to market matters more than scaling we can't prove yet. **Architect:** "The design was sound - distributed authentication with proper fallbacks. If implementation deviated from the spec..."
**Dev:** Monolith, but with clean module boundaries so we can split a service out later without a rewrite. **Dev:** "I followed the architecture doc exactly. The spec didn't account for concurrent session invalidation race conditions."
:::
**PM:** "Both of you missed the bigger issue - we didn't validate session management requirements in the PRD. This is on me for not catching it."
## Starting a party
**TEA:** "And I should've caught this in integration tests. The test scenarios didn't cover concurrent invalidation."
Invoke the skill and say what you want; it works out whether you mean to run a party or build one.
### Creative Brainstorming
| Goal | Type this |
| --- | --- | **You:** "How do we make onboarding feel magical instead of boring?"
| Start a party in the default mode | `/bmad-party-mode` |
| Start in a specific mode | `/bmad-party-mode --mode auto` (also `session`, `subagent`, `agent-team`) | **UX Designer:** "Start with progressive disclosure - reveal features as users need them, not all at once in a tutorial."
| Open a saved party | `/bmad-party-mode --party code-review-crew` |
| Conjure a cast on the spot | "party mode with the bridge crew of the Enterprise" | **Storyteller:** "What if onboarding was a story? Each step reveals a character's journey - the user IS the hero."
| Create or add a party | "party mode, create a new party" |
| Edit an existing party | "party mode, edit the writers' room" | **Game Designer:** "Building on that - what if the first 'quest' is actually solving a real user problem? They learn by doing something valuable."
| Customize the skill | `/bmad-customize bmad-party-mode` |
### Technical Decision
## How a party runs
**You:** "Monolith or microservices for MVP?"
A party can run in four modes. One mode is active per session, and it decides who does the thinking: a single model voicing everyone, or separate agents reasoning on their own.
**Architect:** "Start monolith. Microservices add complexity you don't need at 1000 users."
| Mode | What it does | Reach for it when |
| --- | --- | --- | **PM:** "Agree. Time to market matters more than theoretical scalability."
| `session` | Default. One model voices every persona inline. Fast and fully conversational. | Most conversations — banter, brainstorming, quick back-and-forth. |
| `auto` | Voices inline for light rounds, spawns independent agents only when independence changes the answer. | You want speed most of the time but real independence on the hard rounds. | **Dev:** "Monolith with clear module boundaries. We can extract services later if needed."
| `subagent` | Spawns a separate agent for each persona every substantive round, so no single mind colors them all. | Honest reviews and focus groups, where the voices must not bleed together. |
| `agent-team` | Stands the personas up as a persistent team that address each other directly. Claude Code only. | A live, hands-off round-table where the agents talk among themselves. | :::tip[Better Decisions]
Better decisions through diverse perspectives. Welcome to party mode.
The choice matters because one model voicing five personas can quietly converge: they share a mind. Spawning real agents keeps their reasoning separate, which is the entire point of a review panel or a focus group. `session` is the cheapest and most fluid. The spawning modes cost more but protect independence, and `auto` aims for both by spawning only when a round needs it.
`session` is the default, and every other mode falls back to it when a harness can't do the rest: `agent-team` drops to `subagent`, then to `session`. The configured default lives in your customization, and a runtime override wins for that session.
:::tip[Override for one session]
Start a party with `--mode subagent` (or `auto`, `agent-team`, `session`) to override the configured default just for that run.
:::
## Custom parties
Out of the box, a party uses your installed BMad agents. The larger use is building your own cast from any set of personas you can describe, then saving it to reuse. You author a party through the same skill. It detects whether you want to run one or build one, and writes the result to your overrides through [bmad-customize](../how-to/customize-bmad.md).
Party mode is customizable like every BMad skill. Run `/bmad-customize bmad-party-mode` to set its defaults directly: pin any group you've built as the default party so it loads without a flag, choose which mode it starts in, and set any house rules the room should hold for the whole session.
Two ideas do most of the work.
**Personas** are what make a member unmistakable: how they talk, what they value, how they argue, their pet peeves and blind spots. "Skeptical CFO" is a placeholder. "Won't approve anything without a payback under eighteen months, and says so in the first thirty seconds" is a persona. That detail is what gives a voice you'd recognize with the name labels hidden.
**Scenes** set the stage. A scene is one freeform line: the setting, what's happening, who's hostile to whom, who pushes hardest. The same members play it differently each time, so you define a person once and drop them into a bridge crew on duty, the same crew off-duty in the lounge, or a hostile buyer panel. Members combine into named groups, and you can pin one group as the default room.
### Shapes a party can take
| Shape | What it is |
| --- | --- |
| Themed cast | Famous investors, a TV ensemble — distinct voices gathered around a topic. |
| One-off personas | A persona or two added to the pool, no group needed. |
| Focus group from data | Hand it customer or survey data; it clusters people by what drives their behavior and builds representative personas. Pair it with `subagent` mode so the customers stay independent. |
| Review panel | Purpose-built critical lenses that argue about what matters. The shipped Code Review Crew is one. |
| Open-cast room | No fixed roster. The scene names a universe and the room is cast on the fly as the topic shifts. |
A focus group is the case that pays off most. Feed in real profiles and you get a standing panel of representative customers to test an idea against before you build it, each reacting from their own goals and budget instead of agreeing with the last voice.
## Parties you could build
A party is only personas and a scene, so the range is wide, and none of it needs a new skill or module:
- A founder squad to stress-test a startup idea.
- A compliance team to find the holes before an audit does.
- The authors of the Agile Manifesto, debating a software concept.
- A room of comedians as a writing-partner group.
- Great minds of the past, to work through a question in philosophy or untangle a hard problem.
- A business management team to plan the quarter.
These are starting points. Any set of voices you can describe becomes a party: write the personas, give the room a scene, and you have it.
## The Code Review Crew
Your default party is the agents your installed modules provide. The Code Review Crew is a custom party BMad ships alongside that default — a working template to study before you build your own, not a replacement for it. It's a review panel: five lenses that attack a change from different angles and argue about what actually matters, instead of rubber-stamping it.
| Member | Lens |
| --- | --- |
| Vex | Security — threat-models everything and names the concrete exploit path. |
| Grumbal | The adversary — assumes the code is broken and sets out to prove it. |
| Boundary | Edge cases — every branch, null, race, oversized input, odd timezone. |
| Yui | The craftsman — simplicity, naming, no needless cleverness or duplication. |
| Dana | The pragmatist — counters the perfectionists and ranks what's real versus a nit. |
The crew ships defined but inactive. The members sit in the pool and cost nothing until you summon the group, and they never crowd your default room. Run it with `subagent` mode so each lens reviews on its own before the five clash over the findings.
## Steering the conversation
You drive the room the whole way:
- Bring someone in: "Bring in the UX designer."
- Go deep on one voice: "Winston, take that apart." A direct ask is the cue for one persona to stretch out.
- Switch rooms mid-session: "Switch to the writers' room" swaps the active group and carries the thread over.
- Summon anyone by name, even a custom member who isn't in the current room.
Whichever mode is running, the orchestrator presents the result as one conversation rather than a stack of separate answers, and it keeps the personas in character — it won't break the fourth wall to narrate the mechanism.
:::tip[Mix more than one room]
You aren't limited to a single group. Pull members from several parties into the same conversation, or name a cast on the spot, and let them mix. Picture the Golden Girls thrown into an architecture review with Martin Fowler and Linus Torvalds, sparring over a change request: you can imagine how that goes.
:::
## The room remembers
Give a party a memory and it picks up where you left off. It keeps its own record of your past sessions — the dynamics that built up between members, the threads you left open, and where earlier conversations landed. Reopen it a week later and that history is intact: two members who came to blows last time still open a little frosty, and a sharp line from a past session can resurface as an organic callback.
It's memory, not a transcript. The room carries the few things worth remembering, not a log of everything said, so the next conversation feels continuous without dragging the whole past into it. It happens on its own, in the background — nothing to save, and the room never breaks character to announce it.
A character who turns up on the fly is remembered too — a walk-on from an open-cast scene, or someone you add mid-conversation. At the end of a session the room offers to keep the new arrivals, folding them into the party so they can come back next time.
Memory is set per party. When you create or save a party you're asked whether it should remember; the default installed-agent room remembers unless you turn it off. Set or change any of this through `/bmad-customize bmad-party-mode`.
## A keepsake of the session
When you wrap up, the orchestrator offers a keepsake: a single self-contained HTML document of the session to keep or share. It lays the conversation out by persona rather than dumping a raw transcript. Decline it and the party simply ends.
:::tip[Better decisions]
The value of a party is the disagreement. Diverse perspectives in one room catch what a single line of thinking misses.
::: :::

View File

@ -22,7 +22,7 @@ Le skill `bmad-customize` est un assistant de rédaction guidée pour les **opti
:::note[Prérequis] :::note[Prérequis]
- BMad installé dans votre projet (voir [Comment installer BMad](./install-bmad.md)) - BMad installé dans votre projet (voir [Comment installer BMad](./install-bmad.md))
- Un moyen dexécuter le script de résolution — BMad adopte `uv` comme standard (`uv run`, qui provisionne Python pour vous) ; un simple `python3` 3.11+ sur votre PATH fonctionne toujours pendant la transition. Le script nutilise que `tomllib` de la bibliothèque standard, il ny a donc rien à `pip install`. - Python 3.11+ sur votre PATH (pour le script de résolution — utilise `tomllib` de la bibliothèque standard, pas de `pip install`, pas de `uv`, pas de virtualenv)
- Un éditeur de texte pour les fichiers TOML - Un éditeur de texte pour les fichiers TOML
::: :::
@ -201,15 +201,15 @@ persistent_facts = [
## Comment fonctionne la résolution ## Comment fonctionne la résolution
À lactivation, le SKILL.md de lagent exécute un script Python partagé qui effectue la fusion à trois couches et renvoie le bloc résolu en JSON. Le script utilise uniquement le module `tomllib` de la bibliothèque standard Python (aucune dépendance externe). BMad adopte `uv run` comme standard pour exécuter ces scripts (uv provisionne un Python adapté pour vous) ; un simple `python3` fonctionne toujours pendant la transition : À lactivation, le SKILL.md de lagent exécute un script Python partagé qui effectue la fusion à trois couches et renvoie le bloc résolu en JSON. Le script utilise le module `tomllib` de la bibliothèque standard Python (aucune dépendance externe), donc `python3` suffit :
```bash ```bash
uv run {project-root}/_bmad/scripts/resolve_customization.py \ python3 {project-root}/_bmad/scripts/resolve_customization.py \
--skill {skill-root} \ --skill {skill-root} \
--key agent --key agent
``` ```
**Prérequis** : Python 3.11+ (les versions antérieures nincluent pas `tomllib`). Rien à `pip install`. Lexécution via `uv run` est le standard à venir — uv résout un interpréteur adapté pour vous. Si vous lexécutez directement avec `python3` pendant la transition, vérifiez votre version avec `python3 --version` ; certaines plateformes (macOS sans Homebrew, Ubuntu 22.04) ont `python3` par défaut en 3.10 ou antérieur, vous devrez peut-être installer 3.11+ séparément. **Prérequis** : Python 3.11+ (les versions antérieures nincluent pas `tomllib`). Pas de `pip install`, pas de `uv`, pas de virtualenv. Vérifiez avec `python3 --version`. Certaines plateformes (macOS sans Homebrew, Ubuntu 22.04) ont `python3` par défaut en 3.10 ou antérieur, vous devrez peut-être installer 3.11+ séparément.
`--skill` pointe vers le répertoire installé du skill (où se trouve `customize.toml`). Le nom du skill est déduit du basename du répertoire, et le script cherche automatiquement `_bmad/custom/{skill-name}.toml` et `{skill-name}.user.toml`. `--skill` pointe vers le répertoire installé du skill (où se trouve `customize.toml`). Le nom du skill est déduit du basename du répertoire, et le script cherche automatiquement `_bmad/custom/{skill-name}.toml` et `{skill-name}.user.toml`.
@ -217,17 +217,17 @@ Exemples dutilisation :
```bash ```bash
# Résoudre le bloc agent complet # Résoudre le bloc agent complet
uv run {project-root}/_bmad/scripts/resolve_customization.py \ python3 {project-root}/_bmad/scripts/resolve_customization.py \
--skill /chemin/absolu/vers/bmad-agent-pm \ --skill /chemin/absolu/vers/bmad-agent-pm \
--key agent --key agent
# Résoudre un seul champ # Résoudre un seul champ
uv run {project-root}/_bmad/scripts/resolve_customization.py \ python3 {project-root}/_bmad/scripts/resolve_customization.py \
--skill /chemin/absolu/vers/bmad-agent-pm \ --skill /chemin/absolu/vers/bmad-agent-pm \
--key agent.icon --key agent.icon
# Dump complet # Dump complet
uv run {project-root}/_bmad/scripts/resolve_customization.py \ python3 {project-root}/_bmad/scripts/resolve_customization.py \
--skill /chemin/absolu/vers/bmad-agent-pm --skill /chemin/absolu/vers/bmad-agent-pm
``` ```

View File

@ -113,7 +113,7 @@ La magie se produit dans les idées 50100. Le workflow encourage la générat
1. Lit lentrée et tout document annexe lié 1. Lit lentrée et tout document annexe lié
2. Distille en un noyau à cinq champs via un modèle configurable; redirige lexcédent vers des fichiers compagnons correctement nommés 2. Distille en un noyau à cinq champs via un modèle configurable; redirige lexcédent vers des fichiers compagnons correctement nommés
3. Exécute une auto-validation en deux passes (règles de cohérence, puis préservation de chaque affirmation essentielle de la source) 3. Exécute une auto-validation en deux passes (règles de cohérence, puis préservation de chaque affirmation essentielle de la source)
4. Écrit `SPEC.md`, les compagnons associés, et un `.memlog.md` sous `{output_folder}/specs/spec-{slug}/` 4. Écrit `SPEC.md`, les compagnons associés, et un `.decision-log.md` sous `{output_folder}/specs/spec-{slug}/`
La loi Spec impose huit règles : les capacités expriment à la fois lintention et le critère de succès; les intentions décrivent le QUOI, pas le COMMENT; les contraintes guident réellement les décisions; les non-objectifs sont explicites; les signaux de succès sont concrets; les identifiants de capacité sont stables; chaque affirmation essentielle de la source est préservée; la rédaction est concise. La loi Spec impose huit règles : les capacités expriment à la fois lintention et le critère de succès; les intentions décrivent le QUOI, pas le COMMENT; les contraintes guident réellement les décisions; les non-objectifs sont explicites; les signaux de succès sont concrets; les identifiants de capacité sont stables; chaque affirmation essentielle de la source est préservée; la rédaction est concise.
@ -123,7 +123,7 @@ La loi Spec impose huit règles : les capacités expriment à la fois linten
- `slug` (optionnel) — Requis uniquement lorsque lentrée est succincte et quaucun slug ne peut être dérivé du nom de fichier source - `slug` (optionnel) — Requis uniquement lorsque lentrée est succincte et quaucun slug ne peut être dérivé du nom de fichier source
- `target_spec_path` (optionnel) — Définir pour mettre à jour une spécification existante au lieu den créer une nouvelle - `target_spec_path` (optionnel) — Définir pour mettre à jour une spécification existante au lieu den créer une nouvelle
**Sortie :** Dossier de spécification contenant `SPEC.md`, les éventuels fichiers compagnons, et un `.memlog.md`. Les appelants en mode headless reçoivent une réponse JSON avec le statut du résultat et la liste des fichiers écrits ou modifiés. **Sortie :** Dossier de spécification contenant `SPEC.md`, les éventuels fichiers compagnons, et un `.decision-log.md`. Les appelants en mode headless reçoivent une réponse JSON avec le statut du résultat et la liste des fichiers écrits ou modifiés.
:::note[Contrat de mutation] :::note[Contrat de mutation]
`bmad-spec` est le seul outil autorisé à écrire `SPEC.md` et les fichiers compagnons de la spécification. Les autres compétences produisent leurs propres artefacts natifs et invoquent `bmad-spec` en mode headless lorsquelles ont besoin dexprimer une intention sous forme de contrat canonique ou de proposer des mises à jour. `bmad-spec` est le seul outil autorisé à écrire `SPEC.md` et les fichiers compagnons de la spécification. Les autres compétences produisent leurs propres artefacts natifs et invoquent `bmad-spec` en mode headless lorsquelles ont besoin dexprimer une intention sous forme de contrat canonique ou de proposer des mises à jour.

View File

@ -47,13 +47,13 @@ Définissez ce quil faut construire et pour qui.
| Workflow | Objectif | Livrable | | Workflow | Objectif | Livrable |
|------------|--------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------| |------------|--------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------|
| `bmad-prd` | Créez, mettez à jour ou validez un PRD[^1] — découverte accompagnée, trois intentions en un seul skill | Création/Mise à jour : `prd.md`, `addendum.md`, `.memlog.md`; Validation : `validation-report.html` + `.md` | | `bmad-prd` | Créez, mettez à jour ou validez un PRD[^1] — découverte accompagnée, trois intentions en un seul skill | Création/Mise à jour : `prd.md`, `addendum.md`, `decision-log.md`; Validation : `validation-report.html` + `.md` |
| `bmad-ux` | Concevez lexpérience utilisateur (lorsque lUX compte) | `DESIGN.md`, `EXPERIENCE.md` | | `bmad-ux` | Concevez lexpérience utilisateur (lorsque lUX compte) | `DESIGN.md`, `EXPERIENCE.md` |
:::tip[Trois intentions en un seul skill] :::tip[Trois intentions en un seul skill]
`bmad-prd` couvre lintégralité du cycle de vie du PRD. Précisez votre intention lors de lappel, sinon le skill vous la demandera : `bmad-prd` couvre lintégralité du cycle de vie du PRD. Précisez votre intention lors de lappel, sinon le skill vous la demandera :
- **Créer** — nouveau PRD à partir de zéro via une découverte accompagnée; produit `prd.md`, `addendum.md` et `.memlog.md` - **Créer** — nouveau PRD à partir de zéro via une découverte accompagnée; produit `prd.md`, `addendum.md` et `decision-log.md`
- **Mettre à jour** — réconcilie un PRD existant avec un signal de changement, en mettant en évidence les conflits avant dappliquer les modifications - **Mettre à jour** — réconcilie un PRD existant avec un signal de changement, en mettant en évidence les conflits avant dappliquer les modifications
- **Valider** — évalue un PRD à laide dune liste de contrôle configurable et produit un rapport de constats structuré au format HTML - **Valider** — évalue un PRD à laide dune liste de contrôle configurable et produit un rapport de constats structuré au format HTML
::: :::

View File

@ -147,7 +147,7 @@ Tous les workflows de cette phase sont optionnels. [**Vous ne savez pas lequel c
**Pour les voies BMad Method et Enterprise :** **Pour les voies BMad Method et Enterprise :**
1. Exécutez `bmad-prd` dans un nouveau chat — précisez votre intention (Create / Update / Validate) ou laissez le skill vous la demander 1. Exécutez `bmad-prd` dans un nouveau chat — précisez votre intention (Create / Update / Validate) ou laissez le skill vous la demander
2. Résultat : `prd.md`, `addendum.md`, `.memlog.md` 2. Résultat : `prd.md`, `addendum.md`, `decision-log.md`
:::note[Intentions de `bmad-prd`] :::note[Intentions de `bmad-prd`]

View File

@ -22,7 +22,7 @@ The `bmad-customize` skill is a guided authoring helper for the **per-skill agen
:::note[Prerequisites] :::note[Prerequisites]
- BMad installed in your project (see [How to Install BMad](./install-bmad.md)) - BMad installed in your project (see [How to Install BMad](./install-bmad.md))
- A way to run the resolver script — BMad is standardizing on `uv` (`uv run`, which provisions Python for you); a plain `python3` 3.11+ on your PATH still works during the transition. The script uses only stdlib `tomllib`, so there's nothing to `pip install`. - Python 3.11+ on your PATH (for the resolver script -- uses stdlib `tomllib`, no `pip install`, no `uv`, no virtualenv)
- A text editor for TOML files - A text editor for TOML files
::: :::
@ -201,15 +201,15 @@ persistent_facts = [
## How Resolution Works ## How Resolution Works
On activation, the agent's SKILL.md runs a shared Python script that does the three-layer merge and returns the resolved block as JSON. The script uses only the Python standard library's `tomllib` module (no external dependencies). BMad is standardizing on `uv run` to invoke these scripts (uv provisions a suitable Python for you); a plain `python3` still works during the transition: On activation, the agent's SKILL.md runs a shared Python script that does the three-layer merge and returns the resolved block as JSON. The script uses the Python standard library's `tomllib` module (no external dependencies), so plain `python3` is enough:
```bash ```bash
uv run {project-root}/_bmad/scripts/resolve_customization.py \ python3 {project-root}/_bmad/scripts/resolve_customization.py \
--skill {skill-root} \ --skill {skill-root} \
--key agent --key agent
``` ```
**Requirements**: Python 3.11+ (earlier versions don't include `tomllib`); nothing to `pip install`. Running via `uv run` is the going-forward standard — uv resolves a suitable interpreter for you. If you run it with `python3` directly during the transition, check your version with `python3 --version`: some platforms (macOS without Homebrew, Ubuntu 22.04) default `python3` to 3.10 or earlier, so you may need to install 3.11+ separately. **Requirements**: Python 3.11+ (earlier versions don't include `tomllib`). No `pip install`, no `uv`, no virtualenv. Check with `python3 --version`. Some platforms (macOS without Homebrew, Ubuntu 22.04) default `python3` to 3.10 or earlier, so you may need to install 3.11+ separately.
`--skill` points at the skill's installed directory (where `customize.toml` lives). The skill name is derived from the directory's basename, and the script looks up `_bmad/custom/{skill-name}.toml` and `{skill-name}.user.toml` automatically. `--skill` points at the skill's installed directory (where `customize.toml` lives). The skill name is derived from the directory's basename, and the script looks up `_bmad/custom/{skill-name}.toml` and `{skill-name}.user.toml` automatically.
@ -217,17 +217,17 @@ Useful invocations:
```bash ```bash
# Resolve the full agent block # Resolve the full agent block
uv run {project-root}/_bmad/scripts/resolve_customization.py \ python3 {project-root}/_bmad/scripts/resolve_customization.py \
--skill /abs/path/to/bmad-agent-pm \ --skill /abs/path/to/bmad-agent-pm \
--key agent --key agent
# Resolve a single field # Resolve a single field
uv run {project-root}/_bmad/scripts/resolve_customization.py \ python3 {project-root}/_bmad/scripts/resolve_customization.py \
--skill /abs/path/to/bmad-agent-pm \ --skill /abs/path/to/bmad-agent-pm \
--key agent.icon --key agent.icon
# Full dump # Full dump
uv run {project-root}/_bmad/scripts/resolve_customization.py \ python3 {project-root}/_bmad/scripts/resolve_customization.py \
--skill /abs/path/to/bmad-agent-pm --skill /abs/path/to/bmad-agent-pm
``` ```

View File

@ -1,55 +0,0 @@
---
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

@ -1,76 +0,0 @@
---
title: "아이디어 단련"
description: 페르소나 기반 질문으로 아이디어를 압박 검증해 단단하게 만들거나, 입증하거나, 적은 비용으로 폐기합니다
sidebar:
order: 14
---
아직 다듬어지지 않은 아이디어를 지금 대화 안에서 압박 검증하세요. 생각을 바꾸는 비용이 아직 0에 가까울 때 검증하는 것이 가장 비용이 적습니다.
## 아이디어 단련이란?
`bmad-forge-idea`를 실행하면 엄격한 질문자가 아이디어를 하나씩 파고듭니다. 살아남은 것이 실제로 행동할 수 있는 확신이 될 때까지 묻습니다. 이 스킬은 도메인을 가리지 않습니다. 소프트웨어 기능, 비즈니스 모델, 연구 가설, 계속 머릿속을 맴도는 개인적 결정에도 사용할 수 있습니다.
남는 것은 더 날카로운 사고입니다. 정제된 `forged-idea.md`는 가능한 종료 형태 중 하나일 뿐이며, 세션은 사용자를 "이제 만들까요?" 쪽으로 몰지 않습니다.
## 왜 일찍 압박 검증해야 하나요?
가장 위험한 적은 자기 아이디어 안에서 보지 못한 구멍입니다. 검토되지 않은 가정이나 해결되지 않은 갈래는 균열입니다. 지금 놓친 균열은 나중에 빌드나 출시 단계에서 훨씬 더 큰 비용으로 다시 나타납니다.
대화는 그것을 잡아내기에 비용이 가장 적게 드는 곳입니다. 여기서 생각을 바꾸는 데는 비용이 들지 않습니다. 단련 과정은 그 낮은 비용을 의도적으로 활용해, 아직 비용 없이 고칠 수 있는 약점을 공격합니다.
## 세션 진행 방식
질문자는 의존성 순서에 따라 한 번에 하나의 질문을 던지고, 매번 자신의 권장 답도 함께 내놓습니다. 열린 질문보다 반박할 기준이 있는 답이 논의를 더 멀리 밀고 갑니다. 사용자가 찾아오라고 보내는 대신, 찾을 수 있는 답은 스스로 찾습니다.
아이디어가 기존 프로젝트 안에 있다면 그 프로젝트 자료가 판단 기준이 됩니다. 질문자는 사용자의 주장을 이미 있는 자료와 대조하고 모순을 짚습니다. 용어도 같은 검토를 받습니다. 어떤 용어가 흐릿하거나 두 의미를 동시에 담고 있다면, 갈래를 해결하기 전에 정확한 선택을 강제합니다. 과부하된 단어 위에 세운 갈래는 거짓으로 해결되기 때문입니다.
## 대화 구성
단련 과정에는 참여자가 있습니다. 주제가 정해지면 각 갈래마다 얼굴 없는 하나의 어시스턴트가 아니라 두 인물이 함께 나섭니다. 하나는 설치된 명단에서 고릅니다. [파티 모드](./party-mode.md)와 [이름 있는 에이전트](./named-agents.md)의 같은 출연진에서 가져온, 사용자가 알아볼 에이전트나 페르소나입니다. 다른 하나는 주제에 맞춰 즉석에서 만든 인물입니다. 적대적인 경쟁자, 회의적인 CFO, 바로 이런 계획이 실패하는 것을 여러 번 본 도메인 전문가일 수 있습니다.
사용자는 언제든 대화를 조종할 수 있습니다. 특정 인물을 지명하거나, 저장된 파티를 부르거나, **adversarial on this** 기능을 호출해 한 주장을 끝까지 공격하게 하고 사용자가 방어할 수 있습니다.
## 기본 동의는 없습니다
반사적인 동의는 이 스킬이 거부하려고 만들어진 실패 유형입니다. 아이디어를 이해했다는 말은 그 아이디어를 지지한다는 뜻이 아닙니다. 단련 과정은 어떤 것도 검증을 통과하기 전에는 칭찬하지 않습니다. 약점을 공격하거나 강점을 더 밀어붙이고, 실제로 얻어낸 것에만 공을 줍니다.
이는 [적대적 리뷰](./adversarial-review.md)의 의도적인 반대편입니다. 적대적 리뷰에서는 리뷰어가 문제를 찾도록 지시받고, 사용자는 거짓 양성을 걸러냅니다. 여기서는 질문자가 공짜 동의를 하지 않습니다. 그래서 긴장감이 유지되고, 사용자는 그 압력 아래에서 더 깊게 생각합니다. 편안한 세션보다 좋은 아이디어를 남기는 쪽에 맞춥니다.
## 세션 종료 방식
세션은 생각이 정리된 지점에서 끝납니다. 모든 결말은 실제 결과입니다. 단련 과정은 결과를 표시한 독립 보고서를 작성합니다.
| 결과 | 의미 |
| --- | --- |
| **단련됨** | 아이디어가 살아남았습니다. 확정한 결정과 폐기된 것 및 그 이유를 `forged-idea.md`로 정제합니다. 제품 개념이라면 `bmad-spec`, `bmad-prd`, `bmad-prfaq`의 입력으로 사용할 수 있습니다. |
| **폐기됨** | 아이디어가 살아남지 못했습니다. 보고서는 폐기 이유를 기록합니다. 그것을 적은 비용으로 알아낸 것 자체가 이득입니다. |
| **명확해짐** | 이제 더 명확하게 생각할 수 있습니다. 별도 산출물이 필요하지 않으며, 세션 자체로 충분합니다. |
## 사용 시점
이미 아이디어가 있고, 그 아이디어에 투자하기 전에 단단하게 만들거나 폐기하고 싶을 때 사용하세요. 아직 선택지를 생성하는 중이거나 에이전트들의 집단 결정을 원한다면 건너뛰세요.
| 스킬 | 사용 시점 | 목소리 |
| --- | --- | --- |
| `bmad-forge-idea` | 아이디어가 있고 스트레스 테스트하거나 폐기하고 싶을 때 | 질문자와 두 인물이 참여하는 대화 |
| `bmad-prfaq` | 제품을 추진하기로 정했고 고객 우선 관점에서 입증하고 싶을 때 | Working Backwards 코치 |
| `bmad-brainstorming` | 아직 아이디어가 없고 선택지를 생성해야 할 때 | 퍼실리테이션 코치 |
| `bmad-party-mode` | 에이전트들이 함께 토론하거나 결정하길 원할 때 | 한 대화 안의 전체 명단 |
| `bmad-review-adversarial-general` | 산출물이 있고 결함을 찾아야 할 때 | 반드시 이슈를 찾아야 하는 리뷰어 |
## 예시
:::note[예시]
**사용자:** "앱에 AI 생성 주간 요약을 넣고 싶어요. 사용자에게 매주 월요일 요약 이메일을 보내는 겁니다."
**Forge:** "기능 전에, 누가 이걸 요청했나요? 제 권장 답은 아무도 요청하지 않았다는 것입니다. 지금은 'AI 요약은 가치 있어 보인다'에서 출발하고 있습니다. 실제 신호가 있다면 반박해 주세요."
**회의적인 CFO(즉석 생성):** "월요일 요약은 전체 활성 사용자 기반에 대해 반복 추론 비용을 영구적으로 발생시킵니다. 어떤 매출 항목이 그 비용을 지불하나요?"
**사용자:** "리텐션이요, 아마도요. 사람들이 다시 오는 걸 잊으니까요."
**Forge:** "그렇다면 핵심 주장은 '월요일 이메일이 이탈 직전 사용자를 되돌린다'이지, 'AI 요약은 가치 있다'가 아닙니다. 둘은 다른 아이디어입니다. 어느 쪽을 단련할까요?"
:::
첫 아이디어는 기능이었습니다. 질문 두 개 뒤에 실제 아이디어는 모델 없이도 일반 이메일로 테스트할 수 있는 리텐션 가설이 됩니다.

View File

@ -1,150 +1,59 @@
--- ---
title: "파티 모드" title: "파티 모드"
description: AI 에이전트를 하나의 대화에 모아 실행하고, 직접 출연진을 만들고, 얼마나 독립적으로 사고할지 선택합니다 description: 다중 에이전트 협업 - 모든 AI 에이전트를 하나의 대화에 모읍니다
sidebar: sidebar:
order: 11 order: 11
--- ---
파티 모드는 AI 에이전트를 한자리에 모아 서로, 그리고 사용자와 대화하게 합니다. 이 문서는 파티가 무엇인지, 파티를 실행하는 네 가지 방식, 설치된 에이전트 대신 직접 페르소나 출연진을 만드는 방법, 그리고 파티가 세션 사이에 사용자를 기억하는 방식을 설명합니다. 모든 AI 에이전트를 하나의 대화에 모으세요.
## 파티 모드란? ## 파티 모드란?
`bmad-party-mode`를 실행하면 이미 설치된 BMad 에이전트들이 한 대화에 모입니다. PM, 아키텍트, 개발자, UX 디자이너, 그리고 선택한 모듈이 제공하는 다른 에이전트들이 함께 들어옵니다. 설치된 이 명단이 기본 파티이며 별도 설정 없이 바로 사용할 수 있습니다. 이들은 각자 캐릭터에 맞게 답하고, 동의하거나 반대하며, 서로의 생각을 이어갑니다. 사용자는 대화를 조종합니다. 후속 질문을 하고, 반박하고, 한 관점을 앞으로 끌어내거나, 주제를 바꿀 수 있습니다. 대화는 사용자가 끝낼 때까지 계속됩니다. `bmad-party-mode`를 실행하면 PM, 아키텍트, 개발자, UX 디자이너 등 필요한 AI 팀 전체가 한 대화방에 모입니다. 파티 모드가 논의를 조율하고 메시지마다 설치된 에이전트 중 적합한 에이전트를 선택합니다. 에이전트들은 각 페르소나에 맞게 답하고, 동의하거나 반대하며, 서로의 아이디어를 이어받아 발전시킵니다.
이 방식이 작동하는 이유는 페르소나마다 우선순위가 다르기 때문입니다. 아키텍트는 설계를 지키고, PM은 범위를 지키고, 개발자는 실제로 만들 수 있는지를 지킵니다. 이들을 같은 대화에 넣으면 절충점이 스프린트 3주 차가 아니라 지금 드러납니다. 대화는 원하는 만큼 계속됩니다. 후속 질문을 묻고, 답에 반박하고, 토론 방향을 바꿀 수 있습니다. 끝날 때까지 에이전트들과 실제 주고받는 대화를 합니다.
**잘 맞는 경우:** **잘 맞는 경우:**
- 실제 절충이 필요한 결정 - 절충이 필요한 결정
- 브레인스토밍과 "무엇을 놓쳤나?" 점검 - 브레인스토밍 세션
- 사후 분석과 회고 - 문제가 생겼을 때의 사후 분석
- 실행 전에 계획을 압박 검증하기 - 스프린트 회고와 계획
파티 모드는 페르소나들이 의견을 갖고 충돌하기 때문에 빠르고 꽤 재미있는 브레인스토밍 방법이기도 합니다. 다른 어떤 워크플로 안에서도 파티를 시작할 수 있습니다. 브레인스토밍 중, PRD 작성 중, 코딩 중, 영업 각도를 잡는 중, 창작물을 다듬는 중에도 가능합니다. 눈앞의 문제에 더 많은 관점이 필요할 때, 하던 일을 내려놓지 않고 파티를 불러오세요. ## 예시
:::note[예시] ### 나쁜 아키텍처 지적하기
**사용자:** MVP에는 모놀리스가 좋을까요, 마이크로서비스가 좋을까요?
**아키텍트:** 모놀리스로 시작하세요. 사용자 1,000명 규모에서는 마이크로서비스가 필요 없는 운영 비용을 더합니다. **사용자:** "좋아요 팀, 지난 스프린트는 엉망이었어요. 인증 서비스가 세 번이나 내려갔습니다. 어디서 문제가 생겼나요?"
**PM:** 동의합니다. 아직 증명하지 못한 확장성보다 출시 속도가 더 중요합니다. **아키텍트:** "설계 자체는 건전했습니다. 분산 인증과 적절한 대체 경로가 있었죠. 구현이 사양에서 벗어났다면..."
**개발자:** 모놀리스가 좋습니다. 다만 나중에 서비스 하나를 분리해도 재작성하지 않도록 명확한 모듈 경계를 둡시다. **개발자:** "저는 아키텍처 문서를 그대로 따랐습니다. 사양이 동시 세션 무효화 경쟁 조건을 다루지 않았어요."
:::
## 파티 시작하기 **PM:** "두 분 모두 더 큰 이슈를 놓쳤습니다. PRD에서 세션 관리 요구사항을 검증하지 않았어요. 그걸 잡지 못한 건 제 책임입니다."
스킬을 호출하고 원하는 것을 말하세요. 스킬이 사용자가 파티를 실행하려는지, 새로 만들려는지 판단합니다. **TEA:** "통합 테스트에서 제가 잡았어야 했습니다. 테스트 시나리오가 동시 무효화를 다루지 않았어요."
| 목표 | 입력 | ### 창의적 브레인스토밍
| --- | --- |
| 기본 모드로 파티 시작 | `/bmad-party-mode` |
| 특정 모드로 시작 | `/bmad-party-mode --mode auto` (`session`, `subagent`, `agent-team`도 가능) |
| 저장된 파티 열기 | `/bmad-party-mode --party code-review-crew` |
| 즉석에서 출연진 만들기 | "엔터프라이즈호 브리지 크루로 파티 모드" |
| 파티 생성 또는 추가 | "파티 모드, 새 파티 만들어줘" |
| 기존 파티 편집 | "파티 모드, writers' room 편집해줘" |
| 스킬 커스터마이즈 | `/bmad-customize bmad-party-mode` |
## 파티 실행 방식 **사용자:** "온보딩이 지루하지 않고 특별하게 느껴지게 하려면 어떻게 해야 할까요?"
파티 실행 방식은 네 가지입니다. 세션당 하나의 모드가 활성화되며, 그 모드는 누가 사고하는지를 결정합니다. 하나의 모델이 모두의 목소리를 내는지, 별도 에이전트들이 각자 추론하는지가 달라집니다. **UX 디자이너:** "점진적 공개부터 시작하세요. 기능을 튜토리얼에서 한꺼번에 보여주지 말고, 사용자가 필요로 할 때 드러내는 겁니다."
| 모드 | 하는 일 | 사용 시점 | **스토리텔러:** "온보딩이 하나의 이야기라면 어떨까요? 각 단계가 캐릭터의 여정을 드러내고, 사용자가 바로 그 주인공이 되는 겁니다."
| --- | --- | --- |
| `session` | 기본값입니다. 하나의 모델이 모든 페르소나를 인라인으로 연기합니다. 빠르고 대화가 자연스럽습니다. | 대부분의 대화. 농담, 브레인스토밍, 빠른 주고받기. |
| `auto` | 가벼운 라운드는 인라인으로 진행하고, 독립성이 답을 바꾸는 경우에만 독립 에이전트를 생성합니다. | 대부분은 속도를 원하지만 어려운 라운드에서는 실제 독립성이 필요할 때. |
| `subagent` | 의미 있는 라운드마다 페르소나별 별도 에이전트를 생성합니다. 한 생각이 모두를 물들이지 않습니다. | 정직한 리뷰와 포커스 그룹처럼 목소리가 섞이면 안 될 때. |
| `agent-team` | 페르소나를 지속 팀으로 세워 서로 직접 대화하게 합니다. Claude Code 전용입니다. | 에이전트들이 서로 말하는 실시간 원탁 토론을 손 놓고 지켜보고 싶을 때. |
이 선택은 중요합니다. 하나의 모델이 다섯 페르소나를 연기하면 조용히 한쪽으로 모이기 쉽습니다. 결국 같은 마음을 공유하기 때문입니다. 실제 에이전트를 생성하면 추론이 분리되고, 리뷰 패널이나 포커스 그룹에서 중요한 독립성을 지킬 수 있습니다. `session`은 비용이 가장 적고 흐름이 유연합니다. 생성 모드는 비용이 더 들지만 독립성을 보호하고, `auto`는 필요한 라운드에만 생성해 둘 사이를 노립니다. **게임 디자이너:** "그 아이디어를 이어가 보죠. 첫 번째 '퀘스트'가 실제 사용자 문제를 해결하는 일이라면 어떨까요? 사용자는 가치 있는 일을 해내며 배웁니다."
`session`이 기본값입니다. 다른 모드를 실행할 수 없는 환경에서는 순서대로 되돌아갑니다. `agent-team``subagent`로, 다시 `session`으로 내려갑니다. 설정된 기본값은 커스터마이징에 저장되고, 실행 시점의 override가 해당 세션에서 우선합니다. ### 기술 결정
:::tip[한 세션만 override] **사용자:** "MVP에는 모놀리스가 좋을까요, 마이크로서비스가 좋을까요?"
`--mode subagent` 또는 `auto`, `agent-team`, `session`으로 파티를 시작하면 설정된 기본값을 그 실행에서만 바꿀 수 있습니다.
:::
## 커스텀 파티 **아키텍트:** "모놀리스로 시작하세요. 사용자 1,000명 규모에서는 마이크로서비스가 필요 없는 복잡성을 더합니다."
기본적으로 파티는 설치된 BMad 에이전트를 사용합니다. 더 크게 쓰려면 설명할 수 있는 페르소나 집합을 직접 출연진으로 만들고, 저장해 재사용하면 됩니다. 파티 작성도 같은 스킬로 합니다. 스킬은 사용자가 파티를 실행하려는지 만들려는지 감지하고, 결과를 [bmad-customize](../how-to/customize-bmad.md)로 오버라이드에 씁니다. **PM:** "동의합니다. 이론적인 확장성보다 출시 속도가 더 중요합니다."
파티 모드도 다른 BMad 스킬처럼 커스터마이즈됩니다. `/bmad-customize bmad-party-mode`를 실행해 기본값을 직접 설정하세요. 만든 그룹을 기본 파티로 고정해 플래그 없이 로드되게 하거나, 시작 모드를 고르거나, 파티 전체가 세션 내내 지킬 규칙을 설정할 수 있습니다. **개발자:** "명확한 모듈 경계를 둔 모놀리스가 좋겠습니다. 필요해지면 나중에 서비스를 분리할 수 있습니다."
두 가지 개념이 대부분의 일을 합니다.
**페르소나**는 멤버를 알아볼 수 있게 만드는 요소입니다. 말하는 방식, 중요하게 여기는 것, 논쟁 방식, 집착하는 문제, 보지 못하는 지점이 여기에 들어갑니다. "회의적인 CFO"는 임시 표현입니다. "18개월 안에 회수 계획이 없으면 승인하지 않고, 그 말을 첫 30초 안에 꺼내는 사람"은 페르소나입니다. 그 정도의 구체성이 있어야 이름표를 가려도 알아볼 수 있는 관점이 됩니다.
**장면**은 무대를 설정합니다. 장면은 한 줄의 자유 형식 문장입니다. 배경, 벌어지는 일, 누가 누구에게 적대적인지, 누가 가장 세게 밀어붙이는지를 적습니다. 같은 멤버도 장면마다 다르게 움직입니다. 한 사람을 한 번 정의해 두고, 임무 중인 브리지 크루, 근무 후 라운지의 같은 크루, 적대적인 구매자 패널에 넣을 수 있습니다. 멤버들은 이름 있는 그룹으로 묶이고, 그룹 하나를 기본 파티로 고정할 수 있습니다.
### 파티의 형태
| 형태 | 의미 |
| --- | --- |
| 테마 출연진 | 유명 투자자, TV 앙상블처럼 특정 주제 주변에 모인 구분되는 목소리. |
| 일회성 페르소나 | 그룹 없이도 후보군에 추가한 하나 또는 두 페르소나. |
| 데이터 기반 포커스 그룹 | 고객 또는 설문 데이터를 주면 행동 동인을 기준으로 군집화하고 대표 페르소나를 만듭니다. 고객들이 독립적으로 반응하도록 `subagent` 모드와 함께 쓰세요. |
| 리뷰 패널 | 중요한 것을 두고 논쟁하도록 설계된 비판 렌즈들입니다. 함께 제공되는 Code Review Crew가 예입니다. |
| 열린 출연진 파티 | 고정 명단이 없습니다. 장면이 세계관을 정하고, 주제가 바뀔 때마다 즉석으로 출연진을 정합니다. |
가장 큰 효과가 나는 경우는 포커스 그룹입니다. 실제 프로필을 넣으면 대표 고객 패널이 만들어지고, 제품을 만들기 전에 아이디어를 시험합니다. 각 고객은 마지막 목소리에 동의하는 대신 자신의 목표와 예산에서 반응합니다.
## 만들 수 있는 파티
파티는 페르소나와 장면뿐이므로 범위가 넓고, 새 스킬이나 모듈이 필요하지 않습니다.
- 스타트업 아이디어를 스트레스 테스트하는 창업자 팀.
- 감사 전에 구멍을 찾는 컴플라이언스 팀.
- 소프트웨어 개념을 두고 토론하는 애자일 선언문 작성자들.
- 글쓰기 파트너 그룹으로서의 코미디언 파티.
- 철학적 질문을 풀거나 어려운 문제를 해체하는 과거의 위대한 사상가들.
- 분기 계획을 세우는 비즈니스 경영진.
이는 출발점일 뿐입니다. 설명할 수 있는 어떤 관점의 집합이든 파티가 됩니다. 페르소나를 쓰고, 파티에 장면을 주면 됩니다.
## Code Review Crew
기본 파티는 설치된 모듈이 제공하는 에이전트입니다. Code Review Crew는 그 기본 파티와 함께 제공되는 커스텀 파티입니다. 직접 파티를 만들기 전에 참고할 수 있는 작동 템플릿이지 기본값을 대체하지 않습니다. 이는 리뷰 패널입니다. 다섯 가지 렌즈가 변경을 서로 다른 각도에서 공격하고, 형식적인 승인 대신 실제로 중요한 것이 무엇인지 논쟁합니다.
| 멤버 | 렌즈 |
| --- | --- |
| Vex | 보안. 모든 것을 위협 모델링으로 보고 구체적인 악용 경로를 말합니다. |
| Grumbal | 적대자. 코드는 깨졌다고 가정하고 그것을 증명하려고 합니다. |
| Boundary | 엣지 케이스. 모든 분기, `null`, 경쟁 상태, 과도하게 큰 입력, 특이한 시간대를 봅니다. |
| Yui | 장인. 단순성, 이름, 불필요한 영리함이나 중복 제거를 봅니다. |
| Dana | 실용주의자. 완벽주의자들에게 반박하고 실제 문제와 사소한 지적을 구분합니다. |
이 팀은 정의되어 있지만 비활성 상태로 제공됩니다. 멤버들은 후보군에 있고 그룹을 소환하기 전까지 비용을 만들지 않으며, 기본 파티를 어지럽히지 않습니다. 다섯 렌즈가 각자 검토한 뒤 발견 사항을 두고 충돌하도록 `subagent` 모드로 실행하세요.
## 대화 조종하기
사용자는 내내 대화를 조종합니다.
- 누군가를 들이기: "UX 디자이너를 불러와."
- 한 관점을 깊게 파기: "Winston, 저걸 해체해 봐." 직접 요청하면 한 페르소나가 깊게 답하라는 신호가 됩니다.
- 세션 중 파티 바꾸기: "작가 회의실로 전환해." 활성 그룹을 바꾸고 대화 맥락은 이어갑니다.
- 현재 파티에 없는 커스텀 멤버라도 이름으로 소환하기.
어떤 모드로 실행 중이든 오케스트레이터는 결과를 별도 답변 묶음이 아니라 하나의 대화로 제시합니다. 페르소나를 캐릭터 안에 유지하며, 작동 방식을 설명하느라 몰입을 깨지 않습니다.
:::tip[둘 이상의 파티 섞기]
한 그룹에 제한되지 않습니다. 여러 파티의 멤버를 같은 대화로 끌어오거나, 그 자리에서 출연진을 지명해 섞어도 됩니다. 예를 들어 Golden Girls를 Martin Fowler, Linus Torvalds와 함께 아키텍처 리뷰에 불러 변경 요청을 두고 논쟁하게 하세요. 어떤 일이 벌어질지 상상해 보세요.
:::
## 파티는 기억합니다
파티에 기억 기능을 주면 중단했던 지점에서 이어갑니다. 파티는 지난 세션의 자체 기록을 유지합니다. 멤버 사이에 쌓인 역학, 열어 둔 실마리, 이전 대화가 어디에 도착했는지를 기억합니다. 일주일 뒤 다시 열어도 그 이력은 남아 있습니다. 지난번에 충돌했던 두 멤버는 조금 차갑게 시작하고, 예전 세션의 날카로운 한 줄이 자연스럽게 다시 떠오를 수 있습니다.
이는 대화록이 아니라 기억입니다. 파티는 모든 말을 기록하지 않고, 기억할 가치가 있는 몇 가지만 가져갑니다. 그래서 다음 대화가 이어지는 느낌을 주면서도 과거 전체를 끌고 오지 않습니다. 이 과정은 백그라운드에서 자동으로 일어납니다. 따로 저장할 것이 없고, 파티는 캐릭터를 깨고 기억 기능을 설명하지 않습니다.
즉석에서 등장한 캐릭터도 기억될 수 있습니다. 열린 출연진 장면에 잠깐 등장한 인물이거나, 대화 중 추가한 사람일 수 있습니다. 세션 끝에서 파티는 새로 온 인물을 유지할지 제안하고, 유지하면 파티에 접어 넣어 다음에도 다시 올 수 있게 합니다.
메모리는 파티별로 설정됩니다. 파티를 만들거나 저장할 때 기억할지 묻습니다. 기본 설치 에이전트 파티는 끄지 않는 한 기억합니다. 이 설정은 `/bmad-customize bmad-party-mode`에서 바꿀 수 있습니다.
## 세션 보관본
마무리할 때 오케스트레이터는 보관본을 제안합니다. 보관하거나 공유할 수 있는 독립 HTML 문서 하나입니다. 원문 대화록을 그대로 쏟아내는 대신 페르소나별로 대화를 배치합니다. 거절하면 파티는 그대로 끝납니다.
:::tip[더 나은 결정] :::tip[더 나은 결정]
파티의 가치는 의견 차이에 있습니다. 한자리에 모인 다양한 관점은 한 줄의 사고가 놓치는 것을 잡아냅니다. 다양한 관점을 통해 더 나은 결정을 내립니다. 파티 모드에 오신 것을 환영합니다.
::: :::

View File

@ -22,7 +22,7 @@ sidebar:
:::note[필수 조건] :::note[필수 조건]
- 프로젝트에 BMad 설치([BMad 설치 방법](./install-bmad.md) 참고) - 프로젝트에 BMad 설치([BMad 설치 방법](./install-bmad.md) 참고)
- 해석 스크립트를 실행할 방법. BMad는 `uv run` 표준으로 이동 중이며, `uv`가 Python을 준비해 줍니다. 전환 기간에는 PATH의 일반 `python3` 3.11+도 작동합니다. 스크립트는 stdlib `tomllib`만 사용하므로 `pip install`할 것은 없습니다 - PATH의 Python 3.11+(병합 스크립트용, stdlib `tomllib`만 사용하며 `pip install`, `uv`, virtualenv 불필요)
- TOML 파일을 편집할 텍스트 에디터 - TOML 파일을 편집할 텍스트 에디터
::: :::
@ -198,15 +198,15 @@ persistent_facts = [
## 해석이 작동하는 방식 ## 해석이 작동하는 방식
활성화 시 에이전트의 SKILL.md가 공유 Python 스크립트를 실행해 3계층 병합을 수행하고 해석된 블록을 JSON으로 반환합니다. 스크립트는 Python 표준 라이브러리의 `tomllib`만 사용합니다. BMad는 이 스크립트를 `uv run`으로 호출하는 방향으로 표준화하고 있습니다. `uv`가 적절한 Python을 준비해 주며, 전환 기간에는 일반 `python3`도 작동합니다. 활성화 시 에이전트의 SKILL.md가 공유 Python 스크립트를 실행해 3계층 병합을 수행하고 해석된 블록을 JSON으로 반환합니다. 스크립트는 Python 표준 라이브러리의 `tomllib`만 사용하므로 기본 `python3`이면 충분합니다.
```bash ```bash
uv run {project-root}/_bmad/scripts/resolve_customization.py \ python3 {project-root}/_bmad/scripts/resolve_customization.py \
--skill {skill-root} \ --skill {skill-root} \
--key agent --key agent
``` ```
**요구사항**: Python 3.11+(이전 버전에는 `tomllib`이 없습니다). `pip install`할 것은 없습니다. 앞으로의 표준은 `uv run`입니다. `uv`가 적절한 인터프리터를 해결해 줍니다. 전환 기간에 `python3`로 직접 실행한다면 `python3 --version`으로 버전을 확인하세요. Homebrew 없는 macOS나 Ubuntu 22.04 같은 플랫폼은 기본 `python3`이 3.10 이하일 수 있으므로 3.11+를 별도 설치해야 할 수 있습니다. **요구사항**: Python 3.11+(이전 버전에는 `tomllib`이 없습니다). `pip install`, `uv`, virtualenv는 필요 없습니다. `python3 --version`으로 확인하세요. Homebrew 없는 macOS나 Ubuntu 22.04 같은 플랫폼은 기본 `python3`이 3.10 이하일 수 있으므로 3.11+를 별도 설치해야 할 수 있습니다.
`--skill`은 스킬이 설치된 디렉터리(`customize.toml`이 있는 위치)를 가리킵니다. 스킬 이름은 디렉터리의 basename에서 파생되며, 스크립트는 `_bmad/custom/{skill-name}.toml``{skill-name}.user.toml`을 자동으로 찾습니다. `--skill`은 스킬이 설치된 디렉터리(`customize.toml`이 있는 위치)를 가리킵니다. 스킬 이름은 디렉터리의 basename에서 파생되며, 스크립트는 `_bmad/custom/{skill-name}.toml``{skill-name}.user.toml`을 자동으로 찾습니다.
@ -214,17 +214,17 @@ uv run {project-root}/_bmad/scripts/resolve_customization.py \
```bash ```bash
# 전체 에이전트 블록 해석 # 전체 에이전트 블록 해석
uv run {project-root}/_bmad/scripts/resolve_customization.py \ python3 {project-root}/_bmad/scripts/resolve_customization.py \
--skill /abs/path/to/bmad-agent-pm \ --skill /abs/path/to/bmad-agent-pm \
--key agent --key agent
# 단일 필드 해석 # 단일 필드 해석
uv run {project-root}/_bmad/scripts/resolve_customization.py \ python3 {project-root}/_bmad/scripts/resolve_customization.py \
--skill /abs/path/to/bmad-agent-pm \ --skill /abs/path/to/bmad-agent-pm \
--key agent.icon --key agent.icon
# 전체 덤프 # 전체 덤프
uv run {project-root}/_bmad/scripts/resolve_customization.py \ python3 {project-root}/_bmad/scripts/resolve_customization.py \
--skill /abs/path/to/bmad-agent-pm --skill /abs/path/to/bmad-agent-pm
``` ```

View File

@ -1,55 +0,0 @@
---
title: "아이디어 압박 검증하기"
description: bmad-forge-idea 스킬로 투자 전에 아이디어를 단단하게 만들고, 입증하거나 폐기합니다
sidebar:
order: 12
---
`bmad-forge-idea` 스킬을 사용해 반쯤 형성된 아이디어를 적대적 질문 아래에 두세요. 아이디어는 충분히 검증된 확신으로 살아남거나, 적은 비용으로 폐기됩니다.
## 사용 시점
- 시간이나 돈을 쓰기 전에 아이디어를 스트레스 테스트하고 싶습니다
- 격려가 아니라 폐기할지 말지에 대한 정직한 판단이 필요합니다
- 결정의 여러 갈래 중 하나를 선택해야 하며 각 갈래를 해결해야 합니다
- 아이디어가 기존 프로젝트 안에 있고 이미 있는 것과 대조해야 합니다
## 건너뛸 시점
- 아직 아이디어가 없고 선택지를 생성해야 합니다. `bmad-brainstorming`을 사용하세요
- 제품을 추진하기로 정했고 고객 우선 관점에서 입증하고 싶습니다. `bmad-prfaq`를 사용하세요
- 에이전트들이 함께 결정을 토론하길 원합니다. `bmad-party-mode`를 사용하세요
:::note[필수 조건]
없습니다. 단련 과정은 일반 대화에서 실행됩니다. 설치된 에이전트와 설정된 페르소나 명단이 있으면 세션이 더 풍부해지지만, 없어도 작동합니다.
:::
## 세션 실행하기
### 1. 스킬 호출하기
IDE에서 `bmad-forge-idea`를 입력하거나, "아이디어를 단련해줘" 또는 "이걸 압박 검증해줘"라고 말하세요. 같은 메시지에 아이디어를 적어도 되고, 첫 질문을 기다려도 됩니다.
### 2. 목표 말하기
원하는 것을 말하세요. 아이디어를 단단하게 만들고 싶은지, 입증하거나 폐기하고 싶은지, 아니면 그저 끝까지 생각해 보고 싶은지 알려줍니다. 목표는 질문 방향을 정합니다. 입증은 가장 중요한 주장부터 공격하고, 단련은 각 갈래를 해결된 답으로 밀어붙입니다.
### 3. 한 갈래씩 사고 방어하기
질문자는 한 번에 하나의 질문을 던지고, 사용자가 반박할 수 있도록 자신의 권장 답을 함께 내놓습니다. 솔직하게 답하세요. 흐릿한 용어나 프로젝트 현실과 맞지 않는 주장을 지적하면, 다음으로 넘어가기 전에 먼저 정리하세요.
### 4. 대화 조종하기
모든 갈래는 두 참여자와 함께 진행됩니다. 하나는 사용자 명단에서, 하나는 주제가 즉석에서 불러낸 인물입니다. 특정 페르소나를 이름으로 부르거나, 저장된 파티를 소환하거나, "adversarial on this"라고 말해 한 주장을 공격하게 하고 직접 방어하세요.
### 5. 종료 지점에 도착하기
아이디어가 단련되거나, 폐기되거나, 단순히 더 명확해질 때까지 각 갈래를 해결된 답으로 밀고 가세요. 끝났다고 말해도 되고, forge가 종료를 판단하게 둬도 됩니다.
## 얻는 결과
Forge는 모든 실행마다 결과를 표시한 독립적인 `forge-report.html`을 작성합니다. 단련된 아이디어는 확정한 결정과 폐기한 것 및 그 이유를 담은 `forged-idea.md`로도 정제됩니다. 제품 개념이라면 이 파일을 `bmad-spec`, `bmad-prd`, `bmad-prfaq`의 입력으로 사용할 수 있습니다. 폐기되거나 명확해진 세션에는 별도 산출물이 필요하지 않습니다. 보고서 자체로 충분합니다.
:::tip[아이디어가 폐기되게 두세요]
아이디어가 버티지 못한다는 사실을 적은 비용으로 알아내는 것이 이득입니다. 세션을 "예"로 유도하지 마세요.
:::

View File

@ -18,7 +18,6 @@ IDE에서 스킬 이름(예: `bmad-help`)을 입력해 어떤 핵심 도구든
| [`bmad-help`](#bmad-help) | 작업 | 다음에 무엇을 해야 할지 상황에 맞게 안내 | | [`bmad-help`](#bmad-help) | 작업 | 다음에 무엇을 해야 할지 상황에 맞게 안내 |
| [`bmad-brainstorming`](#bmad-brainstorming) | 워크플로 | 대화형 브레인스토밍 세션 진행 | | [`bmad-brainstorming`](#bmad-brainstorming) | 워크플로 | 대화형 브레인스토밍 세션 진행 |
| [`bmad-party-mode`](#bmad-party-mode) | 워크플로 | 다중 에이전트 그룹 토론 조율 | | [`bmad-party-mode`](#bmad-party-mode) | 워크플로 | 다중 에이전트 그룹 토론 조율 |
| [`bmad-forge-idea`](#bmad-forge-idea) | 워크플로 | 아이디어를 단련하고, 입증하거나, 적은 비용으로 폐기할 때까지 압박 검증 |
| [`bmad-spec`](#bmad-spec) | 워크플로 | 모든 의도 입력을 후속 작업의 표준 계약인 SPEC 커널과 동반 파일로 정제 | | [`bmad-spec`](#bmad-spec) | 워크플로 | 모든 의도 입력을 후속 작업의 표준 계약인 SPEC 커널과 동반 파일로 정제 |
| [`bmad-advanced-elicitation`](#bmad-advanced-elicitation) | 작업 | LLM 출력을 반복 개선 방식으로 끌어올림 | | [`bmad-advanced-elicitation`](#bmad-advanced-elicitation) | 작업 | LLM 출력을 반복 개선 방식으로 끌어올림 |
| [`bmad-review-adversarial-general`](#bmad-review-adversarial-general) | 작업 | 빠진 것과 틀린 것을 찾는 비판적 리뷰 | | [`bmad-review-adversarial-general`](#bmad-review-adversarial-general) | 작업 | 빠진 것과 틀린 것을 찾는 비판적 리뷰 |
@ -71,7 +70,7 @@ IDE에서 스킬 이름(예: `bmad-help`)을 입력해 어떤 핵심 도구든
**입력:** 브레인스토밍 주제 또는 문제 설명, 선택 사항 컨텍스트 파일 **입력:** 브레인스토밍 주제 또는 문제 설명, 선택 사항 컨텍스트 파일
**출력:** 세션 보관용 독립 `brainstorm.html`, 후속 스킬용 선택적 `brainstorm-intent.md`, 그리고 `.memlog.md` 세션 기록 **출력:** 생성된 모든 아이디어가 담긴 `brainstorming-session-{date}.md`
:::note[수량 목표] :::note[수량 목표]
핵심은 아이디어 50-100개 지점에서 나옵니다. 이 워크플로는 정리 전에 100개 이상의 아이디어 생성을 권장합니다. 핵심은 아이디어 50-100개 지점에서 나옵니다. 이 워크플로는 정리 전에 100개 이상의 아이디어 생성을 권장합니다.
@ -99,28 +98,6 @@ IDE에서 스킬 이름(예: `bmad-help`)을 입력해 어떤 핵심 도구든
**출력:** 에이전트 페르소나가 유지되는 실시간 다중 에이전트 대화 **출력:** 에이전트 페르소나가 유지되는 실시간 다중 에이전트 대화
## bmad-forge-idea
**아이디어를 단련하고, 입증하거나, 적은 비용으로 폐기할 때까지 압박 검증합니다.** 적대적인 질문자가 반쯤 형성된 아이디어를 한 번에 하나의 질문으로 몰아붙입니다. 각 갈래마다 두 인물을 데려오며, 살아남은 것이 확신을 갖고 행동할 수 있는 것이 될 때까지 진행합니다.
**사용 시점:**
- 투자하기 전에 아이디어를 스트레스 테스트하고 싶습니다
- 아이디어를 폐기할지 말지에 대한 정직한 판단이 필요합니다
- 동의만 하는 상대가 아니라 반박하는 사고 파트너가 필요합니다
**작동 방식:**
1. 먼저 목표를 정하고 그 목표에 맞춰 질문 방향을 조정합니다
2. 의존성 순서에 따라 한 번에 하나의 질문을 다루며, 사용자가 반박할 수 있도록 권장 답을 제시합니다
3. 각 갈래마다 두 관점을 데려옵니다. 하나는 설치된 명단에서, 하나는 주제가 즉석에서 불러낸 인물입니다
4. 흐릿한 용어를 공격하고 기존 프로젝트 자료와 주장을 대조합니다
5. 단련됨, 폐기됨, 명확해짐 중 하나로 도착하며, 보관 가능한 독립 보고서를 남깁니다
**입력:** 기능, 비즈니스 모델, 연구 가설, 개인적 결정 등 어떤 도메인의 아이디어든 가능
**출력:** 아이디어가 단련되면 선택적으로 `forged-idea.md` 정제본, 모든 실행마다 `forge-report.html` 보관용 보고서
## bmad-spec ## bmad-spec
**모든 의도 입력을 후속 작업의 표준 SPEC 계약으로 정제합니다.** 간단한 아이디어, PRD, GDD, RFC, 브레인 덤프, 회의록, UX 폴더, 여러 소스가 섞인 입력을 받아 다섯 필드 커널(Why, Capabilities, Constraints, Non-goals, Success signal)을 담은 `SPEC.md`와 커널에 들어가지 않는 핵심 내용을 위한 동반 파일을 만듭니다. **모든 의도 입력을 후속 작업의 표준 SPEC 계약으로 정제합니다.** 간단한 아이디어, PRD, GDD, RFC, 브레인 덤프, 회의록, UX 폴더, 여러 소스가 섞인 입력을 받아 다섯 필드 커널(Why, Capabilities, Constraints, Non-goals, Success signal)을 담은 `SPEC.md`와 커널에 들어가지 않는 핵심 내용을 위한 동반 파일을 만듭니다.
@ -136,17 +113,17 @@ IDE에서 스킬 이름(예: `bmad-help`)을 입력해 어떤 핵심 도구든
1. 입력과 연결된 보조 자료를 읽습니다. 1. 입력과 연결된 보조 자료를 읽습니다.
2. 설정 가능한 템플릿으로 다섯 필드 커널을 정제하고, 넘치는 내용은 적절한 이름의 동반 파일로 보냅니다. 2. 설정 가능한 템플릿으로 다섯 필드 커널을 정제하고, 넘치는 내용은 적절한 이름의 동반 파일로 보냅니다.
3. 두 단계 자체 검증을 실행합니다. 먼저 일관성 규칙을 확인하고, 다음으로 모든 핵심 소스 주장이 보존됐는지 확인합니다. 3. 두 단계 자체 검증을 실행합니다. 먼저 일관성 규칙을 확인하고, 다음으로 모든 핵심 소스 주장이 보존됐는지 확인합니다.
4. `{output_folder}/specs/spec-{slug}/` 아래에 `SPEC.md`, 동반 파일, `.memlog.md`를 씁니다. 4. `{output_folder}/specs/spec-{slug}/` 아래에 `SPEC.md`, 동반 파일, `.decision-log.md`를 씁니다.
Spec Law는 여덟 가지 규칙을 강제합니다. capabilities는 의도와 성공 기준을 모두 담고, intents는 HOW가 아니라 WHAT이며, constraints는 실제 의사결정에 영향을 주고, non-goals는 명시적이며, success signals는 구체적이고, capability ID는 안정적이며, 모든 핵심 소스 주장은 보존되고, 문장은 간결해야 합니다. Spec Law는 여덟 가지 규칙을 강제합니다. capabilities는 의도와 성공 기준을 모두 담고, intents는 HOW가 아니라 WHAT이며, constraints는 실제 의사결정에 영향을 주고, non-goals는 명시적이며, success signals는 구체적이고, capability ID는 안정적이며, 모든 핵심 소스 주장은 보존되고, 문장은 간결해야 합니다.
**입력:** **입력:**
- `input`(필수) - 경로 또는 인라인 텍스트. 모호한 아이디어, 브레인 덤프, PRD, GDD, RFC, 제품 브리프, 대화록, 목업 폴더, 여러 소스 조합. - `input`(필수) - 경로 또는 인라인 텍스트. 모호한 아이디어, 브레인 덤프, PRD, GDD, RFC, brief, transcript, mockup 폴더, 여러 소스 조합.
- `slug`(선택 사항) - 입력이 빈약하고 소스 파일명에서 slug를 만들 수 없을 때만 필요합니다. - `slug`(선택 사항) - 입력이 빈약하고 소스 파일명에서 slug를 만들 수 없을 때만 필요합니다.
- `target_spec_path`(선택 사항) - 새 spec을 만드는 대신 기존 spec을 업데이트할 때 설정합니다. - `target_spec_path`(선택 사항) - 새 spec을 만드는 대신 기존 spec을 업데이트할 때 설정합니다.
**출력:** `SPEC.md`, 동반 파일, `.memlog.md`가 들어 있는 spec 폴더. Headless 호출자는 결과 상태와 작성 또는 수정된 파일 목록을 담은 JSON 응답을 받습니다. **출력:** `SPEC.md`, 동반 파일, `.decision-log.md`가 들어 있는 spec 폴더. Headless 호출자는 결과 상태와 작성 또는 수정된 파일 목록을 담은 JSON 응답을 받습니다.
:::note[변경 계약] :::note[변경 계약]
`bmad-spec``SPEC.md`와 spec이 작성한 동반 파일을 쓸 수 있는 유일한 도구입니다. 다른 스킬은 자체 네이티브 산출물을 만들고, 의도를 표준 계약으로 표현하거나 업데이트를 제안해야 할 때 headless 모드로 `bmad-spec`을 호출합니다. `bmad-spec``SPEC.md`와 spec이 작성한 동반 파일을 쓸 수 있는 유일한 도구입니다. 다른 스킬은 자체 네이티브 산출물을 만들고, 의도를 표준 계약으로 표현하거나 업데이트를 제안해야 할 때 headless 모드로 `bmad-spec`을 호출합니다.
@ -263,7 +240,7 @@ Spec Law는 여덟 가지 규칙을 강제합니다. capabilities는 의도와
**작동 방식:** **작동 방식:**
1. 5개 구조 모델(튜토리얼, 참조, 설명, 프롬프트, 전략) 문서를 분석합니다 1. 5개 구조 모델(튜토리얼, 참조, 설명, 프롬프트, 전략)에 대해 문서를 분석합니다
2. 중복, 범위 위반, 묻힌 정보를 식별합니다 2. 중복, 범위 위반, 묻힌 정보를 식별합니다
3. 우선순위 권장 사항을 생성합니다: `CUT`, `MERGE`, `MOVE`, `CONDENSE`, `QUESTION`, `PRESERVE` 3. 우선순위 권장 사항을 생성합니다: `CUT`, `MERGE`, `MOVE`, `CONDENSE`, `QUESTION`, `PRESERVE`
4. 총 단어 감소량과 감소율을 추정합니다 4. 총 단어 감소량과 감소율을 추정합니다

View File

@ -11,7 +11,7 @@ BMad Method(BMM)는 BMad 생태계의 모듈이며 컨텍스트 엔지니어링
언제든 무엇을 해야 할지 확실하지 않다면 `bmad-help` 스킬이 흐름을 잡아 주고 다음 단계를 알려줍니다. 이 문서를 참조로 사용할 수도 있지만, 이미 BMad Method를 설치했다면 `bmad-help`가 더 대화형이고 훨씬 빠릅니다. 또한 BMad Method를 확장한 다른 모듈이나 함께 쓰는 보완 모듈을 사용한다면 `bmad-help`도 사용 가능한 항목을 모두 파악해 현재 상황에 가장 적절한 조언을 제공합니다. 언제든 무엇을 해야 할지 확실하지 않다면 `bmad-help` 스킬이 흐름을 잡아 주고 다음 단계를 알려줍니다. 이 문서를 참조로 사용할 수도 있지만, 이미 BMad Method를 설치했다면 `bmad-help`가 더 대화형이고 훨씬 빠릅니다. 또한 BMad Method를 확장한 다른 모듈이나 함께 쓰는 보완 모듈을 사용한다면 `bmad-help`도 사용 가능한 항목을 모두 파악해 현재 상황에 가장 적절한 조언을 제공합니다.
마지막으로 중요한 점: 아래 모든 워크플로는 스킬로 직접 실행해도 되고, 먼저 에이전트를 로드한 뒤 에이전트 메뉴 항목을 사용해 원하는 도구에서 실행해도 됩니다. 마지막으로 중요한 점: 아래 모든 워크플로는 스킬로 직접 실행하거나, 먼저 에이전트를 로드한 뒤 에이전트 메뉴 항목을 사용해 원하는 도구에서 실행할 수 있습니다.
<iframe src="/workflow-map-diagram-ko.html" title="BMad Method 워크플로 맵 다이어그램" width="100%" height="100%" style="border-radius: 8px; border: 1px solid #334155; min-height: 900px;"></iframe> <iframe src="/workflow-map-diagram-ko.html" title="BMad Method 워크플로 맵 다이어그램" width="100%" height="100%" style="border-radius: 8px; border: 1px solid #334155; min-height: 900px;"></iframe>
@ -25,11 +25,10 @@ BMad Method(BMM)는 BMad 생태계의 모듈이며 컨텍스트 엔지니어링
| 워크플로 | 목적 | 산출물 | | 워크플로 | 목적 | 산출물 |
| --- | --- | --- | | --- | --- | --- |
| `bmad-brainstorming` | 브레인스토밍 코치의 안내를 받아 프로젝트 아이디어를 발산합니다 | `brainstorm.html` 보관본과 선택적 `brainstorm-intent.md` | | `bmad-brainstorming` | 브레인스토밍 코치의 안내를 받아 프로젝트 아이디어를 발산합니다 | `brainstorming-report.md` |
| `bmad-forge-idea` | 아이디어를 단련하고, 입증하거나, 적은 비용으로 폐기할 때까지 압박 검증합니다 | 매 실행마다 `forge-report.html`; 아이디어가 단련되면 `forged-idea.md` |
| `bmad-domain-research`, `bmad-market-research`, `bmad-technical-research` | 시장, 기술, 도메인 가정을 검증합니다 | 연구 발견 사항 | | `bmad-domain-research`, `bmad-market-research`, `bmad-technical-research` | 시장, 기술, 도메인 가정을 검증합니다 | 연구 발견 사항 |
| `bmad-product-brief` | 전략적 비전을 포착합니다. 개념이 명확할 때 가장 좋습니다 | `brief.md` + `addendum.md`, 필요한 HTML 또는 프레젠테이션 출력 | | `bmad-product-brief` | 전략적 비전을 포착합니다. 개념이 명확할 때 가장 좋습니다 | `product-brief.md` |
| `bmad-prfaq` | 워킹 백워드 방식으로 제품 개념을 고객 우선 관점에서 스트레스 테스트합니다 | `prfaq-{project}.md` | | `bmad-prfaq` | 워킹 백워드 방식으로 제품 개념을 스트레스 테스트하고 다듬습니다 | `prfaq-{project}.md` |
## 단계 2: 계획 ## 단계 2: 계획
@ -37,13 +36,13 @@ BMad Method(BMM)는 BMad 생태계의 모듈이며 컨텍스트 엔지니어링
| 워크플로 | 목적 | 산출물 | | 워크플로 | 목적 | 산출물 |
| --- | --- | --- | | --- | --- | --- |
| `bmad-prd` | PRD를 생성, 업데이트, 검증합니다. 안내형 발견 과정과 세 가지 의도를 하나의 스킬에 담았습니다 | 생성/업데이트: `prd.md`, `addendum.md`, `.memlog.md`; 검증: `validation-report.html` + `.md` | | `bmad-prd` | PRD를 생성, 업데이트, 검증합니다. 안내형 발견 과정과 세 가지 의도를 하나의 스킬에 담았습니다 | 생성/업데이트: `prd.md`, `addendum.md`, `decision-log.md`; 검증: `validation-report.html` + `.md` |
| `bmad-ux` | UX가 중요할 때 사용자 경험을 설계합니다. DESIGN.md(시각)와 EXPERIENCE.md(동작)라는 두 핵심 문서를 만듭니다 | `DESIGN.md`, `EXPERIENCE.md`, `.memlog.md` | | `bmad-ux` | UX가 중요할 때 사용자 경험을 설계합니다. DESIGN.md(시각)와 EXPERIENCE.md(동작)라는 두 핵심 문서를 만듭니다 | `DESIGN.md`, `EXPERIENCE.md`, `.decision-log.md` |
:::tip[하나의 스킬 안에 세 의도] :::tip[하나의 스킬 안에 세 의도]
`bmad-prd`는 전체 PRD 수명주기를 처리합니다. 호출할 때 의도를 말하거나 스킬이 물어보게 하세요. `bmad-prd`는 전체 PRD 수명주기를 처리합니다. 호출할 때 의도를 말하거나 스킬이 물어보게 하세요.
- **생성** - 안내형 발견 과정으로 처음부터 새 PRD를 만듭니다. `prd.md`, `addendum.md`, `.memlog.md`를 생성합니다 - **생성** - 안내형 발견 과정을 통해 처음부터 새 PRD를 만듭니다. `prd.md`, `addendum.md`, `decision-log.md`를 생성합니다
- **업데이트** - 기존 PRD와 변경 신호를 조정하고, 변경을 적용하기 전에 충돌을 식별합니다 - **업데이트** - 기존 PRD와 변경 신호를 조정하고, 변경을 적용하기 전에 충돌을 식별합니다
- **검증** - 설정 가능한 체크리스트로 PRD를 비판적으로 검토하고 구조화된 HTML 발견 사항 보고서를 생성합니다 - **검증** - 설정 가능한 체크리스트로 PRD를 비판적으로 검토하고 구조화된 HTML 발견 사항 보고서를 생성합니다
::: :::
@ -58,13 +57,13 @@ BMad Method(BMM)는 BMad 생태계의 모듈이며 컨텍스트 엔지니어링
| 워크플로 | 목적 | 산출물 | | 워크플로 | 목적 | 산출물 |
| --- | --- | --- | | --- | --- | --- |
| `bmad-architecture` | 기술적 결정을 명시적으로 만듭니다 | 기본 핵심 문서는 `ARCHITECTURE-SPINE.md`이며, 필요한 출력이나 프레젠테이션 형태로 확장해 씁니다 | | `bmad-create-architecture` | 기술적 결정을 명시적으로 만듭니다 | ADR이 있는 `architecture.md` |
| `bmad-create-epics-and-stories` | 요구사항을 구현 가능한 작업으로 나눕니다 | 스토리가 있는 에픽 파일 | | `bmad-create-epics-and-stories` | 요구사항을 구현 가능한 작업으로 나눕니다 | 스토리가 있는 에픽 파일 |
| `bmad-check-implementation-readiness` | 구현 전 관문 점검 | 통과/우려/실패 결정 | | `bmad-check-implementation-readiness` | 구현 전 관문 점검 | 통과/우려/실패 결정 |
## 단계 4: 구현 ## 단계 4: 구현
스토리 하나씩 구현합니다. 4단계 에픽 및 스토리 자동화도 이제 지원합니다. 사용자는 흐름에 얼마나 개입할지 고르면 됩니다. 전체 흐름을 타도 되고, 빠른 흐름으로 바로 넘어가도 됩니다. 스토리 하나씩 구현합니다. 전체 4단계 자동화는 곧 제공됩니다.
| 워크플로 | 목적 | 산출물 | | 워크플로 | 목적 | 산출물 |
| --- | --- | --- | | --- | --- | --- |

View File

@ -69,10 +69,10 @@ BMad는 전문 AI 에이전트가 있는 안내형 워크플로를 통해 소프
| 단계 | 이름 | 일어나는 일 | | 단계 | 이름 | 일어나는 일 |
| --- | --- | --- | | --- | --- | --- |
| 1 | 분석 | 브레인스토밍, 리서치, 아이디어 단련, 제품 개요 또는 PRFAQ *(선택)* | | 1 | 분석 | 브레인스토밍, 리서치, 제품 개요 또는 PRFAQ *(선택)* |
| 2 | 계획 | PRD, UX, SPEC으로 요구사항과 설계를 작성 | | 2 | 계획 | 요구사항 작성(PRD 또는 사양) |
| 3 | 솔루션 설계 | 아키텍처 핵심 문서 또는 상세 프로젝트/시스템 아키텍처 설계 | | 3 | 솔루션 설계 | 아키텍처 설계 *(BMad Method/엔터프라이즈 전용)* |
| 4 | 구현 | 빠른 개발 또는 자동화된 에픽 전달로 에픽별, 스토리별 구현 | | 4 | 구현 | 에픽별, 스토리별 구현 |
단계, 워크플로, 컨텍스트 관리를 살펴보려면 **[워크플로 맵 열기](../reference/workflow-map.md)**를 확인하세요. 단계, 워크플로, 컨텍스트 관리를 살펴보려면 **[워크플로 맵 열기](../reference/workflow-map.md)**를 확인하세요.
@ -138,17 +138,16 @@ BMad 도움말이 완료된 작업을 감지하고 정확한 다음 단계를
이 단계의 모든 워크플로는 선택 사항입니다. [**무엇을 써야 할지 모르겠나요?**](../explanation/analysis-phase.md) 이 단계의 모든 워크플로는 선택 사항입니다. [**무엇을 써야 할지 모르겠나요?**](../explanation/analysis-phase.md)
- **브레인스토밍**(`bmad-brainstorming`) - 안내형 아이디어 발산 - **브레인스토밍**(`bmad-brainstorming`) - 안내형 아이디어 발산
- **아이디어 단련**(`bmad-forge-idea`) - 아이디어가 단단해지거나 적은 비용으로 폐기될 때까지 압박 검증
- **리서치**(`bmad-market-research` / `bmad-domain-research` / `bmad-technical-research`) - 시장, 도메인, 기술 리서치 - **리서치**(`bmad-market-research` / `bmad-domain-research` / `bmad-technical-research`) - 시장, 도메인, 기술 리서치
- **제품 개요**(`bmad-product-brief`) - 개념이 명확할 때 권장되는 기초 문서 - **제품 개요**(`bmad-product-brief`) - 개념이 명확할 때 권장되는 기초 문서
- **PRFAQ**(`bmad-prfaq`) - 제품 개념을 고객 우선 관점에서 스트레스 테스트하는 워킹 백워드 챌린지 - **PRFAQ**(`bmad-prfaq`) - 제품 개념을 압박하고 다듬는 워킹 백워드 챌린지
### 2단계: 계획(필수) ### 2단계: 계획(필수)
**BMad Method 및 엔터프라이즈 트랙:** **BMad Method 및 엔터프라이즈 트랙:**
1. 새 채팅에서 `bmad-prd`를 실행합니다. 의도(생성, 업데이트, 검증)를 직접 말하거나 스킬이 묻게 둡니다 1. 새 채팅에서 `bmad-prd`를 실행합니다. 의도(생성, 업데이트, 검증)를 직접 말하거나 스킬이 묻게 둡니다
2. 출력: `prd.md`, `addendum.md`, `.memlog.md` 2. 출력: `prd.md`, `addendum.md`, `decision-log.md`
:::note[`bmad-prd` 의도] :::note[`bmad-prd` 의도]
- **생성** - 처음부터 코칭형 발견 과정을 진행합니다. 스킬이 워크스페이스 폴더 이름을 정하고 만족할 만한 PRD까지 안내합니다 - **생성** - 처음부터 코칭형 발견 과정을 진행합니다. 스킬이 워크스페이스 폴더 이름을 정하고 만족할 만한 PRD까지 안내합니다

View File

@ -18,7 +18,6 @@ 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-help`](#bmad-help) | Task | Get context-aware guidance on what to do next |
| [`bmad-brainstorming`](#bmad-brainstorming) | Workflow | Facilitate interactive brainstorming sessions | | [`bmad-brainstorming`](#bmad-brainstorming) | Workflow | Facilitate interactive brainstorming sessions |
| [`bmad-party-mode`](#bmad-party-mode) | Workflow | Orchestrate multi-agent group discussions | | [`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-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-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 | | [`bmad-review-adversarial-general`](#bmad-review-adversarial-general) | Task | Cynical review that finds what's missing and what's wrong |
@ -71,7 +70,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 **Input:** Brainstorming topic or problem statement, optional context file
**Output:** a self-contained `brainstorm.html` keepsake of the session, an optional `brainstorm-intent.md` for downstream skills, and a `.memlog.md` session record **Output:** `brainstorming-session-{date}.md` with all generated ideas
:::note[Quantity Target] :::note[Quantity Target]
The magic happens in ideas 50100. The workflow encourages generating 100+ ideas before organization. The magic happens in ideas 50100. The workflow encourages generating 100+ ideas before organization.
@ -99,28 +98,6 @@ The magic happens in ideas 50100. The workflow encourages generating 100+ ide
**Output:** Real-time multi-agent conversation with maintained agent personalities **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 ## 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. **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.
@ -136,7 +113,7 @@ The magic happens in ideas 50100. The workflow encourages generating 100+ ide
1. Reads the input and any ancillary linked materials. 1. Reads the input and any ancillary linked materials.
2. Distills into the five-field kernel using a configurable template; routes overflow into appropriately-named companions. 2. Distills into the five-field kernel using a configurable template; routes overflow into appropriately-named companions.
3. Runs a two-pass self-validate (coherence rules, then preservation of every load-bearing source claim). 3. Runs a two-pass self-validate (coherence rules, then preservation of every load-bearing source claim).
4. Writes `SPEC.md`, sibling companions, and a `.memlog.md` under `{output_folder}/specs/spec-{slug}/`. 4. Writes `SPEC.md`, sibling companions, and a `.decision-log.md` under `{output_folder}/specs/spec-{slug}/`.
Spec Law enforces eight rules: capabilities carry both intent and success; intents are WHAT not HOW; constraints actually bend decisions; non-goals are explicit; success signals are concrete; capability IDs are stable; every load-bearing source claim is preserved; prose is lean. Spec Law enforces eight rules: capabilities carry both intent and success; intents are WHAT not HOW; constraints actually bend decisions; non-goals are explicit; success signals are concrete; capability IDs are stable; every load-bearing source claim is preserved; prose is lean.
@ -146,7 +123,7 @@ Spec Law enforces eight rules: capabilities carry both intent and success; inten
- `slug` (optional) — required only when input is sparse and no slug is derivable from a source filename. - `slug` (optional) — required only when input is sparse and no slug is derivable from a source filename.
- `target_spec_path` (optional) — set to update an existing spec instead of creating a new one. - `target_spec_path` (optional) — set to update an existing spec instead of creating a new one.
**Output:** Spec folder containing `SPEC.md`, any companion files, and a `.memlog.md`. Headless callers receive a JSON response with the result status and the list of files written or modified. **Output:** Spec folder containing `SPEC.md`, any companion files, and a `.decision-log.md`. Headless callers receive a JSON response with the result status and the list of files written or modified.
:::note[Mutation contract] :::note[Mutation contract]
`bmad-spec` is the only writer of `SPEC.md` and of spec-authored companions. Other skills produce their own native artifacts and invoke `bmad-spec` headless when they need to express intent as the canonical contract or propose updates. `bmad-spec` is the only writer of `SPEC.md` and of spec-authored companions. Other skills produce their own native artifacts and invoke `bmad-spec` headless when they need to express intent as the canonical contract or propose updates.

View File

@ -35,11 +35,10 @@ it**](../explanation/analysis-phase.md).
| Workflow | Purpose | Produces | | Workflow | Purpose | Produces |
|---------------------------------------------------------------------------|----------------------------------------------------------------------------|---------------------------| |---------------------------------------------------------------------------|----------------------------------------------------------------------------|---------------------------|
| `bmad-brainstorming` | Brainstorm Project Ideas with guided facilitation of a brainstorming coach | `brainstorm.html` keepsake plus an optional `brainstorm-intent.md` | | `bmad-brainstorming` | Brainstorm Project Ideas with guided facilitation of a brainstorming coach | `brainstorming-report.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-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 | `brief.md` + `addendum.md`, plus any desired HTML or presentation output | | `bmad-product-brief` | Capture strategic vision — best when your concept is clear | `product-brief.md` |
| `bmad-prfaq` | Working Backwards — stress-test your product concept customer-first | `prfaq-{project}.md` | | `bmad-prfaq` | Working Backwards — stress-test and forge your product concept | `prfaq-{project}.md` |
## Phase 2: Planning ## Phase 2: Planning
@ -47,13 +46,13 @@ Define what to build and for whom.
| Workflow | Purpose | Produces | | Workflow | Purpose | Produces |
|-------------------------|-------------------------------------------------------------------------------------|---------------------------------------------------| |-------------------------|-------------------------------------------------------------------------------------|---------------------------------------------------|
| `bmad-prd` | Create, update, or validate a PRD — facilitated discovery, three intents in one skill | Create/Update: `prd.md`, `addendum.md`, `.memlog.md`; Validate: `validation-report.html` + `.md` | | `bmad-prd` | Create, update, or validate a PRD — facilitated discovery, three intents in one skill | Create/Update: `prd.md`, `addendum.md`, `decision-log.md`; Validate: `validation-report.html` + `.md` |
| `bmad-ux` | Design user experience (when UX matters) — DESIGN.md (visual) + EXPERIENCE.md (behavioral) spine pair | `DESIGN.md`, `EXPERIENCE.md`, `.memlog.md` | | `bmad-ux` | Design user experience (when UX matters) — DESIGN.md (visual) + EXPERIENCE.md (behavioral) spine pair | `DESIGN.md`, `EXPERIENCE.md`, `.decision-log.md` |
:::tip[Three intents in one skill] :::tip[Three intents in one skill]
`bmad-prd` handles the full PRD lifecycle. State your intent when invoking or the skill will ask: `bmad-prd` handles the full PRD lifecycle. State your intent when invoking or the skill will ask:
- **Create** — new PRD from scratch via coached discovery; produces `prd.md`, `addendum.md`, and `.memlog.md` - **Create** — new PRD from scratch via coached discovery; produces `prd.md`, `addendum.md`, and `decision-log.md`
- **Update** — reconcile an existing PRD with a change signal, surfacing conflicts before applying changes - **Update** — reconcile an existing PRD with a change signal, surfacing conflicts before applying changes
- **Validate** — critique a PRD against a configurable checklist and produce a structured HTML findings report - **Validate** — critique a PRD against a configurable checklist and produce a structured HTML findings report
::: :::
@ -68,13 +67,13 @@ Decide how to build it and break work into stories.
| Workflow | Purpose | Produces | | Workflow | Purpose | Produces |
|---------------------------------------|--------------------------------------------|-----------------------------| |---------------------------------------|--------------------------------------------|-----------------------------|
| `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-architecture` | Make technical decisions explicit | `architecture.md` with ADRs |
| `bmad-create-epics-and-stories` | Break requirements into implementable work | Epic files with stories | | `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 | | `bmad-check-implementation-readiness` | Gate check before implementation | PASS/CONCERNS/FAIL decision |
## Phase 4: Implementation ## Phase 4: Implementation
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. Build it, one story at a time. Coming soon, full phase 4 automation!
| Workflow | Purpose | Produces | | 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 | | Phase | Name | What Happens |
| ----- | -------------- | ------------------------------------------------------------ | | ----- | -------------- | ------------------------------------------------------------ |
| 1 | Analysis | Brainstorming, research, forge idea, product brief or PRFAQ _(optional)_ | | 1 | Analysis | Brainstorming, research, product brief or PRFAQ _(optional)_ |
| 2 | Planning | Create requirements and design PRD, UX, SPEC | | 2 | Planning | Create requirements (PRD or spec) |
| 3 | Solutioning | Design architecture spine or detailed project or system architectures | | 3 | Solutioning | Design architecture _(BMad Method/Enterprise only)_ |
| 4 | Implementation | Build epic by epic, story by story with quick dev or automated epic delivery | | 4 | Implementation | Build epic by epic, story by story |
**[Open the Workflow Map](../reference/workflow-map.md)** to explore phases, workflows, and context management. **[Open the Workflow Map](../reference/workflow-map.md)** to explore phases, workflows, and context management.
@ -139,17 +139,16 @@ 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) All workflows in this phase are optional. [**Not sure which to use?**](../explanation/analysis-phase.md)
- **brainstorming** (`bmad-brainstorming`) — Guided ideation - **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 - **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 - **product-brief** (`bmad-product-brief`) — Recommended foundation document when your concept is clear
- **prfaq** (`bmad-prfaq`) — Working Backwards challenge to stress-test your product concept customer-first - **prfaq** (`bmad-prfaq`) — Working Backwards challenge to stress-test and forge your product concept
### Phase 2: Planning (Required) ### Phase 2: Planning (Required)
**For BMad Method and Enterprise tracks:** **For BMad Method and Enterprise tracks:**
1. Run `bmad-prd` in a new chat — state your intent (Create / Update / Validate) or let the skill ask 1. Run `bmad-prd` in a new chat — state your intent (Create / Update / Validate) or let the skill ask
2. Output: `prd.md`, `addendum.md`, `.memlog.md` 2. Output: `prd.md`, `addendum.md`, `decision-log.md`
:::note[`bmad-prd` intents] :::note[`bmad-prd` intents]

View File

@ -22,7 +22,7 @@ Skill `bmad-customize` là trợ lý tạo cấu hình có hướng dẫn cho **
:::note[Điều kiện tiên quyết] :::note[Điều kiện tiên quyết]
- BMad đã được cài trong dự án của bạn (xem [Cách cài đặt BMad](./install-bmad.md)) - BMad đã được cài trong dự án của bạn (xem [Cách cài đặt BMad](./install-bmad.md))
- Một cách để chạy resolver script — BMad đang chuẩn hóa sang `uv` (`uv run`, tự cấp Python cho bạn); một `python3` 3.11+ thuần trên PATH vẫn dùng được trong giai đoạn chuyển đổi. Script chỉ dùng stdlib `tomllib`, nên không cần `pip install` gì cả. - Python 3.11+ có trên PATH của bạn (để chạy resolver; dùng stdlib `tomllib`, không cần `pip install`, `uv` hay virtualenv)
- Một trình soạn thảo văn bản cho file TOML - Một trình soạn thảo văn bản cho file TOML
::: :::
@ -201,15 +201,15 @@ persistent_facts = [
## Cách quá trình resolve diễn ra ## Cách quá trình resolve diễn ra
Khi agent được kích hoạt, `SKILL.md` của nó sẽ gọi một shared Python script để merge ba lớp nói trên và trả về block kết quả ở dạng JSON. Script này chỉ dùng `tomllib` của Python stdlib (không có dependency ngoài). BMad đang chuẩn hóa sang `uv run` để chạy các script này (uv tự cấp một bản Python phù hợp cho bạn); một `python3` thuần vẫn dùng được trong giai đoạn chuyển đổi: Khi agent được kích hoạt, `SKILL.md` của nó sẽ gọi một shared Python script để merge ba lớp nói trên và trả về block kết quả ở dạng JSON. Script này dùng `tomllib` của Python stdlib, nên `python3` thuần là đủ:
```bash ```bash
uv run {project-root}/_bmad/scripts/resolve_customization.py \ python3 {project-root}/_bmad/scripts/resolve_customization.py \
--skill {skill-root} \ --skill {skill-root} \
--key agent --key agent
``` ```
**Yêu cầu**: Python 3.11+ vì các phiên bản cũ hơn không có `tomllib`; không cần `pip install` gì. Chạy qua `uv run` là chuẩn về sau — uv tự tìm một bản interpreter phù hợp cho bạn. Nếu bạn chạy trực tiếp bằng `python3` trong giai đoạn chuyển đổi, hãy kiểm tra phiên bản bằng `python3 --version`: trên một số nền tảng, `python3` mặc định vẫn là 3.10 hoặc thấp hơn, nên có thể bạn sẽ phải cài 3.11+ riêng. **Yêu cầu**: Python 3.11+ vì các phiên bản cũ hơn không có `tomllib`. Không cần `pip install`, không cần `uv`, không cần virtualenv. Bạn có thể kiểm tra bằng `python3 --version`. Trên một số nền tảng, `python3` mặc định vẫn là 3.10 hoặc thấp hơn, nên có thể bạn sẽ phải cài 3.11+ riêng.
`--skill` trỏ vào thư mục skill đã cài, nơi có file `customize.toml`. Tên skill được lấy từ basename của thư mục, sau đó script sẽ tự tìm `_bmad/custom/{skill-name}.toml``{skill-name}.user.toml`. `--skill` trỏ vào thư mục skill đã cài, nơi có file `customize.toml`. Tên skill được lấy từ basename của thư mục, sau đó script sẽ tự tìm `_bmad/custom/{skill-name}.toml``{skill-name}.user.toml`.
@ -217,17 +217,17 @@ Một số lệnh hữu ích:
```bash ```bash
# Resolve toàn bộ block agent # Resolve toàn bộ block agent
uv run {project-root}/_bmad/scripts/resolve_customization.py \ python3 {project-root}/_bmad/scripts/resolve_customization.py \
--skill /duong-dan/tuyet-doi/toi/bmad-agent-pm \ --skill /duong-dan/tuyet-doi/toi/bmad-agent-pm \
--key agent --key agent
# Resolve một trường cụ thể # Resolve một trường cụ thể
uv run {project-root}/_bmad/scripts/resolve_customization.py \ python3 {project-root}/_bmad/scripts/resolve_customization.py \
--skill /duong-dan/tuyet-doi/toi/bmad-agent-pm \ --skill /duong-dan/tuyet-doi/toi/bmad-agent-pm \
--key agent.icon --key agent.icon
# Dump toàn bộ # Dump toàn bộ
uv run {project-root}/_bmad/scripts/resolve_customization.py \ python3 {project-root}/_bmad/scripts/resolve_customization.py \
--skill /duong-dan/tuyet-doi/toi/bmad-agent-pm --skill /duong-dan/tuyet-doi/toi/bmad-agent-pm
``` ```

View File

@ -0,0 +1,237 @@
{
"skill_name": "bmad-product-brief",
"_design_notes": "Single-shot evals across two patterns. Pattern A (A1-A8) tests artifact correctness given complete inputs in headless mode. Pattern B tests process discipline (decision log fidelity, polish execution, intent boundaries) by inspecting transcript and side-artifacts. Facilitation/conversation-quality evals are deferred to a future multi-turn simulator.",
"evals": [
{
"id": "A1",
"_pattern": "artifact-correctness",
"prompt": "Run headless. Create a product brief for InsuLens.\n\nContext (use exactly this — do not invent):\n- Product: a smartphone app that pairs with off-the-shelf $200 thermal imaging accessories (FLIR ONE Pro and Seek Compact Pro). The app guides homeowners through a structured walkthrough and produces a professional-grade insulation audit in under 20 minutes.\n- Target: suburban homeowners aged 35-65 with houses built before 2000 (poor original insulation, rising energy bills).\n- Validation evidence: 50 user interviews completed in Q4 2025; 78% expressed willingness to pay $49 for a one-time audit if results were credible.\n- Stakes: this brief is the primary input investors will read before our first Series A pitch call.\n- Hardware dependency: requires a thermal imaging accessory (we do not manufacture hardware).\n- Known unknowns: insurance/warranty implications of homeowner-driven audits; whether the 78% intent translates to paid conversion at scale.\nRight-size for investor-stage rigor. Output a JSON status block at the end with status, intent, and artifact paths.",
"expected_output": "A run folder containing brief.md (with valid YAML frontmatter) and decision-log.md. Brief is 1-2 pages, addresses target audience, hardware dependency, validation evidence, and surfaces unknowns alongside knowns. Final assistant message includes JSON with status='complete', intent='create', and artifact paths.",
"files": [],
"expectations": [
"A run folder is created with brief.md and decision-log.md",
"brief.md has YAML frontmatter containing all four required fields: title, status, created (ISO 8601), updated (ISO 8601)",
"brief.md frontmatter status is 'draft' or 'final' (not missing or empty)",
"brief.md word count is between 250 and 1500",
"brief.md identifies the suburban-homeowner-aged-35-65 target audience",
"brief.md references the thermal imaging hardware dependency (FLIR ONE Pro / Seek Compact Pro or equivalent)",
"brief.md references the 50-user interview validation OR the 78% willingness-to-pay finding",
"brief.md surfaces at least one explicit unknown or unvalidated assumption",
"brief.md does not introduce facts absent from the input prompt (no invented competitors, fabricated metrics, or unstated partnerships)",
"The final assistant message contains a JSON object with keys 'status', 'intent', and at least one artifact path",
"The JSON 'intent' value is 'create'"
]
},
{
"id": "A2",
"_pattern": "artifact-correctness",
"prompt": "Run headless. Create a brief for our app idea.",
"expected_output": "Headless mode with insufficient context should halt with status='blocked' and a reason field. No (or only skeleton) brief should be written. The skill must not invent a product to draft against.",
"files": [],
"expectations": [
"The final assistant message contains a JSON object with key 'status' set to 'blocked'",
"The JSON output includes a 'reason' field explaining what context is missing",
"If brief.md exists at all, its body content (excluding frontmatter) is fewer than 100 words OR is a placeholder skeleton — the skill did not invent a product",
"The transcript contains no clarifying questions sent back to the user (headless mode honored)"
]
},
{
"id": "A3",
"_pattern": "artifact-correctness",
"prompt": "Run headless. Create a brief for our neighborhood compost coordinator app idea — we are moving forward with it. Q2 brainstorming session notes are at evals/bmm-skills/bmad-product-brief/files/q2-brainstorm.md; pull only what is relevant to the compost concept.",
"expected_output": "Brief focuses tightly on the compost coordinator concept. Source brainstorm is filtered, not ingested wholesale. Decision-log records that filtering occurred.",
"files": ["evals/bmm-skills/bmad-product-brief/files/q2-brainstorm.md"],
"expectations": [
"brief.md addresses the neighborhood compost coordinator concept",
"brief.md does not introduce content from unrelated brainstorm topics (weather + mood, meditation chime, podcasting tool, craft beer subscription, AI sommelier, office plants, ride coordinator, cookbook app, AR home staging)",
"brief.md word count is between 250 and 1500",
"brief.md incorporates at least 2 specific details from the compost section of the brainstorm (e.g., two-sided market with apartment dwellers and home compost-pile owners, hyperlocal neighborhood scope, free-at-launch with eventual subscription, Portland Sunnyside/Hawthorne pilot)",
"decision-log.md indicates the brainstorm was filtered for relevance, not ingested whole"
]
},
{
"id": "A4",
"_pattern": "artifact-correctness",
"prompt": "Run headless. Validate the brief at evals/bmm-skills/bmad-product-brief/files/mossridge-brief/brief.md — the Mossridge Public Library board meets Monday and we need this to land. Read the addendum and decision-log in the same folder first. Cite specific sections, identify weaknesses, caveat what cannot be evaluated. Return inline only — no separate validation file.",
"expected_output": "Inline critique citing specific sections from the input brief. No new files. Caveats at least one claim that cannot be evaluated from the brief alone. Offers to roll findings into an Update.",
"files": [
"evals/bmm-skills/bmad-product-brief/files/mossridge-brief/brief.md",
"evals/bmm-skills/bmad-product-brief/files/mossridge-brief/addendum.md",
"evals/bmm-skills/bmad-product-brief/files/mossridge-brief/decision-log.md"
],
"expectations": [
"The final output cites specific section names or line content from the input brief (not generic feedback)",
"The output identifies at least one specific weakness or area for improvement in the input brief",
"The output explicitly caveats at least one claim that cannot be evaluated from the brief alone (e.g., community demand, funding feasibility, volunteer sustainability)",
"The output offers to roll findings into an Update (or equivalent next-step proposal)",
"The final assistant message contains a JSON object with intent='validate'"
]
},
{
"id": "A5",
"_pattern": "artifact-correctness",
"prompt": "Run headless. Create a brief for: a weekend-project iOS app called Sproutkeeper that reminds houseplant owners when to water their plants based on plant type and indoor humidity sensor data. Target is hobbyist plant owners. MVP scope only, single-developer side project, no investors, no team, just personal evening project.",
"expected_output": "Lightweight brief right-sized to a side project. Low rigor. No investor-grade framing.",
"files": [],
"expectations": [
"The final assistant message contains a JSON object with intent='create'",
"brief.md exists at the path referenced in the JSON output",
"brief.md is right-sized for a side project (closer to 250-500 words than 1500)",
"brief.md does not include investor-grade framing (no 'Series A inputs', 'TAM/SAM/SOM', 'go-to-market strategy' boilerplate when the user said this is a personal evening project)",
"The transcript contains no clarifying questions to the user",
"Sections that do not earn their place for a side project are dropped or kept minimal (e.g., no extensive Risk or Success Criteria padding)"
]
},
{
"id": "A6",
"_pattern": "artifact-correctness",
"prompt": "Run headless. Create a brief from this memo. It is from our last working group on a new microcredential program at Branfield Community College. Memo is at evals/bmm-skills/bmad-product-brief/files/branfield-memo.md. Use what is there; do not re-elicit facts already present.",
"expected_output": "Brief reflects content from the memo. No re-asking for facts already present. Decision-log notes ingestion of the memo.",
"files": ["evals/bmm-skills/bmad-product-brief/files/branfield-memo.md"],
"expectations": [
"brief.md incorporates at least 3 distinct facts or decisions present in the input memo",
"decision-log.md references having used the memo as source material",
"The transcript does not ask the user to re-state the program name, target student, or core curriculum focus if those are present in the memo",
"brief.md does not invent program details not present in the memo"
]
},
{
"id": "A7",
"_pattern": "artifact-correctness",
"prompt": "Run headless. Create a brief for Brightway — our smart bike helmet with crash detection, turn signals, and braking lights. Meridian Insights produced a market research report on e-mobility at evals/bmm-skills/bmad-product-brief/files/meridian-mobility-report.md. Use only what is relevant to the safety helmet category — do not let the e-scooter, charging-infrastructure, or bike-share segments bleed into the brief.",
"expected_output": "Brief focuses on the smart bike helmet concept. Pulls relevant findings from the helmet section. Other mobility segments do not appear.",
"files": ["evals/bmm-skills/bmad-product-brief/files/meridian-mobility-report.md"],
"expectations": [
"brief.md addresses the Brightway smart bike helmet concept",
"brief.md does not introduce content from unrelated mobility segments (e-scooters, charging infrastructure, bike-share, vehicle-to-grid)",
"brief.md word count is between 250 and 1500",
"brief.md incorporates at least 2 specific findings from the smart helmet section of the report (e.g., market sizing, key players, crash detection technology trends, regulatory or insurance landscape)",
"decision-log.md indicates the report was filtered to the helmet category rather than ingested whole"
]
},
{
"id": "A8",
"_pattern": "artifact-correctness",
"prompt": "Run headless. Create a brief for Pantry Bridge — a meal-kit subscription targeted at adults 65+ who live alone and want fresh meals without grocery shopping. Customer research transcripts are at evals/bmm-skills/bmad-product-brief/files/pantry-bridge-interviews.md. Pull what is relevant from the older-adult interviews; do not conflate insights from the working-parent, student, or corporate-buyer personas.",
"expected_output": "Brief focuses on the older-adult target persona. Eleanor's interview drives the insights. Other personas do not pollute the brief.",
"files": ["evals/bmm-skills/bmad-product-brief/files/pantry-bridge-interviews.md"],
"expectations": [
"brief.md addresses the Pantry Bridge older-adult meal-kit concept",
"brief.md does not conflate insights from non-target personas (working parent Susan, college student Marcus, corporate cafeteria buyer Dimitri)",
"brief.md word count is between 250 and 1500",
"brief.md incorporates at least 2 specific insights from Eleanor's interview (e.g., grocery-trip difficulty, portion sizing, dietary restrictions, social aspects of meals, trust concerns)",
"decision-log.md notes which interviews were used and which were excluded"
]
},
{
"id": "B1",
"_pattern": "process-discipline",
"prompt": "Run headless. Create a brief for HelmStack — an open-source observability platform for distributed systems.\n\nWe have made these specific decisions and want each captured in the decision log with rationale:\n\n1. Pricing: Free open-source core; paid SaaS at $29/seat/month. Rejected paid-one-shot-license model because it would limit network effects in the OSS community.\n2. Launch: Invite-only beta for 6 weeks before public launch. Rejected open public launch — operational risk too high before stability is proven on real workloads.\n3. Stack: TypeScript + Postgres for the backend. Rejected Go + MongoDB — TypeScript aligned better with our team's existing skills and the frontend codebase.\n4. ICP: 5-50 person engineering teams for MVP. Rejected enterprise-first focus because the sales cycle is too long for our capital runway.\n5. Self-host: SaaS-only at launch; self-host arrives in v2. Rejected concurrent self-host because it would slow shipping velocity past our funding window.\n\nProduce brief.md and decision-log.md.",
"expected_output": "Decision log contains all five named decisions with rationale captured. Brief reflects the decisions but the decision log is the canonical record.",
"files": [],
"expectations": [
"decision-log.md exists in the run folder",
"decision-log.md captures the pricing decision (free OSS + $29/seat SaaS) with the rejected alternative (paid one-shot license) and rationale (network effects)",
"decision-log.md captures the invite-only-beta decision with the rejected alternative (open public launch) and rationale (operational risk before stability)",
"decision-log.md captures the platform-stack decision (TypeScript + Postgres) with the rejected alternative (Go + MongoDB) and rationale (team skills / frontend alignment)",
"decision-log.md captures the ICP decision (5-50 person eng teams) with rationale referencing sales cycle / runway",
"decision-log.md captures the self-host-timing decision (SaaS-only at launch, self-host v2) with rationale (shipping velocity / funding window)"
]
},
{
"id": "B2",
"_pattern": "process-discipline",
"prompt": "Run headless. Create a brief for HelmStack — an open-source observability platform for distributed systems.\n\nWe have made these specific decisions and want each captured in the decision log with rationale:\n\n1. Pricing: Free open-source core; paid SaaS at $29/seat/month. Rejected paid-one-shot-license model because it would limit network effects in the OSS community.\n2. Launch: Invite-only beta for 6 weeks before public launch. Rejected open public launch — operational risk too high before stability is proven on real workloads.\n3. Stack: TypeScript + Postgres for the backend. Rejected Go + MongoDB — TypeScript aligned better with our team's existing skills and the frontend codebase.\n4. ICP: 5-50 person engineering teams for MVP. Rejected enterprise-first focus because the sales cycle is too long for our capital runway.\n5. Self-host: SaaS-only at launch; self-host arrives in v2. Rejected concurrent self-host because it would slow shipping velocity past our funding window.\n\nProduce brief.md and decision-log.md.",
"expected_output": "Brief is consistent with the decision log: every decision in the log is reflected in the brief, and no claim in the brief is absent from the input prompt or the log. Tests bidirectional fidelity.",
"files": [],
"expectations": [
"brief.md mentions the OSS-core + paid-SaaS pricing structure",
"brief.md references the invite-only-beta launch sequencing OR identifies the launch model consistent with the decision log",
"brief.md references the platform-stack choice (TypeScript + Postgres) OR is silent on stack — but does not contradict it (no mention of Go, MongoDB, etc.)",
"brief.md identifies 5-50 person eng teams as the ICP (or equivalent — small-to-mid-size eng teams)",
"brief.md does not introduce decisions, competitors, partnerships, metrics, or product features absent from both the input prompt and decision-log.md (no invented facts)",
"Each substantive decision in decision-log.md has a corresponding reflection in brief.md (no log-to-brief drops)"
]
},
{
"id": "B3",
"_pattern": "process-discipline",
"prompt": "Run headless. Create a product brief for InsuLens.\n\nContext (use exactly this — do not invent):\n- Product: a smartphone app that pairs with off-the-shelf $200 thermal imaging accessories (FLIR ONE Pro and Seek Compact Pro). The app guides homeowners through a structured walkthrough and produces a professional-grade insulation audit in under 20 minutes.\n- Target: suburban homeowners aged 35-65 with houses built before 2000.\n- Validation: 50 user interviews completed in Q4 2025; 78% willingness to pay $49 for a one-time audit.\n- Stakes: Series A pitch input.\n- Hardware: requires a thermal accessory (we do not manufacture hardware).\n\nProduce brief.md and decision-log.md. Run the polish phase before presenting.",
"expected_output": "The transcript shows the polish phase executing — the skill invokes bmad-editorial-review-structure and bmad-editorial-review-prose, either via the Skill tool directly or via Agent tool calls whose description or prompt targets those editorial skills. Both passes must occur after the initial draft is written and before the final JSON status block.",
"files": [],
"expectations": [
"The transcript contains either a Skill tool call invoking bmad-editorial-review-structure, OR an Agent tool call whose description or prompt references structural review or bmad-editorial-review-structure",
"The transcript contains either a Skill tool call invoking bmad-editorial-review-prose, OR an Agent tool call whose description or prompt references prose review or bmad-editorial-review-prose",
"Both editorial-pass dispatches (Skill or Agent) occur after the first Write tool call that creates brief.md",
"Both editorial-pass dispatches (Skill or Agent) occur before the final assistant message containing the JSON status block"
]
},
{
"id": "B5",
"_pattern": "process-discipline",
"prompt": "Run headless. Update the brief at evals/bmm-skills/bmad-product-brief/files/forkbird-brief/brief.md — we have decided to add B2B catering services for corporate events, in addition to the direct-to-consumer delivery model. Read the existing decision-log.md and addendum.md in the same folder first.",
"expected_output": "The skill MUST detect the contradiction with the prior 'rejected B2B catering for MVP' decision (in decision-log.md) before applying the change. Acceptable resolutions: (a) halt with blocked status surfacing the conflict, or (b) apply the change with addendum.md capturing the override and rationale. Brief must not silently flip without acknowledging the prior decision.",
"files": [
"evals/bmm-skills/bmad-product-brief/files/forkbird-brief/brief.md",
"evals/bmm-skills/bmad-product-brief/files/forkbird-brief/addendum.md",
"evals/bmm-skills/bmad-product-brief/files/forkbird-brief/decision-log.md"
],
"expectations": [
"The transcript or output explicitly references the prior 'rejected B2B catering for MVP' decision from decision-log.md",
"The contradiction is surfaced before the brief body is modified (a Read of decision-log.md occurs before the Edit/Write to brief.md, AND the conflict is named in the assistant output)",
"Either the JSON status is 'blocked' with the conflict in the reason field, OR addendum.md is updated with an override entry capturing the rationale for reversing the prior decision",
"If the brief is updated, decision-log.md gains a new entry referencing the catering reversal",
"If the brief is updated, the YAML frontmatter 'updated' field is later than the original 'created' field"
]
},
{
"id": "B6",
"_pattern": "process-discipline",
"prompt": "Run headless. Update the brief at evals/bmm-skills/bmad-product-brief/files/forkbird-brief/brief.md — we have signed our fifth chef partner (Chicago metro). Add this to the existing operating-model and what's-known sections. Read the existing decision-log.md first.",
"expected_output": "Clean update — does not contradict any prior decision. Brief gets updated, decision-log gains a new entry, YAML 'updated' bumps but 'created' stays the same. No spurious addendum since this is a status update, not an override.",
"files": [
"evals/bmm-skills/bmad-product-brief/files/forkbird-brief/brief.md",
"evals/bmm-skills/bmad-product-brief/files/forkbird-brief/addendum.md",
"evals/bmm-skills/bmad-product-brief/files/forkbird-brief/decision-log.md"
],
"expectations": [
"brief.md is updated to reflect the signed fifth chef partner in Chicago",
"brief.md frontmatter 'updated' field is later than the original 'created' timestamp; 'created' is unchanged",
"decision-log.md contains a new entry referencing the fifth chef signing",
"The transcript does not surface a fictional contradiction — this is a clean update, not an override of a prior decision"
]
},
{
"id": "B7",
"_pattern": "process-discipline",
"prompt": "Run headless. Validate the brief at evals/bmm-skills/bmad-product-brief/files/mossridge-brief/brief.md — we are presenting to the library board Monday. Read the addendum and decision-log in the same folder. Cite specific sections. Return inline only.",
"expected_output": "Validate is read-only. No new files created. No existing files modified. Critique returned inline in the assistant output.",
"files": [
"evals/bmm-skills/bmad-product-brief/files/mossridge-brief/brief.md",
"evals/bmm-skills/bmad-product-brief/files/mossridge-brief/addendum.md",
"evals/bmm-skills/bmad-product-brief/files/mossridge-brief/decision-log.md"
],
"expectations": [
"No new files appear in the mossridge-brief artifacts directory after the run (only the three input files)",
"The input brief.md, addendum.md, and decision-log.md are byte-identical to the staged fixtures (no Edit/Write tool calls modified them)",
"The transcript contains no Write tool calls and no Edit tool calls targeting the mossridge-brief folder",
"The final assistant message contains a JSON object with intent='validate'"
]
},
{
"id": "C1",
"_pattern": "config-compliance",
"prompt": "Run headless. Create a product brief for TaskFlow — a lightweight daily planning app for freelancers who juggle multiple clients. Core idea: a single daily view that pulls together tasks, time blocks, and client context so the freelancer always knows what to work on next. Target is independent freelancers, 1-3 clients at a time, who currently manage their day across sticky notes, calendar apps, and spreadsheets. MVP is mobile-first. No investors — the founder is bootstrapping.",
"expected_output": "Brief written in Spanish (document_output_language=Spanish). Assistant's conversational output reflects the configured British-accent communication style. Brief lands at the custom output path (test-output/artifacts/briefs/...) rather than the default _bmad-output path. Brief is right-sized for a bootstrapped solo project.",
"files": [],
"expectations": [
"brief.md exists under test-output/artifacts/briefs/ (the custom planning_artifacts path), not under _bmad-output/",
"The final JSON status block artifact paths reference test-output/ rather than _bmad-output/",
"brief.md body is written in Spanish — the majority of prose content (headings, section bodies) is in Spanish, not English",
"brief.md covers the TaskFlow concept: freelancer daily planning, multi-client context, the sticky-notes-plus-calendar-plus-spreadsheet problem",
"brief.md is right-sized for a bootstrapped side project — appropriate depth and scope for a solo-founder app with no investor audience, no TAM/SAM/SOM framing, no Series A language, and no sections that pad for enterprise credibility",
"The assistant's non-document output (transcript text content outside of brief.md) contains at least one marker of British informal register (e.g., 'mate', 'cheers', 'brilliant', 'sorted', 'innit', 'blimey', 'proper', 'right then', or equivalent pub-idiom phrasing)"
]
}
]
}

View File

@ -0,0 +1,46 @@
# Working Group Notes — Microcredential Program
**Branfield Community College**
**Meeting:** 2026-04-22
**Attendees:** Provost, Workforce Dev Director, Chair of Industry Advisory Board, two faculty leads (Data Analytics, Healthcare Admin), Financial Aid Director
## Why we're doing this
Regional employer survey (Q1 2026) showed 340+ unfilled mid-skill jobs in the three-county area. State workforce board approved a $1.4M grant if we can launch by fall 2027 with at least three tracks. Existing AAS programs are too long for working adults — average completion 3.5 years.
## What we're building
Six-month stackable microcredentials. Three tracks at launch:
1. **Data Analytics** (SQL, Excel/Power BI, intro Python). Faculty lead Marisol Reyes. Strongest employer demand. Will be MVP — first to launch, used to validate format.
2. **Healthcare Admin** (medical coding, EHR systems, patient workflow). Faculty lead Dev Patel. Aging population in region drives demand.
3. **Sustainable Construction** (green building practices, retrofit basics, code compliance). New faculty hire required.
Stackable means credits transfer into related AAS or BAS later if the student wants.
## Decisions made today
- **Data Analytics is MVP.** Launch fall 2027, others phase in spring/fall 2028. Validate format before scaling.
- **Hybrid delivery.** Two evenings/week in person + asynchronous online. Board rejected pure-online (concerns about adult learner outcomes data).
- **Stipend program.** Up to $3,000/student for low-income students, funded from the state grant. Means-tested.
- **Industry Advisory Board** has approval authority on curriculum. Three employers committed (regional hospital, mid-size data consultancy, county housing authority). All three commit to interview every graduate.
- **Cohort cap: 24 per track per term.** Driven by classroom size and faculty load.
## Open questions
- Childcare for evening sessions — can we partner with the campus childcare center? Deferred to next meeting.
- Marketing — provost wants to know cost per enrolled student before approving budget. Need workforce dev to model.
- Do we offer a tuition payment plan in addition to the stipend? Financial aid director thinks yes; provost wants to see uptake projections first.
## What we're NOT doing
- Not pursuing pure-online delivery (rejected — see above).
- Not launching all three tracks at once (rejected — risk concentration, faculty bandwidth).
- Not building employer-customized cohorts (rejected — too operationally complex for MVP).
## Next steps
- Workforce Dev: marketing cost model by 2026-05-15.
- Provost: childcare partnership exploratory conversation.
- Faculty leads: draft data analytics curriculum outline by 2026-06-01.
- Reconvene 2026-05-20.

View File

@ -0,0 +1,40 @@
# Addendum — Forkbird Kitchen
## Options considered (and not taken)
### B2B / corporate catering
Considered as a parallel revenue stream from day one. Rejected for MVP. Different operational rhythm (bulk orders, fixed delivery windows, invoiced billing), different customer (procurement, not eaters), different unit economics. Splitting attention at launch risked degrading both. Revisit if consumer foundation is established by month 12.
### Subscription / meal plan
Considered as a recurring-revenue layer. Rejected for MVP. Operationally expensive at our planned scale: requires demand forecasting per subscriber, kitchen scheduling locked further out, and packaging/refrigerated handling we are not yet equipped for. Reasonable to revisit once kitchen utilization stabilizes.
### Retail / grocery channel
Considered (refrigerated meals in Whole Foods, Sprouts). Rejected for MVP. Different product (cold meals, longer shelf life, different texture profile), different go-to-market (broker relationships, slotting fees, category management). Parked for year 2 — would require a separate product line, not a channel extension.
### Lower-priced everyday tier
Considered. Rejected for now. The brand position is chef-driven; introducing a value tier alongside risks the premium signal in marketplace search ranking and review patterns. Explored alternative of separate brand for value tier; deferred.
## Personas (extended)
**The plant-based weekday professional.** Lives in a dense urban neighborhood, orders 46 times a month, splits between own-cooking and delivery. Sources of dissatisfaction with current options: chain plant-based menus feel formulaic, fine-dining plant-based is too expensive for weeknight, marketplace search surfaces too many low-quality options.
**The dietary-flex household member.** One person in a household is plant-based by preference; the other(s) are not. Ordering pattern is "tonight one of us wants Forkbird, the other wants something else." We benefit from being a dependable single-cuisine option that doesn't require negotiating across diets.
## Sizing notes
- Total addressable: ~6.2M urban professionals across 5 metros eating plant-based 3+ times/week (based on 2024 Plant Based Foods Association data, urban segmentation).
- Serviceable addressable (within delivery radius of planned kitchens at launch): ~840K.
- Realistic Y1 capture (per metro forecast): 0.4% of SAM = 3,360 active customers across all metros.
## Sourcing standard — exact wording
"For each dish on the menu, we publish the source of every ingredient that represents at least 5% of cost. We commit that at least 60% of total ingredient weight is sourced within 200 miles of the kitchen preparing that dish. Both numbers are auditable; we publish them per-dish in the app. If we cannot meet the 60% local threshold for a dish, the dish does not ship."
## Technical constraints
- Marketplace integration (DoorDash, UberEats, Grubhub) requires their menu management API. We are using a third-party middleware (Olo) to avoid maintaining three separate integrations.
- Ingredient transparency display requires structured data per dish. We need an ingredient-master database; current option is to extend our recipe-management software vendor.

View File

@ -0,0 +1,56 @@
---
title: Forkbird Kitchen — Product Brief
status: final
created: 2026-02-14
updated: 2026-02-14
---
# Forkbird Kitchen
## What it is
A delivery-only ghost kitchen brand offering chef-driven plant-based meals in five US metros: San Francisco, New York, Los Angeles, Seattle, and Chicago. Launch operating model is direct-to-consumer through our own iOS/Android app and the major third-party marketplaces (DoorDash, UberEats, Grubhub).
## Who it's for
Urban professionals aged 2845 who eat plant-based meals at least three times a week, value chef-driven food over chain alternatives, and order delivery 4+ times monthly. Initial geographic focus is dense neighborhoods within 3-mile delivery radii of partner kitchens.
We are not building for: families with children (different ticket size and ordering pattern), occasional plant-based eaters (price sensitivity too high for our positioning), or office lunch (different time-of-day operation).
## Why it wins
Three things are deliberately stacked:
1. **Chef partnerships, not chef-as-marketing.** Each metro has a named chef (with prior fine-dining or notable plant-based credit) who designs the rotating menu and earns equity in that metro's P&L. They are not endorsers; they are operators.
2. **Ingredient sourcing standards.** Published per-dish: where it came from, how it was farmed, what portion of cost it represents. No dish ships if we can't source within 200 miles for ≥60% of ingredient weight. This is auditable, not marketing copy.
3. **Speed without cars.** Average ticket-to-door is 28 minutes from order placement, achieved by tight delivery radii and dense order density per kitchen. Long delivery erodes plant-based texture more than animal protein — speed is product, not logistics.
## Operating model
Five kitchens, one per metro, each leased space inside an existing food-prep facility. No customer-facing storefronts. App orders go through our stack; marketplace orders pass through their stacks. Menu rotates every six weeks per chef.
Pricing tier: $14$22 per entrée before delivery. We are deliberately at chef-driven positioning, not value positioning.
## What's known
- Demand validated through three pop-up dinners in SF and NY (Q4 2025). 480 covers, 78% repeat intent based on post-event survey.
- Operating partner identified in each metro. Leases signed for SF, NY, LA. Seattle and Chicago in negotiation.
- Three of five chefs signed; two in active conversations.
## What's unknown
- Whether ingredient-sourcing transparency is a differentiator at point of sale (in-app) or only in marketing. Our hypothesis is "both" but we have not tested in-app.
- Marketplace economics. DoorDash takes 1530% depending on tier; we are modeling the lower tier but have not negotiated.
- Whether the 3-mile radius holds outside SF/NY (lower density in LA/Chicago).
## Risks
- Chef churn. If a metro chef leaves, the metro brand loses its anchor. Mitigation: equity vesting over 24 months, named-chef terms in operating agreement.
- Sourcing cost volatility. 60% local-within-200-miles can spike with weather/supply disruption. We have not modeled the worst case.
- Marketplace dependency. If DoorDash terms shift adversely, our blended margin is at risk. We are deliberately building the owned-app channel to reduce this dependency.
## Success criteria for first 12 months
- 4 of 5 metros operating profitably at the unit level (kitchen + chef + delivery economics) by month 9
- 30% of orders through owned app (vs. marketplaces) by month 12
- Chef retention 100% through year 1

View File

@ -0,0 +1,27 @@
# Decision Log — Forkbird Kitchen
## 2026-01-08
- **Brand position: chef-driven, premium plant-based.** Considered value tier; rejected for MVP. Premium positioning is the wedge against marketplace generic plant-based.
## 2026-01-12
- **Five-metro launch: SF, NY, LA, Seattle, Chicago.** Considered three-metro start; rejected as not enough density to test the chef-equity model meaningfully.
- **Ghost kitchen, no storefront.** Storefronts ruled out — capex too high for MVP, dilutes the speed advantage.
## 2026-01-19
- **Pricing tier $14$22 per entrée.** Modeled against three competitor sets: chain plant-based, fine-dining plant-based delivery, generic mid-tier delivery. Sits cleanly above chain, below fine-dining.
- **Chef equity in metro P&L.** Rejected flat fee + revenue share alternative; equity creates the operator incentive we want.
## 2026-01-26
- **Rejected B2B catering segment for MVP.** Different operational rhythm and customer; would split attention at launch and risk degrading both consumer and B2B execution. Revisit in year 2 if consumer foundation is solid. (Discussion: 2 hours; chef partners weighed in against splitting focus; CFO modeled the dilution effect on consumer kitchen utilization.)
- **Rejected subscription model for MVP.** Operationally expensive at planned scale; revisit once kitchen utilization stabilizes.
## 2026-02-02
- **Sourcing standard: 60% within 200 miles, published per-dish.** Considered weaker thresholds (50% / 250 miles); rejected as not differentiating enough to be worth publishing. The number has to be defensible.
- **Marketplace channel mix: own app + DoorDash + UberEats + Grubhub.** Considered own-app only; rejected as too slow on demand acquisition. Considered marketplaces only; rejected — own app is critical to long-term margin.
## 2026-02-09
- **Six-week menu rotation per chef.** Considered four-week (more freshness) and eight-week (more operational stability). Six is the compromise; reassess after first two cycles.
- **Marketing budget: 60% acquisition / 40% brand.** Rejected pure-acquisition because chef-driven positioning needs brand-level signal that paid acquisition alone won't carry.
## 2026-02-14
- **Brief finalized for Series A inputs.** Status moved to final.

View File

@ -0,0 +1,116 @@
# E-Mobility Market Report 2026
**Prepared by:** Meridian Insights
**Date:** Q2 2026
**Coverage:** North America, with comparative reference to EU markets
**Engagement code:** MI-2026-EMOB-007
---
## Executive Summary
The e-mobility category continues a multi-year structural shift from "alternative transportation" to mainstream mobility infrastructure. North American unit volume across e-bikes, e-scooters, and connected safety hardware grew 18% year-over-year in 2025, against a 6% growth rate for traditional bicycles. Three macro factors are durably reshaping the category: regulatory clarity at the state level (29 US states now have explicit e-bike classifications, up from 14 in 2022), insurance industry interest in telematics-style risk pricing, and a generational shift in commuting preferences among the 28-44 cohort.
This report covers seven segments of the broader e-mobility landscape: e-bike retail, e-scooter regulation, bike-share systems, charging infrastructure, smart helmet hardware, and grid-integration trends. Findings are synthesized from 142 stakeholder interviews, 18 retailer site visits, government regulatory filings, and proprietary point-of-sale data from 4,200 specialty retail outlets.
---
## Methodology
Quantitative data was sourced from Meridian's proprietary Mobility Retail Panel (MRP), which aggregates POS data from independent specialty retailers and select chain operators. Where panel data is incomplete or lagging, we supplemented with manufacturer-reported shipment volumes and customs/import filings. Qualitative findings draw on 142 interviews conducted between November 2025 and March 2026 with retailers, fleet operators, regulators, manufacturers, and end users.
Helmet category sizing uses a separate methodology described in Section 8, blending CPSC compliance filings, manufacturer disclosures, and a sample purchase-intent survey of 3,400 cyclists.
---
## Section 3: Market Sizing — Total E-Mobility
The North American e-mobility market reached an estimated $14.7B in retail volume in 2025, up from $12.5B in 2024. The largest segment by volume is e-bikes at $7.2B, followed by e-scooter retail at $2.8B (excluding shared-fleet operations), bike-share and dockless mobility services at $2.1B, charging infrastructure at $1.8B, and connected safety hardware at $0.8B.
Compound annual growth rate (CAGR) forecasts through 2030 vary substantially by segment. We forecast 14% CAGR for e-bikes, 6% for e-scooters (decelerating as the regulatory regime stabilizes), 9% for bike-share, 22% for charging infrastructure (driven by both bike and scooter charging), and 31% for connected safety hardware (off a smaller base). Vehicle-to-grid (V2G) integration is too early to forecast reliably; we treat it as an emerging segment.
---
## Section 4: E-Bike Market Deep Dive
E-bikes represent the largest single segment by retail value. The 2025 unit mix favored Class 1 (pedal-assist, max assisted speed 20 mph) at 58% of units, Class 2 (throttle, max 20 mph) at 24%, and Class 3 (pedal-assist, max 28 mph) at 18%. Class 3 is the fastest-growing classification on a unit basis, driven by suburban commuter demand.
Manufacturer concentration shifted in 2025. The top 10 brands by unit volume now hold 64% of the market, up from 51% in 2022 — consolidation that mirrors patterns seen in the traditional bicycle market in the early 2000s. Specialized, Trek, and Cannondale (operating their respective electric sub-brands) represent the top three. Direct-to-consumer brands (Rad Power, Lectric, Aventon) collectively hold approximately 19% of retail value.
Retail channel split favored independent specialty bike shops at 47% of unit volume, with direct-to-consumer at 28%, big-box retail at 17%, and e-commerce marketplaces (Amazon, Walmart.com) at 8%. The independent specialty channel commands a price premium of approximately 22% over comparable D2C alternatives, attributed to in-store fitting, post-sale service relationships, and higher-margin component upgrades.
Notable trends in 2025: cargo e-bike sub-segment grew 41% YoY (small base, dense urban geographies); battery range claims continue to drift upward with manufacturer claims of 60+ mile range becoming standard for $2,500+ price points; bottom-bracket motor placement (mid-drive) gained share over hub-drive in the $3,000+ tier.
---
## Section 5: E-Scooter Regulatory Landscape
The North American e-scooter regulatory environment matured significantly during 2024-2025 after several years of municipal experimentation and reactive policymaking. Forty-one US cities now operate under what we classify as "stable" regulatory regimes (defined as: explicit operating permit framework, defined sidewalk/bike-lane rules, helmet provisions, and revenue-share or fee structures with the city). This is up from 19 cities in 2022.
The regulatory shift has compressed operator margins. Permit fees and per-trip surcharges in major markets (Los Angeles, Chicago, Atlanta, Denver) range from $0.15 to $0.42 per trip, against average ride revenue of $5.40. Several major operators have exited markets where permit economics have proven unviable; Lime exited five secondary US markets in 2025 citing exactly this reason.
Helmet requirements remain inconsistent. Thirteen US states require helmets for riders under 18 only; seven require them for all riders; the rest leave it to municipalities. Enforcement is widely acknowledged to be minimal even where mandates exist. EU markets are substantially stricter, with mandatory helmet provisions in France, Germany, and Italy applying to all e-scooter riders.
Insurance treatment is also fragmenting. Five US states have classified e-scooters as "motor vehicles" requiring liability coverage, raising the floor on operating costs for shared-fleet providers. Most states still treat them as bicycles for insurance purposes.
---
## Section 6: Bike-Share and Dockless Mobility
Docked bike-share systems (Citi Bike, Divvy, Bluebikes, Capital Bikeshare) continue stable, slow growth. Capital Bikeshare reported 5.1M trips in 2025 (5% growth); Citi Bike reported 38M (8% growth). Docked systems benefit from station infrastructure that creates predictability for riders and meters demand-side adoption.
Dockless bike-share (without fixed stations) is largely consolidated; the experimentation phase ended in 2023. Lyft operates the dominant national network through its acquired bike-share division, with regional players in select markets. Operating economics for dockless are structurally weaker than docked due to vehicle redistribution costs, vandalism rates, and the absence of station-driven advertising revenue.
A notable trend is the convergence of bike-share and dockless e-bike subscription models. Several operators now offer monthly memberships that include unlimited 30-minute trips on dockless e-bikes within a service zone. Adoption is concentrated in dense urban cores where car-free lifestyles are practical.
---
## Section 7: Charging Infrastructure Trends
Charging infrastructure for e-bikes and e-scooters has emerged as a meaningful sub-segment, growing 28% in 2025. The dominant form factor remains residential at-home wall chargers (87% of installed base), but commercial charging — at workplaces, transit stations, and apartment buildings — is the fastest-growing sub-segment.
Standardization remains a constraint. Battery interfaces have not converged; Bosch, Shimano, and various proprietary systems coexist. The European Union's USB-C mandate for portable electronics has not yet extended to e-mobility; industry observers expect regulatory pressure to follow within 3-5 years.
Workplace charging is increasingly common in tech and creative-industry employers; we estimate 31% of large urban employers in tech-heavy metros now offer workplace e-bike charging, up from 12% in 2022. Apartment buildings lag — 7% of class-A multifamily properties offer common-area charging, with retrofit cost cited as the primary barrier.
Public charging at transit hubs (subway/light rail stations) remains a stated priority across most major metro transit authorities, but actual installation lags policy commitments significantly. Funding fragmentation and permitting delays are the consistently cited bottlenecks.
---
## Section 8: Smart Helmet Category
The connected safety hardware category — colloquially "smart helmets" — is the smallest segment we cover by retail value but has the strongest growth profile. The North American smart helmet market reached $810M in retail value in 2025, up from $480M in 2023, representing a 30% CAGR. We forecast $2.4B by 2030, contingent on the resolution of two open questions detailed below.
**Category definition.** We define "smart helmets" as helmets that include at least one connected safety feature: turn signals (typically wireless-controlled), braking lights (auto-activated via accelerometer), crash detection (auto-notification to emergency contacts on detected impact), or integrated navigation/audio (bone-conduction speakers, often paired with smartphone apps). Helmets with passive integrated lighting only (no connectivity) are excluded from this category and tracked under traditional helmet retail.
**Key players.** The category remains fragmented; no single manufacturer commands more than 15% market share. Top five by 2025 retail volume: Lumos Helmet (US, market leader at ~14% share with strong DTC presence), Sena Technologies (Korea, intercom heritage, ~11%), Coros (US/China, multi-sport, ~9%), Specialized ANGi (US, premium tier at ~7%), and POC Aid (Sweden, premium safety positioning at ~6%). Approximately 30 smaller brands hold the remaining share.
**Crash detection technology.** Two architectures dominate: single-accelerometer crash detection (lower cost, higher false-positive rate) and multi-sensor fusion (accelerometer + gyroscope + GPS movement signature, lower false-positive rate but higher BOM cost). Insurance industry sources indicate that multi-sensor systems are likely to become a baseline requirement for any insurance discount programs, given that single-accelerometer systems triggered roughly 1 false alert per 47 hours of riding in our test panel.
**Regulatory landscape.** Smart helmets sit at the intersection of two regulatory regimes: the Consumer Product Safety Commission's bicycle helmet standard (16 CFR 1203, governing impact protection) and the Federal Communications Commission's regulation of intentional radiators (governing the radio components for Bluetooth/cellular). Compliance with both is non-trivial. Eight smart helmet brands have had FCC Part 15 violations issued since 2023, typically for emissions exceeding limits during compliance testing. EU markets additionally require EN 1078 certification for the helmet shell; this is widely held but adds 3-5 months to a typical product development timeline.
**Insurance industry interest.** Major auto insurers (State Farm, Progressive, Geico, Nationwide) are actively piloting telematics-style discount programs for cyclists who use connected safety helmets. The proposed structure mirrors auto-insurance "good driver" discount frameworks, with discounts of 5-15% on cycling-specific insurance riders or umbrella policies. As of Q1 2026, three insurers have public pilot programs and one (Progressive) has announced general availability for 2027. This could materially accelerate category adoption if discounts materialize at the upper end of the proposed range.
**Distribution.** D2C dominates at 58% of retail value, reflecting the still-emerging category and the absence of strong channel inventory in independent bike shops. The specialty bike shop channel is growing rapidly (up from 12% to 22% of retail value over 2023-2025) as the category gains category-management attention from major distributors. Big-box channels (REI, Dick's Sporting Goods) are present but shallow in selection — typically 4-8 SKUs versus 40+ in dedicated specialty.
**Open questions for the segment.** Our growth forecast is conditioned on (a) the proportion of insurers that follow Progressive into general availability of connected-safety discounts; (b) whether multi-sensor crash detection becomes a category baseline (lifting ASP) or remains a premium-tier feature; and (c) whether the current high false-positive rate of single-accelerometer systems triggers a consumer backlash that suppresses category trust before insurance discounts arrive. The downside scenario produces a 2030 category size of $1.4B versus our base-case $2.4B.
---
## Section 9: Vehicle-to-Grid Integration
Vehicle-to-grid (V2G) integration of e-bike and e-scooter batteries is an emerging area, but practical commercial deployment is years away. The thesis is that fleet-scale dockless e-bikes and e-scooters represent meaningful aggregate battery capacity that could participate in demand-response markets, particularly in deregulated electricity markets.
Several technical preconditions must be met: standardized battery interfaces (currently absent), bidirectional charging hardware (rare), aggregator software stack (early-stage), and regulatory clarity on energy market participation by mobility fleets (pre-policy). We treat this as a watch item for 2028+ rather than a current investable theme.
---
## Section 10: Outlook
Our base-case forecast for North American e-mobility is $22.5B by 2030, with the e-bike segment reaching $11.8B (the largest), connected safety hardware reaching $2.4B (the fastest-growing in percentage terms), and charging infrastructure reaching $4.2B (driven by commercial and multifamily retrofit demand). Bike-share and dockless mobility plateau in the $2.5-3.0B range as urban density limits adoption ceilings.
The largest single uncertainty in this forecast is the trajectory of insurance industry adoption of connected-safety telematics, which could accelerate or substantially constrain the smart helmet segment and, secondarily, influence rider behavior across the broader category. We will revisit forecasts in our Q4 2026 update.
---
*This report is prepared for the exclusive use of Meridian Insights subscribers. Reproduction or external distribution without written permission is prohibited.*

View File

@ -0,0 +1,41 @@
# Addendum — Mossridge Tool Lending Library
## Options considered
### Paid lending model (rejected)
Considered charging a nominal per-loan fee ($2$5) to cover replacement and maintenance. Rejected as inconsistent with library mission of free access. Board has previously stated free access is non-negotiable for core services. A donation jar at checkout was proposed as a soft alternative; deferred.
### Hardware store partnership (considered, deferred)
Mossridge Hardware (the store committing in-kind donations) offered to host a satellite lending point. Considered; deferred to year 2. The integration adds operational complexity (split inventory, cross-location tracking) we are not equipped for at launch. Reasonable to revisit once the main location is established.
### Mobile lending van (rejected)
Proposed by a board member to serve outlying areas. Rejected for MVP — capital cost ($35K+ for vehicle + outfitting) exceeds the entire grant. Could be a year-three expansion if demand validates.
### Skills classes alongside tool loans (deferred)
Considered offering "how to use a power drill" classes as a value-add. Deferred — interesting but distinct programming, not part of the lending service's MVP scope. Adult Services Librarian is interested in piloting separately.
## Reference programs reviewed
- Berkeley Tool Lending Library (operating since 1979, ~3,000 tools, 250+ daily loans). Funded as a city service.
- Oakland Tool Lending Library (operating since 2000, smaller catalog, library-staffed).
- Toronto Tool Library (nonprofit, member-supported, paid model — different funding architecture).
Direct correspondence with Berkeley TLL staff (March 2026) suggested:
- Theft has been low (~2% annually) due to library card requirement and community norms
- The biggest sustainability risk has been staff hours, not tool replacement
- Most successful programs have a paid coordinator role, not pure volunteer
## Potential expansion (year 2+)
- Hardware store satellite location
- Specialty tool categories: woodworking, automotive, sewing
- Skills classes paired with relevant tool checkouts
- Seed/cuttings library co-located in spring/summer
## Insurance and liability — current state
Library counsel (Town of Mossridge legal department) has been consulted informally. Formal opinion pending. Existing policy covers patrons in the building; coverage for tool use off-premises is the open question. Awaiting written response before submitting grant application.

View File

@ -0,0 +1,57 @@
---
title: Mossridge Public Library — Tool Lending Library Proposal
status: final
created: 2026-04-30
updated: 2026-04-30
---
# Tool Lending Library at Mossridge Public Library
## What we're proposing
A free tool-lending service operated out of the Mossridge Public Library, modeled on similar programs in Berkeley, Oakland, and Toronto. Cardholders borrow hand and power tools (drills, saws, ladders, sanders, plumbing snakes, gardening tools) for up to seven days, free of charge.
## Why now
Mossridge residents face rising costs of home maintenance and DIY supplies. Anecdotally, demand for community-shared resources is high — staff have fielded "do you lend tools?" requests for years. A tool library extends the library's mission of equitable access to information and skill-building into the practical-skills domain.
## Who it serves
Mossridge residents with active library cards. Primary audience: single-family homeowners doing their own home repairs, renters making minor improvements with landlord permission, hobbyist woodworkers and gardeners. Estimated 8,000 households in the library's service area.
## Service design
- **Catalog:** Approximately 200 tools to start, prioritizing the most-requested categories (drilling, cutting, sanding, ladders, garden).
- **Loan period:** Seven days, one renewal allowed if no holds.
- **Borrower requirements:** Active library card, signed liability waiver, completed safety briefing for power tools.
- **Location:** Library basement, currently underutilized storage. Accessible by elevator.
- **Hours:** TuesdaySaturday during library hours; tools returned via after-hours drop slot when closed.
## Funding
- ARPA infrastructure grant: $42,000 (anticipated, application pending)
- Friends of the Mossridge Library matching funds: $10,000 (committed)
- In-kind tool donations from Mossridge Hardware (committed in principle)
Year-one operating cost is estimated at $48,000, primarily tool purchase, maintenance supplies, and shelving/storage retrofit. Ongoing cost (year two and beyond) projected at $12,000 annually for replacement tools and consumables.
## Operations
The service will be run by trained library volunteers, supervised by the Adult Services Librarian. Volunteer training program to be developed in partnership with Mossridge Vocational Center. Estimated 46 active volunteers needed at any given time, with a roster of 1215 trained volunteers to provide coverage.
## Risks
- **Theft and loss.** Tools are valuable and portable. Mitigation: deposit on power tools (refundable), card-required checkout, photo documentation at loan and return.
- **Liability.** Borrower waivers will be required; the library's existing insurance policy is being reviewed for coverage.
- **Demand uncertainty.** We do not yet know the actual borrowing volume the service will see.
## Success criteria
- Launch by Q3 2027 with a catalog of 200 tools.
- 300 unique borrowers in the first year of operation.
- Zero serious injury incidents.
- Tool loss rate under 5% per year.
## What we're asking
Board approval to proceed with the ARPA grant application and finalize the service design for fall 2027 launch.

View File

@ -0,0 +1,29 @@
# Decision Log — Mossridge Tool Lending Library
## 2026-03-04
- **Pursuing the project.** Adult Services Librarian + Library Director agreed there's enough informal demand signal (years of "do you lend tools?" inquiries) to investigate seriously. Acknowledged that informal inquiries are not the same as validated demand.
## 2026-03-11
- **Reference programs to study: Berkeley, Oakland, Toronto.** Selected based on size, longevity, and accessibility of operational data.
## 2026-03-25
- **Initial scope: hand and power tools only.** Rejected including specialty categories (sewing, electronics test gear, automotive) for MVP. Reason: staff expertise and storage. Revisit year 2.
- **Free model.** Confirmed — paid model rejected as inconsistent with library mission. Donation jar approved as soft revenue.
## 2026-04-01
- **Volunteer-run model.** Selected to keep ongoing operating costs low. Acknowledged risk: Berkeley correspondence flagged staff-hours as the biggest sustainability concern in similar programs. Plan to revisit at year-one review.
## 2026-04-08
- **Funding architecture: ARPA grant + Friends matching + in-kind donations.** Considered municipal budget request; rejected as too slow (next budget cycle is 18 months out). Grant is faster but requires fall 2027 launch deadline.
## 2026-04-15
- **Launch timing: Q3 2027.** Driven by ARPA grant deadline, not by service-readiness analysis. Acknowledged this is grant-driven, not user-driven, timing.
- **Year-one target: 300 unique borrowers.** Set by analogy to comparable programs scaled to Mossridge population. No local validation underlying this number.
## 2026-04-22
- **Hardware store satellite deferred to year 2.** Operational complexity exceeds our launch capacity.
- **Liability: pending formal opinion from town legal.** Borrower waiver in draft.
## 2026-04-30
- **Brief finalized for board meeting.** Status moved to final.
- **Open items acknowledged for board discussion:** demand validation method, volunteer sustainability, written legal opinion on off-premises tool use coverage.

View File

@ -0,0 +1,90 @@
# Pantry Bridge — Customer Research Transcripts
**Project:** Pantry Bridge meal-kit concept exploration
**Research firm:** In-house
**Round:** Discovery interviews, March 2026
**Format:** 45-minute semi-structured interviews, video; excerpts below are lightly edited for length and clarity
The four interviews below cover four distinct potential customer segments. We are sharing all four for context, though the team's current product hypothesis targets one specific segment.
---
## Interview 1 — Susan, 38, working parent
**Household:** Two kids (ages 6 and 9), spouse works full-time, both parents work demanding office jobs. Suburban Chicago.
**Susan:** "Honestly, the question is just — can I get dinner on the table by 6:30 without it being chicken nuggets again? My kids don't eat anything green unless we play games about it. My husband and I both have late meetings sometimes. We've tried HelloFresh, we've tried Blue Apron, we tried Home Chef. They all kind of work, and they all kind of don't.
The thing that breaks them for us is the prep time. The boxes say 30 minutes but you need to add 10-15 to actually get it done. By Wednesday night I don't have 45 minutes. So we end up using the boxes on weekends and ordering takeout three nights a week, which is the opposite of what the boxes are supposed to do.
If you really wanted to crack it for families like ours: pre-chopped vegetables, sauces that are actually finished and not 'whisk these eight things together.' I'll pay more for less prep. And the recipe books need to read like the kid is going to eat it — not like 'spicy harissa-rubbed cauliflower steaks.'
Portion sizing — most kits send way too much for our family. We're a family of four but the kids each eat about 60% of a meal. We end up with leftovers that go bad. Better sizing would help."
**Interviewer:** What about price?
**Susan:** "We spend $250-350 a week on groceries currently and probably another $200 on takeout. So a meal kit that replaces three nights of takeout could be $200 a month and we'd still come out ahead. Most kits are priced fine; it's the time that breaks them."
---
## Interview 2 — Marcus, 21, college student
**Household:** Junior at state university, off-campus apartment shared with two roommates, kitchen has a microwave, a stovetop, and a half-broken oven. Limited budget.
**Marcus:** "I'm probably the wrong person for this conversation, no offense. I'm not really a meal-kit person. My food situation is, like, dining hall meal plan when I can use it, and the rest is whatever's cheap and fast. Trader Joe's frozen stuff. Eggs. Pasta. Costco runs with my roommates once a month.
I tried a meal kit when my mom signed me up as a 'starting college' gift. It was nice, but it was $80 a week for two people, which is way out of budget. And honestly, the thing they don't get is that I don't have time at 7 PM to cook. I have time at 11 PM. I want to grab something on my way back from the library and not think.
If you're trying to do meal kits for college students — and I don't really think you should — but if you were, the price has to be like $5 a meal. And it has to be food that survives in a fridge for two weeks because we don't shop on a weekly schedule. We shop when we run out.
Snacks matter more to us than meals, actually. Like, the moment when I'm desperate is 10 PM in the library, not 7 PM. Solve that and I might pay attention."
**Interviewer:** Do you have any dietary restrictions?
**Marcus:** "I'm vegetarian, sort of. I eat fish. So pescatarian I guess. But mostly because meat is expensive."
---
## Interview 3 — Eleanor, 71, retired, lives alone
**Household:** Widow, lives alone in the same single-family home she's been in for 36 years. Suburban Cleveland. Two adult children live out of state. Drives during the day but no longer at night.
**Eleanor:** "I'll tell you what I miss. I miss cooking for someone. My husband Walter passed five years ago this June, and the hardest thing — well, not the hardest, but one of them — is that I don't really cook anymore. I cook eggs. I cook a piece of fish. I open a can of soup more often than I'd like to admit. I used to make Sunday dinners that would feed eight people. Now I eat standing up at the counter half the time.
The grocery store is genuinely difficult. I drive there, I park in the back of the lot because I can usually find a spot, and then it's a long walk in. I get tired by the time I'm in the dairy aisle. Carrying the bags from the car to the kitchen — that's a project. My daughter wants me to use grocery delivery and I've tried, but the apps are all designed for someone twenty years younger than me. Tiny buttons, asking me to click through six screens to add a single tomato. I get frustrated and give up.
What I would actually want — and I've thought about this — is meals for one person. Real portions. Not a frozen TV dinner. Not 'serves four, freeze the rest.' I have a freezer full of leftovers I'll never eat. Just one good meal that I can heat up or finish cooking, that tastes like food I would have made.
I'm watching my sodium because of my blood pressure. Watching sugar too — borderline diabetic, my doctor calls it. So I read labels carefully. The frozen meals you can buy in stores are loaded with both. I'd pay more for less of both, if I trusted that the labels were accurate.
The other thing — and please put this in your notes — is that I'm careful about who I let into my house and what I sign up for. There are scams. My friend Marian got taken for $4,000 last year. So if some company asks for my information, I want to know who they are. I want a real customer service number with a real person. I want it to feel like a real business, not a flashy app.
I don't want it to feel like 'old-people food.' That's an important thing. The Meals on Wheels program in our township is wonderful but it's clearly designed for people who are sicker than I am. I'm not sick. I just live alone and grocery shopping is a lot."
**Interviewer:** What would the ideal experience look like?
**Eleanor:** "Someone delivers good food, in real portions, made with the kind of ingredients I would have used. I can heat it up or finish it. It doesn't taste like a hospital. The packaging is something I can actually open without a knife. I get a phone call once in a while from a person, not a robot. The price is reasonable — I'm on a fixed income but I can spend on things that matter. Eating well matters."
---
## Interview 4 — Dimitri, 44, Director of Food Services, mid-size hospital
**Organization:** 340-bed hospital, food service operates patient meals, staff cafeteria, and a small retail café. Reports to the COO.
**Dimitri:** "I'm probably also not who you should be talking to, but happy to share. We don't buy meal kits. We buy ingredients in institutional volumes from Sysco and US Foods primarily, with some specialty buys for dietary restrictions. We feed about 1,800 people a day across patients, staff, and visitors.
What I deal with that you might find interesting is the patient diet matrix. We have to produce meals that meet specific medical requirements — renal diets, cardiac diets, diabetic diets, dysphagia textures, allergen-free, religious restrictions. Each patient gets a tray that meets their specific orders. It's complex.
If a meal kit company wanted to play in our world, they'd be selling to me at the institutional level — bulk pricing, multi-year contracts, ability to deliver consistent specs across thousands of meals. That's not really a 'meal kit' anymore; that's wholesale food service.
Now, where I might be a buyer in a different sense: my staff cafeteria. We're trying to compete with grab-and-go culture. If you produced ready-to-heat meals targeting our staff demographic — nurses, doctors, techs, who are working 12-hour shifts and want real food, not a sandwich — I might pay attention. But the price point would have to make sense for institutional buying, and you'd need to integrate with our existing food safety protocols.
For consumer meal kits, I'm probably not your customer. We did try one when my wife and I were both working through COVID, and we let the subscription lapse after about three months. Fine product, just didn't fit our patterns."
---
## Note from the research lead
These four interviews were selected to represent the range of segments we've considered. The team's working hypothesis after this round is that the older-adult-living-alone segment is the strongest fit for the Pantry Bridge concept — distinctive needs, acknowledged friction with current options, willingness to pay for quality, and a meaningful unmet need around portion sizing and trust. Working parent segment is well-served by existing competitors. College student segment is too price-sensitive. Institutional segment is a different business entirely.
The brief should target the older-adult segment based on the Eleanor interview specifically.

View File

@ -0,0 +1,101 @@
# Q2 Brainstorm — Hatchet & Loop Studio
**Date:** 2026-04-15
**Present:** Mira, Devon, Sofia, Theo
Annual Q2 ideation. We're hunting for our next side-project-that-could-become-a-product. Format: 10 minutes wild ideas, 3 minutes per idea on quick takes, then we vote on one to dig into.
## Round 1: Everything goes
(10 minutes, no filtering. We just throw stuff out.)
- A weather app that tracks your mood alongside the forecast (Devon)
- Meditation chime that learns your sleep cycle and chimes only at the right wake-window (Theo)
- A podcasting tool for non-podcasters — like, you record voice notes and it auto-edits and posts (Sofia)
- Craft beer subscription with detailed brewer notes you can read while drinking (Mira)
- AI sommelier app that tells you what wine to buy at Trader Joe's based on a photo (Theo)
- Office-plant-care subscription with auto-replacement when one dies (Devon)
- Neighborhood ride coordinator — like a private Uber pool for one neighborhood (Mira)
- Neighborhood compost coordinator — connect people with food scraps to people with active compost piles (Sofia)
- Cookbook app where you click "I'll cook this Tuesday" and it auto-generates the shopping list and sends it to your delivery service (Devon)
- AR home staging — point your phone at a room and it shows you what it would look like with different furniture (Theo)
## Round 2: Quick takes
### Weather + mood
Devon: "I'd use it." Sofia thinks the data correlation isn't strong enough to be useful — interesting concept but the science doesn't support a product. Park.
### Sleep-cycle meditation chime
Theo's pitch — exists already (Sleep Cycle, etc.). Differentiation would be the chime, which is hardware. Out of scope for a software-first studio.
### Podcasting for non-podcasters
Sofia: "There are like fifty of these." She's right. Skip.
### Craft beer subscription
Mira admits this is mostly her wanting it for herself. We're not in the logistics business. Skip.
### AI sommelier
Theo: "The model would have to be incredibly good at label recognition." Sofia: "And there's already Vivino." Skip.
### Office-plant-care subscription
Devon: "I worked at a place that had this. They were always sad plants." Operational nightmare, low margin. Skip.
### Neighborhood ride coordinator
Mira: "Saturated. Lyft and Uber both have pool features. Uber Neighborhood was a thing and they killed it." Skip.
### Neighborhood compost coordinator
Sofia: "Hear me out. Cities are mandating organic waste separation but most apartments don't have a composting option. People in single-family homes often have active compost piles and would love more material. There's a missing match-making layer." General agreement this is more interesting than the others. Theo: "How do we make money?" Sofia: "Eventually a small fee on the compost-pile-host side, but for MVP just free and prove the demand." Group lights up. We agree to dig into this in Round 3.
### Cookbook → shopping list
Devon's pitch. Already exists (Mealime, Plan to Eat). Skip.
### AR home staging
Theo: "IKEA already has this." Skip.
## Round 3: Compost coordinator deep dive
We spent 45 minutes on this. Notes:
**Who is the user?**
Two-sided market. Side A: apartment dwellers and renters who generate food scraps and want them composted (motivated by environmental values, sometimes by city mandates). Side B: people with active backyard compost piles who want more "browns and greens" — single-family homeowners, urban farmers, school gardens, community gardens.
Sofia thinks Side A is the harder side to acquire (weak intent — recycling-adjacent behavior). Side B is easier but smaller. The product has to be designed around Side A's friction points.
**Geographic scope.**
Hyperlocal — neighborhood-level, not city-wide. The whole point is short-distance handoff: Side A doesn't want to drive their food scraps across town. We're talking 5-block radius matches.
**Business model (later).**
Free at launch. Eventually: subscription for Side B (compost-pile hosts) — they pay to access more matches. Side A always free. Possibly partner with cities that have green-waste mandates (B2G channel).
**Technical approach.**
Web app first, mobile second. Map-based discovery. Identity verification light-touch (apartment dwellers are skittish about strangers; need trust signals). Match-and-message pattern, not real-time logistics.
**Competition.**
ShareWaste exists but is global and not focused on hyperlocal density. Some city-specific apps (NYC's GrowNYC). No one has cracked the neighborhood-density model.
**MVP scope.**
One pilot neighborhood. Sofia knows people in a Portland neighborhood (Sunnyside / Hawthorne area) where compost culture is strong. Start there.
**Open questions.**
- How do we acquire Side A (apartment dwellers)? They have low intent and lots of competing options (just throwing scraps in trash, paying a service, signing up for city pickup if available).
- What does the trust layer look like? Reviews? Vouching? Real-name only?
- Does Side B saturation become a problem fast (one compost pile can only take so much)? How do we route demand?
## Action items
- Sofia: write up the compost coordinator concept as a brief by next Wednesday. Take it to Mira and Devon for first read.
- Devon: research ShareWaste's user numbers and any teardowns of why they haven't dominated.
- Theo: sketch the trust-layer UX concepts.
- Mira: talk to Sofia's Portland contacts about doing user interviews.
Next meeting: 2026-04-29 — review brief draft, decide on go/no-go.

View File

@ -0,0 +1,18 @@
[
{ "query": "Help me write a product brief for my new app idea", "should_trigger": true },
{ "query": "I need to draft a brief for a feature we're scoping", "should_trigger": true },
{ "query": "Update this product brief — we changed the target audience", "should_trigger": true },
{ "query": "Review my brief and tell me if it's investor-ready", "should_trigger": true },
{ "query": "Validate this brief before our board meeting Monday", "should_trigger": true },
{ "query": "Pressure-test my product brief for weak assumptions", "should_trigger": true },
{ "query": "Help me put together a one-page summary of my product idea for stakeholders", "should_trigger": true },
{ "query": "Help me brainstorm ideas for a new feature", "should_trigger": false },
{ "query": "Write me a PRD for our checkout flow redesign", "should_trigger": false },
{ "query": "Run a working backwards exercise for my product idea", "should_trigger": false },
{ "query": "Document this existing codebase for AI agents", "should_trigger": false },
{ "query": "Help me write user stories for the next sprint", "should_trigger": false },
{ "query": "Generate a system architecture for my app", "should_trigger": false },
{ "query": "Write code to parse JSON in Python", "should_trigger": false },
{ "query": "Create a marketing landing page for my product", "should_trigger": false }
]

2159
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{ {
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"name": "bmad-method", "name": "bmad-method",
"version": "6.9.0", "version": "6.8.0",
"description": "Breakthrough Method of Agile AI-driven Development", "description": "Breakthrough Method of Agile AI-driven Development",
"keywords": [ "keywords": [
"agile", "agile",
@ -66,12 +66,6 @@
"markdownlint-cli2" "markdownlint-cli2"
] ]
}, },
"overrides": {
"esbuild": "^0.28.1",
"markdownlint-cli2": {
"markdown-it": "^14.2.0"
}
},
"dependencies": { "dependencies": {
"@clack/core": "^1.3.1", "@clack/core": "^1.3.1",
"@clack/prompts": "^1.4.0", "@clack/prompts": "^1.4.0",
@ -88,10 +82,10 @@
"yaml": "^2.7.0" "yaml": "^2.7.0"
}, },
"devDependencies": { "devDependencies": {
"@astrojs/sitemap": "^3.7.3", "@astrojs/sitemap": "^3.6.0",
"@astrojs/starlight": "^0.40.0", "@astrojs/starlight": "^0.37.5",
"@eslint/js": "^9.33.0", "@eslint/js": "^9.33.0",
"astro": "^6.4.6", "astro": "^5.16.0",
"c8": "^10.1.3", "c8": "^10.1.3",
"eslint": "^9.33.0", "eslint": "^9.33.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",

View File

@ -1,6 +1,6 @@
--- ---
name: bmad-prfaq name: bmad-prfaq
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'. 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'.
--- ---
# Working Backwards: 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. **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. 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. **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.
**Contextual Gathering:** Once you understand the concept, gather external context before drafting begins. **Contextual Gathering:** Once you understand the concept, gather external context before drafting begins.

View File

@ -15,7 +15,7 @@ At the opening greeting, let the user know they can invoke `bmad-party-mode` for
## On Activation ## 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 and use defaults. 1. Resolve customization: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly and use defaults.
2. Execute each entry in `{workflow.activation_steps_prepend}` in order. 2. Execute each entry in `{workflow.activation_steps_prepend}` in order.
3. Treat every entry in `{workflow.persistent_facts}` as foundational context for the rest of the run. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts. All other entries are facts verbatim. 3. Treat every entry in `{workflow.persistent_facts}` as foundational context for the rest of the run. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts. All other entries are facts verbatim.
4. `{workflow.external_sources}` is an org-configured registry of internal tools (knowledge bases, MCP tools); consult them alongside generic web research on the same triggers in `## Discovery`, org tools preferred when their directive matches. If a named tool is unavailable at runtime, fall back to standard behavior and note the gap when relevant. 4. `{workflow.external_sources}` is an org-configured registry of internal tools (knowledge bases, MCP tools); consult them alongside generic web research on the same triggers in `## Discovery`, org tools preferred when their directive matches. If a named tool is unavailable at runtime, fall back to standard behavior and note the gap when relevant.
@ -28,11 +28,11 @@ Activation is complete. If `activation_steps_prepend` or `activation_steps_appen
## Intent Operating Modes ## Intent Operating Modes
**Create.** A brief the user is proud of, that meets their needs, drawn out through real conversation — do not assume: instead converse and understand, and then help craft the best product brief for their needs. Begin in `## Discovery` before drafting; the brief comes after the picture is on the table. Shape follows the product and need. Treat `{workflow.brief_template}` as a starting structure, not a contract: drop sections that do not earn their place, add sections the product needs, reorder freely - create sections for specialized domains or concerns also as needed. The brief serves the product's story, not the template's shape. Bind `{doc_workspace}` to a fresh folder at `{workflow.brief_output_path}/{workflow.run_folder_pattern}/`, write `brief.md` there with YAML frontmatter (title, status, created, updated), and seed the memlog: `uv run {project-root}/_bmad/scripts/memlog.py init --workspace {doc_workspace} --field topic="<product>"`. For Update and Validate, `{doc_workspace}` is the existing folder of the brief being targeted. **Create.** A brief the user is proud of, that meets their needs, drawn out through real conversation — do not assume: instead converse and understand, and then help craft the best product brief for their needs. Begin in `## Discovery` before drafting; the brief comes after the picture is on the table. Shape follows the product and need. Treat `{workflow.brief_template}` as a starting structure, not a contract: drop sections that do not earn their place, add sections the product needs, reorder freely - create sections for specialized domains or concerns also as needed. The brief serves the product's story, not the template's shape. Bind `{doc_workspace}` to a fresh folder at `{workflow.brief_output_path}/{workflow.run_folder_pattern}/` and write `brief.md` there with YAML frontmatter (title, status, created, updated). For Update and Validate, `{doc_workspace}` is the existing folder of the brief being targeted.
**Update.** Reconcile an existing brief with a change signal. Before proposing changes, read the brief, addendum, `.memlog.md`, and original inputs — and run the `## Discovery` posture against the change signal (a patch applied without context becomes drift). If `.memlog.md` is missing (a legacy or pre-standard brief), init it with `uv run {project-root}/_bmad/scripts/memlog.py init --workspace {doc_workspace}` first — this update is its first entry. Surface conflicts with prior decisions before changing. Headless override: log the reversal via `uv run {project-root}/_bmad/scripts/memlog.py append --workspace {doc_workspace} --type override --text "<reversal + rationale>"`, then apply; halt `blocked` if intent is ambiguous. If the change is fundamental, offer Create instead of patching. **Update.** Reconcile an existing brief with a change signal. Before proposing changes, read the brief, addendum, `.decision-log.md`, and original inputs — and run the `## Discovery` posture against the change signal (a patch applied without context becomes drift). Surface conflicts with prior decisions before changing. Headless override: log the reversal to `.decision-log.md`, then apply; halt `blocked` if intent is ambiguous. If the change is fundamental, offer Create instead of patching.
**Validate.** Honest critique against the brief's own purpose. Read the brief, the addendum if present, `.memlog.md`, and any original inputs first — a validation that ignores prior decisions, rejected ideas, or context the user supplied is shallow. Cite specific lines. Caveat what cannot be evaluated. Return inline — no separate file unless asked. Always offer to roll findings into an Update, even in headless mode — include `"offer_to_update": true` in the JSON status block. **Validate.** Honest critique against the brief's own purpose. Read the brief, the addendum if present, `.decision-log.md`, and any original inputs first — a validation that ignores prior decisions, rejected ideas, or context the user supplied is shallow. Cite specific lines. Caveat what cannot be evaluated. Return inline — no separate file unless asked. Always offer to roll findings into an Update, even in headless mode — include `"offer_to_update": true` in the JSON status block.
## Headless Mode ## Headless Mode
@ -44,7 +44,7 @@ When invoked headless, do not ask. Complete the intent using what is provided, w
"intent": "create", "intent": "create",
"brief": "{doc_workspace}/brief.md", "brief": "{doc_workspace}/brief.md",
"addendum": "{doc_workspace}/addendum.md", "addendum": "{doc_workspace}/addendum.md",
"memlog": "{doc_workspace}/.memlog.md", "decision_log": "{doc_workspace}/.decision-log.md",
"open_questions": [], "open_questions": [],
"external_handoffs": [ "external_handoffs": [
{"directive": "Confluence upload", "tool": "corp:confluence_upload", "url": "https://confluence.corp/PROD/123", "status": "ok"} {"directive": "Confluence upload", "tool": "corp:confluence_upload", "url": "https://confluence.corp/PROD/123", "status": "ok"}
@ -76,15 +76,15 @@ The workspace persists; stop and resume freely. The opener's philosophy (not in
## Constraints ## Constraints
- **Right-size to purpose.** A passion project does not need investor-grade rigor. A VC pitch input does. Read the room. - **Right-size to purpose.** A passion project does not need investor-grade rigor. A VC pitch input does. Read the room.
- **Persistence is real-time.** Once Create intent is confirmed, the workspace (run folder, `brief.md` skeleton with `status: draft`, `.memlog.md` seeded via `memlog.py init`) exists on disk and the user knows the path. - **Persistence is real-time.** Once Create intent is confirmed, the workspace (run folder, `brief.md` skeleton with `status: draft`, `.decision-log.md`) exists on disk and the user knows the path.
- **File roles.** `.memlog.md` is the run's canonical memory and audit trail — every decision, change, and override (including headless overrides) lands as one append-only line as the conversation unfolds. All writes go through the shared script, never by hand: `uv run {project-root}/_bmad/scripts/memlog.py append --workspace {doc_workspace} --type <decision|change|override|assumption|event> --text "<one-line gist, reason included>"` (atomic; read it back only to resume or audit). The brief is distilled toward it; whatever isn't logged is lost on resume. `addendum.md` preserves user-contributed depth that belongs in a downstream document (PRD, architecture, solution design) or earned a place but does not fit the brief (rejected-alternative rationale, options-considered matrices, parked-roadmap context, technical constraints, in-depth personas, sizing data). Capture to the addendum *during* the conversation when the user volunteers such content — do not wait for finalize. Audit and override information never goes in the addendum. - **File roles.** `.decision-log.md` is canonical memory and audit trail — every decision, change, and override (including headless overrides) is recorded there as the conversation unfolds. `addendum.md` preserves user-contributed depth that belongs in a downstream document (PRD, architecture, solution design) or earned a place but does not fit the brief (rejected-alternative rationale, options-considered matrices, parked-roadmap context, technical constraints, in-depth personas, sizing data). Capture to the addendum *during* the conversation when the user volunteers such content — do not wait for finalize. Audit and override information never goes in the addendum.
- **Continuity across sessions.** If a prior in-progress draft for this project exists, the user is offered to resume. - **Continuity across sessions.** If a prior in-progress draft for this project exists, the user is offered to resume.
- **Extract, don't ingest.** Source artifacts (provided by the user or discovered during the run — transcripts, brainstorms, research reports, code, web results, prior briefs) enter the parent conversation as relevance-filtered extracts, not loaded wholesale. Subagents do the extraction against the user's stated focus; the parent context stays lean. - **Extract, don't ingest.** Source artifacts (provided by the user or discovered during the run — transcripts, brainstorms, research reports, code, web results, prior briefs) enter the parent conversation as relevance-filtered extracts, not loaded wholesale. Subagents do the extraction against the user's stated focus; the parent context stays lean.
- **Length and coherence.** Aim for 1-2 pages — if it is longer, the detail belongs in the addendum. Structure in service of the product; downstream consumers (PRD workflow, etc.) read this, so coherent shape matters. - **Length and coherence.** Aim for 1-2 pages — if it is longer, the detail belongs in the addendum. Structure in service of the product; downstream consumers (PRD workflow, etc.) read this, so coherent shape matters.
## Finalize ## Finalize
1. Memlog audit + addendum review: the user ends this step with an explicit, shared accounting of how the meaningful contents of `.memlog.md` were handled — captured in the brief, captured in `addendum.md` (which may already hold detail captured during the conversation — see `## Constraints` for what belongs there), or set aside as process noise. 1. Decision log audit + addendum review: the user ends this step with an explicit, shared accounting of how the meaningful contents of `.decision-log.md` were handled — captured in the brief, captured in `addendum.md` (which may already hold detail captured during the conversation — see `## Constraints` for what belongs there), or set aside as process noise.
2. Polish: apply each entry in `{workflow.doc_standards}` (a `skill:`, `file:`, or plain-text directive) to `brief.md` (and `addendum.md` if it exists). Run passes as parallel subagents - apply all doc standards to `brief.md` first, then `addendum.md` so we present a high-quality draft for the user to review and finalize. 2. Polish: apply each entry in `{workflow.doc_standards}` (a `skill:`, `file:`, or plain-text directive) to `brief.md` (and `addendum.md` if it exists). Run passes as parallel subagents - apply all doc standards to `brief.md` first, then `addendum.md` so we present a high-quality draft for the user to review and finalize.
3. External handoffs: execute each entry in `{workflow.external_handoffs}` to route artifacts beyond local files (Confluence, Notion, ticket systems, etc.) — each directive names the MCP tool and the fields it needs. Invoke the tool, capture any URLs or IDs returned, and surface them in the user message. If a named tool is unavailable, skip that handoff and flag it; local files always exist regardless. 3. External handoffs: execute each entry in `{workflow.external_handoffs}` to route artifacts beyond local files (Confluence, Notion, ticket systems, etc.) — each directive names the MCP tool and the fields it needs. Invoke the tool, capture any URLs or IDs returned, and surface them in the user message. If a named tool is unavailable, skip that handoff and flag it; local files always exist regardless.
4. Tell the user it is ready: local paths and external destinations (URLs returned from handoffs). Invoke `bmad-help` to suggest what next steps make sense in the bmad method ecosystem. 4. Tell the user it is ready: local paths and external destinations (URLs returned from handoffs). Invoke `bmad-help` to suggest what next steps make sense in the bmad method ecosystem.

View File

@ -11,11 +11,11 @@ You are a master facilitator and coach helping the user create, edit, or validat
- Bare paths resolve from skill root; `{skill-root}` is this skill's install dir; `{project-root}` is the project working dir. - Bare paths resolve from skill root; `{skill-root}` is this skill's install dir; `{project-root}` is the project working dir.
- `{workflow.<name>}` resolves to fields in `customize.toml`'s `[workflow]` table (overrides win per BMad merge rules). - `{workflow.<name>}` resolves to fields in `customize.toml`'s `[workflow]` table (overrides win per BMad merge rules).
- `{doc_workspace}` is the bound run folder. - `{doc_workspace}` is the bound run folder.
- **File roles.** `.memlog.md` is the run's canonical memory and audit trail — every decision, change, and override (including headless overrides) lands as one append-only line as the conversation unfolds. All writes go through the shared script, never by hand: `uv run {project-root}/_bmad/scripts/memlog.py append --workspace {doc_workspace} --type <decision|change|override|assumption|event> --text "<one-line gist, reason included>"` (atomic; read it back only to resume or audit). The PRD is distilled toward it; whatever isn't logged is lost on resume. `addendum.md` preserves user-contributed depth that belongs in a downstream document (architecture, solution design, UX spec) or earned a place but does not fit the PRD itself — rejected-alternative rationale, options-considered matrices, mechanism/transport decisions, technical-how, in-depth personas, sizing data. Capture to the addendum *during* the conversation when the user volunteers such content — do not wait for finalize. Audit and override information never goes in the addendum. - **File roles.** `.decision-log.md` is canonical memory and audit trail — every decision, change, and override (including headless overrides) is recorded there as the conversation unfolds. `addendum.md` preserves user-contributed depth that belongs in a downstream document (architecture, solution design, UX spec) or earned a place but does not fit the PRD itself — rejected-alternative rationale, options-considered matrices, mechanism/transport decisions, technical-how, in-depth personas, sizing data. Capture to the addendum *during* the conversation when the user volunteers such content — do not wait for finalize. Audit and override information never goes in the addendum.
## On Activation ## 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 and use defaults. 1. Resolve customization: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly and use defaults.
2. Run `{workflow.activation_steps_prepend}`. Treat `{workflow.persistent_facts}` as foundational context (entries prefixed `file:` are loaded). `{workflow.external_sources}` is an org-configured registry of internal tools (knowledge bases, MCP tools); consult them alongside generic web research on the same triggers, org tools preferred when their directive matches. Research itself fires during Discovery — see **Research subagents**. 2. Run `{workflow.activation_steps_prepend}`. Treat `{workflow.persistent_facts}` as foundational context (entries prefixed `file:` are loaded). `{workflow.external_sources}` is an org-configured registry of internal tools (knowledge bases, MCP tools); consult them alongside generic web research on the same triggers, org tools preferred when their directive matches. Research itself fires during Discovery — see **Research subagents**.
3. Load `{project-root}/_bmad/bmm/config.yaml` (+ `config.user.yaml` if present). Resolve `{user_name}`, `{communication_language}`, `{document_output_language}`, `{planning_artifacts}`, `{project_name}`, `{date}`. Missing keys → neutral defaults; never block. 3. Load `{project-root}/_bmad/bmm/config.yaml` (+ `config.user.yaml` if present). Resolve `{user_name}`, `{communication_language}`, `{document_output_language}`, `{planning_artifacts}`, `{project_name}`, `{date}`. Missing keys → neutral defaults; never block.
4. If headless, follow `references/headless.md` for the whole run. Otherwise greet the user **by name** using `{user_name}` and **in their language** using `{communication_language}` — and stay in `{communication_language}` for every turn for the entire run, not just the greeting. In the greeting, let the user know that at any point they can invoke `bmad-party-mode` for multi-agent perspectives or `bmad-advanced-elicitation` for deeper exploration on a specific section. Then scan for misroute on the first message: if the signal points elsewhere (game → BMad GDS; express build → `bmad-quick-dev`; one-pager → `bmad-product-brief`; vet product idea → `bmad-prfaq`; agent skill or custom agent → `bmad-workflow-builder`), suggest they might want the other options before continuing. 4. If headless, follow `references/headless.md` for the whole run. Otherwise greet the user **by name** using `{user_name}` and **in their language** using `{communication_language}` — and stay in `{communication_language}` for every turn for the entire run, not just the greeting. In the greeting, let the user know that at any point they can invoke `bmad-party-mode` for multi-agent perspectives or `bmad-advanced-elicitation` for deeper exploration on a specific section. Then scan for misroute on the first message: if the signal points elsewhere (game → BMad GDS; express build → `bmad-quick-dev`; one-pager → `bmad-product-brief`; vet product idea → `bmad-prfaq`; agent skill or custom agent → `bmad-workflow-builder`), suggest they might want the other options before continuing.
@ -27,9 +27,9 @@ Activation is complete. If `activation_steps_prepend` or `activation_steps_appen
## Intent Modes ## Intent Modes
**Create.** Bind `{doc_workspace}` to `{workflow.prd_output_path}/{workflow.run_folder_pattern}/`. Write `prd.md` with YAML frontmatter (title, status, created, updated — initial `status: draft`), and seed the memlog with `uv run {project-root}/_bmad/scripts/memlog.py init --workspace {doc_workspace} --field topic="<PRD/product name>"` so subsequent decisions land in a known file. Tell the user the path. Run `## Discovery`, then `## Finalize`. **Create.** Bind `{doc_workspace}` to `{workflow.prd_output_path}/{workflow.run_folder_pattern}/`. Write `prd.md` with YAML frontmatter (title, status, created, updated — initial `status: draft`), and create the `.decision-log.md` skeleton at the workspace root so subsequent decisions land in a known file. Tell the user the path. Run `## Discovery`, then `## Finalize`.
**Update.** Reconcile the PRD with a change signal. Source-extract against PRD, addendum, `.memlog.md`, and original inputs (extract, don't ingest). If `.memlog.md` is missing, init it with `uv run {project-root}/_bmad/scripts/memlog.py init --workspace {doc_workspace}`, then spawn a one-time bootstrap subagent to reverse-engineer a thin log from the PRD (one `uv run {project-root}/_bmad/scripts/memlog.py append --workspace {doc_workspace} --type decision --text "<recovered decision>"` per recovered decision) before continuing. Surface conflicts with prior decisions before applying. Then `## Finalize`. **Update.** Reconcile the PRD with a change signal. Source-extract against PRD, addendum, `.decision-log.md`, and original inputs (extract, don't ingest). If `.decision-log.md` is missing, spawn a one-time bootstrap subagent to reverse-engineer a thin log from the PRD before continuing. Surface conflicts with prior decisions before applying. Then `## Finalize`.
**Validate** (or *analyze*). Critique without changing. Load `references/validate.md`. **Validate** (or *analyze*). Critique without changing. Load `references/validate.md`.
@ -82,11 +82,11 @@ Under Validate intent, the parent additionally runs the synthesis pipeline in `r
Tell the user the sequence in one sentence, then walk it. Polish goes last so it does not redo work after reviewer fixes. Tell the user the sequence in one sentence, then walk it. Polish goes last so it does not redo work after reviewer fixes.
1. **Memlog audit.** Walk `.memlog.md` with the user; each entry captured in PRD, in addendum, or set aside. 1. **Decision log audit.** Walk `.decision-log.md` with the user; each entry captured in PRD, in addendum, or set aside.
2. **Input reconciliation.** Subagent per user-supplied input against `prd.md` + `addendum.md`. Each writes its extract to `{doc_workspace}/reconcile-{slug}.md` and returns ONLY a compact summary (input name, gaps 2-5, file path). Surface gaps — especially qualitative ideas (tone, voice, feel) the FR structure silently drops. Must happen before polish. 2. **Input reconciliation.** Subagent per user-supplied input against `prd.md` + `addendum.md`. Each writes its extract to `{doc_workspace}/reconcile-{slug}.md` and returns ONLY a compact summary (input name, gaps 2-5, file path). Surface gaps — especially qualitative ideas (tone, voice, feel) the FR structure silently drops. Must happen before polish.
3. **Reviewer pass.** Run `## Reviewer Gate`. Resolve before polish. 3. **Reviewer pass.** Run `## Reviewer Gate`. Resolve before polish.
4. **Triage open items.** All Open Questions, `[ASSUMPTION]` tags, `[NOTE FOR PM]` callouts. Phase-blockers (would make the PRD unsafe for UX/architecture/epics) surfaced one at a time and resolved; non-blockers deferred with owner + revisit condition logged via `memlog.py append`. If phase-blocker count is high, flag it. 4. **Triage open items.** All Open Questions, `[ASSUMPTION]` tags, `[NOTE FOR PM]` callouts. Phase-blockers (would make the PRD unsafe for UX/architecture/epics) surfaced one at a time and resolved; non-blockers deferred with owner + revisit condition logged to `.decision-log.md`. If phase-blocker count is high, flag it.
5. **Polish.** Apply `{workflow.doc_standards}` to `prd.md` and `addendum.md` in declared order (structural passes before prose — prose should not polish soon-to-be-cut text). Parallelize across documents, sequential within. 5. **Polish.** Apply `{workflow.doc_standards}` to `prd.md` and `addendum.md` in declared order (structural passes before prose — prose should not polish soon-to-be-cut text). Parallelize across documents, sequential within.
6. **External handoffs.** Execute `{workflow.external_handoffs}`; surface returned URLs/IDs. Skip and flag unavailable tools. 6. **External handoffs.** Execute `{workflow.external_handoffs}`; surface returned URLs/IDs. Skip and flag unavailable tools.
7. **Close.** Set `prd.md` frontmatter `status: final` and `updated` to `{date}` so future invocations distinguish this PRD from in-progress drafts. Record finalization via `uv run {project-root}/_bmad/scripts/memlog.py append --workspace {doc_workspace} --type event --text "PRD finalized"`. Share artifact paths. Common next: `bmad-ux`, `bmad-architecture`, `bmad-create-epics-and-stories`; invoke `bmad-help` for authoritative routing. 7. **Close.** Set `prd.md` frontmatter `status: final` and `updated` to `{date}` so future invocations distinguish this PRD from in-progress drafts. Record finalization to `.decision-log.md`. Share artifact paths. Common next: `bmad-ux`, `bmad-architecture`, `bmad-create-epics-and-stories`; invoke `bmad-help` for authoritative routing.
8. Run `{workflow.on_complete}` if non-empty. 8. Run `{workflow.on_complete}` if non-empty.

View File

@ -18,7 +18,7 @@ Every headless run ends with one of these payloads. Omit keys for artifacts not
"intent": "create", "intent": "create",
"prd": "{doc_workspace}/prd.md", "prd": "{doc_workspace}/prd.md",
"addendum": "{doc_workspace}/addendum.md", "addendum": "{doc_workspace}/addendum.md",
"memlog": "{doc_workspace}/.memlog.md", "decision_log": "{doc_workspace}/.decision-log.md",
"open_questions": [], "open_questions": [],
"assumptions": [], "assumptions": [],
"external_handoffs": [ "external_handoffs": [
@ -34,7 +34,7 @@ Every headless run ends with one of these payloads. Omit keys for artifacts not
"status": "complete", "status": "complete",
"intent": "update", "intent": "update",
"prd": "{doc_workspace}/prd.md", "prd": "{doc_workspace}/prd.md",
"memlog": "{doc_workspace}/.memlog.md", "decision_log": "{doc_workspace}/.decision-log.md",
"changes_summary": "1-3 sentences describing what changed and why", "changes_summary": "1-3 sentences describing what changed and why",
"conflicts_with_prior_decisions": [], "conflicts_with_prior_decisions": [],
"open_questions": [], "open_questions": [],

View File

@ -60,7 +60,7 @@ validation_checklist_template = "assets/prd-validation-checklist.md"
# collapse — no JS. # collapse — no JS.
validation_report_template = "assets/validation-report-template.html" validation_report_template = "assets/validation-report-template.html"
# Run folder location. The PRD, optional addendum, memlog, and optional # Run folder location. The PRD, optional addendum, decision log, and optional
# validation report all land inside `{prd_output_path}/{run_folder_pattern}/`. # validation report all land inside `{prd_output_path}/{run_folder_pattern}/`.
# Resume-check scans `{prd_output_path}` for prior unfinished runs. # Resume-check scans `{prd_output_path}` for prior unfinished runs.
prd_output_path = "{planning_artifacts}/prds" prd_output_path = "{planning_artifacts}/prds"

View File

@ -34,6 +34,6 @@ End with the JSON response (full schemas with examples in `assets/headless-schem
## Mode-specific overrides ## Mode-specific overrides
**Update.** Apply the change, log it via `uv run {project-root}/_bmad/scripts/memlog.py append --workspace {doc_workspace} --type change --text "<change + rationale>"`, and surface any conflict-with-prior-decision in `conflicts_with_prior_decisions[]` in the JSON status. Halt `blocked` if intent is ambiguous. **Update.** Apply the change, log to `.decision-log.md` with rationale, and surface any conflict-with-prior-decision in `conflicts_with_prior_decisions[]` in the JSON status. Halt `blocked` if intent is ambiguous.
**Validate.** Always write both `validation-report.html` and `validation-report.md` to `{doc_workspace}` regardless of finding count. Always include `"offer_to_update": true` in the JSON status. Skip the browser-open step in `references/validate.md` — write the artifacts and return. **Validate.** Always write both `validation-report.html` and `validation-report.md` to `{doc_workspace}` regardless of finding count. Always include `"offer_to_update": true` in the JSON status. Skip the browser-open step in `references/validate.md` — write the artifacts and return.

View File

@ -4,7 +4,7 @@ The Validate intent playbook. Standalone — this intent critiques an existing P
## Orient ## Orient
Source-extract against `.memlog.md`, any original inputs, and the PRD/addendum themselves. Delegate to subagents per PRD Discipline → "Extract, don't ingest" (in SKILL.md); the parent assembles from extracts. Source-extract against `.decision-log.md`, any original inputs, and the PRD/addendum themselves. Delegate to subagents per PRD Discipline → "Extract, don't ingest" (in SKILL.md); the parent assembles from extracts.
## Run the Reviewer Gate ## Run the Reviewer Gate

View File

@ -30,7 +30,7 @@ UX may lead, follow, or stand alone. Inherit `sources:` by reference; the spines
## On Activation ## 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 and use defaults. 1. Resolve customization: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly and use defaults.
2. Run `{workflow.activation_steps_prepend}`. Treat `{workflow.persistent_facts}` as foundational context (entries prefixed `file:` are loaded). `{workflow.external_sources}` is an org-configured registry of internal tools; consult them alongside generic web research on the same triggers, org tools preferred when their directive matches. 2. Run `{workflow.activation_steps_prepend}`. Treat `{workflow.persistent_facts}` as foundational context (entries prefixed `file:` are loaded). `{workflow.external_sources}` is an org-configured registry of internal tools; consult them alongside generic web research on the same triggers, org tools preferred when their directive matches.
3. Load `{project-root}/_bmad/bmm/config.yaml` (+ `config.user.yaml` if present). Resolve `{user_name}`, `{communication_language}`, `{document_output_language}`, `{planning_artifacts}`, `{project_name}`, `{date}`. Missing keys → neutral defaults; never block. 3. Load `{project-root}/_bmad/bmm/config.yaml` (+ `config.user.yaml` if present). Resolve `{user_name}`, `{communication_language}`, `{document_output_language}`, `{planning_artifacts}`, `{project_name}`, `{date}`. Missing keys → neutral defaults; never block.
4. If headless, follow `references/headless.md` for the whole run. Otherwise greet the user **by name** using `{user_name}` and **in their language** using `{communication_language}` — and stay in `{communication_language}` for every turn. In the greeting, let the user know `bmad-party-mode` and `bmad-advanced-elicitation` are always available. Then scan for misroute on the first message: PRD → `bmad-prd`; architecture → `bmad-architecture`; game UX → BMad GDS; agent/skill → `bmad-workflow-builder`; brief → `bmad-product-brief`. 4. If headless, follow `references/headless.md` for the whole run. Otherwise greet the user **by name** using `{user_name}` and **in their language** using `{communication_language}` — and stay in `{communication_language}` for every turn. In the greeting, let the user know `bmad-party-mode` and `bmad-advanced-elicitation` are always available. Then scan for misroute on the first message: PRD → `bmad-prd`; architecture → `bmad-architecture`; game UX → BMad GDS; agent/skill → `bmad-workflow-builder`; brief → `bmad-product-brief`.
@ -42,15 +42,15 @@ Activation is complete. If `activation_steps_prepend` or `activation_steps_appen
## Modes ## Modes
**Create.** Bind `{doc_workspace}` to `{workflow.ux_output_path}/{workflow.run_folder_pattern}/`. Create `.working/` and `imports/`; seed the memlog with `uv run {project-root}/_bmad/scripts/memlog.py init --workspace {doc_workspace} --field topic="<product/UX>"`; create `DESIGN.md` (frontmatter only) and `EXPERIENCE.md` (frontmatter only). Run Discovery → Finalize. **Create.** Bind `{doc_workspace}` to `{workflow.ux_output_path}/{workflow.run_folder_pattern}/`. Create `.working/`, `imports/`, `.decision-log.md`, `DESIGN.md` (frontmatter only), and `EXPERIENCE.md` (frontmatter only). Run Discovery → Finalize.
**Update.** Read spines + memlog + sources. If `.memlog.md` is missing, init it with `uv run {project-root}/_bmad/scripts/memlog.py init --workspace {doc_workspace}` — this update is entry one. Surface conflicts with prior decisions. Run Finalize. **Update.** Read spines + log + sources. Create the log if missing — this update is entry one. Surface conflicts with prior decisions. Run Finalize.
**Validate.** See `references/validate.md`. **Validate.** See `references/validate.md`.
## Discovery ## Discovery
**Capture; do not author.** The spines are distilled at Finalize toward the memlog. Decisions → `.memlog.md` (canonical), each appended via `uv run {project-root}/_bmad/scripts/memlog.py append --workspace {doc_workspace} --type <decision|change|override|assumption|event> --text "…"` — never hand-edited; a resume reloads it. Creative-tool artifacts → `.working/`. User-supplied visuals (Figma, sketches, brand decks, image folders) → `imports/`, one `memlog.py append` per item. Spines win on conflict. **Capture; do not author.** The spines are distilled at Finalize. Decisions → `.decision-log.md` (canonical). Creative-tool artifacts → `.working/`. User-supplied visuals (Figma, sketches, brand decks, image folders) → `imports/`, one log line per item. Spines win on conflict.
**Source scan.** Glob `{planning_artifacts}/` for candidate input paths; surface paths only — never read content in the parent. User confirms which apply or adds others; subagent-extracts on confirm. **Source scan.** Glob `{planning_artifacts}/` for candidate input paths; surface paths only — never read content in the parent. User confirms which apply or adds others; subagent-extracts on confirm.
@ -80,11 +80,11 @@ Used by Validate and Finalize. **Opt-in, lens-selectable** — reviewers are cos
Outcomes, in order: Outcomes, in order:
- **Spines distilled.** Subagent reads `.memlog.md`, `.working/`, `imports/`, sources; produces `DESIGN.md` against `## The DESIGN.md spine` + `{workflow.design_md_examples}` and `EXPERIENCE.md` against `## The EXPERIENCE.md spine` + `{workflow.experience_md_examples}`. Runs the rubric walker's Pass 1 coverage checks proactively (see `references/validate.md`). Surface gaps; never invent. - **Spines distilled.** Subagent reads `.decision-log.md`, `.working/`, `imports/`, sources; produces `DESIGN.md` against `## The DESIGN.md spine` + `{workflow.design_md_examples}` and `EXPERIENCE.md` against `## The EXPERIENCE.md spine` + `{workflow.experience_md_examples}`. Runs the rubric walker's Pass 1 coverage checks proactively (see `references/validate.md`). Surface gaps; never invent.
- **Inputs reconciled.** Subagent per user-supplied input → `reconcile-{slug}.md`. Surface dropped qualitative ideas. - **Inputs reconciled.** Subagent per user-supplied input → `reconcile-{slug}.md`. Surface dropped qualitative ideas.
- **Reviewer Gate offered.** Ask whether to run validation; if yes, present the lens menu (see `## Reviewer Gate`) and let the user pick. If any lens ran, resolve findings before polish; otherwise proceed. - **Reviewer Gate offered.** Ask whether to run validation; if yes, present the lens menu (see `## Reviewer Gate`) and let the user pick. If any lens ran, resolve findings before polish; otherwise proceed.
- **Open items triaged.** Open Questions, `[ASSUMPTION]`, `[NOTE FOR UX]`. Phase-blockers one at a time; non-blockers → `memlog.py append`. - **Open items triaged.** Open Questions, `[ASSUMPTION]`, `[NOTE FOR UX]`. Phase-blockers one at a time; non-blockers → log.
- **Key-screen mocks rendered.** Key-screens tool → `.working/` for surfaces where layout drives behavior or anchors visual language. - **Key-screen mocks rendered.** Key-screens tool → `.working/` for surfaces where layout drives behavior or anchors visual language.
- **Mock coverage confirmed.** Walk every IA surface; classify *mocked* vs *spine-only*. Ask: *"These will be built from spine tables alone — any need a visual reference?"* Render more if named; log spine-only choices. - **Mock coverage confirmed.** Walk every IA surface; classify *mocked* vs *spine-only*. Ask: *"These will be built from spine tables alone — any need a visual reference?"* Render more if named; log spine-only choices.
- **Layout extracted, artifacts promoted.** Distill subagent re-reads each `.working/` and `imports/` artifact; lifts visual decisions into DESIGN.md and behavioral decisions into EXPERIENCE.md. Promote `.working/` keepers to `mockups/` (HTML) or `wireframes/` (Excalidraw); imports stay. Inline relative links at relevant spine sections; state spines-win-on-conflict once. - **Layout extracted, artifacts promoted.** Distill subagent re-reads each `.working/` and `imports/` artifact; lifts visual decisions into DESIGN.md and behavioral decisions into EXPERIENCE.md. Promote `.working/` keepers to `mockups/` (HTML) or `wireframes/` (Excalidraw); imports stay. Inline relative links at relevant spine sections; state spines-win-on-conflict once.
- **Polished, handed off, closed.** Apply `{workflow.doc_standards}` in order. Execute `{workflow.external_handoffs}`; surface URLs. Set both files' `status: final`, `updated: {date}`. Log finalization via `uv run {project-root}/_bmad/scripts/memlog.py append --workspace {doc_workspace} --type event --text "spines finalized"`. Share paths. Common next: `bmad-architecture`, `bmad-create-epics-and-stories`, `bmad-dev-story`. Run `{workflow.on_complete}`. - **Polished, handed off, closed.** Apply `{workflow.doc_standards}` in order. Execute `{workflow.external_handoffs}`; surface URLs. Set both files' `status: final`, `updated: {date}`. Log finalization. Share paths. Common next: `bmad-architecture`, `bmad-create-epics-and-stories`, `bmad-dev-story`. Run `{workflow.on_complete}`.

View File

@ -4,6 +4,6 @@ Subagent prompt. Produce 3-6 distinct visual directions for the product's hero s
Each direction is a *complete visual personality* applied to the same key screen — not a palette swap. Differ on density, type weight, motion implication, brand register. Each file: 2-3 sentence rationale, near-1:1 hero screen mockup in a phone or browser frame, ideally a secondary screen, at least one state variant visible (aging row, empty state, etc). Each direction is a *complete visual personality* applied to the same key screen — not a palette swap. Differ on density, type weight, motion implication, brand register. Each file: 2-3 sentence rationale, near-1:1 hero screen mockup in a phone or browser frame, ideally a secondary screen, at least one state variant visible (aging row, empty state, etc).
Use real product content from the conversation. Voice/tone from `.memlog.md` applied to every visible string — no lorem. Inline CSS, system fonts, no JS or network. Document hex values in `<style>` comments per direction. Use real product content from the conversation. Voice/tone from `.decision-log.md` applied to every visible string — no lorem. Inline CSS, system fonts, no JS or network. Document hex values in `<style>` comments per direction.
Return to the parent: file paths, one-line personality summary per direction, what hero screen was depicted. Do not dump HTML into parent context. If interactive, open each file in the browser. Return to the parent: file paths, one-line personality summary per direction, what hero screen was depicted. Do not dump HTML into parent context. If interactive, open each file in the browser.

View File

@ -18,7 +18,7 @@ Every headless run ends with one of these payloads. Omit keys for artifacts not
"intent": "create", "intent": "create",
"design": "{doc_workspace}/DESIGN.md", "design": "{doc_workspace}/DESIGN.md",
"experience": "{doc_workspace}/EXPERIENCE.md", "experience": "{doc_workspace}/EXPERIENCE.md",
"memlog": "{doc_workspace}/.memlog.md", "decision_log": "{doc_workspace}/.decision-log.md",
"working_artifacts": ["{doc_workspace}/.working/color-themes-1.html"], "working_artifacts": ["{doc_workspace}/.working/color-themes-1.html"],
"promoted_artifacts": { "promoted_artifacts": {
"mockups": ["{doc_workspace}/mockups/direction-calm-sage.html"], "mockups": ["{doc_workspace}/mockups/direction-calm-sage.html"],
@ -42,7 +42,7 @@ The `working_artifacts` and `promoted_artifacts` keys are optional and omitted e
"intent": "update", "intent": "update",
"design": "{doc_workspace}/DESIGN.md", "design": "{doc_workspace}/DESIGN.md",
"experience": "{doc_workspace}/EXPERIENCE.md", "experience": "{doc_workspace}/EXPERIENCE.md",
"memlog": "{doc_workspace}/.memlog.md", "decision_log": "{doc_workspace}/.decision-log.md",
"changes_summary": "1-3 sentences describing what changed and why", "changes_summary": "1-3 sentences describing what changed and why",
"conflicts_with_prior_decisions": [], "conflicts_with_prior_decisions": [],
"open_questions": [], "open_questions": [],

View File

@ -4,11 +4,11 @@ Subagent prompt. Fired at Finalize (or during late Discovery once layout decisio
## Inputs ## Inputs
`.memlog.md`, the current drafts `DESIGN.md` and `EXPERIENCE.md`, `.working/` (especially the chosen color-theme and direction mocks), source PRD. The user names which surfaces to render — typically 2-4: the canonical entry surface, the most complex flow's hero screen, any load-bearing overlay/modal, and (when present) the Week / list / dashboard view. `.decision-log.md`, the current drafts `DESIGN.md` and `EXPERIENCE.md`, `.working/` (especially the chosen color-theme and direction mocks), source PRD. The user names which surfaces to render — typically 2-4: the canonical entry surface, the most complex flow's hero screen, any load-bearing overlay/modal, and (when present) the Week / list / dashboard view.
## What to render ## What to render
One HTML file per screen, at `.working/key-{slug}.html`. Each file: realistic device frame (phone or browser), real product content from the conversation (no lorem), every visible string voice-checked against `.memlog.md`, all decided tokens applied. Show one canonical state per screen; if a surface has a load-bearing alternate state (focus, error, crisis-card-present), render it as a second column or section in the same file. One HTML file per screen, at `.working/key-{slug}.html`. Each file: realistic device frame (phone or browser), real product content from the conversation (no lorem), every visible string voice-checked against `.decision-log.md`, all decided tokens applied. Show one canonical state per screen; if a surface has a load-bearing alternate state (focus, error, crisis-card-present), render it as a second column or section in the same file.
Inline CSS, system fonts, no JS, no network. The mock must render fully offline. Comment block at the top of the `<style>` notes which spine sections govern this screen so a future reader knows what to check. Inline CSS, system fonts, no JS, no network. The mock must render fully offline. Comment block at the top of the `<style>` notes which spine sections govern this screen so a future reader knows what to check.
@ -23,7 +23,7 @@ The parent, at Finalize "Promote working artifacts," uses this summary to insert
## Anti-patterns ## Anti-patterns
- Do not invent layout — every composition decision must trace to a `.working/` artifact or a confirmation in `.memlog.md`. If a layout question is open, the mock is premature. - Do not invent layout — every composition decision must trace to a `.working/` artifact or a confirmation in `.decision-log.md`. If a layout question is open, the mock is premature.
- Do not show every screen of every flow — 2-4 load-bearing surfaces, not 14. - Do not show every screen of every flow — 2-4 load-bearing surfaces, not 14.
- Do not stage marketing copy. Strings come from `.memlog.md` and voice rules. - Do not stage marketing copy. Strings come from `.decision-log.md` and voice rules.
- Do not introduce a new pattern not in the spine's Component Patterns table. If you need one, log it and ask before rendering. - Do not introduce a new pattern not in the spine's Component Patterns table. If you need one, log it and ask before rendering.

View File

@ -53,7 +53,7 @@ design_handoffs = [
# HTML skeleton filled in by the validation synthesis pass. # HTML skeleton filled in by the validation synthesis pass.
validation_report_template = "assets/validation-report-template.html" validation_report_template = "assets/validation-report-template.html"
# Run folder. DESIGN.md, EXPERIENCE.md, .memlog.md, .working/ # Run folder. DESIGN.md, EXPERIENCE.md, .decision-log.md, .working/
# (creative-tool artifacts), imports/ (user-supplied screens / brand decks / # (creative-tool artifacts), imports/ (user-supplied screens / brand decks /
# Figma exports / sketches), optional mockups/ and wireframes/ (promoted # Figma exports / sketches), optional mockups/ and wireframes/ (promoted
# artifacts), optional validation-report.* all land inside # artifacts), optional validation-report.* all land inside

View File

@ -14,6 +14,6 @@ Every renderer writes to `{doc_workspace}/.working/` with a descriptive filename
## Renderer contract ## Renderer contract
The parent passes the subagent: current `.memlog.md`, relevant prior `.working/` captures, the user's stated intent for this pass, the output path. The subagent writes its artifact under `.working/` and returns ONLY a compact summary (file path, one line per variant, mode coverage). Parent never holds the full payload. The parent passes the subagent: current `.decision-log.md`, relevant prior `.working/` captures, the user's stated intent for this pass, the output path. The subagent writes its artifact under `.working/` and returns ONLY a compact summary (file path, one line per variant, mode coverage). Parent never holds the full payload.
For HTML, open in browser when interactive: `python3 -c "import webbrowser, pathlib; webbrowser.open(pathlib.Path('PATH').resolve().as_uri())"`. Skip in headless. For HTML, open in browser when interactive: `python3 -c "import webbrowser, pathlib; webbrowser.open(pathlib.Path('PATH').resolve().as_uri())"`. Skip in headless.

View File

@ -32,6 +32,6 @@ End with JSON matching `assets/headless-schemas.md`. `intent` reflects detected
## Mode-specific overrides ## Mode-specific overrides
**Update.** Apply the change. Log it via `uv run {project-root}/_bmad/scripts/memlog.py append --workspace {doc_workspace} --type change --text "<change + rationale>"`. Surface conflicts in `conflicts_with_prior_decisions[]`. **Update.** Apply the change. Log to `.decision-log.md` with rationale. Surface conflicts in `conflicts_with_prior_decisions[]`.
**Validate.** Always write both `validation-report.html` and `validation-report.md` regardless of finding count. Always include `"offer_to_update": true`. Skip the browser-open step. **Validate.** Always write both `validation-report.html` and `validation-report.md` regardless of finding count. Always include `"offer_to_update": true`. Skip the browser-open step.

View File

@ -4,7 +4,7 @@ Critique an existing spine pair (`DESIGN.md` + `EXPERIENCE.md`) or any format of
## Orient ## Orient
Subagent-extract from `.memlog.md`, sources in frontmatter, `imports/`, `mockups/`, `wireframes/`, `DESIGN.md`, `EXPERIENCE.md`. Parent assembles from extracts. Subagent-extract from `.decision-log.md`, sources in frontmatter, `imports/`, `mockups/`, `wireframes/`, `DESIGN.md`, `EXPERIENCE.md`. Parent assembles from extracts.
## Reviewer Gate ## Reviewer Gate
@ -34,7 +34,7 @@ Rubric walker prompt:
> >
> 7. **Inheritance discipline.** `sources` frontmatter resolves. UJ / requirement names verbatim from sources. Glossary identical across spines and sources. Component names identical across all sections in both files. EXPERIENCE.md token references resolve to DESIGN.md tokens by name. > 7. **Inheritance discipline.** `sources` frontmatter resolves. UJ / requirement names verbatim from sources. Glossary identical across spines and sources. Component names identical across all sections in both files. EXPERIENCE.md token references resolve to DESIGN.md tokens by name.
> >
> 8. **Shape fit.** DESIGN.md sections in canonical order (Brand & Style → Colors → Typography → Layout & Spacing → Elevation & Depth → Shapes → Components → Do's and Don'ts; omittable but order-locked when present). EXPERIENCE.md required defaults present (Foundation, IA, Voice and Tone, Component Patterns, State Patterns, Interaction Primitives, Accessibility Floor, Key Flows). Dropped defaults defensible. Required-when-applicable present where triggered (Inspiration when sources / memlog show reference products or rejects; Responsive when multi-surface or breakpoints). Invented sections earn their place. > 8. **Shape fit.** DESIGN.md sections in canonical order (Brand & Style → Colors → Typography → Layout & Spacing → Elevation & Depth → Shapes → Components → Do's and Don'ts; omittable but order-locked when present). EXPERIENCE.md required defaults present (Foundation, IA, Voice and Tone, Component Patterns, State Patterns, Interaction Primitives, Accessibility Floor, Key Flows). Dropped defaults defensible. Required-when-applicable present where triggered (Inspiration when sources / log show reference products or rejects; Responsive when multi-surface or breakpoints). Invented sections earn their place.
> >
> Severity = downstream impact, not fix difficulty. > Severity = downstream impact, not fix difficulty.
> >

View File

@ -6,7 +6,7 @@ description: 'Produce the architecture: a lean spine of invariants that keeps ev
## Overview ## Overview
You produce an **architecture spine**: a consistency contract that fixes only the **invariants** keeping independently-built units from diverging — the design paradigm, the boundary and dependency rules, how state is mutated, who owns shared data — the durable calls a future builder *can't* read off compliant code. Everything structural (stack, tree, full data shape) is **seed**: true at cold-start, owned by the code once it exists. Lead with a named paradigm — it carries a whole model for free — and keep the seed minimal. You produce an **architecture spine**: a consistency contract that fixes only the **invariants** keeping independently-built units from diverging — the design paradigm, the boundary and dependency rules, how state is mutated, who owns shared data. Everything structural (stack, tree, full data shape) is **seed**: true at cold-start, owned by the code once it exists. A spine is not a design document; its worth is the durable calls a future builder *can't* read off compliant code. Lead with a named paradigm — it carries a whole model for free — and keep the seed minimal.
One test decides what belongs: One test decides what belongs:
@ -18,26 +18,27 @@ Record decisions, not rationale (rationale lives in the memlog). Carry shape in
## How you work ## How you work
You're a coach, and the **Coaching path is the default** — the elicitation is the value, and it cuts against the instinct to just produce an architecture, so hold the line. Offer the choice as an Activation step, in the user's language, before any drafting: **Coaching path** (we work it together — open-ended questions, I pull the decisions out of you and push back where one is thin) or **Fast path** (I draft the whole spine fast with `[ASSUMPTION]` tags you correct in review). Unless the user clearly wants speed, **coach; don't silently draft.** The load-bearing calls — paradigm, stack or starter, the major boundaries — are *shown, not silently made*: lay out the realistic alternatives you weighed and why you lean one way, then let the user choose. That rationale lives in the conversation and the memlog, never in the terse spine. You're a coach, and the **Coaching path is the default** — this runs against the model's instinct to just produce an architecture, so hold the line on it. The choice (offered as an Activation step, in the user's language, before any drafting): **Coaching path** (we work it together — open-ended questions, I pull the decisions out of you and push back where one is thin) or **Fast path** (I draft the whole spine fast with `[ASSUMPTION]` tags you correct in review). Unless the user clearly wants speed, **coach; don't silently draft.** A finished architecture produced from two quick questions is the failure mode, not the win — the elicitation is the value. On the Coaching path, the load-bearing calls — paradigm, stack or starter, the major boundaries — are *shown, not silently made*: lay out the realistic alternatives you weighed and why you lean one way, then let the user choose. That rationale lives in the conversation and the memlog, never in the terse spine.
Elicit, don't quiz: open-ended "how are you thinking about X?" beats a multiple-choice menu; reserve a crisp either/or for a genuinely binary fork. On the Fast path, inferring and tagging *is* the job. Elicit, don't quiz: open-ended "how are you thinking about X?" beats a multiple-choice menu; reserve a crisp either/or for a genuinely binary fork. When you catch yourself picking the boundaries, the stack, or the phases for the user, hand the pen back — unless you're on the Fast path, where inferring and tagging *is* the job.
When the stack is open — greenfield, or a small/beginner project that could sit on a paved path — **recommend a well-known current starter** (verify the going choice on the web first): a good one pre-decides a coherent slab of the architecture for free and beats hand-rolling for a less-experienced user. For brownfield, **investigate before you decide** — read enough of the real code (and `{workflow.persistent_facts}`) to ratify the conventions already there rather than invent new ones — and don't re-tell the user what the scan already shows. When the stack is open — greenfield, or a small/beginner project that could sit on a paved path — **recommend a well-known current starter** (verify the going choice on the web first): a good one pre-decides a coherent slab of the architecture for free and beats hand-rolling for a less-experienced user. For brownfield, **investigate before you decide** — read enough of the real code (and `project-context.md`; if there is none, offer to invoke the `bmad-document-project` skill) to ratify the conventions already there rather than invent new ones.
## Read the input to know the job ## Read the input to know the job
The input itself tells you what kind of job this is — read it rather than quizzing the user about it. A spec package (`SPEC.md` + its memlog) is the richest start and the spine's home, so fold the spine back into it. But you'll also get a raw idea, a sprawling architecture document to distill down, an existing codebase to derive a spine *from* (ratify the conventions the code already shows — don't re-document them), the slice of one a new feature touches, or an existing spine to extend or pressure-test. Prefer a `.memlog.md` over re-reading the source it came from. Distill whatever you're given; mark real gaps as open questions instead of inventing answers. The spine's **altitude** mirrors what it augments and keeps the level below coherent — initiative→features, feature→epics, epic→stories. Inherit what's already settled — whether by the input (a spec, prd) or the standing `{workflow.persistent_facts}` — silently; don't re-decide or re-ask it. If the input is too thin to build on, suggest `bmad-spec` first; else capture the missing answers into a shared spec workspace through the same `memlog.py`, so `bmad-spec` can later derive `SPEC.md` without drift. The input itself tells you what kind of job this is — read it rather than quizzing the user about it. A spec package (`SPEC.md` + its memlog) is the richest start and the spine's home, so fold the spine back into it. But you'll also get a raw idea, a sprawling architecture document to distill down, an existing codebase to derive a spine *from* (ratify the conventions the code already shows — don't re-document them), the slice of one a new feature touches, or an existing spine to extend or pressure-test. Prefer a `.memlog.md` over re-reading the source it came from. Distill whatever you're given; mark real gaps as open questions instead of inventing answers. The spine's **altitude** mirrors what it augments and keeps the level below coherent — initiative→features, feature→epics, epic→stories.
**Inheriting a parent spine** (e.g. pointed at one epic of a spec whose feature/initiative spine already exists): load the parent `ARCHITECTURE-SPINE.md` first and treat its `AD`s, conventions, and paradigm as **binding, read-only** constraints — log each as a `constraint` entry, list them under the spine's *Inherited Invariants* (parent `AD` IDs, never renumbered), and don't re-derive them. Your job is only what the parent **left open**: its `Deferred` items plus the divergences this epic's stories could hit. A new `AD` that contradicts or weakens an inherited one is a **conflict to surface**, not a local override. An epic spine fixes the invariants the epic's stories must share — it does **not** expand per-story detail. **Inheriting a parent spine** (e.g. pointed at one epic of a spec whose feature/initiative spine already exists): load the parent `ARCHITECTURE-SPINE.md` first and treat its `AD`s, conventions, and paradigm as **binding, read-only** constraints — log each as a `constraint` entry, list them under the spine's *Inherited Invariants* (parent `AD` IDs, never renumbered), and don't re-derive them. Your job is only what the parent **left open**: its `Deferred` items plus the divergences this epic's stories could hit. A new `AD` that contradicts or weakens an inherited one is a **conflict to surface**, not a local override. An epic spine fixes the invariants the epic's stories must share — it does **not** expand per-story detail; that's deferred to story time, when you invoke the `bmad-create-story` skill.
## How a run works ## How a run works
The **memlog** (`.memlog.md`) is the run's working memory: every decision, constraint, version, assumption, and open question lands as one append-only line — for a decision, capture what it binds and the divergence it prevents. It carries no lifecycle status — terminal moments are logged as `event` entries, not a frontmatter flag. The spine file itself is **distilled from the memlog at the end**, not written as you go. Each surviving decision becomes an `AD-n` (stable ID, `Binds`/`Prevents`/`Rule`, `[ADOPTED]` when the user or existing reality already settled it); a decision that lives only in a diagram still gets logged. Resume a prior run by reloading its memlog. The **memlog** (`.memlog.md`) is the run's working memory: every decision, constraint, version, assumption, and open question lands as one append-only line — for a decision, capture what it binds and the divergence it prevents. It is the shared canonical memlog (the same `{project-root}/_bmad/scripts/memlog.py` bmad-spec writes through), so it carries no lifecycle status — terminal moments are logged as `event` entries, not a frontmatter flag. The spine is **distilled from the memlog at the end**, not written as you go. Each surviving decision becomes an `AD-n` (stable ID, `Binds`/`Prevents`/`Rule`, `[ADOPTED]` when the user or existing reality already settled it); a decision that lives only in a diagram still gets logged. Resume a prior run by reloading its memlog.
Writes go through the shared script (don't read the file back except on resume): Writes go through the shared script (don't read the file back except on resume):
- `uv run {project-root}/_bmad/scripts/memlog.py init --workspace {doc_workspace} --field scope="…" --field purpose="…" --field altitude="…"` - `python3 {project-root}/_bmad/scripts/memlog.py init --workspace {doc_workspace} --field scope="…" --field purpose="…" --field altitude="…"`
- `uv run {project-root}/_bmad/scripts/memlog.py append --workspace {doc_workspace} --type <decision|constraint|version|assumption|question|direction|event> --text "…"` - `python3 {project-root}/_bmad/scripts/memlog.py append --workspace {doc_workspace} --type <decision|constraint|version|assumption|question|direction|event> --text "…"`
- A terminal moment (spine finalized, a validation verdict) is an `append --type event` entry — there is no status field to set.
## Resolution rules ## Resolution rules
@ -48,9 +49,9 @@ Writes go through the shared script (don't read the file back except on resume):
## On Activation ## On Activation
**Forwarded activation:** if a caller (e.g. the `bmad-create-architecture` shim) invoked you with a stated intent and pre-resolved customization fields, honor them verbatim — skip your own intent inference, use the supplied values for those named fields, and resolve only the remaining fields from your own `customize.toml`. **Forwarded activation:** if a caller (e.g. the `bmad-create-architecture` shim) invoked you with a stated intent and pre-resolved customization fields, honor them verbatim — skip your own intent inference, use the supplied values for those named fields, and resolve only the remaining fields from your own `customize.toml`. So a legacy per-project override still reaches the run.
1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow` (on failure read `{skill-root}/customize.toml`, use defaults). Run `{workflow.activation_steps_prepend}`, then `{workflow.activation_steps_append}`. Hold `{workflow.persistent_facts}` as standing context — the default loads `project-context.md`, load-bearing for brownfield — and consult `{workflow.external_sources}` on demand. 1. Resolve customization: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow` (on failure read `{skill-root}/customize.toml`, use defaults). Run `{workflow.activation_steps_prepend}`, then `{workflow.activation_steps_append}`. Hold `{workflow.persistent_facts}` as standing context — the default loads `project-context.md`, load-bearing for brownfield — and consult `{workflow.external_sources}` on demand.
2. Load `{project-root}/_bmad/bmm/config.yaml` (+ `config.user.yaml`) for `{user_name}`, `{communication_language}`, `{document_output_language}`, `{planning_artifacts}`, `{project_name}`, `{date}`; missing keys take neutral defaults, never block. 2. Load `{project-root}/_bmad/bmm/config.yaml` (+ `config.user.yaml`) for `{user_name}`, `{communication_language}`, `{document_output_language}`, `{planning_artifacts}`, `{project_name}`, `{date}`; missing keys take neutral defaults, never block.
3. Headless (no interactive user) → follow `references/headless.md` for the whole run. Otherwise greet `{user_name}` in `{communication_language}`. Detect the intent from the conversation and input — **create** (the default), **update** an existing spine, or **validate** one (see those sections). If the real ask is requirements / UX / a capability contract / epic breakdown / an agent, invoke the `bmad-prd`, `bmad-ux`, `bmad-spec`, `bmad-create-epics-and-stories`, or `bmad-workflow-builder` (if the BMad Builder module is installed) skill instead. 3. Headless (no interactive user) → follow `references/headless.md` for the whole run. Otherwise greet `{user_name}` in `{communication_language}`. Detect the intent from the conversation and input — **create** (the default), **update** an existing spine, or **validate** one (see those sections). If the real ask is requirements / UX / a capability contract / epic breakdown / an agent, invoke the `bmad-prd`, `bmad-ux`, `bmad-spec`, `bmad-create-epics-and-stories`, or `bmad-workflow-builder` (if the BMad Builder module is installed) skill instead.
4. If a run folder for this target already exists under `{workflow.spine_output_path}`, offer to resume from its memlog rather than restart. 4. If a run folder for this target already exists under `{workflow.spine_output_path}`, offer to resume from its memlog rather than restart.
@ -61,13 +62,13 @@ For a new spine, bind `{doc_workspace}` to `{workflow.spine_output_path}/{workfl
## Reviewer Gate ## Reviewer Gate
The spine's pre-handoff review — full mechanics in `references/reviewer-gate.md`. Load it when finalizing or validating: a deterministic `lint_spine.py` pass, then a rubric walker (good-spine checklist) + every `{workflow.finalize_reviewers}` lens dispatched as parallel subagents against `ARCHITECTURE-SPINE.md`, scaled to stakes. At Finalize you apply the clear fixes; under the Validate intent you deliver a bespoke HTML report and then get user input. The spine's pre-handoff review — full mechanics in `references/reviewer-gate.md`. Load it when finalizing or validating: a deterministic `lint_spine.py` pass, then a rubric walker (good-spine checklist) + every `{workflow.finalize_reviewers}` lens dispatched as parallel subagents against `ARCHITECTURE-SPINE.md`, scaled to stakes. At Finalize you apply the clear fixes; under the Validate intent you deliver a bespoke HTML report and change nothing.
## Finalize ## Finalize
Walk the sequence; reviewer fixes land before polish. Walk the sequence; reviewer fixes land before polish.
1. **Distill.** Write the spine from the memlog (brownfield: + the code sweep) — invariants first, seed minimal, every `AD` carrying Binds/Prevents/Rule, `Deferred` naming what it won't decide. No placeholders; never invent to fill a gap. The template's `<!-- -->` notes are guidance — act on them, then strip them; the finished spine carries no template comment, and only the diagrams that convey the structure (as many as the altitude needs, valid mermaid). Sweep the breadth the altitude owns — every structural dimension is decided, deferred, or an open question; a whole dimension left silent (e.g. the operational/environmental envelope: deployment & environments, infra/provider strategy, operations) is the failure, not a clean spine. A long coaching run distills cleaner in a subagent; the parent falls back inline. 1. **Distill.** Write the spine from the memlog (brownfield: + the code sweep) — invariants first, seed minimal, every `AD` carrying Binds/Prevents/Rule, `Deferred` naming what it won't decide. No placeholders; never invent to fill a gap. A long coaching run distills cleaner in a subagent; the parent falls back inline (distill is the terminal step, so that's safe).
2. **Reconcile inputs.** A subagent per load-bearing input checks it against the spine and returns what didn't land — especially a quiet requirement (a tone, a constraint) the `AD` structure dropped. Before the gate. 2. **Reconcile inputs.** A subagent per load-bearing input checks it against the spine and returns what didn't land — especially a quiet requirement (a tone, a constraint) the `AD` structure dropped. Before the gate.
3. **Reviewer pass.** Run the Reviewer Gate (`references/reviewer-gate.md`). Resolve before polish. 3. **Reviewer pass.** Run the Reviewer Gate (`references/reviewer-gate.md`). Resolve before polish.
4. **Triage.** Open questions and `[ASSUMPTION]` tags: blockers (unsafe for what's next) resolved one at a time; the rest deferred with a revisit condition in the memlog. 4. **Triage.** Open questions and `[ASSUMPTION]` tags: blockers (unsafe for what's next) resolved one at a time; the rest deferred with a revisit condition in the memlog.
@ -78,7 +79,7 @@ Walk the sequence; reviewer fixes land before polish.
## Update ## Update
Amend an existing spine or provided artifact. Resume from its `.memlog.md` (the authority on what was decided), not the rendered spine. Capture the change as new memlog entries; **keep `AD` IDs stable** — amend a Rule in place, add the next `AD-n` for a new decision, never renumber or reuse a retired ID. Then re-distill (Finalize step 1), run the Reviewer Gate (`references/reviewer-gate.md`), and close as in Finalize. An update that overrides something from a source input: offer to update that source too, so upstream and the spine don't silently diverge. Amend an existing spine. Resume from its `.memlog.md` (the authority on what was decided), not the rendered spine. Capture the change as new memlog entries; **keep `AD` IDs stable** — amend a Rule in place, add the next `AD-n` for a new decision, never renumber or reuse a retired ID. Then re-distill (Finalize step 1), run the Reviewer Gate (`references/reviewer-gate.md`), and close as in Finalize. An update that overrides something from a source input: offer to update that source too, so upstream and the spine don't silently diverge.
## Validate ## Validate

View File

@ -8,58 +8,106 @@ scope: '{what this spine governs}'
status: draft # draft · final status: draft # draft · final
created: '{date}' created: '{date}'
updated: '{date}' updated: '{date}'
binds: [] # capability / unit IDs governed (from the driving spec; at epic altitude, also the inherited parent AD ids) stack: # SEED — verified current at authoring; the code owns this once it exists
languages: []
frameworks: []
key_deps: [] # name@version
binds: [] # capability / unit IDs governed (from the driving spec; at epic altitude, also the parent AD IDs inherited)
sources: [] sources: []
companions: [] companions: []
--- ---
# Architecture Spine — {name} # Architecture Spine — {name}
<!-- TEMPLATE GUIDE — act on these comments, then delete them; never emit a comment in the finished spine. This is a shape, not a script: keep only the sections this spine needs and cut the rest (no empty headers). A small intent may be just paradigm + a few ADs + conventions; a platform earns more. An inherited epic spine is usually mostly Inherited Invariants + a thin Deferred. Decisions, not rationale (rationale lives in the memlog). Carry shape in diagrams; prose only where it must. --> > A consistency contract, not a design document. It fixes the **invariants** that keep the
> independently-built level below ({features | epics | stories}) coherent — the durable rules a
> clean codebase can't reveal. Structure is **seed**: the code owns the detail, the spine keeps the shape.
> Decisions, not rationale (that lives in the memlog). Diagrams over prose.
>
> **Scale to the job — drop any section a project doesn't need.** A small intent may be just a
> paradigm + a few `AD`s + conventions, seed omitted; a platform earns the full set. An inherited
> epic spine is usually mostly Inherited Invariants + a thin Deferred. Empty sections are cut, not left as headers.
## Design Paradigm ## Design Paradigm
<!-- Name the pattern (a known one loads a whole model for free) and map its layers to namespaces/directories. The smallest, most durable thing here. --> Name the pattern — a known one loads a whole model for free — and map its layers to namespaces /
directories. The smallest, most durable thing in the file.
## Inherited Invariants ## Inherited Invariants
<!-- Only when this spine inherits a higher-altitude parent. The parent's ADs/conventions/paradigm that bind here, by their ORIGINAL ids — read-only, never renumbered, not re-derived. A local decision that contradicts one is a conflict to surface, not an override. Cut this section otherwise. --> Present only when this spine inherits a parent at a higher altitude (e.g. an epic spine under a
feature/initiative spine). The parent's `AD`s, conventions, and paradigm that bind here, listed by
their original parent IDs — **read-only, never renumbered, not re-derived**. This spine adds only
what the parent left open; anything here that a local decision would contradict is a conflict to
surface, not override.
| Inherited | From parent | Binds here | | Inherited | From parent | Binds here |
| --- | --- | --- | | --- | --- | --- |
| {AD-id / convention} | {parent spine} | {what it constrains in this scope} | | {AD-n / convention} | {parent spine} | {what it constrains in this scope} |
## Invariants & Rules ## Invariants & Rules
<!-- The durable heart: calls a future builder can't read off compliant code. One block per decision: stable ascending id (never reused/renumbered), Binds, Prevents (the divergence), Rule (enforceable). Tag [ADOPTED] when the user or existing reality settled it. Include a dependency-direction diagram (who may depend on whom) — it IS a rule; author it as valid mermaid, never an empty graph. --> The durable heart: the calls a future builder can't read from compliant code. Each `AD-n` has a
stable ID (never reused), a binding scope, the divergence it prevents, and an enforceable rule.
Cover the boundary/dependency rules (who may depend on whom) and how state is mutated — a
dependency-direction diagram says these better than prose. An `AD-n` the user asserted as
already-settled (or one verified from existing reality) carries an `[ADOPTED]` tag after its
title, so its provenance is legible versus decisions made here.
```mermaid
flowchart LR
%% arrows = allowed dependency direction (a rule, not just structure)
```
### AD-1 — {decision} ### AD-1 — {decision}
- **Binds:** {capability / unit ids / fr/nfr's, areas, or `all`} - **Binds:** {capability / unit IDs, areas, or `all`}
- **Prevents:** {the divergence this stops} - **Prevents:** {the divergence this stops}
- **Rule:** {the constraint downstream must follow} - **Rule:** {the constraint downstream must follow}
## Consistency Conventions ## Consistency Conventions
<!-- Defaults that bind where independent builders would drift. Cut rows that don't apply; add rows the project needs. --> The defaults that bind everything where independent builders would otherwise drift. Cut rows that
don't apply.
| Concern | Convention | | Concern | Convention |
| --- | --- | | --- | --- |
| Naming (entities, files, interfaces, events) | | | Naming (entities, files, interfaces, events) | |
| Data & formats (ids, dates, error shapes, envelopes) | | | Data & formats (IDs, dates, error shapes, envelopes) | |
| State & cross-cutting (mutation, errors, logging, config, auth) | | | State & cross-cutting (mutation, errors, logging, config, auth) | |
## Stack
<!-- SEED — verified current at authoring; the code owns this once it exists. Name + version only; the why lives in the memlog. One row per language, framework, key dependency, platform, or chain that's pinned. -->
| Name | Version |
| --- | --- |
| {language / framework / key dep / platform / chain} | {pinned version} |
## Structural Seed ## Structural Seed
<!-- The shapes worth fixing at cold-start — not a fixed list. Include only what's non-obvious at this altitude, and use as many diagrams as convey it, each as VALID mermaid (never a placeholder or empty graph). Candidates: system/container/context view; DEPLOYMENT & ENVIRONMENTS and external provider/infra topology (cover the operational envelope here when this altitude owns it — don't let it fall through); core-entity ERD (names + relationships only; an attribute that's itself an invariant is an AD, not a diagram); a minimal source tree. The code owns the detail — this is scaffold, not a mirror to maintain. --> Cold-start scaffolding, kept minimal — include an item only where its shape is non-obvious at this
altitude (at epic altitude the parent usually already fixed it, so the seed is often empty). The code
owns the **detail** (every file, every column); once code exists it becomes the source of truth for
detail, and this seed is a starting scaffold, not a mirror to maintain against it. Evolve a seed item
only when the **shape** itself changes — a new container, a new core entity, a stack bump — and let
the memlog keep the history.
- **Stack & Versions** — the substrate (mirrors frontmatter `stack`).
- **System Shape** — a container/context view (at epic altitude, the slice of the parent system this scope touches). Use `flowchart` with a `subgraph` per boundary; C4 mermaid is experimental and won't render in most viewers.
- **Core Entities** — an ERD of entities and their relationships. Names and relationships only; attributes belong to the code unless one is itself an invariant (then it's an `AD`, not seed).
- **Project Structure** — a minimal source tree, only as deep as consistency needs.
```mermaid
flowchart TD
user(["{actor}"])
subgraph sys["{system boundary}"]
a["{container}<br/>{tech} — {role}"]
end
db[("{datastore}")]
ext["{external system}"]
user --> a
a --> db
a -->|{via port}| ext
```
```mermaid
erDiagram
ENTITY_A ||--o{ ENTITY_B : "{relationship}"
ENTITY_B ||--o| ENTITY_C : "{relationship}"
```
```text ```text
{root}/ {root}/
@ -68,12 +116,14 @@ companions: []
## Capability → Architecture Map ## Capability → Architecture Map
<!-- Present when a spec drove this run. Bridges the spec's capabilities to where they live + what governs them; the consistency auditor's checklist. Cut otherwise. --> Bridges the spec's capabilities to the architecture (and is the consistency auditor's checklist).
Present when a spec drove this run.
| Capability / Area | Lives in | Governed by | | Capability / Area | Lives in | Governed by |
| --- | --- | --- | | --- | --- | --- |
| {CAP-id / area} | {component / module} | {AD-id, convention, paradigm} | | {CAP-n / area} | {component / module} | {AD-n, convention, paradigm} |
## Deferred ## Deferred
<!-- Decisions intentionally pushed down, each with the reason it can wait — including whole dimensions this altitude doesn't own yet. The half of the contract that keeps the spine lean. --> Decisions intentionally pushed down, each with the reason it can wait. The half of the contract
that keeps the spine lean.

View File

@ -2,12 +2,12 @@
The spine's pre-handoff review. Runs at Finalize (after distill + reconcile) and *is* the Validate intent. The difference is the ending: at Finalize you apply the clear fixes yourself; under Validate you report and don't change the spine. The spine's pre-handoff review. Runs at Finalize (after distill + reconcile) and *is* the Validate intent. The difference is the ending: at Finalize you apply the clear fixes yourself; under Validate you report and don't change the spine.
Cheap deterministic pass first: `uv run {skill-root}/scripts/lint_spine.py --workspace {doc_workspace}` settles the mechanical misses (placeholders, duplicate `AD` IDs, missing Binds/Prevents/Rule, unpinned Stack versions), so reviewers spend judgment on the semantic half. Cheap deterministic pass first: `python3 {skill-root}/scripts/lint_spine.py --workspace {doc_workspace}` settles the mechanical misses (placeholders, duplicate `AD` IDs, missing Binds/Prevents/Rule, unpinned deps), so reviewers spend judgment on the semantic half.
Assemble the menu: a **rubric walker** that judges the spine against the good-spine checklist below, **+ every entry in `{workflow.finalize_reviewers}`**, + ad-hoc lenses you invent or offer as the spine's rigor, altitude, and criticality warrant — a security/compliance lens for regulated stakes, a seam reviewer cross-team, a data-integrity lens for a heavy data model. Scale *whether and how heavily the gate runs* to the stakes: a throwaway prototype may run it quietly or skip the gate entirely; a high-criticality or platform-altitude spine earns more lenses and the explicit all / subset / skip menu. But once the gate runs, the `{workflow.finalize_reviewers}` always run — they are the configured floor, never cherry-picked out; only the ad-hoc lenses are optional. (Headless never skips the gate.) Assemble the menu: a **rubric walker** that judges the spine against the good-spine checklist below, **+ every entry in `{workflow.finalize_reviewers}`**, + ad-hoc lenses you invent or offer as the spine's rigor, altitude, and criticality warrant — a security/compliance lens for regulated stakes, a seam reviewer cross-team, a data-integrity lens for a heavy data model. Scale *whether and how heavily the gate runs* to the stakes: a throwaway prototype may run it quietly or skip the gate entirely; a high-criticality or platform-altitude spine earns more lenses and the explicit all / subset / skip menu. But once the gate runs, the `{workflow.finalize_reviewers}` always run — they are the configured floor, never cherry-picked out; only the ad-hoc lenses are optional. (Headless never skips the gate.)
Dispatch every entry as a **parallel subagent against `ARCHITECTURE-SPINE.md`** (prefix convention: `skill:` / `file:` / plain text). Each writes its full review to `{doc_workspace}/reviews/review-{slug}.md` — a subfolder, so the gate's scratch stays out of the deliverable folder — and returns ONLY a compact summary (verdict, top 25 findings, file path) — the parent never holds full review text. An inline self-check does not count: the independent context is the point, because a fresh reviewer finds the divergences the author talks past. If subagents are unavailable, run sequentially — write the file first, then flush it from context. Dispatch every entry as a **parallel subagent against `ARCHITECTURE-SPINE.md`** (prefix convention: `skill:` / `file:` / plain text). Each writes its full review to `{doc_workspace}/review-{slug}.md` and returns ONLY a compact summary (verdict, top 25 findings, file path) — the parent never holds full review text. An inline self-check does not count: the independent context is the point, because a fresh reviewer finds the divergences the author talks past. If subagents are unavailable, run sequentially — write the file first, then flush it from context.
**Good-spine checklist** (what the rubric walker judges): it fixes the real divergence points for the level below and misses none; every `AD`'s Rule is enforceable and actually prevents its stated divergence; nothing under Deferred could let two units diverge; named tech is verified-current; it ratifies rather than contradicts a brownfield codebase; if a spec drove it, it covers that spec's capabilities; if a parent spine is inherited, no new `AD` weakens or contradicts an inherited one; and every dimension the altitude owns is decided, deferred, or an open question — a whole dimension left silent is a finding, especially the operational/environmental envelope (deployment & environments, infra/provider strategy, operations) a domain-focused draft skips. **Good-spine checklist** (what the rubric walker judges): it fixes the real divergence points for the level below and misses none; every `AD`'s Rule is enforceable and actually prevents its stated divergence; nothing under Deferred could let two units diverge; named tech is verified-current; it ratifies rather than contradicts a brownfield codebase; if a spec drove it, it covers that spec's capabilities; and if a parent spine is inherited, no new `AD` weakens or contradicts an inherited one.
Surface findings tiered, never dumped: a one-sentence gate verdict, then critical + high; medium/low roll into a tail ("plus N more in {file}"). Per finding: autofix, discuss, defer to Deferred / open items, or ignore. **At Finalize this is your own gate — apply the clear fixes rather than handing over a list; surface only what genuinely needs the user.** Under the **Validate intent**, fold every reviewer's output into one bespoke HTML + markdown report and open the HTML. Surface findings tiered, never dumped: a one-sentence gate verdict, then critical + high; medium/low roll into a tail ("plus N more in {file}"). Per finding: autofix, discuss, defer to Deferred / open items, or ignore. **At Finalize this is your own gate — apply the clear fixes rather than handing over a list; surface only what genuinely needs the user.** Under the **Validate intent**, fold every reviewer's output into one bespoke HTML + markdown report and open the HTML.

View File

@ -13,7 +13,7 @@ It reads ARCHITECTURE-SPINE.md from a workspace and reports, as compact JSON on
- placeholder literal TBD / TODO / "similar to AD-n" / unfilled {template-token} - placeholder literal TBD / TODO / "similar to AD-n" / unfilled {template-token}
- ad_id duplicate or non-monotonic AD-n identifiers - ad_id duplicate or non-monotonic AD-n identifiers
- ad_fields an AD-n block missing Binds / Prevents / Rule - ad_fields an AD-n block missing Binds / Prevents / Rule
- version_pin a ## Stack table row with no version - version_pin a frontmatter key_deps entry with no @version
Fenced code blocks are blanked (replaced with equal-count blank lines) before scanning, so Fenced code blocks are blanked (replaced with equal-count blank lines) before scanning, so
mermaid and source trees don't trip false positives AND reported line numbers still line up mermaid and source trees don't trip false positives AND reported line numbers still line up
@ -150,62 +150,65 @@ def find_ad_issues(body: str, offset: int) -> list[dict]:
return findings return findings
def find_unpinned_stack(body: str, offset: int) -> list[dict]: def find_unpinned_deps(frontmatter: str) -> list[dict]:
"""Flag a `## Stack` table row that names something but leaves its version blank or a
placeholder. Pinning lives in the body table now, not frontmatter. A row whose name is
still a `{token}` skeleton is left to the placeholder pass, not double-reported here.
Fences are blanked first (like find_placeholders / find_ad_issues), so a pipe-row or
heading inside a code block is never read as live Stack content. The heading match is
`## Stack` with a word boundary, so a renamed heading (`## Stack & Versions`) still
counts. Name and Version columns are located from the header row, so a reordered table
pairs name to version correctly; both default to the canonical positions (0, 1)."""
findings: list[dict] = [] findings: list[dict] = []
in_stack = False lines = frontmatter.splitlines()
header_seen = False in_key_deps = False
name_idx, ver_idx = 0, 1 key_indent = 0
scan = blank_fences(body) for raw in lines:
for i, raw in enumerate(scan.splitlines()): stripped = raw.strip()
if HEADING.match(raw): if not stripped or stripped.startswith("#"):
in_stack = re.match(r"^##\s+Stack\b", raw) is not None
header_seen = False
name_idx, ver_idx = 0, 1
continue continue
if not in_stack or not raw.lstrip().startswith("|"): indent = len(raw) - len(raw.lstrip())
m = re.match(r"key_deps:\s*(.*)$", stripped)
if m:
in_key_deps = True
key_indent = indent
inline = _strip_comment(m.group(1)).strip()
if inline and inline not in ("[]", "[ ]"):
# inline list form: key_deps: [a@1, b] — consumed here, no block follows
for item in re.findall(r"[^\[\],]+", inline.strip("[]")):
_check_dep(item.strip().strip("'\""), findings)
in_key_deps = False
continue continue
if set(raw.strip()) <= set("|-: "): if in_key_deps:
continue # separator row if indent <= key_indent and not stripped.startswith("-"):
cells = _table_cells(raw) in_key_deps = False
if not header_seen: continue
header_seen = True if stripped.startswith("-"):
for j, c in enumerate(cells): # block-sequence form: `- name@version`
if c.lower() == "name": _check_dep(_strip_comment(stripped[1:]).strip().strip("'\""), findings)
name_idx = j else:
elif c.lower() == "version": # map form: `name: version` — pinned iff a non-empty value is present
ver_idx = j mm = re.match(r"([^:]+):\s*(.*)$", stripped)
continue if mm:
name = cells[name_idx] if len(cells) > name_idx else "" name = mm.group(1).strip().strip("'\"")
version = cells[ver_idx] if len(cells) > ver_idx else "" val = _strip_comment(mm.group(2)).strip().strip("'\"")
if not name or TEMPLATE_TOKEN.search(name): if name and not val:
continue findings.append({
if not version or TEMPLATE_TOKEN.search(version): "category": "version_pin",
findings.append({ "severity": "medium",
"category": "version_pin", "detail": f"key_deps entry {name!r} has no version pin",
"severity": "medium", "location": f"{SPINE} frontmatter stack.key_deps",
"detail": f"Stack entry {name!r} has no version", })
"location": f"{SPINE} (line {offset + i + 1})",
})
return findings return findings
def _table_cells(row: str) -> list[str]: def _strip_comment(s: str) -> str:
"""Split a markdown table row into trimmed cells, dropping the leading/trailing pipe.""" """Drop a trailing YAML ` # comment`, leaving an inline `name@1.2` intact."""
s = row.strip() return re.sub(r"(^|\s)#.*$", "", s)
if s.startswith("|"):
s = s[1:]
if s.endswith("|"): def _check_dep(item: str, findings: list[dict]) -> None:
s = s[:-1] if not item or item.startswith("#"):
return [c.strip() for c in s.split("|")] return
if "@" not in item:
findings.append({
"category": "version_pin",
"severity": "medium",
"detail": f"key_deps entry {item!r} has no @version pin",
"location": f"{SPINE} frontmatter stack.key_deps",
})
def lint(text: str) -> dict: def lint(text: str) -> dict:
@ -214,7 +217,7 @@ def lint(text: str) -> dict:
findings += find_frontmatter_placeholders(frontmatter) findings += find_frontmatter_placeholders(frontmatter)
findings += find_placeholders(body, offset) findings += find_placeholders(body, offset)
findings += find_ad_issues(body, offset) findings += find_ad_issues(body, offset)
findings += find_unpinned_stack(body, offset) findings += find_unpinned_deps(frontmatter)
counts: dict[str, int] = {} counts: dict[str, int] = {}
for f in findings: for f in findings:
counts[f["severity"]] = counts.get(f["severity"], 0) + 1 counts[f["severity"]] = counts.get(f["severity"], 0) + 1

View File

@ -6,7 +6,7 @@
The spine under test: a clean spine lints empty; the linter catches exactly the The spine under test: a clean spine lints empty; the linter catches exactly the
mechanical defects a prompt is unreliable at literal placeholders, AD-n id breakage, mechanical defects a prompt is unreliable at literal placeholders, AD-n id breakage,
AD-n blocks missing required fields, and unpinned Stack versions. AD-n blocks missing required fields, and unpinned dependency versions.
""" """
import importlib.util import importlib.util
import json import json
@ -26,6 +26,10 @@ _SPEC.loader.exec_module(lint_spine)
CLEAN = """--- CLEAN = """---
name: 'Demo' name: 'Demo'
stack:
key_deps:
- fastapi@0.115
- pydantic@2.9
--- ---
## Invariants & Rules ## Invariants & Rules
@ -46,13 +50,6 @@ name: 'Demo'
flowchart LR flowchart LR
A --> B{decision} A --> B{decision}
``` ```
## Stack
| Name | Version |
| --- | --- |
| fastapi | 0.115 |
| pydantic | 2.9 |
""" """
@ -111,32 +108,30 @@ def test_missing_field_caught():
def test_unpinned_dep_caught(): def test_unpinned_dep_caught():
text = CLEAN.replace("| fastapi | 0.115 |", "| fastapi | |") text = CLEAN.replace("- fastapi@0.115", "- fastapi")
result = lint_spine.lint(text) result = lint_spine.lint(text)
assert "version_pin" in cats(result) assert "version_pin" in cats(result)
def test_placeholder_version_caught(): def test_inline_key_deps_unpinned():
text = CLEAN.replace("| fastapi | 0.115 |", "| fastapi | {pin} |") text = CLEAN.replace(" key_deps:\n - fastapi@0.115\n - pydantic@2.9", " key_deps: [fastapi, redis@7]")
result = lint_spine.lint(text) result = lint_spine.lint(text)
assert any(f["category"] == "version_pin" and "fastapi" in f["detail"] for f in result["findings"]) pins = [f for f in result["findings"] if f["category"] == "version_pin"]
assert len(pins) == 1 and "fastapi" in pins[0]["detail"]
def test_no_stack_section_ok(): def test_empty_key_deps_ok():
text = CLEAN.split("## Stack")[0] text = CLEAN.replace(" key_deps:\n - fastapi@0.115\n - pydantic@2.9", " key_deps: []")
result = lint_spine.lint(text) result = lint_spine.lint(text)
assert "version_pin" not in cats(result) assert "version_pin" not in cats(result)
def test_stack_skeleton_row_not_version_pinned(): def test_yaml_comments_not_parsed_as_deps():
# a leftover {token} name is the placeholder pass's job, not a double-reported version_pin # a SEED comment on the key_deps line must not read as an unpinned dependency
text = CLEAN.replace("| fastapi | 0.115 |", "| {language / framework} | {pinned version} |") text = CLEAN.replace(
result = lint_spine.lint(text) " key_deps:\n - fastapi@0.115\n - pydantic@2.9",
assert "version_pin" not in cats(result) " key_deps: # SEED — verified current 2026-06\n - fastapi@0.115 # web framework",
)
def test_stack_html_comment_not_parsed_as_row():
text = CLEAN.replace("## Stack\n", "## Stack\n\n<!-- SEED — verified current 2026-06 -->\n")
result = lint_spine.lint(text) result = lint_spine.lint(text)
assert "version_pin" not in cats(result) assert "version_pin" not in cats(result)
@ -158,8 +153,7 @@ def test_no_frontmatter_body_still_scanned():
def test_frontmatter_value_with_dashes_not_truncated(): def test_frontmatter_value_with_dashes_not_truncated():
# a value containing '---' must not be read as the closing fence (line-exact close) # a value containing '---' must not be read as the closing fence (line-exact close)
text = ("---\nname: 'x'\nscope: 'phase 1 --- phase 2'\n---\n\n" text = "---\nscope: 'phase 1 --- phase 2'\nstack:\n key_deps:\n - fastapi\n---\n\n## Invariants\n"
"## Stack\n\n| Name | Version |\n| --- | --- |\n| fastapi | |\n")
result = lint_spine.lint(text) result = lint_spine.lint(text)
assert any(f["category"] == "version_pin" for f in result["findings"]) # read past the inline --- assert any(f["category"] == "version_pin" for f in result["findings"]) # read past the inline ---
@ -174,55 +168,19 @@ def test_ad_heading_in_fence_not_counted():
assert result["ok"] is True # the fenced AD-2 is not a live AD → no ad_fields/ad_id finding assert result["ok"] is True # the fenced AD-2 is not a live AD → no ad_fields/ad_id finding
def test_stack_table_flags_only_the_unpinned_row(): def test_map_form_key_deps_unpinned_caught():
text = ("---\nname: 'x'\n---\n\n## Stack\n\n| Name | Version |\n| --- | --- |\n" text = "---\nstack:\n key_deps:\n fastapi: '0.115'\n redis:\n---\n\n## Invariants\n"
"| fastapi | 0.115 |\n| redis | |\n")
result = lint_spine.lint(text) result = lint_spine.lint(text)
pins = [f for f in result["findings"] if f["category"] == "version_pin"] pins = [f for f in result["findings"] if f["category"] == "version_pin"]
assert len(pins) == 1 and "redis" in pins[0]["detail"] assert len(pins) == 1 and "redis" in pins[0]["detail"]
def test_stack_table_all_pinned_ok(): def test_map_form_key_deps_pinned_ok():
text = ("---\nname: 'x'\n---\n\n## Stack\n\n| Name | Version |\n| --- | --- |\n" text = "---\nstack:\n key_deps:\n fastapi: '0.115'\n---\n\n## Invariants\n"
"| fastapi | 0.115 |\n")
result = lint_spine.lint(text) result = lint_spine.lint(text)
assert "version_pin" not in cats(result) assert "version_pin" not in cats(result)
def test_fenced_stack_rows_not_parsed():
# an illustrative fenced table under ## Stack must not be read as live rows (fences are
# blanked first, like every other pass) — a blank-version row inside a fence is not a finding
text = ("---\nname: 'x'\n---\n\n## Stack\n\n| Name | Version |\n| --- | --- |\n"
"| fastapi | 0.115 |\n\n```text\n| example | |\n```\n")
result = lint_spine.lint(text)
assert "version_pin" not in cats(result)
def test_fenced_stack_heading_not_live():
# a `## Stack` heading shown inside a code fence is not the live Stack section
text = ("---\nname: 'x'\n---\n\n## Docs\n\n```md\n## Stack\n\n| foo | |\n```\n")
result = lint_spine.lint(text)
assert "version_pin" not in cats(result)
def test_renamed_stack_heading_still_scanned():
# the heading match is word-boundary, so a varied `## Stack` heading still counts
text = ("---\nname: 'x'\n---\n\n## Stack & Versions\n\n| Name | Version |\n| --- | --- |\n"
"| redis | |\n")
result = lint_spine.lint(text)
pins = [f for f in result["findings"] if f["category"] == "version_pin"]
assert len(pins) == 1 and "redis" in pins[0]["detail"]
def test_reordered_columns_pair_name_to_version():
# Version-then-Name header: the unpinned row must still be flagged by its real name
text = ("---\nname: 'x'\n---\n\n## Stack\n\n| Version | Name |\n| --- | --- |\n"
"| 0.115 | fastapi |\n| | redis |\n")
result = lint_spine.lint(text)
pins = [f for f in result["findings"] if f["category"] == "version_pin"]
assert len(pins) == 1 and "redis" in pins[0]["detail"]
def test_placeholder_line_number_is_absolute(): def test_placeholder_line_number_is_absolute():
# a TBD after a multi-line fence reports its real file line (fence blanked, not collapsed) # a TBD after a multi-line fence reports its real file line (fence blanked, not collapsed)
text = ( text = (

View File

@ -350,7 +350,6 @@ Amelia (Developer): "I found our retrospectives from Epic {{prev_epic_num}}. Let
**Action Item Follow-Through:** **Action Item Follow-Through:**
- For each action item from Epic {{prev_epic_num}} retro, check if it was completed - For each action item from Epic {{prev_epic_num}} retro, check if it was completed
- Cross-check the action_items section in {sprint_status_file} (if present) for Epic {{prev_epic_num}} entries and their current status
- Look for evidence in current epic's story records - Look for evidence in current epic's story records
- Mark each action item: ✅ Completed, ⏳ In Progress, ❌ Not Addressed - Mark each action item: ✅ Completed, ⏳ In Progress, ❌ Not Addressed
@ -1404,19 +1403,6 @@ Amelia (Developer): "See you all when prep work is done. Meeting adjourned!"
<action>Find development_status key "epic-{{epic_number}}-retrospective"</action> <action>Find development_status key "epic-{{epic_number}}-retrospective"</action>
<action>Verify current status (typically "optional" or "pending")</action> <action>Verify current status (typically "optional" or "pending")</action>
<action>Update development_status["epic-{{epic_number}}-retrospective"] = "done"</action> <action>Update development_status["epic-{{epic_number}}-retrospective"] = "done"</action>
<action>Append each Epic {{epic_number}} action item to the action_items section, creating the section after development_status if missing. One entry per item:</action>
```yaml
action_items:
- epic: {{epic_number}}
action: "{{action_description}}"
owner: "{{owner}}"
status: open
```
<action>Quote action and owner values so punctuation (e.g., "#") cannot break YAML parsing</action>
<action>Update Epic {{prev_epic_num}} action_items entries based on Step 4 follow-through: ✅ Completed → done, ⏳ In Progress → in-progress, ❌ Not Addressed → keep existing status (do not modify)</action>
<action>Update last_updated field to current date</action> <action>Update last_updated field to current date</action>
<action>Save file, preserving ALL comments and structure including STATUS DEFINITIONS</action> <action>Save file, preserving ALL comments and structure including STATUS DEFINITIONS</action>
@ -1426,7 +1412,6 @@ action_items:
Retrospective key: epic-{{epic_number}}-retrospective Retrospective key: epic-{{epic_number}}-retrospective
Status: {{previous_status}} → done Status: {{previous_status}} → done
Action items recorded: {{action_count}}
</output> </output>
</check> </check>

View File

@ -151,7 +151,6 @@ development_status:
- If existing `{status_file}` exists and has more advanced status, preserve it - If existing `{status_file}` exists and has more advanced status, preserve it
- Never downgrade status (e.g., don't change `done` to `ready-for-dev`) - Never downgrade status (e.g., don't change `done` to `ready-for-dev`)
- If existing `{status_file}` has an `action_items` section, carry it over unchanged
**Status Flow Reference:** **Status Flow Reference:**
@ -195,18 +194,12 @@ development_status:
# - optional: Can be completed but not required # - optional: Can be completed but not required
# - done: Retrospective has been completed # - done: Retrospective has been completed
# #
# Action Item Status:
# - open: Committed during a retrospective, not yet addressed
# - in-progress: Actively being worked on
# - done: Completed
#
# WORKFLOW NOTES: # WORKFLOW NOTES:
# =============== # ===============
# - Epic transitions to 'in-progress' automatically when first story is created # - Epic transitions to 'in-progress' automatically when first story is created
# - Stories can be worked in parallel if team capacity allows # - Stories can be worked in parallel if team capacity allows
# - Developer typically creates next story after previous one is 'done' to incorporate learnings # - Developer typically creates next story after previous one is 'done' to incorporate learnings
# - Dev moves story to 'review', then runs code-review (fresh context, different LLM recommended) # - Dev moves story to 'review', then runs code-review (fresh context, different LLM recommended)
# - Retrospective appends its action items to action_items; sprint-status surfaces open ones
generated: { date } generated: { date }
last_updated: { date } last_updated: { date }
@ -222,7 +215,6 @@ development_status:
<action>Write the complete sprint status YAML to {status_file}</action> <action>Write the complete sprint status YAML to {status_file}</action>
<action>CRITICAL: Metadata appears TWICE - once as comments (#) for documentation, once as YAML key:value fields for parsing</action> <action>CRITICAL: Metadata appears TWICE - once as comments (#) for documentation, once as YAML key:value fields for parsing</action>
<action>Ensure all items are ordered: epic, its stories, its retrospective, next epic...</action> <action>Ensure all items are ordered: epic, its stories, its retrospective, next epic...</action>
<action>If the existing file had an action_items section, write it back unchanged after development_status</action>
</step> </step>
<step n="5" goal="Validate and report"> <step n="5" goal="Validate and report">
@ -231,8 +223,7 @@ development_status:
- [ ] Every epic in epic files appears in {status_file} - [ ] Every epic in epic files appears in {status_file}
- [ ] Every story in epic files appears in {status_file} - [ ] Every story in epic files appears in {status_file}
- [ ] Every epic has a corresponding retrospective entry - [ ] Every epic has a corresponding retrospective entry
- [ ] No development_status items in {status_file} that don't exist in epic files - [ ] No items in {status_file} that don't exist in epic files
- [ ] action_items section (if it existed) carried over unchanged
- [ ] All status values are legal (match state machine definitions) - [ ] All status values are legal (match state machine definitions)
- [ ] File is valid YAML syntax - [ ] File is valid YAML syntax
@ -300,16 +291,6 @@ optional ↔ done
- **optional**: Ready to be conducted but not required - **optional**: Ready to be conducted but not required
- **done**: Finished - **done**: Finished
**Action Item Status:**
```
open → in-progress → done
```
- **open**: Committed during a retrospective, not yet addressed
- **in-progress**: Actively being worked on
- **done**: Completed
### Guidelines ### Guidelines
1. **Epic Activation**: Mark epic as `in-progress` when starting work on its first story 1. **Epic Activation**: Mark epic as `in-progress` when starting work on its first story

View File

@ -7,8 +7,7 @@
- [ ] Every epic found in epic\*.md files appears in sprint-status.yaml - [ ] Every epic found in epic\*.md files appears in sprint-status.yaml
- [ ] Every story found in epic\*.md files appears in sprint-status.yaml - [ ] Every story found in epic\*.md files appears in sprint-status.yaml
- [ ] Every epic has a corresponding retrospective entry - [ ] Every epic has a corresponding retrospective entry
- [ ] No development_status items in sprint-status.yaml that don't exist in epic files - [ ] No items in sprint-status.yaml that don't exist in epic files
- [ ] action_items section (if it existed) carried over unchanged
### Parsing Verification ### Parsing Verification

View File

@ -26,17 +26,11 @@
# - optional: Can be completed but not required # - optional: Can be completed but not required
# - done: Retrospective has been completed # - done: Retrospective has been completed
# #
# Action Item Status:
# - open: Committed during a retrospective, not yet addressed
# - in-progress: Actively being worked on
# - done: Completed
#
# WORKFLOW NOTES: # WORKFLOW NOTES:
# =============== # ===============
# - Mark epic as 'in-progress' when starting work on its first story # - Mark epic as 'in-progress' when starting work on its first story
# - Developer typically creates next story ONLY after previous one is 'done' to incorporate learnings # - Developer typically creates next story ONLY after previous one is 'done' to incorporate learnings
# - Dev moves story to 'review', then Dev runs code-review (fresh context, ideally different LLM) # - Dev moves story to 'review', then Dev runs code-review (fresh context, ideally different LLM)
# - Retrospective appends its action items to action_items; sprint-status surfaces open ones
# EXAMPLE STRUCTURE (your actual epics/stories will replace these): # EXAMPLE STRUCTURE (your actual epics/stories will replace these):
@ -60,10 +54,3 @@ development_status:
2-2-chat-interface: backlog 2-2-chat-interface: backlog
2-3-llm-integration: backlog 2-3-llm-integration: backlog
epic-2-retrospective: optional epic-2-retrospective: optional
# Action items committed during retrospectives (section created by the retrospective workflow)
action_items:
- epic: 1
action: "Add error-handling review to the code review checklist"
owner: "Charlie"
status: open

View File

@ -112,14 +112,12 @@ Run `/bmad:bmm:workflows:sprint-planning` to generate it, then rerun sprint-stat
<action>Map legacy epic status "contexted" → "in-progress"</action> <action>Map legacy epic status "contexted" → "in-progress"</action>
<action>Count epic statuses: backlog, in-progress, done</action> <action>Count epic statuses: backlog, in-progress, done</action>
<action>Count retrospective statuses: optional, done</action> <action>Count retrospective statuses: optional, done</action>
<action>Parse action_items list if present. Set open_action_items = entries with status "open" or "in-progress"</action>
<action>Validate all statuses against known values:</action> <action>Validate all statuses against known values:</action>
- Valid story statuses: backlog, ready-for-dev, in-progress, review, done, drafted (legacy) - Valid story statuses: backlog, ready-for-dev, in-progress, review, done, drafted (legacy)
- Valid epic statuses: backlog, in-progress, done, contexted (legacy) - Valid epic statuses: backlog, in-progress, done, contexted (legacy)
- Valid retrospective statuses: optional, done - Valid retrospective statuses: optional, done
- Valid action item statuses: open, in-progress, done
<check if="any status is unrecognized"> <check if="any status is unrecognized">
<output> <output>
@ -134,7 +132,6 @@ Run `/bmad:bmm:workflows:sprint-planning` to generate it, then rerun sprint-stat
- Stories: backlog, ready-for-dev, in-progress, review, done - Stories: backlog, ready-for-dev, in-progress, review, done
- Epics: backlog, in-progress, done - Epics: backlog, in-progress, done
- Retrospectives: optional, done - Retrospectives: optional, done
- Action items: open, in-progress, done
</output> </output>
<ask>How should these be corrected? <ask>How should these be corrected?
{{#each invalid_entries}} {{#each invalid_entries}}
@ -184,14 +181,6 @@ Enter corrections (e.g., "1=in-progress, 2=backlog") or "skip" to continue witho
**Next Recommendation:** /bmad:bmm:workflows:{{next_workflow_id}} ({{next_story_id}}) **Next Recommendation:** /bmad:bmm:workflows:{{next_workflow_id}} ({{next_story_id}})
{{#if open_action_items}}
**Open Action Items:**
{{#each open_action_items}}
- {{action}} — {{status}} (epic {{epic}}, owner: {{owner}})
{{/each}}
{{/if}}
{{#if risks}} {{#if risks}}
**Risks:** **Risks:**
{{#each risks}} {{#each risks}}
@ -254,7 +243,6 @@ If the command targets a story, set `story_key={{next_story_id}}` when prompted.
<template-output>epic_backlog = {{epic_backlog}}</template-output> <template-output>epic_backlog = {{epic_backlog}}</template-output>
<template-output>epic_in_progress = {{epic_in_progress}}</template-output> <template-output>epic_in_progress = {{epic_in_progress}}</template-output>
<template-output>epic_done = {{epic_done}}</template-output> <template-output>epic_done = {{epic_done}}</template-output>
<template-output>open_action_items = {{open_action_items}}</template-output>
<template-output>risks = {{risks}}</template-output> <template-output>risks = {{risks}}</template-output>
<action>Return to caller</action> <action>Return to caller</action>
</step> </step>
@ -295,7 +283,6 @@ If the command targets a story, set `story_key={{next_story_id}}` when prompted.
- Stories: backlog, ready-for-dev, in-progress, review, done (legacy: drafted) - Stories: backlog, ready-for-dev, in-progress, review, done (legacy: drafted)
- Epics: backlog, in-progress, done (legacy: contexted) - Epics: backlog, in-progress, done (legacy: contexted)
- Retrospectives: optional, done - Retrospectives: optional, done
- Action items (if present): open, in-progress, done
<check if="any invalid status found"> <check if="any invalid status found">
<template-output>is_valid = false</template-output> <template-output>is_valid = false</template-output>
<template-output>error = "Invalid status values: {{invalid_entries}}"</template-output> <template-output>error = "Invalid status values: {{invalid_entries}}"</template-output>

View File

@ -18,7 +18,7 @@ The session runs in one of three stances, chosen by the user — set explicitly
## On Activation ## On Activation
1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, use a subagent to read `{skill-root}/customize.toml` directly with defaults. 1. Resolve customization: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, use a subagent to read `{skill-root}/customize.toml` directly with defaults.
2. Run each `{workflow.activation_steps_prepend}` entry. Treat each `{workflow.persistent_facts}` entry as foundational context (`file:`-prefixed entries are paths/globs under `{project-root}` — load their contents; others are facts verbatim). 2. Run each `{workflow.activation_steps_prepend}` entry. Treat each `{workflow.persistent_facts}` entry as foundational context (`file:`-prefixed entries are paths/globs under `{project-root}` — load their contents; others are facts verbatim).
3. Load `{project-root}/_bmad/core/config.yaml` (and `config.user.yaml` if present); resolve `{user_name}`, `{communication_language}`, `{document_output_language}`, `{output_folder}`, `{project_name}`, `{date}`. Missing → neutral defaults; never block. 3. Load `{project-root}/_bmad/core/config.yaml` (and `config.user.yaml` if present); resolve `{user_name}`, `{communication_language}`, `{document_output_language}`, `{output_folder}`, `{project_name}`, `{date}`. Missing → neutral defaults; never block.
4. **If launched headless** (a machine signal, not a human asking for output — `references/headless.md` lists them): load `references/headless.md` and follow it for the whole run. It is the *only* context where you generate ideas yourself; never load it otherwise. 4. **If launched headless** (a machine signal, not a human asking for output — `references/headless.md` lists them): load `references/headless.md` and follow it for the whole run. It is the *only* context where you generate ideas yourself; never load it otherwise.
@ -34,19 +34,21 @@ These fight your defaults, in every mode; hold them deliberately. The stance you
- **Keep shifting the creative domain** — every 510 turns (or ~10 ideas when you're generating), usually by moving to the next technique. - **Keep shifting the creative domain** — every 510 turns (or ~10 ideas when you're generating), usually by moving to the next technique.
- **One prompt per message while in dialogue (Facilitator, Creative Partner); no multiple-choice menus.** Don't stack questions into a wall or hand a menu that invites lazy picking — both pull the user out of generating. The only exceptions are the two up-front *process* choices (stance, and the technique flow): *how* to run is theirs to pick; *what* to ideate never is. - **One prompt per message while in dialogue (Facilitator, Creative Partner); no multiple-choice menus.** Don't stack questions into a wall or hand a menu that invites lazy picking — both pull the user out of generating. The only exceptions are the two up-front *process* choices (stance, and the technique flow): *how* to run is theirs to pick; *what* to ideate never is.
**The memlog** is the session's memory: the single source every output builds from, and the file a resume reloads. Whatever isn't in it is gone. Log every idea, decision, question, and bit of user direction — anything you'd regret losing if the window closed — one line each, the gist in the user's meaning, in time order; never edit or reorder. Skip your prompts and small talk. All writes to memlog are atomic and use the script `memlog.py` invoked as follows: **The memlog** is the session's memory: the single source every output builds from, and the file a resume reloads. Whatever isn't in it is gone. Log every idea, decision, question, and bit of user direction — anything you'd regret losing if the window closed — one line each, the gist in the user's meaning, in time order; never edit or reorder. Skip your prompts and small talk. All writes go through `scripts/memlog.py` (atomic; don't read it back mid-session — resume is the one exception):
- `uv run {project-root}/_bmad/scripts/memlog.py init --workspace {doc_workspace} --field topic="<topic>" --field goal="<goal>" --field mode="<facilitator|partner|autonomous>"` — create it once topic, goal, and stance are known. - `memlog.py init --workspace {doc_workspace} --field topic="<topic>" --field goal="<goal>" --field mode="<facilitator|partner|autonomous>"` — create it once topic, goal, and stance are known.
- `uv run {project-root}/_bmad/scripts/memlog.py append --workspace {doc_workspace} --type <kind> --text "<one-line gist>"` — log one entry. `--type``idea`/`insight`/`question`/`decision`/`direction`/`technique` (a switch: `--text "started <name>"`); omit for a plain note. Add `--by user`/`--by coach` to mark authorship — **required in Creative Partner mode** (renders `(idea by user)`); skip it otherwise. - `memlog.py append --workspace {doc_workspace} --type <kind> --text "<one-line gist>"` — log one entry. `--type``idea`/`insight`/`question`/`decision`/`direction`/`technique` (a switch: `--text "started <name>"`); omit for a plain note. Add `--by user`/`--by coach` to mark authorship — **required in Creative Partner mode** (renders `(idea by user)`); skip it otherwise.
- `uv run {project-root}/_bmad/scripts/memlog.py set --workspace {doc_workspace} --key status --value complete` — flip status at wrap-up. - `memlog.py set --workspace {doc_workspace} --key status --value complete` — flip status at wrap-up.
(Each is `python3 {skill-root}/scripts/memlog.py …`.)
## Run a Session ## Run a Session
Open with one compound question what are we brainstorming, and what's the goal or why behind it (along with asking if there are any inputs or special requests). The why shapes technique choice and synthesis (*kids' iPhone apps to build with your own kids* vs. *to win market share* point different ways). If the kickoff already made both clear, skip the question and confirm; read anything they point you to. Derive a kebab-case `{topic_slug}` and bind `{doc_workspace} = {workflow.output_dir}/{workflow.output_folder_name}/`. Open with one compound question **what are we brainstorming, and what's the goal or why behind it?** (plus any inputs or special requests). The why shapes technique choice and synthesis (*kids' iPhone apps to build with your own kids* vs. *to win market share* point different ways). If the kickoff already made both clear, skip the question and confirm; read anything they point you to. Derive a kebab-case `{topic_slug}` and bind `{doc_workspace} = {workflow.output_dir}/{workflow.output_folder_name}/`.
Now set the **stance** and the **technique batch** in one step — the composer page does both, so make it the default. Now set the **stance** and the **technique batch** in one step — the composer page does both, so make it the default.
**The composer page (primary).** The file is `{skill-root}/assets/brain-selector.html`. With a customized catalog (overridden `{workflow.brain_methods}` or any `{workflow.additional_techniques}`), regenerate it first: `uv run {skill-root}/scripts/brain.py --file {workflow.brain_methods} [--extra {doc_workspace}/extra-techniques.json] html --out {doc_workspace}/brain-selector.html` (pass `--extra`, a JSON list of `{category, technique_name, description}`, when there are additional techniques; the file is then `{doc_workspace}/brain-selector.html`). Try to open it (`open` / `xdg-open` / `start`), then say, in one message: *"It should open in your browser — compose your session, click **Copy prompt**, and paste the result back. If it didn't open, open `<path>` yourself, or say 'let's do it in chat'."* You can't see their browser, so never claim it opened. **The composer page (primary).** The file is `{skill-root}/assets/brain-selector.html`. With a customized catalog (overridden `{workflow.brain_methods}` or any `{workflow.additional_techniques}`), regenerate it first: `python3 {skill-root}/scripts/brain.py --file {workflow.brain_methods} [--extra {doc_workspace}/extra-techniques.json] html --out {doc_workspace}/brain-selector.html` (pass `--extra`, a JSON list of `{category, technique_name, description}`, when there are additional techniques; the file is then `{doc_workspace}/brain-selector.html`). Try to open it (`open` / `xdg-open` / `start`), then say, in one message: *"It should open in your browser — compose your session, click **Copy prompt**, and paste the result back. If it didn't open, open `<path>` yourself, or say 'let's do it in chat'."* You can't see their browser, so never claim it opened.
Read the pasted block: the **`Facilitation mode:`** line → the stance; the **listed techniques** (full category/name/description, some tagged `(random pick)`) → run them as given, no `list`/`show` needed; **`invent N`** / **`you choose N`** → see `## Choosing Techniques`. Read the pasted block: the **`Facilitation mode:`** line → the stance; the **listed techniques** (full category/name/description, some tagged `(random pick)`) → run them as given, no `list`/`show` needed; **`invent N`** / **`you choose N`** → see `## Choosing Techniques`.
@ -61,7 +63,7 @@ For **Facilitator** and **Creative Partner**. (In **Ideate for me** you pick and
Most sessions arrive with a batch already composed on the page — run it as given (each technique's full text is in the paste; no `list`/`show` needed). Two parts of a paste delegate back to you: Most sessions arrive with a batch already composed on the page — run it as given (each technique's full text is in the paste; no `list`/`show` needed). Two parts of a paste delegate back to you:
- **`invent N`** (Inventive Flow) — invent N brand-new techniques on the fly. A line may scope an invention (`invent 1 new technique in the spirit of <category>`, from the page's per-category invent card) — when it does, honor that category's spirit. Announce the order, log each one's name + description, and offer to save a keeper to `{workflow.additional_techniques}` at wrap-up. - **`invent N`** (Inventive Flow) — invent N brand-new techniques on the fly. A line may scope an invention (`invent 1 new technique in the spirit of <category>`, from the page's per-category invent card) — when it does, honor that category's spirit. Announce the order, log each one's name + description, and offer to save a keeper to `{workflow.additional_techniques}` at wrap-up.
- **`you choose N`** (Facilitator Chosen) — pick N techniques fitting the goal, `{workflow.favorite_techniques}` first; confirm exact names with a scoped `uv run {skill-root}/scripts/brain.py --file {workflow.brain_methods} list --category <cat>`. Never pull the library whole into context. - **`you choose N`** (Facilitator Chosen) — pick N techniques fitting the goal, `{workflow.favorite_techniques}` first; confirm exact names with a scoped `python3 {skill-root}/scripts/brain.py --file {workflow.brain_methods} list --category <cat>`. Never pull the library whole into context.
If they didn't use the page, load `references/in-chat-techniques.md` and pick the batch in chat (**34 is the sweet spot**). If they didn't use the page, load `references/in-chat-techniques.md` and pick the batch in chat (**34 is the sweet spot**).

View File

@ -17,7 +17,7 @@ Pick by what the decision needs:
- **PMI (Plus / Minus / Interesting)** — when one strong candidate needs pressure-testing before commitment: list its pluses, minuses, and the merely-interesting, then judge. - **PMI (Plus / Minus / Interesting)** — when one strong candidate needs pressure-testing before commitment: list its pluses, minuses, and the merely-interesting, then judge.
- **MoSCoW** — when scoping a build: sort into Must / Should / Could / Won't-this-time. - **MoSCoW** — when scoping a build: sort into Must / Should / Could / Won't-this-time.
Log the surviving directions and the reasoning with `uv run {project-root}/_bmad/scripts/memlog.py append --workspace {doc_workspace} --type decision --text "<one-line gist>"` (use `--by` in Creative Partner mode). Two or three convergence moves chained is fine (e.g. cluster → score the clusters); more than that is usually over-processing. Log the surviving directions and the reasoning with `python3 {skill-root}/scripts/memlog.py append --type decision --text "<one-line gist>"` (use `--by` in Creative Partner mode). Two or three convergence moves chained is fine (e.g. cluster → score the clusters); more than that is usually over-processing.
## Then finalize ## Then finalize

View File

@ -9,7 +9,7 @@ In Facilitator mode this is the one place your own creative contribution is welc
1. **Hand them the mirror first.** Reflect a vivid sampling of *their* ideas back — deliberately include the odd, random, or buried ones from earlier, not just the recent obvious ones (in Creative Partner mode the `(... by user)` tags tell you which were theirs). Ask what they see now: conclusions, synergies, themes, the few that actually matter. Let them connect first; their own pattern-recognition is the point. 1. **Hand them the mirror first.** Reflect a vivid sampling of *their* ideas back — deliberately include the odd, random, or buried ones from earlier, not just the recent obvious ones (in Creative Partner mode the `(... by user)` tags tell you which were theirs). Ask what they see now: conclusions, synergies, themes, the few that actually matter. Let them connect first; their own pattern-recognition is the point.
2. **Then add the connections they would miss.** Lean in creatively — not new raw ideas, but the non-obvious links: this idea from technique one quietly solves that tension from technique four; these three are one idea wearing three hats; this wildcard is the real breakthrough. 2. **Then add the connections they would miss.** Lean in creatively — not new raw ideas, but the non-obvious links: this idea from technique one quietly solves that tension from technique four; these three are one idea wearing three hats; this wildcard is the real breakthrough.
Record the insights and chosen directions with `uv run {project-root}/_bmad/scripts/memlog.py append --workspace {doc_workspace} --type insight --text "<insights + chosen directions>"`. **Then run `uv run {project-root}/_bmad/scripts/memlog.py set --workspace {doc_workspace} --key status --value complete`** — the session is done and must stop being offered for resume. Do this even if the user declines every artifact below. Record the insights and chosen directions with `memlog.py append --type insight`. **Then run `python3 {skill-root}/scripts/memlog.py set --workspace {doc_workspace} --key status --value complete`** — the session is done and must stop being offered for resume. Do this even if the user declines every artifact below.
## Artifacts ## Artifacts

View File

@ -14,7 +14,7 @@ When in doubt, you are interactive — a present human asking you to "brainstorm
## The inversion ## The inversion
There is no user to draw ideas out of, so you become the brainstormer. Run a real divergent session against the supplied topic: discover techniques with `uv run {skill-root}/scripts/brain.py --file {workflow.brain_methods} list --all` (the whole catalog is fine here — you are generating, not pacing a user; add `show "<name>"` for a technique's full method on demand), plus any `{workflow.additional_techniques}`, preferring `{workflow.favorite_techniques}` where they fit; work them, and **shift the creative domain every ~10 ideas** exactly as the interactive Stance demands — technical, then experiential, then business, then failure modes, then wildcards. Push past the obvious; the same quantity ambition (aim past 100) and anti-clustering discipline apply. The only thing that changes is that the ideas are now yours to generate. This relaxation is scoped entirely to this file — it never applies to interactive sessions. There is no user to draw ideas out of, so you become the brainstormer. Run a real divergent session against the supplied topic: discover techniques with `python3 {skill-root}/scripts/brain.py --file {workflow.brain_methods} list --all` (the whole catalog is fine here — you are generating, not pacing a user; add `show "<name>"` for a technique's full method on demand), plus any `{workflow.additional_techniques}`, preferring `{workflow.favorite_techniques}` where they fit; work them, and **shift the creative domain every ~10 ideas** exactly as the interactive Stance demands — technical, then experiential, then business, then failure modes, then wildcards. Push past the obvious; the same quantity ambition (aim past 100) and anti-clustering discipline apply. The only thing that changes is that the ideas are now yours to generate. This relaxation is scoped entirely to this file — it never applies to interactive sessions.
## Inputs the caller is expected to provide ## Inputs the caller is expected to provide
@ -29,9 +29,9 @@ Free-form structured payload in the first message; provide what applies:
## Run ## Run
1. Bind `{doc_workspace}` and create the memlog with `uv run {project-root}/_bmad/scripts/memlog.py init --workspace {doc_workspace} --field topic="<topic>" [--field goal="<goal>"]`. It remains the canonical source every artifact derives from. 1. Bind `{doc_workspace}` and create the memlog with `python3 {skill-root}/scripts/memlog.py init --workspace {doc_workspace} --field topic="<topic>" [--field goal="<goal>"]`. It remains the canonical source every artifact derives from.
2. Run the divergent session per **The inversion**, capturing each idea with `uv run {project-root}/_bmad/scripts/memlog.py append --workspace {doc_workspace} --type idea --text "<idea>"` as it lands, and marking each technique switch with `uv run {project-root}/_bmad/scripts/memlog.py append --workspace {doc_workspace} --type technique --text "started <name>"`. 2. Run the divergent session per **The inversion**, capturing each idea with `memlog.py append --workspace {doc_workspace} --type idea --text "<idea>"` as it lands, and marking each technique switch with `memlog.py append --type technique --text "started <name>"`.
3. Synthesize: surface the conclusions, connections, and the few directions that matter; record them with `uv run {project-root}/_bmad/scripts/memlog.py append --workspace {doc_workspace} --type insight --text "<insights>"`, then run `uv run {project-root}/_bmad/scripts/memlog.py set --workspace {doc_workspace} --key status --value complete`. 3. Synthesize: surface the conclusions, connections, and the few directions that matter; record them with `memlog.py append --type insight`, then run `memlog.py set --workspace {doc_workspace} --key status --value complete`.
4. Produce the requested artifacts from the log — `brainstorm.html` (the imaginative, self-contained, no-template report) and/or the succinct `brainstorm-intent.md` — the same artifacts `references/finalize.md` describes, delegating each to a subagent that reads the log as its sole source. (Headless produces the `artifacts` payload directly; it does not ask, unlike the interactive opt-in.) 4. Produce the requested artifacts from the log — `brainstorm.html` (the imaginative, self-contained, no-template report) and/or the succinct `brainstorm-intent.md` — the same artifacts `references/finalize.md` describes, delegating each to a subagent that reads the log as its sole source. (Headless produces the `artifacts` payload directly; it does not ask, unlike the interactive opt-in.)
5. Execute each entry in `{workflow.external_handoffs}` (capture returned URLs/IDs into the JSON `external_handoffs` array; skip and flag unavailable tools — local files always exist). Then run `{workflow.on_complete}` if non-empty. 5. Execute each entry in `{workflow.external_handoffs}` (capture returned URLs/IDs into the JSON `external_handoffs` array; skip and flag unavailable tools — local files always exist). Then run `{workflow.on_complete}` if non-empty.

View File

@ -7,7 +7,7 @@ Loaded only when the user won't use the composer page (no browser, headless, or
- **Category** — the user names 1n categories; `random --category` draws the batch from them. No listing needed. - **Category** — the user names 1n categories; `random --category` draws the batch from them. No listing needed.
- **Inventive Flow** — invent at least 3 techniques, announce the order before the first, touch no script. Log each one's name + description so you can offer to save a keeper to `{workflow.additional_techniques}` (via `bmad-customize`) at wrap-up. - **Inventive Flow** — invent at least 3 techniques, announce the order before the first, touch no script. Log each one's name + description so you can offer to save a keeper to `{workflow.additional_techniques}` (via `bmad-customize`) at wrap-up.
The library is large — never pull it whole into context. The only way in is the helper, always passing `--file {workflow.brain_methods}`. Subcommands of `uv run {skill-root}/scripts/brain.py --file {workflow.brain_methods}`: The library is large — never pull it whole into context. The only way in is the helper, always passing `--file {workflow.brain_methods}`. Subcommands of `python3 {skill-root}/scripts/brain.py --file {workflow.brain_methods}`:
- `categories` — names + counts; the cheap survey map. - `categories` — names + counts; the cheap survey map.
- `list --category X [--category Y]` — the index (name + gist) for those categories. Bare `list` is refused by the script. - `list --category X [--category Y]` — the index (name + gist) for those categories. Bare `list` is refused by the script.

View File

@ -5,6 +5,6 @@ The user handed you the topic and wants to see what you come up with on your own
- **Run a real divergent session yourself.** Pick and run techniques on your own (use `brain.py` as in `## Choosing Techniques`, but *you* choose — no menu for the user), capturing each idea to the memlog with `--type idea --by coach`, marking each technique switch with a `technique` entry, shifting the creative domain every ~10 ideas, aiming past 100. Push past the obvious. - **Run a real divergent session yourself.** Pick and run techniques on your own (use `brain.py` as in `## Choosing Techniques`, but *you* choose — no menu for the user), capturing each idea to the memlog with `--type idea --by coach`, marking each technique switch with a `technique` entry, shifting the creative domain every ~10 ideas, aiming past 100. Push past the obvious.
- **Don't pepper the user with questions** — this is your run. One quick confirm of topic and goal up front is plenty. - **Don't pepper the user with questions** — this is your run. One quick confirm of topic and goal up front is plenty.
- **When it's mined out, synthesize and produce the keepsake.** Go to `## Wrap-Up` (`references/finalize.md`): record the insights, mark the memlog complete, and **auto-generate the imaginative HTML keepsake — don't ask first; the keepsake is the result you promised to show them.** Offer the other artifacts (intent doc, etc.) after. - **When it's mined out, synthesize and produce the keepsake.** Go to `## Wrap-Up` (`references/finalize.md`): record the insights, mark the memlog complete, and **auto-generate the imaginative HTML keepsake — don't ask first; the keepsake is the result you promised to show them.** Offer the other artifacts (intent doc, etc.) after.
- **Then, because a human is here, offer to keep going together.** They may want to push an idea further or react to what you found — if so, switch into **Facilitator** or **Creative Partner** (load that frame), **record the switch in the memlog** so a resume restores the new stance — `uv run {project-root}/_bmad/scripts/memlog.py set --workspace {doc_workspace} --key mode --value <facilitator|partner>` — and continue from the same memlog. - **Then, because a human is here, offer to keep going together.** They may want to push an idea further or react to what you found — if so, switch into **Facilitator** or **Creative Partner** (load that frame), **record the switch in the memlog** so a resume restores the new stance — `python3 {skill-root}/scripts/memlog.py set --workspace {doc_workspace} --key mode --value <facilitator|partner>` — and continue from the same memlog.
This is the interactive sibling of headless mode (`references/headless.md`): the same self-generation, but a person is present to receive the output and may continue. headless is the no-human, returns-JSON runner; this one greets, presents, and hands off. This is the interactive sibling of headless mode (`references/headless.md`): the same self-generation, but a person is present to receive the output and may continue. headless is the no-human, returns-JSON runner; this one greets, presents, and hands off.

View File

@ -0,0 +1,202 @@
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# ///
"""memlog — an append-only memory log: LLM-optimal working memory for a skill.
A memlog is the dense, chronological record of everything that mattered in a piece of
work every item the user generated or accepted kept minimal like human memory: only
what's important, never bloated. It persists ACROSS sessions, so a fresh session can
load it and continue. It is NOT a deliverable; downstream artifacts (a brief, a PRD, a
deck, a report) are *derived* from it on demand. The host skill supplies the vocabulary
by how it calls `append` the tool stays neutral.
It is a FLAT log: there are no sections or grouping. Every entry is one line, recorded
at the END in the order it happened. The chronology itself is the structure an event
like "started technique X" is just another entry, same as an idea or an insight.
Two invariants make it trustworthy:
1. Append-only, chronological. Entries land at the end, in the order they happen.
Nothing is ever inserted backward, reordered, or grouped.
2. Write-only / blind. Every command is an atomic, context-free write and echoes the
new state as JSON, so the caller never re-reads the file mid-session. The one time
the file is read is on resume and the caller reads it itself, not via this script.
The file shape (.memlog.md):
---
topic: Onboarding flow for a budgeting app
goal: lift week-1 retention
status: active
updated: 2026-05-30T14:22
---
- (note) user picked techniques: SCAMPER, then Six Thinking Hats
- (technique) started SCAMPER
- (idea) skip the signup wall: let people try with sample data first
- (idea) auto-import one bank account so the first screen shows real numbers
- (question) is open-banking consent too heavy for step one?
- (technique) started Six Thinking Hats
- (idea) black-hat: imported transactions look scary before they're categorized
- (insight) the "scary numbers" risk and the "real numbers" idea are one lever: show real data, pre-categorized
- (direction) user wants to optimize for the anxious first-timer, not the power user
- (decision) lead with one pre-categorized account; defer multi-account import
Each entry may carry an optional `--type` what KIND it is (idea, insight, question,
decision, technique, ) and an optional `--by` naming who it came from (e.g. `user`,
`coach`), for sessions where authorship matters. Both render into one short inline tag:
`(idea)`, `(idea by user)`, `(by coach)`. Omit them for a plain note. The host skill
names the vocabulary; the script does not.
Commands:
init --workspace DIR [--field k=v ...] create the memlog (errors if it exists)
append --workspace DIR --text STR [--type T] [--by W] append one entry at the end
set --workspace DIR --key K --value V set/replace a frontmatter field
The workspace is the run folder; the memlog is always {workspace}/.memlog.md.
"""
import argparse
import json
import os
import sys
from datetime import datetime
from pathlib import Path
MEMLOG = ".memlog.md"
def now() -> str:
return datetime.now().strftime("%Y-%m-%dT%H:%M")
def memlog_path(workspace: str) -> Path:
return Path(workspace) / MEMLOG
def split(text: str) -> tuple[dict, str]:
"""Return (frontmatter dict in source order, body str). Frontmatter is plain key: value.
The closing fence is the first line that is *exactly* `---`, so a `---` inside a
field value (topic/goal are free user text) never truncates the frontmatter.
"""
lines = text.splitlines()
if not lines or lines[0] != "---":
raise ValueError(".memlog.md has no frontmatter")
end = next((i for i in range(1, len(lines)) if lines[i] == "---"), None)
if end is None:
raise ValueError(".memlog.md frontmatter is not terminated")
meta: dict[str, str] = {}
for line in lines[1:end]:
if ":" in line:
k, v = line.split(":", 1)
meta[k.strip()] = v.strip()
return meta, "\n".join(lines[end + 1:]).lstrip("\n")
def render(meta: dict, body: str) -> str:
# Neutralize newlines in values so a multi-line field can't break the fence on re-read.
fm = "\n".join(f"{k}: {' '.join(str(v).splitlines())}" for k, v in meta.items())
return "---\n" + fm + "\n---\n\n" + body.rstrip("\n") + "\n"
def touch(meta: dict) -> None:
"""Stamp `updated` and keep it last so the field order stays predictable."""
meta.pop("updated", None)
meta["updated"] = now()
def write_atomic(path: Path, text: str) -> None:
tmp = path.with_suffix(path.suffix + ".tmp")
tmp.write_text(text, encoding="utf-8")
os.replace(tmp, path)
def entry_count(body: str) -> int:
return sum(1 for ln in body.splitlines() if ln.startswith("- "))
def ack(path: Path, meta: dict, body: str) -> None:
"""Echo new state so the caller never re-reads the file to know where it stands."""
print(json.dumps({
"ok": True,
"memlog": str(path),
"status": meta.get("status", ""),
"entries": entry_count(body),
}))
def cmd_init(args) -> int:
path = memlog_path(args.workspace)
if path.exists():
print(f"error: {path} already exists; use append/set to update it", file=sys.stderr)
return 2
path.parent.mkdir(parents=True, exist_ok=True)
meta: dict[str, str] = {}
for pair in args.field or []:
if "=" not in pair:
print(f"error: --field expects key=value, got {pair!r}", file=sys.stderr)
return 2
k, v = pair.split("=", 1)
meta[k.strip()] = v.strip()
meta.setdefault("status", "active")
touch(meta)
write_atomic(path, render(meta, ""))
ack(path, meta, "")
return 0
def cmd_append(args) -> int:
path = memlog_path(args.workspace)
meta, body = split(path.read_text(encoding="utf-8"))
text = " ".join(args.text.split()) # collapse newlines/runs → one-line entry, no prose bloat
label = args.type or ""
if args.by:
label = f"{label} by {args.by}".strip() # attribution: "(idea by user)" / "(by coach)"
tag = f"({label}) " if label else ""
entry = f"- {tag}{text}"
body = (body.rstrip("\n") + "\n" + entry) if body.strip() else entry # always at the end
touch(meta)
write_atomic(path, render(meta, body))
ack(path, meta, body)
return 0
def cmd_set(args) -> int:
path = memlog_path(args.workspace)
meta, body = split(path.read_text(encoding="utf-8"))
meta[args.key] = args.value
touch(meta)
write_atomic(path, render(meta, body))
ack(path, meta, body)
return 0
def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
sub = p.add_subparsers(dest="cmd", required=True)
pi = sub.add_parser("init", help="create the memlog")
pi.add_argument("--workspace", required=True)
pi.add_argument("--field", action="append", metavar="KEY=VALUE", help="frontmatter field (repeatable)")
pi.set_defaults(func=cmd_init)
pa = sub.add_parser("append", help="append one entry at the end")
pa.add_argument("--workspace", required=True)
pa.add_argument("--text", required=True)
pa.add_argument("--type", help="entry kind, rendered as an inline tag")
pa.add_argument("--by", help="who the entry came from (e.g. user, coach); rendered into the tag")
pa.set_defaults(func=cmd_append)
pset = sub.add_parser("set", help="set a frontmatter field")
pset.add_argument("--workspace", required=True)
pset.add_argument("--key", required=True)
pset.add_argument("--value", required=True)
pset.set_defaults(func=cmd_set)
args = p.parse_args(argv)
return args.func(args)
if __name__ == "__main__":
sys.exit(main())

View File

@ -207,11 +207,11 @@ def test_unknown_category_style_uses_fallback_glyph():
def test_shipped_selector_is_in_sync_with_catalog(): def test_shipped_selector_is_in_sync_with_catalog():
# foolproofing: if someone edits brain-methods.csv they must regenerate the page. # foolproofing: if someone edits brain-methods.csv they must regenerate the page.
# Regenerate with: uv run brain.py html --out assets/brain-selector.html # Regenerate with: python3 brain.py html --out assets/brain-selector.html
asset = brain.DEFAULT_FILE.parent / "brain-selector.html" asset = brain.DEFAULT_FILE.parent / "brain-selector.html"
assert asset.is_file(), "missing assets/brain-selector.html — generate it" assert asset.is_file(), "missing assets/brain-selector.html — generate it"
expected = brain.html_doc(brain.load(brain.DEFAULT_FILE)) expected = brain.html_doc(brain.load(brain.DEFAULT_FILE))
assert asset.read_text(encoding="utf-8") == expected, ( assert asset.read_text(encoding="utf-8") == expected, (
"assets/brain-selector.html is stale; regenerate: " "assets/brain-selector.html is stale; regenerate: "
"uv run brain.py html --out assets/brain-selector.html" "python3 brain.py html --out assets/brain-selector.html"
) )

View File

@ -0,0 +1,265 @@
# /// script
# requires-python = ">=3.10"
# dependencies = ["pytest>=8.0"]
# ///
"""Tests for memlog.py. Run: uv run --with pytest pytest scripts/tests/test_memlog.py
The spine under test is the flat, append-only, chronological invariant: every entry is
one line recorded at the end in the order it happened no sections, no grouping.
"""
import json
import sys
from pathlib import Path
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import memlog # noqa: E402
MEMLOG = ".memlog.md"
@pytest.fixture
def ws(tmp_path):
return str(tmp_path)
def read(ws):
return (Path(ws) / MEMLOG).read_text(encoding="utf-8")
def body_of(ws):
return memlog.split(read(ws))[1]
def entries(ws):
return [ln for ln in body_of(ws).splitlines() if ln.startswith("- ")]
def init(ws, **fields):
fields = fields or {"topic": "Reinvent the lunchbox", "goal": "ideas for a pitch"}
argv = ["init", "--workspace", ws]
for k, v in fields.items():
argv += ["--field", f"{k}={v}"]
assert memlog.main(argv) == 0
def append(ws, text, entry_type=None, by=None):
argv = ["append", "--workspace", ws, "--text", text]
if entry_type:
argv += ["--type", entry_type]
if by:
argv += ["--by", by]
assert memlog.main(argv) == 0
# --- init ---------------------------------------------------------------
def test_init_writes_frontmatter_fields(ws):
init(ws)
meta, body = memlog.split(read(ws))
assert meta["topic"] == "Reinvent the lunchbox"
assert meta["goal"] == "ideas for a pitch"
assert meta["status"] == "active"
assert "updated" in meta
assert body.strip() == ""
def test_init_arbitrary_fields(ws):
init(ws, topic="T", audience="board")
meta, _ = memlog.split(read(ws))
assert meta["audience"] == "board"
def test_init_refuses_overwrite(ws):
init(ws)
assert memlog.main(["init", "--workspace", ws, "--field", "topic=other"]) == 2
def test_init_creates_missing_workspace(tmp_path):
nested = str(tmp_path / "a" / "b")
assert memlog.main(["init", "--workspace", nested, "--field", "topic=T"]) == 0
assert (Path(nested) / MEMLOG).is_file()
def test_init_rejects_malformed_field(ws):
assert memlog.main(["init", "--workspace", ws, "--field", "noequals"]) == 2
# --- append: flat chronological order is the whole point -----------------
def test_append_lands_at_end_in_order(ws):
init(ws)
append(ws, "first")
append(ws, "second")
append(ws, "third")
assert entries(ws) == ["- first", "- second", "- third"]
def test_no_sections_or_headings_ever(ws):
init(ws)
append(ws, "started foo", entry_type="technique")
append(ws, "an idea", entry_type="idea")
append(ws, "started bar", entry_type="technique")
assert "## " not in body_of(ws) # the flat log never grows headings
def test_type_renders_as_inline_tag(ws):
init(ws)
append(ws, "the earth revolves around the sun", entry_type="idea")
append(ws, "how do we handle stampede?", entry_type="question")
body = body_of(ws)
assert "- (idea) the earth revolves around the sun" in body
assert "- (question) how do we handle stampede?" in body
def test_append_without_type_is_plain_note(ws):
init(ws)
append(ws, "bare entry")
assert entries(ws) == ["- bare entry"]
def test_append_collapses_newlines_into_one_line(ws):
init(ws)
append(ws, "line one\nline two\n spaced out")
assert entries(ws) == ["- line one line two spaced out"]
def test_revisited_technique_is_just_a_later_entry(ws):
# the user's model: switching techniques is an entry, not a section to return to
init(ws)
append(ws, "started SCAMPER", entry_type="technique")
append(ws, "magnetic latch", entry_type="idea")
append(ws, "started Six Hats", entry_type="technique")
append(ws, "stale data risk", entry_type="idea")
append(ws, "started SCAMPER", entry_type="technique") # back to SCAMPER — just appended again
append(ws, "stackable tiers", entry_type="idea")
assert entries(ws) == [
"- (technique) started SCAMPER",
"- (idea) magnetic latch",
"- (technique) started Six Hats",
"- (idea) stale data risk",
"- (technique) started SCAMPER",
"- (idea) stackable tiers",
]
def test_by_renders_attribution_in_tag(ws):
# Creative Partner mode must record whose idea each one was
init(ws)
append(ws, "magnetic latch lid", entry_type="idea", by="user")
append(ws, "lid doubles as a plate", entry_type="idea", by="coach")
body = body_of(ws)
assert "- (idea by user) magnetic latch lid" in body
assert "- (idea by coach) lid doubles as a plate" in body
def test_by_without_type_renders_alone(ws):
init(ws)
append(ws, "off-the-cuff thought", by="coach")
assert entries(ws) == ["- (by coach) off-the-cuff thought"]
def test_heterogeneous_entry_types_coexist(ws):
init(ws)
append(ws, "an idea", entry_type="idea")
append(ws, "an open question", entry_type="question")
append(ws, "a decision we made", entry_type="decision")
append(ws, "user wants mobile-first", entry_type="direction")
body = body_of(ws)
for tag in ("(idea)", "(question)", "(decision)", "(direction)"):
assert tag in body
# --- set ----------------------------------------------------------------
def test_set_flips_status(ws):
init(ws)
memlog.main(["set", "--workspace", ws, "--key", "status", "--value", "complete"])
assert memlog.split(read(ws))[0]["status"] == "complete"
def test_set_preserves_body(ws):
init(ws)
append(ws, "keep me", entry_type="idea")
memlog.main(["set", "--workspace", ws, "--key", "status", "--value", "complete"])
meta, body = memlog.split(read(ws))
assert meta["status"] == "complete"
assert "- (idea) keep me" in body
def test_set_can_add_new_field(ws):
init(ws)
memlog.main(["set", "--workspace", ws, "--key", "owner", "--value", "BMad"])
assert memlog.split(read(ws))[0]["owner"] == "BMad"
def test_updated_stays_last(ws):
init(ws)
memlog.main(["set", "--workspace", ws, "--key", "owner", "--value", "BMad"])
meta = memlog.split(read(ws))[0]
assert list(meta)[-1] == "updated"
# --- robustness ---------------------------------------------------------
def test_roundtrip_render_is_stable(ws):
init(ws)
append(ws, "one", entry_type="idea")
first = read(ws)
meta, body = memlog.split(first)
assert memlog.render(meta, body) == first
def test_commas_in_field_survive(ws):
init(ws, topic="cars, trains, and planes")
append(ws, "z", entry_type="idea")
meta, _ = memlog.split(read(ws))
assert meta["topic"] == "cars, trains, and planes"
def test_triple_dash_in_field_does_not_corrupt_frontmatter(ws):
# A `---` inside a value must NOT be read as the closing fence: topic stays intact,
# status survives, and the body never leaks frontmatter text.
init(ws, topic="Pricing --- tiers --- and add-ons")
append(ws, "an idea", entry_type="idea")
meta, body = memlog.split(read(ws))
assert meta["topic"] == "Pricing --- tiers --- and add-ons"
assert meta["status"] == "active"
assert entries(ws) == ["- (idea) an idea"]
assert "status:" not in body # frontmatter never bled into the body
def test_triple_dash_status_survives_in_ack(ws, capsys):
init(ws, topic="a --- b")
append(ws, "x", entry_type="idea")
out = json.loads(capsys.readouterr().out.strip().splitlines()[-1])
assert out["status"] == "active" # not "" — frontmatter recovered cleanly
def test_newline_in_field_is_neutralized(ws):
# A value carrying a newline can't break the fence on the next round-trip.
memlog.main(["init", "--workspace", ws, "--field", "topic=line one\nline two"])
append(ws, "x", entry_type="idea")
meta, _ = memlog.split(read(ws))
assert "\n" not in meta["topic"]
assert meta["status"] == "active"
def test_append_emits_json_ack(ws, capsys):
init(ws)
append(ws, "x", entry_type="idea")
out = json.loads(capsys.readouterr().out.strip().splitlines()[-1])
assert out["ok"] is True
assert out["status"] == "active"
assert out["entries"] == 1
assert out["memlog"].endswith(MEMLOG)
assert "section" not in out # sections are gone
def test_ack_entry_count_climbs(ws, capsys):
init(ws)
append(ws, "a")
append(ws, "b")
out = json.loads(capsys.readouterr().out.strip().splitlines()[-1])
assert out["entries"] == 2

View File

@ -13,7 +13,7 @@ Exercises the scanner against a synthesized install tree:
- malformed TOML (surfaces as an error without aborting) - malformed TOML (surfaces as an error without aborting)
- multiple skills roots (e.g. project-local + user-global mix) - multiple skills roots (e.g. project-local + user-global mix)
Run: uv run scripts/tests/test_list_customizable_skills.py Run: python3 scripts/tests/test_list_customizable_skills.py
""" """
from __future__ import annotations from __future__ import annotations

View File

@ -1,79 +0,0 @@
---
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

@ -1,42 +0,0 @@
# 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

@ -1,270 +0,0 @@
#!/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

@ -1,138 +0,0 @@
#!/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

@ -1,58 +1,75 @@
--- ---
name: bmad-party-mode name: bmad-party-mode
description: 'Orchestrates lively group discussions between installed BMAD agents or custom personas, and helps author custom parties. Use when the user requests party mode, a roundtable, or multiple agent perspectives — or wants to create/configure a party, define personas, or build an AI focus-group panel.' description: 'Orchestrates lively group discussions between installed BMAD agents or other personas. Use when the user requests party mode, a roundtable, or multiple agent perspectives.'
--- ---
# Party Mode # Party Mode
Run a round-table where these agents talk to each other and to the user like real, distinct people in conversation. You're the orchestrator. Run a roundtable where BMAD agents talk to each other, and to the user, like a real group of distinct people in conversation. Your job as orchestrator is to make it feel like a genuine conversation: fast, in-character, opinionated, and fun. Everything below is an objective, not a script. Use whatever mechanism your model and harness make available to hit it.
## Conventions ## What "Good" Feels Like
- **Paths:** bare paths (e.g. `references/create-party.md`) resolve from `{skill-root}` (where `customize.toml` lives); `{project-root}`-prefixed paths from the project working dir. `{workflow.<name>}` resolves to `customize.toml`'s `[workflow]` table (overrides win). - **It reads like people talking, not reports being filed.** Short turns. Reactions to what was just said. Banter. The energy of a group chat, not a stack of memos.
- **Scripts** (run via `uv run`): `{project-root}/_bmad/scripts/resolve_customization.py` resolves `{workflow.*}`; `{skill-root}/scripts/resolve_party.py` resolves the roster, `party_mode`, `memory_enabled`, and scene/`open_cast`; `{project-root}/_bmad/scripts/memlog.py` reads/writes per-party memory. - **Every persona is unmistakably themselves:** their voice, humor, pet peeves, and ethos. If you hid the name labels, you'd still know who's speaking.
- **File roles:** a party's memory is the per-party memlog at `{workflow.memory_dir}/<party>/.memlog.md`; custom members and groups live in the user's `customize.toml` overrides. Mechanics in `references/party-memory.md` (memory) and `references/create-party.md` (authoring). - **They clash.** Real drama beats consensus. Agents should challenge each other, push back hard, and get heated when the topic warrants it. Nobody is here to clap each other (or the user) on the back. If a round turns into mutual agreement, it failed: bring in a dissenter or hand someone the contrarian role.
- **Search:** Web-search, don't guess — anything past your cutoff or unfamiliar; subagents too. - **Brevity by default.** A persona goes long only when the user asks that persona to dig into something. Nobody delivers a wall of text unprompted. One voice might run long now and then, but a real group is never everyone monologuing at once.
## On Activation If a round comes back feeling like four essays stapled together, you missed the objective. Tighten it the next round.
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 and use defaults. Then run each `{workflow.activation_steps_prepend}` entry, and hold each `{workflow.persistent_facts}` entry as session-long context (`file:`-prefixed = paths/globs whose contents load as facts; `skill:`-prefixed = a skill to consult; others = literal facts). ## Setup
2. Load `{project-root}/_bmad/core/config.yaml`: greet with `{user_name}`, speak in `{communication_language}`, and resolve `{output_folder}` and `{date}`.
3. **Detect intent and route.** If they want to create or configure a saved party setup (invent a cast, add a persona, distill customer data into a focus-group panel, set a default, or edit an existing custom party), load `references/create-party.md` and follow it. Otherwise run a party — continue below.
4. **Resolve the roster:** `uv run {skill-root}/scripts/resolve_party.py --project-root {project-root} --skill {skill-root}`. It returns the active roster (`{workflow.default_party}` group if set, else the installed agents), the other group names, `party_mode`, `memory_enabled`, and any scene/`open_cast`. Apply them: `open` already in the scene and let it shape how the room behaves; cast `open_cast` rooms on the fly (whoever fits the moment, varying as the topic shifts); if `installed_agents_resolved` is false or codes come back `unresolved`, tell the user, carry on with what returned, and improvise. Overrides: an inline-named cast IS the roster for the session (conjure them, go straight in); `--party <id>` (alias `--group <id>`) overrides the configured `default_party` (unknown id -> show the available names and ask); `--list-groups` for just the menu. Mid-session the same levers apply: switch rooms by re-running `resolve_party.py --party <id>` and carrying the thread over, or summon any collective member by name.
5. **Memory.** If `memory_enabled` (from `resolve_party.py`), follow `references/party-memory.md` for the whole run.
6. **Welcome the user:** show who's in the room (icon, name, one-line role); note other groups can be switched to. Then ask what they want to get into, unless it's already obvious from how the skill was launched.
7. Run each `{workflow.activation_steps_append}` entry; if either hook list was non-empty, confirm every entry ran before continuing.
## Keep It Feeling Like a Party 1. Load `{project-root}/_bmad/core/config.yaml`: greet with `{user_name}`, speak in `{communication_language}`.
2. Resolve the roster:
This is the bar — strive for every one of these, every round. It's the difference between a party and a panel: ```bash
python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root} --key agents
- **It reads like people talking, not a report.** Short turns, real reactions, banter, momentum — a group chat, not a stack of memos. Brevity by default: a persona goes long only when asked. The instant it reads like answers being filed, the party's dead. ```
- **Every voice is unmistakably itself.** Diction, humor, pet peeves, ethos, embedded capabilities — hide the labels and you'd still know who's speaking. Voices are unequal and idiosyncratic: someone dominates, someone keeps dragging it back to their pet topic. Vary who's in the spotlight round to round. A balanced panel is boring. Each entry is keyed by `code` and carries `name`, `title`, `icon`, `description`, `module`, and `team`.
- **They clash, and you don't resolve it.** Challenge, push back hard, get heated when it's warranted; alliances and factions form. Your instinct is to reconcile the voices and tie a bow — resist it. Clean consensus that took no effort is where the party dies. 3. Welcome the user, show who's in the room (icon, name, one-line role), and ask what they want to get into, unless it's already obvious from how they invoked party mode.
- **One exchange, woven — never softened.** Present a single conversation — turns as `{icon} **{name}:**`, back to back — not a row of answers. Add staging and connective tissue, but never change what a persona argued, and never paraphrase their speech in third person; let them say it. Weave the delivery, keep the substance. 4. This is theater of the mind here, so set the stage and vibe, emote and have fun with it - but specifically, dont say things about the mechanics of the party mode and break the 4th wall. Don't say "you have 4 agents in the room" or "agent X says". Instead, just let them talk, and let the user feel like they're in a lively group chat with a bunch of distinct personalities. Dont tell the user you are orchestrating a party mode, just run the party mode. The user should feel like they walked into a room where these people are already talking, not that you just spawned them to talk.
- **Pull the user into the room.** Characters talk *to* them (and each other) — challenge, tease, put a question back. They're a guest who got pulled into the argument, not someone running a panel from outside.
- **Make the collision earn its keep.** Push the voices until their clash surfaces an angle no single one of them (or you) would've reached alone. That's the whole point of more than one mind in the room.
- **Let a history form.** Grudges, alliances, a running bit, a callback to three turns back — let the relationships accrue so these people feel like they're becoming something across the session, not resetting each turn.
- **Commit to the fiction.** The scene and each persona are binding — play the staging, the characters, and the world around the table (stage business, a non-verbal beat, an event that lands mid-sentence) exactly as written, and carry both into any spawned brief. Never break the fourth wall about the mechanism (no "you have 4 agents in the room"). Lean into the world when it heightens the moment; stay out when the scene is just a room.
- **When it sags, change something — don't force it.** A flat turn? Move on, don't retry it. Drifting into Q&A or going in circles? Bring in a new voice, crack a joke, name the impasse, or ask where they want to take it. Never work in a summary or takeaways — they're there if the user asks.
## How It Runs ## How It Runs
Use `{workflow.party_mode}` for the session unless the user passed `--mode <session|auto|subagent|agent-team>` (the older `--subagents` means `subagent`) — runtime intent always wins. One mode is active at a time; if its mechanism isn't available in your harness, fall back to `session` without comment. **Default: you voice the room.** Pick 2 to 4 personas whose perspective fits the moment and let them talk directly, in one flowing exchange, fully in character. This is what keeps it fast and conversational. Vary who shows up round to round and let different voices interject as the topic shifts. Don't fall back on the same three agents every time.
- **`session`** — voice every persona inline, one mind behind every voice. The floor every other mode degrades to; needs no extra instructions. Each turn opens with `{icon} **{name}:**` and then that persona speaks. Present turns back to back so it reads as one conversation. Don't summarize, blend, or narrate what they "would" say. Let them say it.
- **`auto`** — voice inline for ordinary back-and-forth, spawn real agents only when independent thinking changes the outcome. Load `references/mode-auto.md` for that call; when it says to spawn, follow `references/mode-subagent.md`.
- **`subagent`** — spawn a real agent per substantive round so each persona thinks independently. Load `references/mode-subagent.md`, favor faster cheaper models if available for each subagent. **When independence matters, spawn them for real.** If a round's value depends on genuinely independent thinking (deep analysis, an honest review, perspectives that shouldn't be colored by one mind voicing them all), spawn the personas as separate agents using whatever your harness offers. Give each one the objective, their persona, the context, and what the others said if they're reacting. Trust their *thinking*: let them decide what to read and how to reach a view, and don't script their substance with do-and-don't checklists — that's what produces lifeless blobs. But do hold the *form*: a length cap (usually a sentence or three) and the instruction to react to what was just said rather than file a report. Constraining length and stance protects the conversation; constraining their reasoning kills it. Stay in character throughout; a persona goes long only when the user asked it to dig in.
- **`agent-team`** — stand the personas up as a persistent team who address each other directly (Claude Code only). Load `references/mode-agent-team.md`.
Spawn in parallel for independent first-takes — everyone reacts to the topic fresh, fast. Spawn sequentially when you want them reacting to each other's actual words: a real rebuttal has to have heard the thing it's rebutting, and parallel agents can't, so left raw they monologue side by side instead of arguing. Sequential is slower but it's the only way subagents genuinely engage. Either way, keep it to 23 voices a round; more reads as a crowd, not a conversation.
By default you voice the room — for ordinary back-and-forth it's faster and feels more alive — and you reach for spawning when a round genuinely needs independent minds. But when the user asks for subagents (a launch flag like `--subagents`, or just saying so), that's a standing directive for the session: spawn for every substantive round until they say otherwise. Don't relitigate it round by round, and don't fall back to voicing because a moment felt light — the opening banter still gets spawned. A user who pinned the mode already made that call for you.
**Model choice:** match the model to the round. Something quick for banter, something stronger for deep work. If the user pins a model (for example, `--model <name>`), use it for everyone.
## Make It Feel Like One Conversation
Whether you voiced the room or spawned subagents, your job before presenting is the same: make it read like people responding to each other, not a row of separate answers all aimed at the user.
This matters most with subagents. Each one only saw the user's message and the context you handed it, so left raw they all reply to the user in parallel and never to one another. Stitch them together. Reorder turns so a rebuttal lands right after the thing it rebuts. Add the connective phrasing real conversation has ("Hold on, Winston, that's backwards", "Sally's right about the API, but she's missing the cost"). Let one persona pick up a thread another dropped, or cut in mid-thought.
Raw subagent output is raw material, never the final render — you cut it, interleave it, trim it. If a turn is still a full self-contained paragraph after you've woven it, you haven't woven it. The reader should feel a fast exchange, not a panel of separate statements read aloud in a row.
The hard rule: never change what an agent actually argued. You add the connective tissue and the staging; you do not invent positions, soften a stance, or put words in a persona's mouth they didn't say. Weave the delivery, preserve the substance, and always the output reads like that specific character, quirks or speech patterns and all.
## Following the User's Lead
The user steers. Whatever they raise, serve the conversation:
- A new topic: fresh voices, keep it moving.
- "Winston, what do you make of Sally's take?": just Winston, reacting to Sally.
- "Bring in Amelia": Amelia joins, caught up on what's been said.
- "Go deeper on that, John": this is the cue to let John stretch out. Depth is earned by a direct ask.
- A question to the whole room: everyone relevant chimes in.
Any combination, any time, from one voice to the whole table.
## Keeping It Healthy
- **Everyone agreeing?** Drop in a contrarian, or hand someone the devil's-advocate hat.
- **Going in circles?** Name the impasse and ask the user where to point next.
- **User's gone quiet?** Ask straight: keep going, switch topics, or wrap up?
- **A flat turn?** Don't retry it. Move on; the user will ask for more if they want it.
## Wrapping Up ## Wrapping Up
When the user signals done (read the room — don't wait for a magic word): When the user signals they're done (any phrasing: "thanks", "that's all", "end party"), give a quick read-back of the best takeaways and drop back to normal mode. Read the room; don't wait for a magic word.
- Read back the best takeaways.
- If memory is on, top up the memlog with the final outcome and any memorable beat not yet captured (`references/party-memory.md`) — a top-up; memory accrued live.
- Offer a keepsake: a single self-contained very creative HTML of the session, laid out by persona (icons, names, voice), genuinely nice remembrance, with inline SVG/light animation where it lifts the piece — written as a `{date}`-stamped `.html` into `{workflow.output_dir}/`, or wherever they ask.
- If memory is on and new faces showed up who aren't in the party's roster (open-cast walk-ons, or members the user added on the fly), offer once to save them into the users party customization - if yes then follow the instruction in `references/create-party.md` (declinable; don't stall the close).
- Run `{workflow.on_complete}` if non-empty, then drop back to normal mode.

View File

@ -1,175 +0,0 @@
# DO NOT EDIT -- overwritten on every update.
#
# Workflow customization surface for bmad-party-mode.
#
# Override files (not edited here):
# {project-root}/_bmad/custom/bmad-party-mode.toml (team)
# {project-root}/_bmad/custom/bmad-party-mode.user.toml (personal)
[workflow]
# --- Configurable below. Overrides merge per BMad structural rules: ---
# scalars: override wins • plain arrays: append
# arrays of tables keyed by `code`/`id`: matching key replaces, new keys append
# Steps to run before the standard activation (config load, greet).
# Use for pre-flight loads, compliance checks, etc.
activation_steps_prepend = []
# Steps to run after greet but before the room comes alive.
activation_steps_append = []
# Persistent facts the orchestrator keeps in mind for the whole session
# (house rules, running gags, topics to avoid). Each entry is a literal
# sentence, a `skill:`-prefixed reference, or a `file:`-prefixed path/glob whose
# contents load as facts. Default picks up project-context.md if one exists.
persistent_facts = [
"file:{project-root}/**/project-context.md",
]
# Which party loads when the user just says "party mode" with no override.
# Empty = the installed BMAD agents — exactly the default behavior of a plain
# install. Custom members defined below join the POOL (usable in groups, and
# summonable by name) but do NOT crowd this default room. Set this to a
# `party_groups` id to pin a curated room as the default instead. A runtime
# `--party <id>` always wins.
#
# Example (set in team/user override TOML): default_party = "writers-room"
default_party = ""
# How the room is run — who does the talking. A runtime `--mode <value>` wins for
# the session; an unsupported mode (e.g. agent-team outside Claude Code) falls back
# to "session". SKILL.md "How It Runs" is the authority on what each mode does.
# "session" (default) never spawn — one mind voices every persona inline
# "auto" voice inline for light rounds, spawn subagents when independent thinking matters
# "subagent" spawn a real subagent per substantive round, so each persona thinks independently
# "agent-team" persistent agent team addressing each other directly (Claude Code only)
party_mode = "session"
# Where the optional end-of-session keepsake is written. The self-contained HTML
# document lands in `{output_dir}/`. `{output_folder}` and `{date}` come from core
# config; point this elsewhere in your team/user override to redirect keepsakes.
output_dir = "{output_folder}/party-mode"
# Memory for the DEFAULT room (the installed-agent party). When on, the room
# keeps a succinct, append-only memlog (the memlog standard) that it reads on
# entry and writes through the session, so the next time opens remembering the
# last — dynamics carried forward, memorable moments, organic callbacks, where
# things landed. It is memory, not a transcript. Set false to turn the default
# room's memory off. NAMED groups do NOT follow this flag: each carries its own
# `memory = true|false` (see party_groups below). Ad-hoc inline casts are always
# ephemeral until saved as a party.
party_memory = true
# Root for the per-party memlogs. Each party stores at
# `{memory_dir}/<party>/.memlog.md`, where `<party>` is the group id (or
# `installed` for the default room). `{output_folder}` comes from core config;
# point this elsewhere in your team/user override to relocate memory.
memory_dir = "{output_folder}/party-mode/memories"
# Executed when the party wraps (after the read-back, before dropping to normal
# mode). String scalar = one instruction; array = instructions run in order.
on_complete = ""
# ---------------------------------------------------------------------------
# Custom party members — personas, added to the POOL alongside the installed
# agents. The default room stays installed-only; a custom member shows up when a
# group uses them or you summon one by name. Keyed by `code`: an override entry
# with a matching code replaces the base one (retune a shipped member), a new
# code appends. Fields:
# code short unique handle, used in party_groups and to summon them
# name display name
# icon single emoji shown on their turns
# title one-line role/identity
# persona voice, humor, ethos, pet peeves, how they argue — the meat;
# what makes them unmistakably themselves
# capabilities (optional) what they can do when spawned as a real subagent;
# woven into their spawn prompt as guidance, not a hard tool grant
# model (optional) model to use when this member is spawned
#
# The members below ship the "Code Review Crew" (see the party_groups section).
# They cost nothing until summoned — the default room never includes them.
# ---------------------------------------------------------------------------
[[workflow.party_members]]
code = "sec-hawk"
name = "Vex"
icon = "🔒"
title = "Security Engineer"
persona = "Threat-models everything. Hunts injection, broken authz, leaked secrets, SSRF, supply-chain risk. Assumes every input is hostile and every dependency compromised until proven otherwise. Names the exploit path concretely — 'here's how I'd own this box' — never hand-waves 'might be insecure.'"
capabilities = "Reads the code and traces data flow from untrusted input to sink before judging."
[[workflow.party_members]]
code = "adversary"
name = "Grumbal"
icon = "😤"
title = "The Adversary"
persona = "Assumes the code is broken and his job is to prove it. Grumpy, blunt, zero praise sandwiches. Starts from 'this will page someone at 3am' and works backward to the line that does it. Allergic to optimism and 'should be fine.'"
[[workflow.party_members]]
code = "edge-hunter"
name = "Boundary"
icon = "🌶️"
title = "Edge-Case Hunter"
persona = "Walks every branch and boundary. Empty input, null, the off-by-one, the huge payload, the concurrent call, the unicode name, the timezone, the retry storm. Method-driven, not mean: 'what happens when this is called twice at once?'"
[[workflow.party_members]]
code = "craftsman"
name = "Yui"
icon = "🎯"
title = "The Craftsman"
persona = "Cares about simplicity, naming, and reuse. Allergic to cleverness and duplication. 'You reimplemented something that already exists,' 'this name lies about what it does,' 'three nested abstractions where one would do.' Wants the boring, obvious, maintainable version."
[[workflow.party_members]]
code = "shipper"
name = "Dana"
icon = "🚢"
title = "The Pragmatist"
persona = "Counters the perfectionists so the room isn't a pile-on. 'Does this actually matter to a user? Ship the 80%, file the rest.' Pushes back on gold-plating and theoretical risks, forces everyone to rank what's real versus what's a nit."
# ---------------------------------------------------------------------------
# Named party groups — curated rooms picked at runtime with `--party <id>`
# (alias `--group <id>`) or switched to mid-session. Keyed by `id`.
#
# `members` is a list of codes — installed agent codes, custom member codes, or
# a mix. Override by `id` to retune a group; new ids append.
#
# An optional `scene` sets the stage: a freeform line (or a few) describing the
# setting, what's happening, how the room behaves, and any in-the-moment
# character notes — who's had a few, who's hostile to whom, who pressure-tests
# hardest. The same members can power many scenes; define a member once, then
# drop them into different rooms. No fixed vocabulary — the model reads it and
# plays it.
#
# `members` is OPTIONAL. Leave it off and the group is open-cast: the `scene`
# names a pool or universe and the room is cast on the fly — you don't enumerate
# who shows up; the model picks who fits and can vary them by topic. List a few
# members AND a scene to anchor some faces while the scene invites others in.
#
# `memory = true|false` is per group: true keeps the group's own memlog so it
# remembers across sessions; false (the default when omitted) starts fresh each
# time. The create/save/update-party flow asks when you don't say. Faces that
# show up on the fly in a remembered party can be saved into its roster at the
# end of a session.
#
# More examples to drop into your override TOML:
# [[workflow.party_groups]] # anchored room with a scene
# id = "writers-room"
# name = "The Writers' Room"
# scene = "Late-night room, everyone a little punchy. Pitch hard, kill darlings faster."
# members = ["analyst", "tech-writer", "morpheus"]
# memory = true
#
# [[workflow.party_groups]] # open-cast room (no roster; the scene casts it)
# id = "star-wars-rebels"
# name = "Star Wars Rebels"
# scene = "Aboard the Ghost. Figures from the Rebels universe drop in depending on the situation — pick whoever fits the topic, and let the roster shift as the conversation moves."
# memory = true
# ---------------------------------------------------------------------------
[[workflow.party_groups]]
id = "code-review-crew"
name = "Code Review Crew"
scene = "Adversarial code review. Each reviewer attacks from their own lens and they argue with each other about what actually matters — security versus shipping, elegance versus pragmatism. No rubber-stamping, no praise sandwiches: surface the real problems before they ship. Point at the line, name the failure mode, and defend it when someone pushes back. Best run with `--mode subagent` so each lens reviews independently before they clash."
members = ["sec-hawk", "adversary", "edge-hunter", "craftsman", "shipper"]
memory = false # each review stands on its own; flip to true to remember past reviews

View File

@ -1,70 +0,0 @@
# Creating a Party
A guided authoring flow that turns an idea — a themed cast, a one-off persona, or a pile of raw profile data — into custom party members and groups, written to the user's customize.toml override. The output is configuration; `bmad-customize` does the actual write.
## What you're producing
Sparse `[workflow]` override entries for `bmad-party-mode`:
- `[[workflow.party_members]]` — one per persona: `code`, `name`, `icon`, `title`, `persona`, optional `capabilities`, optional `model`.
- `[[workflow.party_groups]]` — when the personas form a named room: `id`, `name`, an optional freeform `scene`, `members` (codes), and `memory` (`true`/`false`). `members` is optional: leave it off for an open-cast room whose `scene` names a pool the model casts from on the fly. `memory` is whether the group remembers across sessions; ask the user when they don't say, default `false`.
- `default_party` — set only if the user wants this group to load by default.
A `scene` is one freeform line (or a few) that sets the stage for a room: the setting, what's happening, how the room behaves, and any in-the-moment character notes — who's three drinks in, who's hostile to whom, who pressure-tests hardest. It's how the same members power many different rooms (a bridge crew on duty vs. the same crew off-duty in the lounge vs. a hostile buyer panel). Define each member once; vary the `scene` per group rather than redefining people. There's no fixed vocabulary — write it plainly and the model plays it.
The `persona` field is the whole game. A flat title produces a flat voice; the detail you elicit is what makes a member unmistakably themselves at the table.
## Find the shape
Open by understanding what they're building. Three common shapes — stay open, anything that yields distinct voices is fair game:
- **A cast** — a themed ensemble ("the Star Trek TOS bridge crew", "a board of famous investors"). Several members plus a group that holds them.
- **One-offs** — a persona or two added to the collective, no group needed.
- **Distilled from data** — the user hands you source material (a spreadsheet of customer profiles, survey exports, interview notes) to compress into N stereotypical personas. This is how you stand up an AI focus group for product ideation or feedback.
- **A panel of lenses** — purpose-built reviewers, each a sharp critical angle (a security engineer, an adversarial skeptic who assumes it's broken, an edge-case hunter, a craftsman who hates cleverness and duplication, a pragmatist who counters perfectionism). The group's `scene` tells them to attack from their lens and argue with each other about what actually matters. A great adversarial-review or red-team room.
- **Open-cast** — no fixed roster at all. The group's `scene` names a pool or universe ("figures from the Star Wars Rebels universe drop in depending on the situation") and the room is cast on the fly. Leave `members` off; the model already knows the universe and picks who fits the moment. Anchor a face or two by listing them if some should always be present.
Ask which they're after if it isn't obvious, then proceed.
**Persisting a cast already in play.** When you arrive here from a live session — the user spun up an ad-hoc cast inline and wants to keep it — the personas are already drafted and voiced. Don't re-interrogate: capture them as they've been playing, give the group an `id` and name, ask the memory and default questions, and go straight to the write.
## Editing an existing party
When the user wants to change a party that already exists (retune a member's persona, add someone to a group, swap the default), read the current state first so you change rather than clobber: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow` returns the merged `party_members`, `party_groups`, and `default_party`. Show the member or group being touched, capture only the delta with the user, and hand that sparse change to `bmad-customize` — it replaces a `party_members`/`party_groups` entry whose `code`/`id` matches and appends the rest, so an edit is just the changed entry, never a full rewrite.
## Keeping new faces from a session
At the end of a remembered party, the room offers to keep the faces that showed up but aren't in its roster — characters cast from an open-cast scene, or members the user added on the fly. They're already drafted and voiced, so don't re-interrogate: capture each as they played (`code`, `name`, `icon`, a one-line `title`, and a `persona` drawn from how they came across), then add them as `party_members`. For a fixed-roster group, also list their codes in the group's `members` so they return as regulars. For an open-cast room, leave `members` empty — listing any member turns the room into a fixed roster and kills its on-the-fly casting; the saved personas now live in the collective, so the scene still names them and they can return without locking the room down. Hand that sparse delta to `bmad-customize` — for a built-in party with no override yet it creates one; for an existing override it merges the new members in.
## Distill from source data (when provided)
When the user points you at data — a file path, a pasted table, exported profiles — read it and compress it into the requested number of representative personas. Cluster by what actually differentiates behavior (goals, budget, pains, adoption posture), not surface demographics alone. Each cluster becomes one persona with a real name and face. Name your reasoning: tell the user which segments you found and which traits drove the split, so they can correct the cut before you flesh the personas out. If they didn't say how many, propose a number from the spread in the data and let them adjust.
For a focus-group panel, independent answers matter more than banter, so offer to set `party_mode` to `subagent` (or remind them `--mode subagent` does it per session) — otherwise one mind voices every customer and they bleed together.
## Flesh out each persona
Draft, don't interrogate. Propose a first cut of each persona and let the user react — far faster than a questionnaire. Push each one until it has a voice you could pick out blind. The dimensions that earn their place:
- **Identity** — name, a one-line title, an emoji that fits.
- **Voice & ethos** — how they talk, what they value, how they argue, their pet peeves.
- **Agenda** — what they're really after in any conversation; what they push for.
- **Quirks** — the specific, human details (a catchphrase, a bias, a blind spot).
- For focus-group personas, also **likes and dislikes**: what would make them champion or reject an idea, and their relationship to the product space.
- **Capabilities** (optional) — if this persona should research or read files when spawned, note it; it becomes soft guidance in their spawn prompt.
Keep pushing for specificity. "Skeptical CFO" is a placeholder; "won't approve anything without a payback under 18 months, and says so in the first thirty seconds" is a persona.
## Close it out
- Ask straight: **anything else about this party to specify** before you write it — a house dynamic, a missing voice, a member who should lead.
- Ask whether **this party should remember across sessions** (unless the user already said). Yes → `memory = true` on the group; no → `memory = false`. One-offs with no group skip this — memory is a group setting.
- Ask whether **this group should be the default party going forward**. Yes → set `default_party` to the group's id. One-offs with no group can't be a default; skip the ask.
## Write via bmad-customize
**First, check for code collisions.** A custom member whose `code` matches an installed agent silently *overrides* that agent in the collective. Before composing, resolve the collective once — `uv run {skill-root}/scripts/resolve_party.py --project-root {project-root} --skill {skill-root}` — and check each new member's `code` against the returned members. On a collision, surface it ("`analyst` would override the installed Analyst — intended, or pick a different code?") and let the user confirm or rename. One check, not a gate.
Compose the sparse override and hand it to `bmad-customize` to place, confirm, and write — target skill `bmad-party-mode`, `[workflow]` surface. Default to the **user** override (`bmad-party-mode.user.toml`); offer the **team** file when the party is meant to be shared. Hand it the exact entries: the `party_members` tables, any `party_groups` table (including its `memory` flag), and `default_party` if the user opted in. Keep it sparse — only the new entries, never a copy of the base customize.toml. `bmad-customize` shows the TOML, waits for an explicit yes, writes, and verifies the merge; don't write the file yourself.
After it lands, tell the user how to use it: `--party <id>` to summon the group, or that it's now the default if they set it.

View File

@ -1,11 +0,0 @@
# Agent-Team Mode
Active when `{workflow.party_mode}` resolves to `agent-team` (or a `--mode agent-team` override). Stand the personas up as a persistent agent team whose members address each other directly, so the back-and-forth happens for real instead of being stitched together after. Claude Code only — if your harness can't stand up a team, fall back to `subagent`, and if that fails too, to `session`.
Your job shifts from weaving to hosting: kick off the topic, keep turns short and in character, pull the thread back when it wanders, and surface the exchange to the user. Voice, brevity, and clash still hold.
In each member's standing brief, carry: their persona; the group's `scene` and any behavioral instructions in the persona as binding direction; their `model` if one is set (a session `--model` pin wins for everyone); and the instruction to check anything that could be stale since the model's training cutoff with web search rather than guessing.
## Model choice
Match the model to the work: something quick for banter, something stronger for deep work. A per-member `model` is used when set; a session `--model <name>` pin overrides it for everyone.

View File

@ -1,13 +0,0 @@
# Auto Mode
Active when `{workflow.party_mode}` resolves to `auto` (or a `--mode auto` override). The blend: voice the room inline by default — fast and conversational — and spawn real independent agents only for the rounds where independence changes the answer. When you do spawn, follow `references/mode-subagent.md` for the mechanics. If your harness can't spawn agents, auto is just `session`.
## When to spawn vs. voice
Spawn independent agents when divergent, uncolored thinking is the value of the round:
- A genuine evaluation, review, or critique — the kind that fails if one mind voices every side and they drift into agreement (code review, red-team, a hard look at a plan).
- The personas would plausibly reach *different* conclusions, and that divergence is the point.
- The user asked someone to dig in, analyze, or research — depth earned by a direct ask.
Voice inline for everything else: banter, reactions, quick takes, the connective back-and-forth that is most of a conversation. When in doubt, voice — spawning is the exception you reach for, not the default.

View File

@ -1,19 +0,0 @@
# Subagent Mode
Active when `{workflow.party_mode}` resolves to `subagent` (or a `--mode subagent` override). Spawn a real agent for every substantive round, the opening banter included, so each persona thinks independently — not one mind voicing them all. A standing directive: don't relitigate it round to round, and don't fall back to voicing because a moment felt light. If your harness can't spawn agents, fall back to `session`.
## Spawning
Give each agent the objective, their persona, the context, and what the others said if they're reacting. For a custom member, hand them their `persona` as their character and fold their `capabilities` note into the brief; spawn them with their `model` if one is set (a session `--model` pin wins for everyone). Always carry two things into the brief: the group's `scene` and any behavioral instructions in the persona are binding direction, and anything that could be stale since the model's training cutoff should be checked with web search rather than guessed.
Trust their *thinking*: let them decide what to read and how to reach a view; don't script their substance with do-and-don't checklists — that's what produces lifeless blobs. But hold the *form*: a length cap (usually a sentence or three) and the instruction to react to what was just said rather than file a report. Constraining length and stance protects the conversation; constraining their reasoning kills it. Stay in character throughout; a persona goes long only when the user asked it to dig in.
Spawn in parallel for independent first-takes; spawn sequentially when you want them reacting to each other's actual words. Keep it to a few voices a round — more reads as a crowd, not a conversation.
## Weave the replies into one conversation
Each agent saw only the user's message and the context you handed it, so left raw they reply in parallel and never to one another. Reorder turns so a rebuttal lands right after what it rebuts, add the connective phrasing real talk has ("Hold on, Winston, that's backwards", "Sally's right about the API, but she's missing the cost"), and let one persona pick up a thread another dropped. Never change what an agent argued — weave delivery, preserve substance.
## Model choice
Match the model to the round: something quick for banter, something stronger for deep work. A per-member `model` is used when set; a session `--model <name>` pin overrides it for everyone.

View File

@ -1,51 +0,0 @@
# Party Memory
The room remembers its past sessions with this user and brings them back to life — in character. Memory is per-party and append-only.
Memory is on when the active party's `memory_enabled` is true — the default room follows `{workflow.party_memory}`, a named group its own `memory` flag (both resolved by `resolve_party.py`); ad-hoc inline casts have none. Read on entry and on any mid-session room switch; write through the session.
## Where it lives
One memlog per party: `{workflow.memory_dir}/{active}/.memlog.md`, where `{active}` is the key `resolve_party.py` already returned — the group id (e.g. `code-review-crew`), or `installed` for the default room. The folder is named after the party.
## Read it on entry — distill, don't dump
The log is append-only and grows every session, so don't pull the raw file into the party. Hand a reader subagent the memlog path (`{workflow.memory_dir}/{active}/.memlog.md`) and have it return a compact brief — a few hundred tokens of *where things stand now*, ready to play in character.
Then let the brief shape the room from the first beat, **in character**: behavioral state resumes (a cold pair opens cold, an alliance opens warm), threads pick up, callbacks land when they fit — organically, not recited on sight. Never break the fourth wall: the room *remembers*; it never announces it loaded anything, and forces nothing that doesn't fit.
## When to write
- **When a memorable beat lands** — a clash that shifts the room's temperature, an alliance forming, a line worth a future callback, a decision, an outcome.
- **A floor.** Once a couple of real exchanges are in from the start, even if nothing dramatic happened, capture what it's about and the opening dynamic.
At wrap-up, if the user does signal done, top up with the final outcome and anything memorable not yet captured.
Writes are silent. The room never announces "noted" or "I'll remember".
## What's worth remembering
The test for every entry: *would this color a future session, or make a callback land, or improve the party?* If not, leave it out. A handful of entries, never a recap, never a transcript. keep each entry as brief as possible but usable by future llm.
## New faces
When a character shows up who isn't in the party's roster — cast from an open-cast scene, or one the user adds on the fly — name them in the entry that captures the moment ("<name> turned up and …") so a recurring face can return next session. At wrap-up these are the faces the room offers to keep, saved into the party's roster through `references/create-party.md` (which writes via `bmad-customize`). Until saved they live only in the memlog, and the room re-conjures them from there.
## Write it
```
uv run {project-root}/_bmad/scripts/memlog.py append \
--workspace {workflow.memory_dir}/{active} \
--type <dynamic|moment|callback|outcome> \
--text "<one succinct line, in the room's own read of it>"
```
Add `--by <persona-code>` when a memory belongs to one character. Choose `init` vs `append` from the existence fact you already hold: the entry-read (and, on a mid-session room switch, that room's read) told you whether the memlog exists — `init --workspace {workflow.memory_dir}/{active}` once before the first append when it doesn't, plain `append` when it does. (`init` errors if the file already exists, so don't call it blind.)
If `memlog.py` is unavailable or a write errors, skip it silently and never stall the party on a failed write.
## Forget
The memlog is append-only by design — no surgical delete. To wipe a party's memory, delete its folder (`{workflow.memory_dir}/{active}/`). To correct a wrong memory, append a new entry that supersedes it; the room reads the latest state.
Keep entries sparse. The distilled read keeps the *room* lean no matter how big the log gets, but the on-disk file still grows append-only.

View File

@ -1,272 +0,0 @@
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.11"
# ///
"""Resolve the party-mode roster, lazily.
Merges the installed BMAD agents with the user's custom `party_members`
into one collective, then projects only what the moment needs:
* default (no flag) the active roster to load on entry: the
`default_party` group if one is configured, else the whole collective.
Other groups come back as names only, so nothing you aren't using is
loaded into the party.
* --list-groups just id + name + size for every configured group. The
cheap menu for "which room?", with no member detail.
* --party <id> full member detail for one chosen group, on demand
(e.g. when the user switches rooms). Unknown id returns the available
names instead of an error wall.
The merge is deterministic (a keyed union; a custom member whose code
matches an installed agent overrides it), so the orchestrator consumes a
resolved roster instead of re-deriving it every session.
Stdlib only (Python 3.11+ for tomllib). Shells out to the project's
resolve_config.py and resolve_customization.py; falls back to reading
customize.toml directly if the customization resolver is unavailable.
resolve_party.py --project-root P --skill S
resolve_party.py --project-root P --skill S --list-groups
resolve_party.py --project-root P --skill S --party writers-room
"""
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)
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_agents(project_root: Path):
"""Installed agents as {code: entry}. Empty dict (with a flag) on failure."""
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
return data.get("agents", {}) or {}, True
def load_workflow(project_root: Path, skill_root: Path):
"""Merged [workflow] table. Falls back to the skill's base customize.toml."""
script = project_root / "_bmad" / "scripts" / "resolve_customization.py"
data = _run_json([sys.executable, str(script), "--skill", str(skill_root), "--key", "workflow"])
if data is not None and "workflow" in data:
return data["workflow"]
# Fallback: read the skill's base customize.toml directly (no override merge).
toml_path = skill_root / "customize.toml"
if toml_path.exists():
try:
with toml_path.open("rb") as f:
return tomllib.load(f).get("workflow", {})
except (OSError, tomllib.TOMLDecodeError):
pass
return {}
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_collective(agents: dict, party_members: list):
"""One pool keyed by code. Custom members override matching installed agents.
Returns (collective, index, installed_codes):
* collective every member (installed + custom), the pool groups draw
from and the orchestrator can summon by name.
* index maps every resolvable token (code, prefix-stripped alias,
lower-cased name) to a canonical code.
* installed_codes the codes occupying an installed-agent slot, in
order. This is the DEFAULT room: installed agents (with any custom
override applied in place), and NOT the pure-custom additions. So
shipping or defining custom members grows the pool without crowding
the default party.
"""
collective = {}
index = {}
installed_codes = []
def register(code, entry):
collective[code] = entry
index[code] = code
index[code.lower()] = code
index[_alias(code).lower()] = code
name = entry.get("name")
if name:
index[name.lower()] = code
for code, info in agents.items():
register(code, {
"code": code,
"name": info.get("name", code),
"icon": info.get("icon", ""),
"title": info.get("title", ""),
"description": info.get("description", ""),
"module": info.get("module", ""),
"team": info.get("team", ""),
"source": "installed",
})
installed_codes.append(code)
for m in party_members or []:
code = m.get("code")
if not code:
continue
# A custom member overrides an installed agent it matches by code/alias/name.
canonical = index.get(code) or index.get(code.lower()) or code
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)
# An override keeps the installed slot; a brand-new custom does not join it.
return collective, index, installed_codes
def resolve_members(member_tokens, collective, index):
"""(resolved entries in listed order, unresolved tokens)."""
resolved, unresolved = [], []
for token in member_tokens or []:
code = index.get(token) or index.get(str(token).lower())
if code and code in collective:
resolved.append(collective[code])
else:
unresolved.append(token)
return resolved, unresolved
def group_menu(groups):
"""Names only — the cheap menu. Open-cast groups (no roster) are flagged."""
out = []
for g in groups or []:
if not isinstance(g, dict) or not g.get("id"):
continue
members = g.get("members", []) or []
entry = {"id": g["id"], "name": g.get("name", g["id"]),
"member_count": len(members)}
if not members:
entry["open_cast"] = True
out.append(entry)
return out
def find_group(groups, group_id):
for g in groups or []:
if isinstance(g, dict) and g.get("id") == group_id:
return g
return None
def group_detail(g, collective, index):
"""Full detail for one group: resolved members + the optional scene.
`scene` is a freeform line the orchestrator plays setting, what's
happening, room dynamics, in-the-moment character notes. Surfaced only
here (when a group is the active/chosen roster), never in the menu.
`members` is optional. With none, the group is open-cast: `open_cast`
is flagged and the scene describes the pool the orchestrator casts from
on the fly (e.g. "figures from the Star Wars Rebels universe"). A few
listed members anchor the room; the scene can still invite more.
"""
raw_members = g.get("members", []) or []
members, unresolved = resolve_members(raw_members, collective, index)
detail = {"active": g["id"], "name": g.get("name", g["id"]),
"members": members, "unresolved": unresolved,
"memory_enabled": bool(g.get("memory", False))}
if g.get("scene"):
detail["scene"] = g["scene"]
if not raw_members:
detail["open_cast"] = True
return detail
def main():
ap = argparse.ArgumentParser(description="Resolve the party-mode roster, lazily.")
ap.add_argument("--project-root", required=True)
ap.add_argument("--skill", required=True, help="Path to the bmad-party-mode skill dir")
ap.add_argument("--party", help="Resolve full detail for this group id")
ap.add_argument("--list-groups", action="store_true", help="Group names only")
args = ap.parse_args()
project_root = Path(args.project_root).resolve()
skill_root = Path(args.skill).resolve()
workflow = load_workflow(project_root, skill_root)
groups = workflow.get("party_groups", []) or []
default_party = workflow.get("default_party", "") or ""
party_mode = workflow.get("party_mode", "session") or "session"
# The global party_memory flag governs only the DEFAULT installed-agent room;
# a named group carries its own `memory` flag (resolved in group_detail).
party_memory = bool(workflow.get("party_memory", True))
# Group menu never needs the (more expensive) installed-agent resolve.
if args.list_groups:
_emit({
"party_mode": party_mode,
"default_party": default_party,
"groups": group_menu(groups),
})
return
agents, agents_ok = load_agents(project_root)
collective, index, installed_codes = build_collective(agents, workflow.get("party_members", []))
if args.party:
g = find_group(groups, args.party)
if g is None:
_emit({"error": "unknown_group", "requested": args.party,
"available": group_menu(groups)})
return
_emit({**group_detail(g, collective, index), "party_mode": party_mode})
return
# Default: the active roster to load on entry.
result = {"party_mode": party_mode, "groups": group_menu(groups),
"installed_agents_resolved": agents_ok}
g = find_group(groups, default_party) if default_party else None
if g is not None:
result.update(group_detail(g, collective, index))
else:
# No default group: the installed agents (custom additions stay in the
# pool but don't crowd the default room), exactly like a plain install.
result.update({"active": "installed",
"members": [collective[c] for c in installed_codes],
"memory_enabled": party_memory})
_emit(result)
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

@ -1,146 +0,0 @@
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.11"
# ///
"""Unit tests for resolve_party.py — merge, alias, override, group resolution."""
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import resolve_party 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 TestBuildCollective(unittest.TestCase):
def test_installed_agents_indexed_by_code_alias_and_name(self):
col, idx, _ = rp.build_collective(AGENTS, [])
self.assertEqual(set(col), {"bmad-agent-analyst", "bmad-agent-pm"})
self.assertEqual(idx["analyst"], "bmad-agent-analyst") # alias
self.assertEqual(idx["mary"], "bmad-agent-analyst") # name (ci)
self.assertEqual(idx["bmad-agent-pm"], "bmad-agent-pm") # full code
self.assertEqual(col["bmad-agent-analyst"]["source"], "installed")
def test_custom_member_appends(self):
col, _, _ = rp.build_collective(AGENTS, [{"code": "morpheus", "name": "Morpheus", "persona": "riddles"}])
self.assertIn("morpheus", col)
self.assertEqual(col["morpheus"]["source"], "custom")
self.assertEqual(col["morpheus"]["persona"], "riddles")
def test_custom_overrides_installed_by_alias(self):
col, _, _ = rp.build_collective(AGENTS, [{"code": "analyst", "name": "Mary-Custom", "persona": "p"}])
# Override lands on the canonical installed code, not a new "analyst" entry.
self.assertNotIn("analyst", col)
self.assertEqual(col["bmad-agent-analyst"]["source"], "custom")
self.assertEqual(col["bmad-agent-analyst"]["name"], "Mary-Custom")
def test_member_without_code_skipped(self):
col, _, _ = rp.build_collective(AGENTS, [{"name": "Nameless"}])
self.assertEqual(set(col), {"bmad-agent-analyst", "bmad-agent-pm"})
class TestResolveMembers(unittest.TestCase):
def setUp(self):
self.col, self.idx, _ = rp.build_collective(AGENTS, [{"code": "morpheus", "name": "Morpheus"}])
def test_resolves_in_listed_order_and_flags_unknowns(self):
resolved, unresolved = rp.resolve_members(["morpheus", "analyst", "ghost"], self.col, self.idx)
self.assertEqual([m["code"] for m in resolved], ["morpheus", "bmad-agent-analyst"])
self.assertEqual(unresolved, ["ghost"])
def test_empty(self):
self.assertEqual(rp.resolve_members([], self.col, self.idx), ([], []))
class TestGroups(unittest.TestCase):
GROUPS = [
{"id": "wr", "name": "Writers", "members": ["analyst", "morpheus"]},
{"id": "bad"}, # no name -> falls back to id; no members -> count 0
{"name": "no-id"}, # dropped from menu
]
def test_menu_is_names_only_with_counts_and_open_cast_flag(self):
menu = rp.group_menu(self.GROUPS)
self.assertEqual(menu, [
{"id": "wr", "name": "Writers", "member_count": 2},
{"id": "bad", "name": "bad", "member_count": 0, "open_cast": True},
])
def test_find_group(self):
self.assertEqual(rp.find_group(self.GROUPS, "wr")["name"], "Writers")
self.assertIsNone(rp.find_group(self.GROUPS, "missing"))
class TestGroupDetail(unittest.TestCase):
def setUp(self):
self.col, self.idx, _ = rp.build_collective(AGENTS, [{"code": "morpheus", "name": "Morpheus"}])
def test_scene_passes_through_when_present(self):
g = {"id": "tos-10-forward", "name": "Ten Forward", "members": ["morpheus"],
"scene": "Late evening, a few rounds in."}
d = rp.group_detail(g, self.col, self.idx)
self.assertEqual(d["scene"], "Late evening, a few rounds in.")
self.assertEqual([m["code"] for m in d["members"]], ["morpheus"])
def test_scene_omitted_when_absent_or_empty(self):
for g in ({"id": "g", "members": ["morpheus"]},
{"id": "g", "members": ["morpheus"], "scene": ""}):
self.assertNotIn("scene", rp.group_detail(g, self.col, self.idx))
def test_anchored_group_is_not_open_cast(self):
g = {"id": "g", "members": ["morpheus"]}
self.assertNotIn("open_cast", rp.group_detail(g, self.col, self.idx))
def test_open_cast_group_flagged_with_empty_members(self):
g = {"id": "rebels", "name": "Star Wars Rebels",
"scene": "Figures from the Rebels universe drop in as the topic calls for them."}
d = rp.group_detail(g, self.col, self.idx)
self.assertTrue(d["open_cast"])
self.assertEqual(d["members"], [])
self.assertEqual(d["scene"][:7], "Figures")
def test_memory_enabled_follows_group_flag_and_defaults_off(self):
on = rp.group_detail({"id": "g", "members": ["morpheus"], "memory": True}, self.col, self.idx)
self.assertTrue(on["memory_enabled"])
off = rp.group_detail({"id": "g", "members": ["morpheus"], "memory": False}, self.col, self.idx)
self.assertFalse(off["memory_enabled"])
absent = rp.group_detail({"id": "g", "members": ["morpheus"]}, self.col, self.idx)
self.assertFalse(absent["memory_enabled"]) # opt-in per named group
class TestInstalledCodesIsDefaultRoom(unittest.TestCase):
"""The default room is installed agents only; pure customs stay in the pool."""
def test_pure_custom_excluded_override_kept_in_default_room(self):
col, _, installed = rp.build_collective(AGENTS, [
{"code": "morpheus", "name": "Morpheus"}, # pure custom
{"code": "analyst", "name": "Mary-Custom", "persona": "p"}, # override
{"code": "sec-hawk", "name": "Vex"}, # shipped crew member
])
# Pure customs are in the pool...
self.assertIn("morpheus", col)
self.assertIn("sec-hawk", col)
# ...but NOT in the default room.
self.assertEqual(installed, ["bmad-agent-analyst", "bmad-agent-pm"])
default_room = [col[c]["code"] for c in installed]
self.assertEqual(default_room, ["bmad-agent-analyst", "bmad-agent-pm"])
# An override keeps its installed slot (and its custom content).
self.assertEqual(col["bmad-agent-analyst"]["name"], "Mary-Custom")
if __name__ == "__main__":
unittest.main()

View File

@ -18,7 +18,7 @@ Multiple skills may call to update the same spec over time.
## On Activation ## 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. 1. Resolve customization: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly.
2. Run `{workflow.activation_steps_prepend}`. Treat `{workflow.persistent_facts}` as foundational context (`file:` entries are loaded). 2. Run `{workflow.activation_steps_prepend}`. Treat `{workflow.persistent_facts}` as foundational context (`file:` entries are loaded).
3. Load `{project-root}/_bmad/core/config.yaml` (and `config.user.yaml` if present), root level and `bmm` section. Resolve `{user_name}`, `{communication_language}`, `{document_output_language}`, `{planning_artifacts}`, `{project_name}`, `{date}`. 3. Load `{project-root}/_bmad/core/config.yaml` (and `config.user.yaml` if present), root level and `bmm` section. Resolve `{user_name}`, `{communication_language}`, `{document_output_language}`, `{planning_artifacts}`, `{project_name}`, `{date}`.
4. Detect mode. **Headless** when any of: no TTY, programmatic caller (another skill or non-interactive runner), or the first message pre-supplies all inputs and asks for an artifact path back. **Interactive** otherwise. In interactive mode, greet by `{user_name}` in `{communication_language}`, stay in that language, and mention that `bmad-party-mode` and `bmad-advanced-elicitation` are available for deeper exploration on any field. 4. Detect mode. **Headless** when any of: no TTY, programmatic caller (another skill or non-interactive runner), or the first message pre-supplies all inputs and asks for an artifact path back. **Interactive** otherwise. In interactive mode, greet by `{user_name}` in `{communication_language}`, stay in that language, and mention that `bmad-party-mode` and `bmad-advanced-elicitation` are available for deeper exploration on any field.
@ -57,8 +57,8 @@ Deriving the contract from a living log instead of editing the contract in place
Writes go through the shared script — `{project-root}/_bmad/scripts/memlog.py`, the same location as `resolve_customization.py` (atomic; never read it back except to resume): Writes go through the shared script — `{project-root}/_bmad/scripts/memlog.py`, the same location as `resolve_customization.py` (atomic; never read it back except to resume):
- `uv run {project-root}/_bmad/scripts/memlog.py init --workspace {spec-folder} --field topic="<what is being specced>"` — once, at create. - `python3 {project-root}/_bmad/scripts/memlog.py init --workspace {spec-folder} --field topic="<what is being specced>"` — once, at create.
- `uv run {project-root}/_bmad/scripts/memlog.py append --workspace {spec-folder} --type <decision|constraint|capability|assumption|question|direction|note|event> --text "<one-line gist, reason included>"` — as each lands. - `python3 {project-root}/_bmad/scripts/memlog.py append --workspace {spec-folder} --type <decision|constraint|capability|assumption|question|direction|note|event> --text "<one-line gist, reason included>"` — as each lands.
- Terminal moments (a validation verdict, "spec finalized") are `--type event` entries; the memlog carries no status field. - Terminal moments (a validation verdict, "spec finalized") are `--type event` entries; the memlog carries no status field.
## The Operation ## The Operation

View File

@ -20,9 +20,9 @@ Name which (or which combination) applies, who is affected, and the backdrop tha
## Capabilities ## Capabilities
- **CAP-1** - id: CAP-1
- **intent:** {One sentence. "User or system can do X to achieve Y." WHAT, not HOW.} intent: {One sentence. "User or system can do X to achieve Y." WHAT, not HOW.}
- **success:** {Testable or demonstrable criterion. Something a test or a real demonstration can decide.} success: {Testable or demonstrable criterion. Something a test or a real demonstration can decide.}
## Constraints ## Constraints

View File

@ -11,4 +11,3 @@ 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-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-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-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
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)

View File

@ -10,14 +10,12 @@ Reads from four layers (highest priority last):
Outputs merged JSON to stdout. Errors go to stderr. Outputs merged JSON to stdout. Errors go to stderr.
Uses only the Python stdlib (`tomllib`) no third-party dependencies. Requires Python 3.11+ (uses stdlib `tomllib`). No `uv`, no `pip install`,
BMad is standardizing on `uv run` to invoke scripts (uv provisions a suitable no virtualenv plain `python3` is sufficient.
interpreter for you); a plain `python3` on PATH still works during the
transition. Either runner needs Python 3.11+ for `tomllib`.
uv run resolve_config.py --project-root /abs/path/to/project python3 resolve_config.py --project-root /abs/path/to/project
uv run resolve_config.py --project-root ... --key core python3 resolve_config.py --project-root ... --key core
uv run resolve_config.py --project-root ... --key agents python3 resolve_config.py --project-root ... --key agents
Merge rules (same as resolve_customization.py): Merge rules (same as resolve_customization.py):
- Scalars: override wins - Scalars: override wins

View File

@ -11,14 +11,12 @@ Skill name is derived from the basename of the skill directory.
Outputs merged JSON to stdout. Errors go to stderr. Outputs merged JSON to stdout. Errors go to stderr.
Uses only the Python stdlib (`tomllib`) no third-party dependencies. Requires Python 3.11+ (uses stdlib `tomllib`). No `uv`, no `pip install`,
BMad is standardizing on `uv run` to invoke scripts (uv provisions a suitable no virtualenv plain `python3` is sufficient.
interpreter for you); a plain `python3` on PATH still works during the
transition. Either runner needs Python 3.11+ for `tomllib`.
uv run resolve_customization.py --skill /abs/path/to/skill-dir python3 resolve_customization.py --skill /abs/path/to/skill-dir
uv run resolve_customization.py --skill ... --key agent python3 resolve_customization.py --skill ... --key agent
uv run resolve_customization.py --skill ... --key agent.menu python3 resolve_customization.py --skill ... --key agent.menu
Merge rules (purely structural no field-name special-casing): Merge rules (purely structural no field-name special-casing):
- Scalars (string, int, bool, float): override wins - Scalars (string, int, bool, float): override wins

View File

@ -3319,185 +3319,137 @@ async function runTests() {
console.log(''); console.log('');
// ============================================================ // ============================================================
// Test Suite 46: uv environment check (version parsing + messaging) // Test Suite 46: Python environment check (version parsing + classification)
// ============================================================ // ============================================================
console.log(`${colors.yellow}Test Suite 46: uv-check version parsing and messaging${colors.reset}\n`); console.log(`${colors.yellow}Test Suite 46: python-check version parsing and classification${colors.reset}\n`);
try { try {
const { parseUvVersion, detectUv } = require('../tools/installer/core/uv-check'); const { parsePythonVersion, classifyPython, detectPython } = require('../tools/installer/core/python-check');
// Version parsing // Version parsing
const plain = parseUvVersion('uv 0.5.31'); const v312 = parsePythonVersion('Python 3.12.1');
assert(plain && plain.major === 0 && plain.minor === 5 && plain.patch === 31, 'parses "uv 0.5.31"'); assert(v312 && v312.major === 3 && v312.minor === 12 && v312.patch === 1, 'parses "Python 3.12.1"');
const brew = parseUvVersion('uv 0.5.31 (Homebrew 2025-02-12)'); const v311 = parsePythonVersion('Python 3.11.0\n');
assert(brew && brew.raw === '0.5.31', 'parses uv version with build suffix'); assert(v311 && v311.raw === '3.11.0', 'parses with trailing newline');
const noPatch = parseUvVersion('uv 1.2'); const v2 = parsePythonVersion('\nPython 2.7.18');
assert(v2 && v2.major === 2, 'parses Python 2 output (stderr-style)');
const noPatch = parsePythonVersion('Python 3.13');
assert(noPatch && noPatch.patch === 0, 'missing patch defaults to 0'); assert(noPatch && noPatch.patch === 0, 'missing patch defaults to 0');
assert(parseUvVersion('') === null, 'empty output returns null'); assert(parsePythonVersion('') === null, 'empty output returns null');
assert(parseUvVersion('command not found: uv') === null, 'non-version output returns null'); assert(parsePythonVersion('command not found: python3') === null, 'non-version output returns null');
assert(parseUvVersion(null) === null, 'null output returns null'); assert(parsePythonVersion(null) === null, 'null output returns null');
// Detection smoke test — must not throw; result is null or well-formed. // Classification against feature requirements
const detectedUv = detectUv(); assert(classifyPython({ major: 3, minor: 11 }) === 'full', '3.11 is full support (tomllib floor)');
assert(detectedUv === null || typeof detectedUv.version.raw === 'string', 'detectUv returns null or a well-formed result'); assert(classifyPython({ major: 3, minor: 13 }) === 'full', '3.13 is full support');
assert(classifyPython({ major: 4, minor: 0 }) === 'full', 'hypothetical 4.0 is full support');
assert(classifyPython({ major: 3, minor: 10 }) === 'partial', '3.10 is partial (memlog yes, tomllib no)');
assert(classifyPython({ major: 3, minor: 8 }) === 'partial', '3.8 is partial (memlog floor)');
assert(classifyPython({ major: 3, minor: 7 }) === 'unsupported', '3.7 is unsupported');
assert(classifyPython({ major: 2, minor: 7 }) === 'unsupported', '2.7 is unsupported');
assert(classifyPython(null) === 'none', 'no python is none');
// checkUvEnvironment branch coverage — stub detection + prompts so the // Detection smoke test — must not throw, and if it finds a Python the
// assertions are deterministic regardless of whether uv is installed. // result must be well-formed. (CI machines may or may not have Python.)
const uvCheck = require('../tools/installer/core/uv-check'); const detected = detectPython();
assert(
detected === null ||
(typeof detected.command === 'string' &&
typeof detected.version.raw === 'string' &&
typeof detected.isRuntimeCommand === 'boolean'),
'detectPython returns null or a well-formed result',
);
// checkPythonEnvironment branch coverage — stub detection, prompts, and
// process.exit so the assertions are deterministic regardless of the
// machine's Python. python-check resolves detectPython via module.exports
// and prompts via the shared module object, so swapping properties works.
const pythonCheck = require('../tools/installer/core/python-check');
const promptsModule = require('../tools/installer/prompts'); const promptsModule = require('../tools/installer/prompts');
const realUv = { detectUv: uvCheck.detectUv, log: promptsModule.log, note: promptsModule.note }; const real = {
const stubUv = (detectResult) => { detectPython: pythonCheck.detectPython,
const seen = { success: [], warn: [], note: [] }; log: promptsModule.log,
uvCheck.detectUv = () => detectResult; note: promptsModule.note,
select: promptsModule.select,
cancel: promptsModule.cancel,
exit: process.exit,
};
const stub = (detectResult, selectAnswer) => {
const seen = { success: [], warn: [], info: [], note: [], select: [], cancel: [], exit: [] };
pythonCheck.detectPython = () => detectResult;
promptsModule.log = { promptsModule.log = {
success: async (m) => void seen.success.push(m), success: async (m) => void seen.success.push(m),
warn: async (m) => void seen.warn.push(m), warn: async (m) => void seen.warn.push(m),
info: async () => {}, info: async (m) => void seen.info.push(m),
error: async () => {}, error: async () => {},
}; };
promptsModule.note = async (m, t) => void seen.note.push(t || m); promptsModule.note = async (m, t) => void seen.note.push(t || m);
promptsModule.select = async (opts) => {
seen.select.push(opts.message);
return selectAnswer;
};
promptsModule.cancel = async (m) => void seen.cancel.push(m);
process.exit = (code) => {
seen.exit.push(code);
throw new Error('__stub_exit__');
};
return seen; return seen;
}; };
try { try {
// Branch: uv present — success, no warning. const v = (major, minor, patch) => ({ major, minor, patch, raw: `${major}.${minor}.${patch}` });
let seen = stubUv({ version: { major: 0, minor: 5, patch: 31, raw: '0.5.31' } });
let result = await uvCheck.checkUvEnvironment();
assert(result.status === 'found' && seen.success.length === 1, 'uv present logs success');
assert(seen.success[0].includes('uv run') && seen.warn.length === 0, 'uv present mentions uv run, no warning');
// Branch: uv missing — warn + setup note, never blocks (no prompt). // Branch: full support via the runtime command — success, no prompt.
seen = stubUv(null); let seen = stub({ command: 'python3', version: v(3, 12, 1), isRuntimeCommand: true }, 'continue');
result = await uvCheck.checkUvEnvironment(); let result = await pythonCheck.checkPythonEnvironment();
assert(result.status === 'missing' && seen.warn.length === 1, 'uv missing warns'); assert(result.status === 'full' && seen.success.length === 1, 'full support via python3 logs success');
assert(seen.warn[0].includes('de facto standard'), 'uv-missing warning frames uv as the de facto standard'); assert(seen.select.length === 0 && seen.warn.length === 0, 'full support via python3 skips warning and ack prompt');
assert(seen.note.length === 1 && seen.note[0].includes('uv'), 'uv missing shows a setup note');
} finally {
uvCheck.detectUv = realUv.detectUv;
promptsModule.log = realUv.log;
promptsModule.note = realUv.note;
}
} catch (error) {
console.log(`${colors.red}Test Suite 46 setup failed: ${error.message}${colors.reset}`);
console.log(error.stack);
failed++;
}
console.log(''); // Branch: modern Python found, but not as `python3` — runtime mismatch.
seen = stub({ command: 'py -3', version: v(3, 12, 0), isRuntimeCommand: false }, 'continue');
result = await pythonCheck.checkPythonEnvironment();
assert(seen.success.length === 0, 'python3-mismatch never reports full support');
assert(
seen.warn.length === 1 && seen.warn[0].includes('python3') && seen.warn[0].includes('py -3'),
'python3-mismatch warns that scripts invoke python3',
);
assert(seen.select.length === 1 && result.status === 'full', 'python3-mismatch still requires the ack prompt');
// ============================================================ // Branch: partial support (3.83.10) — warn + ack, continue returns.
// Test Suite 47: WSL shell using Windows Node guard seen = stub({ command: 'python3', version: v(3, 9, 5), isRuntimeCommand: true }, 'continue');
// ============================================================ result = await pythonCheck.checkPythonEnvironment();
console.log(`${colors.yellow}Test Suite 47: WSL Windows Node guard${colors.reset}\n`); assert(
result.status === 'partial' && seen.warn.length === 1 && seen.warn[0].includes('3.11+'),
'partial support warns about tomllib floor',
);
assert(seen.select.length === 1 && seen.exit.length === 0, 'partial support prompts and continue proceeds');
try { // Branch: no Python, non-interactive — warn + info, never prompts.
const wslNodeCheck = require('../tools/installer/core/wsl-node-check'); seen = stub(null, 'continue');
result = await pythonCheck.checkPythonEnvironment({ nonInteractive: true });
assert(result.status === 'none' && seen.warn[0].includes('No Python found'), 'non-interactive with no Python warns');
assert(seen.select.length === 0 && seen.info.length === 1, 'non-interactive skips the ack prompt and logs continuation');
let detection = wslNodeCheck.detectWindowsNodeFromWsl({ // Branch: no Python, interactive, user quits — cancel message + exit 0.
platform: 'win32', seen = stub(null, 'quit');
env: { WSL_DISTRO_NAME: 'Ubuntu-26.04' },
cwd: String.raw`C:\Windows`,
execPath: String.raw`C:\Program Files\nodejs\node.exe`,
});
assert(detection.isMismatch === true, 'detects Windows Node launched from WSL via WSL_DISTRO_NAME');
detection = wslNodeCheck.detectWindowsNodeFromWsl({
platform: 'win32',
env: { PWD: '/home/devuser/projects/md2pdf' },
cwd: String.raw`\\wsl.localhost\Ubuntu-26.04\home\devuser\projects\md2pdf`,
execPath: String.raw`C:\Program Files\nodejs\node.exe`,
});
assert(detection.isMismatch === true, 'detects Windows Node launched from WSL via Linux PWD / WSL UNC cwd');
detection = wslNodeCheck.detectWindowsNodeFromWsl({
platform: 'win32',
env: {},
cwd: String.raw`\\wsl$\Ubuntu-26.04\home\devuser\projects\md2pdf`,
execPath: String.raw`C:\Program Files\nodejs\node.exe`,
});
assert(detection.isMismatch === true, 'detects Windows Node launched from WSL via legacy WSL UNC cwd');
detection = wslNodeCheck.detectWindowsNodeFromWsl({
platform: 'linux',
env: { WSL_DISTRO_NAME: 'Ubuntu-26.04', PWD: '/home/devuser/projects/md2pdf' },
cwd: '/home/devuser/projects/md2pdf',
execPath: '/usr/bin/node',
});
assert(detection.isMismatch === false, 'allows native Linux Node inside WSL');
detection = wslNodeCheck.detectWindowsNodeFromWsl({
platform: 'win32',
env: { PWD: String.raw`C:\Users\devuser\project` },
cwd: String.raw`C:\Users\devuser\project`,
execPath: String.raw`C:\Program Files\nodejs\node.exe`,
});
assert(detection.isMismatch === false, 'allows normal Windows Node outside WSL');
detection = wslNodeCheck.detectWindowsNodeFromWsl({
platform: 'win32',
env: { PWD: '/c/Users/devuser/project' },
cwd: String.raw`C:\Users\devuser\project`,
execPath: String.raw`C:\Program Files\nodejs\node.exe`,
});
assert(detection.isMismatch === false, 'allows Git Bash Windows-drive PWD outside WSL');
detection = wslNodeCheck.detectWindowsNodeFromWsl({
platform: 'win32',
env: { PWD: '/cygdrive/c/Users/devuser/project' },
cwd: String.raw`C:\Users\devuser\project`,
execPath: String.raw`C:\Program Files\nodejs\node.exe`,
});
assert(detection.isMismatch === false, 'allows Cygwin Windows-drive PWD outside WSL');
const message = wslNodeCheck.formatWindowsNodeFromWslMessage({
isMismatch: true,
reason: 'WSL_DISTRO_NAME is set',
execPath: String.raw`C:\Program Files\nodejs\node.exe`,
});
assert(message.includes('Install Node.js inside WSL'), 'guard message tells user to install Node.js inside WSL');
assert(message.includes(String.raw`C:\Program Files\nodejs\node.exe`), 'guard message includes detected Windows Node path');
const promptsModule = require('../tools/installer/prompts');
const real = {
detectWindowsNodeFromWsl: wslNodeCheck.detectWindowsNodeFromWsl,
log: promptsModule.log,
exit: process.exit,
};
const seen = { errors: [], exit: [] };
wslNodeCheck.detectWindowsNodeFromWsl = () => ({
isMismatch: true,
reason: 'WSL_INTEROP is set',
execPath: String.raw`C:\Program Files\nodejs\node.exe`,
});
promptsModule.log = {
error: async (m) => void seen.errors.push(m),
info: async () => {},
success: async () => {},
warn: async () => {},
message: async () => {},
step: async () => {},
};
process.exit = (code) => {
seen.exit.push(code);
throw new Error('__stub_exit__');
};
try {
let threw = false; let threw = false;
try { try {
await wslNodeCheck.checkWindowsNodeFromWsl(); await pythonCheck.checkPythonEnvironment();
} catch (error) { } catch (error) {
threw = error.message === '__stub_exit__'; threw = error.message === '__stub_exit__';
} }
assert(threw && seen.exit[0] === 1, 'guard exits with code 1 when Windows Node is launched from WSL'); assert(threw && seen.exit.length === 1 && seen.exit[0] === 0, 'quit choice exits 0 (user-cancel convention)');
assert(seen.errors[0].includes('Windows Node.js was launched from a WSL shell'), 'guard logs the mismatch explanation'); assert(seen.cancel.length === 1, 'quit choice shows the cancel guidance');
} finally { } finally {
wslNodeCheck.detectWindowsNodeFromWsl = real.detectWindowsNodeFromWsl; pythonCheck.detectPython = real.detectPython;
promptsModule.log = real.log; promptsModule.log = real.log;
promptsModule.note = real.note;
promptsModule.select = real.select;
promptsModule.cancel = real.cancel;
process.exit = real.exit; process.exit = real.exit;
} }
} catch (error) { } catch (error) {
console.log(`${colors.red}Test Suite 47 setup failed: ${error.message}${colors.reset}`); console.log(`${colors.red}Test Suite 46 setup failed: ${error.message}${colors.reset}`);
console.log(error.stack); console.log(error.stack);
failed++; failed++;
} }

View File

@ -75,9 +75,6 @@ module.exports = {
return; return;
} }
const { checkWindowsNodeFromWsl } = require('../core/wsl-node-check');
await checkWindowsNodeFromWsl();
// Set debug flag as environment variable for all components // Set debug flag as environment variable for all components
if (options.debug) { if (options.debug) {
process.env.BMAD_DEBUG_MANIFEST = 'true'; process.env.BMAD_DEBUG_MANIFEST = 'true';

View File

@ -1233,9 +1233,6 @@ class Installer {
` 1. Launch your AI agent from your project folder`, ` 1. Launch your AI agent from your project folder`,
` 2. Not sure what to do? Invoke the ${color.cyan('bmad-help')} skill and ask it what to do!`, ` 2. Not sure what to do? Invoke the ${color.cyan('bmad-help')} skill and ask it what to do!`,
'', '',
` ${color.cyan('Tip:')} BMAD workflows increasingly run Python scripts via ${color.cyan('uv run')} — uv is`,
` becoming the de facto standard. If you don't have it yet, ask your agent to set it up.`,
'',
` Blog, Docs and Guides: ${color.blue('https://bmadcode.com/')}`, ` Blog, Docs and Guides: ${color.blue('https://bmadcode.com/')}`,
` Community: ${color.blue('https://discord.gg/gk8jAdXWmj')}`, ` Community: ${color.blue('https://discord.gg/gk8jAdXWmj')}`,
); );

View File

@ -0,0 +1,199 @@
const { spawnSync } = require('node:child_process');
const prompts = require('../prompts');
// Python 3.11 added stdlib `tomllib` (PEP 680), which the shared scripts in
// src/scripts/ (resolve_config.py, resolve_customization.py) require to read
// BMAD's TOML config files. memlog.py is more lenient and runs on 3.8+.
const PYTHON_FULL_SUPPORT = { major: 3, minor: 11 };
const PYTHON_PARTIAL_SUPPORT = { major: 3, minor: 8 };
// Every runtime call site (skill steps, on_complete hooks) invokes a literal
// `python3`, so only that command's version vouches for BMAD features. The
// fallback probes exist to tell the user "Python is installed, but not under
// the name BMAD uses" instead of a misleading "No Python found".
const RUNTIME_COMMAND = 'python3';
const PROBE_CANDIDATES =
process.platform === 'win32'
? [
{ command: 'python3', args: ['--version'] },
{ command: 'py', args: ['-3', '--version'] },
{ command: 'python', args: ['--version'] },
]
: [
{ command: 'python3', args: ['--version'] },
{ command: 'python', args: ['--version'] },
];
/**
* Parse a `python --version` output line into version parts.
* Python 3 prints to stdout; Python 2 printed to stderr callers pass both.
* @param {string} output - Combined stdout/stderr from `python --version`
* @returns {{major: number, minor: number, patch: number, raw: string}|null}
*/
function parsePythonVersion(output) {
if (!output) return null;
const match = output.match(/Python\s+(\d+)\.(\d+)(?:\.(\d+))?/);
if (!match) return null;
return {
major: Number(match[1]),
minor: Number(match[2]),
patch: Number(match[3] || 0),
raw: `${match[1]}.${match[2]}.${match[3] || 0}`,
};
}
/**
* Classify a detected Python version against BMAD's feature requirements.
* @param {{major: number, minor: number}|null} version
* @returns {'full'|'partial'|'unsupported'|'none'}
*/
function classifyPython(version) {
if (!version) return 'none';
const { major, minor } = version;
if (major > PYTHON_FULL_SUPPORT.major || (major === PYTHON_FULL_SUPPORT.major && minor >= PYTHON_FULL_SUPPORT.minor)) {
return 'full';
}
if (major === PYTHON_PARTIAL_SUPPORT.major && minor >= PYTHON_PARTIAL_SUPPORT.minor) {
return 'partial';
}
return 'unsupported';
}
/**
* Run one probe candidate and return its parsed version, or null.
* @param {{command: string, args: string[]}} candidate
* @returns {{major: number, minor: number, patch: number, raw: string}|null}
*/
function probeVersion(candidate) {
const run = (extra = {}) =>
spawnSync(candidate.command, candidate.args, {
encoding: 'utf8',
timeout: 5000,
windowsHide: true,
...extra,
});
let result = run();
// Node >=18.20/20.12 refuses to spawn .bat/.cmd without a shell
// (CVE-2024-27980 hardening) and reports EINVAL — pyenv-win ships its
// python shims as .bat. Args here are static literals, so a shell retry
// is injection-safe.
if (result.error && result.error.code === 'EINVAL' && process.platform === 'win32') {
result = run({ shell: true });
}
if (result.error) return null;
return parsePythonVersion(`${result.stdout || ''}\n${result.stderr || ''}`);
}
/**
* Probe the local environment for a Python interpreter.
* Tries each candidate command and returns the first that reports a version.
* `isRuntimeCommand` is true only when the match is `python3` the command
* BMAD scripts actually invoke.
* @returns {{command: string, version: {major: number, minor: number, patch: number, raw: string}, isRuntimeCommand: boolean}|null}
*/
function detectPython() {
for (const candidate of PROBE_CANDIDATES) {
try {
const version = probeVersion(candidate);
if (version) {
const display = candidate.args.length > 1 ? `${candidate.command} ${candidate.args.slice(0, -1).join(' ')}` : candidate.command;
return { command: display, version, isRuntimeCommand: candidate.command === RUNTIME_COMMAND };
}
} catch {
// Candidate not runnable — try the next one.
}
}
return null;
}
function upgradeHints() {
return [
'How to get Python 3.11+ (as `python3`):',
' macOS: brew install python3',
' Windows: winget install Python.Python.3.12 (then ensure `python3` resolves, e.g. enable the python3 alias)',
' Linux/WSL: sudo apt install python3 (Ubuntu 24.04+ ships 3.12; older distros: use pyenv or deadsnakes)',
' Docker: add python3 to your image (e.g. apk add python3 / apt-get install -y python3)',
].join('\n');
}
/**
* Check the local Python environment and warn about degraded BMAD features.
*
* Warn-don't-block: most of BMAD works without Python, so the install always
* may proceed but the user must explicitly acknowledge the warning so it
* can't scroll past unseen. In non-interactive runs (--yes, or stdin is not
* a TTY) the warning is logged and the install continues without a prompt.
*
* @param {Object} [options]
* @param {boolean} [options.nonInteractive=false] - Skip the ack prompt (--yes, or no TTY)
* @returns {Promise<{status: string, detected: Object|null}>}
*/
async function checkPythonEnvironment({ nonInteractive = false } = {}) {
// Called via module.exports so tests can stub detection.
const detected = module.exports.detectPython();
const status = classifyPython(detected ? detected.version : null);
if (status === 'full' && detected.isRuntimeCommand) {
await prompts.log.success(`Python ${detected.version.raw} detected (${detected.command}) — all BMAD features supported.`);
return { status, detected };
}
if (detected && !detected.isRuntimeCommand) {
await prompts.log.warn(
`Python ${detected.version.raw} found via \`${detected.command}\`, but BMAD scripts invoke \`python3\`, which is not on PATH.\n` +
`Python-powered features (memlog session memory, TOML config resolution) won't run until \`python3\` resolves —\n` +
`add a python3 alias/shim, or reinstall Python with the python3 launcher enabled.`,
);
} else if (status === 'partial') {
await prompts.log.warn(
`Python ${detected.version.raw} detected (${detected.command}) — BMAD's TOML config tools need Python 3.11+ (stdlib tomllib).\n` +
`Works: memlog session memory. Won't work: config/customization resolution scripts.`,
);
} else {
const found =
status === 'unsupported' ? `Python ${detected.version.raw} detected (${detected.command}) — too old.` : 'No Python found on PATH.';
await prompts.log.warn(
`${found} BMAD installs fine without it, but Python-powered features\n` +
`(memlog session memory, TOML config resolution) won't run until Python 3.11+ is available.`,
);
}
await prompts.note(upgradeHints(), 'Python 3.11+ recommended');
if (nonInteractive) {
await prompts.log.info('Continuing anyway (non-interactive run). You can fix Python later — no reinstall needed.');
return { status, detected };
}
const choice = await prompts.select({
message: "BMAD's Python-powered features won't work yet. How do you want to proceed?",
choices: [
{
name: 'Continue install',
value: 'continue',
hint: 'BMAD works without Python — you can fix Python later, no reinstall needed',
},
{
name: 'Quit and fix Python first',
value: 'quit',
hint: 'make Python 3.11+ available as python3, then re-run the installer',
},
],
default: 'continue',
});
if (choice === 'quit') {
await prompts.cancel('Make Python 3.11+ available as `python3` (see hints above), then re-run the installer.');
process.exit(0);
}
return { status, detected };
}
module.exports = {
checkPythonEnvironment,
detectPython,
parsePythonVersion,
classifyPython,
PYTHON_FULL_SUPPORT,
PYTHON_PARTIAL_SUPPORT,
};

View File

@ -1,97 +0,0 @@
const { spawnSync } = require('node:child_process');
const prompts = require('../prompts');
// `uv` (https://docs.astral.sh/uv/) is becoming the de facto standard for
// running the Python scripts BMAD workflows shell out to: `uv run <script>`
// resolves the interpreter and any dependencies on demand, so skills don't
// have to assume a particular `python3` is on PATH. The ecosystem is mid-
// migration — some skills still call `python3` directly — so a missing `uv`
// is a warning, not a blocker: BMAD installs and runs either way.
const RUNTIME_COMMAND = 'uv';
/**
* Parse `uv --version` output into version parts.
* Example outputs: "uv 0.5.31", "uv 0.5.31 (Homebrew 2025-02-12)".
* @param {string} output - stdout/stderr from `uv --version`
* @returns {{major: number, minor: number, patch: number, raw: string}|null}
*/
function parseUvVersion(output) {
if (!output) return null;
const match = output.match(/uv\s+(\d+)\.(\d+)(?:\.(\d+))?/i);
if (!match) return null;
return {
major: Number(match[1]),
minor: Number(match[2]),
patch: Number(match[3] || 0),
raw: `${match[1]}.${match[2]}.${match[3] || 0}`,
};
}
/**
* Probe the local environment for `uv`.
* @returns {{version: {major: number, minor: number, patch: number, raw: string}}|null}
*/
function detectUv() {
let result;
try {
result = spawnSync(RUNTIME_COMMAND, ['--version'], {
encoding: 'utf8',
timeout: 5000,
windowsHide: true,
});
} catch {
return null;
}
if (!result || result.error) return null;
const version = parseUvVersion(`${result.stdout || ''}\n${result.stderr || ''}`);
return version ? { version } : null;
}
function setupHints() {
return [
'BMAD workflows increasingly run Python scripts via `uv run`, which manages',
'the interpreter and dependencies for you — no manual venv or pip needed.',
'',
'Easiest path: ask your AI agent to "install and set up uv for me".',
'',
'Or install it yourself:',
' macOS/Linux: curl -LsSf https://astral.sh/uv/install.sh | sh',
' Windows: powershell -c "irm https://astral.sh/uv/install.ps1 | iex"',
' Homebrew: brew install uv',
' Docs: https://docs.astral.sh/uv/getting-started/installation/',
].join('\n');
}
/**
* Check whether `uv` is available and inform the user.
*
* Warn-don't-block, and no acknowledgement prompt: `uv` is on its way to being
* the standard runner for BMAD's Python scripts, but the migration is still in
* progress, so the install never stops on its account. The note tells the user
* how to set it up (preferably by asking their agent).
*
* @returns {Promise<{status: 'found'|'missing', detected: Object|null}>}
*/
async function checkUvEnvironment() {
// Called via module.exports so tests can stub detection.
const detected = module.exports.detectUv();
if (detected) {
await prompts.log.success(`uv ${detected.version.raw} detected — ready to run BMAD's Python-powered scripts via \`uv run\`.`);
return { status: 'found', detected };
}
await prompts.log.warn(
"uv not found on PATH. uv is becoming the de facto standard for running BMAD's Python\n" +
'scripts (`uv run <script>`), and it provisions the interpreter for you. BMAD installs\n' +
'fine without it, but setting up uv now keeps you ahead as workflows adopt it.',
);
await prompts.note(setupHints(), 'uv recommended');
return { status: 'missing', detected: null };
}
module.exports = {
checkUvEnvironment,
detectUv,
parseUvVersion,
};

View File

@ -1,109 +0,0 @@
const prompts = require('../prompts');
const WSL_UNC_PATTERN = /^\\\\wsl(?:\.localhost|\$)?\\/i;
function normalizePath(value) {
return typeof value === 'string' ? value.replaceAll('/', '\\').toLowerCase() : '';
}
function isLinuxStylePath(value) {
return (
typeof value === 'string' &&
value.startsWith('/') &&
!value.startsWith('//') &&
!/^\/[a-z](?:\/|$)/i.test(value) &&
!/^\/cygdrive\/[a-z](?:\/|$)/i.test(value)
);
}
function isWslUncPath(value) {
return WSL_UNC_PATTERN.test(value || '');
}
/**
* Detect the broken interop case where WSL resolved node/npx to Windows.
* @param {Object} [runtime]
* @param {string} [runtime.platform]
* @param {Object} [runtime.env]
* @param {string} [runtime.cwd]
* @param {string} [runtime.execPath]
* @returns {{isMismatch: boolean, reason: string|null, execPath: string}}
*/
function detectWindowsNodeFromWsl(runtime = {}) {
const platform = runtime.platform || process.platform;
const env = runtime.env || process.env;
const cwd = runtime.cwd || safeCwd();
const execPath = runtime.execPath || process.execPath || '';
if (platform !== 'win32') {
return { isMismatch: false, reason: null, execPath };
}
if (env.WSL_DISTRO_NAME) {
return { isMismatch: true, reason: 'WSL_DISTRO_NAME is set', execPath };
}
if (env.WSL_INTEROP) {
return { isMismatch: true, reason: 'WSL_INTEROP is set', execPath };
}
if (isLinuxStylePath(env.PWD)) {
return { isMismatch: true, reason: 'PWD is a Linux path', execPath };
}
if (isWslUncPath(cwd)) {
return { isMismatch: true, reason: 'current directory is a WSL UNC path', execPath };
}
const normalizedExecPath = normalizePath(execPath);
if (normalizedExecPath.includes('\\wsl$\\') || normalizedExecPath.includes('\\wsl.localhost\\')) {
return { isMismatch: true, reason: 'Node executable path is under a WSL UNC path', execPath };
}
return { isMismatch: false, reason: null, execPath };
}
function safeCwd() {
try {
return process.cwd();
} catch {
return '';
}
}
function formatWindowsNodeFromWslMessage(detection) {
const lines = [
'Windows Node.js was launched from a WSL shell.',
'',
'This usually means Node.js is not installed inside the WSL distro, so WSL resolved `node`/`npx` to Windows.',
'The installer cannot safely continue because Linux paths may be interpreted as Windows paths.',
'',
'Install Node.js inside WSL, then rerun the same command from the WSL terminal.',
];
if (detection.execPath) {
lines.push('', `Detected Node executable: ${detection.execPath}`);
}
if (detection.reason) {
lines.push(`Detection signal: ${detection.reason}`);
}
return lines.join('\n');
}
async function checkWindowsNodeFromWsl() {
const detection = module.exports.detectWindowsNodeFromWsl();
if (!detection.isMismatch) {
return detection;
}
await prompts.log.error(formatWindowsNodeFromWslMessage(detection));
process.exit(1);
}
module.exports = {
checkWindowsNodeFromWsl,
detectWindowsNodeFromWsl,
formatWindowsNodeFromWslMessage,
};

View File

@ -162,13 +162,6 @@ platforms:
target_dir: .agents/skills target_dir: .agents/skills
global_target_dir: ~/.config/agents/skills global_target_dir: ~/.config/agents/skills
hermes:
name: "Hermes Agent"
preferred: false
installer:
target_dir: .agents/skills
global_target_dir: ~/.hermes/skills
iflow: iflow:
name: "iFlow" name: "iFlow"
preferred: false preferred: false

View File

@ -12,10 +12,6 @@ startMessage: |
🌟 100% free. 100% open source. Always. 🌟 100% free. 100% open source. Always.
No paywalls. No gated content. Knowledge shared, not sold. No paywalls. No gated content. Knowledge shared, not sold.
🐍 HEADS UP: uv (https://docs.astral.sh/uv/) is becoming the de facto standard
for running the Python scripts BMAD workflows rely on (`uv run <script>`).
If it's not set up yet, ask your AI agent to "install and set up uv for me".
🌐 CONNECT: 🌐 CONNECT:
Website: https://bmadcode.com/ Website: https://bmadcode.com/
Discord: https://discord.gg/gk8jAdXWmj Discord: https://discord.gg/gk8jAdXWmj

View File

@ -161,16 +161,15 @@ class UI {
const messageLoader = new MessageLoader(); const messageLoader = new MessageLoader();
await messageLoader.displayStartMessage(); await messageLoader.displayStartMessage();
// Probe for `uv` before any other prompts: it's becoming the de facto // Probe the local Python before any other prompts: several BMAD features
// runner for the Python scripts BMAD workflows shell out to // (memlog session memory, TOML config resolution) need Python 3.11+ at
// (`uv run <script>`), and uv provisions the interpreter itself, so it's // runtime. Warn-don't-block, but require an explicit ack so the warning
// the single thing worth checking for. The migration is still in progress // can't scroll past unseen. The installer runs in the destination
// (some skills still call `python3` directly), so this is informational — // environment, so probing PATH here tests the right machine.
// warn-don't-block, no ack prompt — and just points the user at setup // Skip the ack when stdin isn't a TTY (CI/Docker/piped): clack's select
// (ideally "ask your agent to set up uv"). The installer runs in the // on closed stdin resolves to cancel, which would silently exit 0.
// destination environment, so probing PATH here tests the right machine. const { checkPythonEnvironment } = require('./core/python-check');
const { checkUvEnvironment } = require('./core/uv-check'); await checkPythonEnvironment({ nonInteractive: !!options.yes || !process.stdin.isTTY });
await checkUvEnvironment();
// Parse channel flags (--channel/--all-*/--next=/--pin) once. Warnings // Parse channel flags (--channel/--all-*/--next=/--pin) once. Warnings
// are surfaced immediately so the user sees them before any git ops run. // are surfaced immediately so the user sees them before any git ops run.

View File

@ -106,7 +106,7 @@ export default defineConfig({
label: 'Tutorials', label: 'Tutorials',
translations: { 'ko-KR': '튜토리얼', 'vi-VN': 'Hướng dẫn nhập môn', 'zh-CN': '教程', 'fr-FR': 'Tutoriels', 'cs-CZ': 'Tutoriály' }, translations: { 'ko-KR': '튜토리얼', 'vi-VN': 'Hướng dẫn nhập môn', 'zh-CN': '教程', 'fr-FR': 'Tutoriels', 'cs-CZ': 'Tutoriály' },
collapsed: false, collapsed: false,
items: [{ autogenerate: { directory: 'tutorials' } }], autogenerate: { directory: 'tutorials' },
}, },
{ {
label: 'How-To Guides', label: 'How-To Guides',
@ -118,7 +118,7 @@ export default defineConfig({
'cs-CZ': 'Praktické návody', 'cs-CZ': 'Praktické návody',
}, },
collapsed: true, collapsed: true,
items: [{ autogenerate: { directory: 'how-to' } }], autogenerate: { directory: 'how-to' },
}, },
{ {
label: 'Explanation', label: 'Explanation',
@ -130,13 +130,13 @@ export default defineConfig({
'cs-CZ': 'Vysvětlení', 'cs-CZ': 'Vysvětlení',
}, },
collapsed: true, collapsed: true,
items: [{ autogenerate: { directory: 'explanation' } }], autogenerate: { directory: 'explanation' },
}, },
{ {
label: 'Reference', label: 'Reference',
translations: { 'ko-KR': '참조', 'vi-VN': 'Tham chiếu', 'zh-CN': '参考', 'fr-FR': 'Référence', 'cs-CZ': 'Reference' }, translations: { 'ko-KR': '참조', 'vi-VN': 'Tham chiếu', 'zh-CN': '参考', 'fr-FR': 'Référence', 'cs-CZ': 'Reference' },
collapsed: true, collapsed: true,
items: [{ autogenerate: { directory: 'reference' } }], autogenerate: { directory: 'reference' },
}, },
// TEA docs moved to standalone module site; keep BMM sidebar focused. // TEA docs moved to standalone module site; keep BMM sidebar focused.
{ {

View File

@ -1,8 +0,0 @@
import { defineCollection } from 'astro:content';
import { docsLoader, i18nLoader } from '@astrojs/starlight/loaders';
import { docsSchema, i18nSchema } from '@astrojs/starlight/schema';
export const collections = {
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
i18n: defineCollection({ loader: i18nLoader(), schema: i18nSchema() }),
};

Some files were not shown because too many files have changed in this diff Show More