bmad-brainstorming: gate technique-flow choice and stop full-catalog dumps

- brain.py: `list` now requires --category or --all; a bare `list` is refused so the full ~100-technique catalog can no longer flood context. --all is the deliberate full-dump escape hatch.
- SKILL.md: technique-flow selection is now a hard gate (present the four ways, wait for the user's pick) instead of a soft default that got skipped; Stance "no multiple-choice" rule scoped to generation, with an explicit carve-out for that one process menu.
- headless.md: use `list --all` deliberately, passing --file.
- customize.toml: fix stale "progressive flows" wording to match the four real flows.
- test_brain.py: regression tests for the list guard (bare refused, --all dumps all).
This commit is contained in:
Brian Madison 2026-05-31 12:40:26 -05:00
parent 796c3f3797
commit 8b178d963c
5 changed files with 52 additions and 19 deletions

View File

@ -34,7 +34,7 @@ These fight your defaults, so hold them deliberately:
- **You do not supply ideas during generative exploration.** Your moves are questions, provocations, constraints, and reflections that make *the user* generate — while creatively guiding within the chosen technique. When the well looks dry, don't fill it: change the technique, shift the angle, or push harder. Supply an idea only when the user *directly asks* — then give exactly one as a spark and hand the pen back. If you reach for that exception repeatedly, that's the signal to change technique, not to keep feeding ideas. This holds for the whole generative session; it relaxes only during synthesis at wrap-up (`references/finalize.md`), never elsewhere in interactive mode.
- **One prompt per message.** Never stack questions into a wall the user reads instead of answers. One provocation, wait, build on what comes back.
- **No multiple-choice offers.** Open-ended keeps them generating; a menu invites them to pick lazily and lets you slip into brainstorming for them.
- **No multiple-choice offers during generation.** Open-ended keeps them generating; a menu invites them to pick lazily and lets you slip into brainstorming for them. The one exception is the process choice that opens `## Choosing Techniques`*how* to run the session is the user's to pick from a menu; *what* to ideate never is.
- **Offer to shift the creative domain every ~510 turns**, usually to the next technique — divergence is a discipline, not a mood.
- **Aim past 100 ideas; resist concluding.** Quantity is the goal, and ideas count only when they emerge through the dialogue or the user keeps them. The urge to organize or wrap is the enemy of divergence — when in doubt, ask one more question. Move to wrap-up only when the user is spent or the topic is genuinely mined out.
@ -64,24 +64,30 @@ Read the chosen `{doc_workspace}/.memlog.md` **in full** into context — the on
## Choosing Techniques — the generative loop
A session runs a small batch of techniques — **34 is the sweet spot**. Pick the batch one of the four ways below, run them in turn, and when the batch is spent the user is done — or, if they're not tapped out, pick another batch the same way.
A session runs a small batch of techniques — **34 is the sweet spot**. Before any ideating, the user picks *how* the batch is chosen. Present the four ways below as a short menu — the label and one line each, in a single message — and **wait for their pick**. This is the one menu in the session (see the Stance); do not assume the default, and do not start a technique or touch the library until they choose.
The library is large, so never read `{workflow.brain_methods}` whole — reach it through the helper script, always passing `--file {workflow.brain_methods}` (it resolves to the shipped catalog by default, a custom one when overridden). Subcommands of `python3 {skill-root}/scripts/brain.py --file {workflow.brain_methods}`:
- **Facilitator Chosen (default)** — you read the goal and pick the batch (favorites first).
- **Browse** — you show the categories, the user opens the ones they want, then takes techniques.
- **Category** — the user names 1n categories; you draw the batch from them at random.
- **Inventive Flow** — you invent techniques on the fly, wild and unpredictable.
- `categories` — names + counts.
- `list [--category X]` — the index (name + gist).
- `show "<name>"` — one technique's full method; call only when it's about to run.
The library is large, so **never pull it whole into context.** The only way in is the helper script, always passing `--file {workflow.brain_methods}` (the shipped catalog by default, a custom one when overridden). Subcommands of `python3 {skill-root}/scripts/brain.py --file {workflow.brain_methods}`:
The `list` gist usually suffices to propose and run a technique; reach for `show` for deeper mechanics. Treat `{workflow.additional_techniques}` as first-class catalog entries (including new categories) and prefer `{workflow.favorite_techniques}` where they fit.
- `categories` — names + counts only. The cheap map; this is how you survey what exists.
- `list --category X [--category Y]` — the index (name + gist) for *those* categories. Always scope it; bare `list` is refused by the script — dumping the whole catalog is the exact failure this avoids.
- `random --category X [...] -n 4` — draw a batch blind, listing nothing.
- `show "<name>"` — one technique's full method; call only the moment it is about to run.
Offer these as levers the user pulls, never a gate:
Once the user has chosen, run that flow and reach no further than the calls it names:
- **Facilitator Chosen (default)** — read the goal and pick 34 fitting techniques (favorites first), then start.
- **Browse**show `categories`, then gists in the ones they pick; the user takes as many as they want, but suggest 34 is the best amount.
- **Category** — the user names 1n categories; draw the batch at random from them (`... random --category X [--category Y] -n 4`), so the progression varies session to session.
- **Inventive Flow** — invent techniques on the fly, wild, creative, and unpredictable; invent at least 3 and announce the order before starting the first. Log each one's name + description so you can offer to save a keeper into `{workflow.additional_techniques}` (via `bmad-customize`) at wrap-up.
- **Facilitator Chosen** — from the goal, your `{workflow.favorite_techniques}`, and the `categories` map, name a batch of 34; confirm exact names with a targeted `list --category` on only the one or two categories you are drawing from. Never enumerate the library to choose.
- **Browse**run `categories`, let the user open the ones they want, `list --category` for *only* those, and let them take as many as they like (34 is the sweet spot).
- **Category** — the user names 1n categories; `random --category` draws the batch from them, so the progression varies session to session. No listing needed.
- **Inventive Flow** — invent at least 3 techniques, announce the order before starting the first, and touch no script. Log each one's name + description so you can offer to save a keeper into `{workflow.additional_techniques}` (via `bmad-customize`) at wrap-up.
Run each technique until it stops producing — logging each idea as an entry, and the switch itself as a `technique` entry when you move on — then announce the new lens so the shift is shared, and let the change of technique do the domain-shifting work from the Stance. When the batch is spent the user is done; if they're not tapped out, pick another batch the same way. Go to `## Wrap-Up` when the user is spent or the topic is mined out.
Treat `{workflow.additional_techniques}` as first-class catalog entries (including new categories) and prefer `{workflow.favorite_techniques}` where they fit. The `list` gist usually suffices to propose and run a technique; reach for `show` for deeper mechanics.
Run each technique until it stops producing — logging each idea as an entry, and the switch itself as a `technique` entry when you move on — then announce the new lens so the shift is shared, and let the change of technique do the domain-shifting work from the Stance. When the batch is spent the user is done; if they're not tapped out, offer the four ways again and run another batch. Go to `## Wrap-Up` when the user is spent or the topic is mined out.
## Wrap-Up — land it

View File

@ -50,7 +50,8 @@ favorite_techniques = []
# the library's shape (category, technique_name, description); a new category is
# just a category value the CSV doesn't have. Entries append, so teams and users
# can each grow the library. The facilitator treats these as first-class
# alongside brain_methods for proposing, browsing, random, and progressive flows.
# alongside brain_methods across every flow — facilitator-chosen, browse,
# category draws, and inventive.
#
# Example (set in team/user override TOML):
# [[workflow.additional_techniques]]

View File

@ -14,7 +14,7 @@ When in doubt, you are interactive — a present human asking you to "brainstorm
## 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 `python3 {skill-root}/scripts/brain.py list` (and `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

View File

@ -11,11 +11,15 @@ complex enough to warrant one. Only `show` resolves detail files, and only for t
technique asked for so the heavy material never enters context until it is run.
Commands:
categories list category names + counts (the cheap entry point)
list [--category C ...] the index: category / name / gist, optionally filtered
show NAME [NAME ...] full gist for each, inlining its detail file if it has one
categories list category names + counts (the cheap entry point)
list --category C [...] the index (name + gist) for those categories
list --all the whole index at once deliberate; large, avoid interactively
show NAME [NAME ...] full gist for each, inlining its detail file if it has one
random [--category C] [-n N] pick N at random (optionally within categories)
`list` refuses to run with neither --category nor --all: dumping the full catalog
into context is a footgun, so it must be an explicit choice.
Default output is lean text for an LLM to read; pass --json for structured output.
"""
import argparse
@ -111,8 +115,9 @@ def main(argv: list[str] | None = None) -> int:
p.add_argument("--json", action="store_true", help="emit structured JSON instead of lean text")
sub = p.add_subparsers(dest="cmd", required=True)
sub.add_parser("categories", help="list category names + counts")
pl = sub.add_parser("list", help="the index: category/name/gist")
pl = sub.add_parser("list", help="the index: category/name/gist (needs --category or --all)")
pl.add_argument("--category", action="append", help="filter to a category (repeatable)")
pl.add_argument("--all", action="store_true", help="dump the entire catalog (deliberate; large)")
ps = sub.add_parser("show", help="full gist + detail file for named techniques")
ps.add_argument("names", nargs="+")
pr = sub.add_parser("random", help="pick techniques at random")
@ -129,6 +134,13 @@ def main(argv: list[str] | None = None) -> int:
if args.cmd == "categories":
print(fmt_categories(categories(rows), args.json))
elif args.cmd == "list":
if not args.category and not args.all:
print(
"error: `list` needs --category (one or more) — or --all to dump the whole "
"catalog on purpose. Use `categories` for the cheap map, or `random` to draw blind.",
file=sys.stderr,
)
return 2
print(fmt_list(filter_cats(rows, args.category), args.json))
elif args.cmd == "show":
found, missing = find(rows, args.names)

View File

@ -95,6 +95,20 @@ def test_list_filtered_text(lib, capsys):
assert len(out) == 1 and out[0].startswith("structured\tSCAMPER Method\t")
def test_list_bare_is_refused(lib, capsys):
# the footgun: bare `list` must NOT dump the catalog into context
assert brain.main(["--file", str(lib), "list"]) == 2
captured = capsys.readouterr()
assert captured.out == "" # nothing leaked to stdout
assert "--category" in captured.err and "--all" in captured.err
def test_list_all_dumps_everything(lib, capsys):
assert brain.main(["--file", str(lib), "list", "--all"]) == 0
out = capsys.readouterr().out.strip().splitlines()
assert len(out) == 4 # the deliberate full-catalog escape hatch
def test_json_output(lib, capsys):
import json
brain.main(["--file", str(lib), "--json", "categories"])