From 397b2a5c876d443d298aa44dab582520dbf094c8 Mon Sep 17 00:00:00 2001 From: Aristo Rinjuang Date: Tue, 9 Jun 2026 10:29:35 +0700 Subject: [PATCH 01/15] feat: add CodeWhale as supported installer platform (#2459) CodeWhale uses .codewhale/skills/ (project) and ~/.codewhale/skills/ (global) for skill directories, matching the existing config-driven installer pattern. - platform-codes.yaml: codewhale entry after codex - test: Test 12b validates install target and setup --- test/test-installation-components.js | 35 +++++++++++++++++++++++++ tools/installer/ide/platform-codes.yaml | 7 +++++ 2 files changed, 42 insertions(+) diff --git a/test/test-installation-components.js b/test/test-installation-components.js index 808ee6faa..d5d1a6e62 100644 --- a/test/test-installation-components.js +++ b/test/test-installation-components.js @@ -446,6 +446,41 @@ async function runTests() { // Test 12: Removed — ancestor conflict check no longer applies (no IDE inherits skills from parent dirs) + // ============================================================ + // Test 12b: CodeWhale Native Skills Install + // ============================================================ + console.log(`${colors.yellow}Test Suite 12b: CodeWhale Native Skills${colors.reset}\n`); + + try { + clearCache(); + const platformCodes12b = await loadPlatformCodes(); + const codewhaleInstaller = platformCodes12b.platforms.codewhale?.installer; + + assert(codewhaleInstaller?.target_dir === '.codewhale/skills', 'CodeWhale target_dir uses native skills path'); + + const tempProjectDir12b = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-codewhale-test-')); + const installedBmadDir12b = await createTestBmadFixture(); + + const ideManager12b = new IdeManager(); + await ideManager12b.ensureInitialized(); + const result12b = await ideManager12b.setup('codewhale', tempProjectDir12b, installedBmadDir12b, { + silent: true, + selectedModules: ['bmm'], + }); + + assert(result12b.success === true, 'CodeWhale setup succeeds against temp project'); + + const skillFile12b = path.join(tempProjectDir12b, '.codewhale', 'skills', 'bmad-master', 'SKILL.md'); + assert(await fs.pathExists(skillFile12b), 'CodeWhale install writes SKILL.md directory output'); + + await fs.remove(tempProjectDir12b); + await fs.remove(path.dirname(installedBmadDir12b)); + } catch (error) { + assert(false, 'CodeWhale native skills migration test succeeds', error.message); + } + + console.log(''); + // ============================================================ // Test 13: Cursor Native Skills Install // ============================================================ diff --git a/tools/installer/ide/platform-codes.yaml b/tools/installer/ide/platform-codes.yaml index b8f18436d..2bde8b245 100644 --- a/tools/installer/ide/platform-codes.yaml +++ b/tools/installer/ide/platform-codes.yaml @@ -70,6 +70,13 @@ platforms: target_dir: .agents/skills global_target_dir: ~/.codex/skills + codewhale: + name: "CodeWhale" + preferred: false + installer: + target_dir: .codewhale/skills + global_target_dir: ~/.codewhale/skills + codebuddy: name: "CodeBuddy" preferred: false From fdd65dc3d92b0a570996f778076c4753c896d906 Mon Sep 17 00:00:00 2001 From: PinkyD Date: Wed, 10 Jun 2026 09:23:26 -0700 Subject: [PATCH 02/15] fix(skills): pass diff output inline to the blind-hunter reviewer (#2463) The Blind Hunter subagent intentionally has no tool access, but the review steps never said how {diff_output} should be delivered. Orchestrators typically wrote the diff to a temp file and asked the agent to read it, which silently fails (0 tool calls), and the 10-finding requirement then pushes the agent to hallucinate findings against code it never saw. State explicitly that the diff is passed inline in the subagent prompt, in both bmad-code-review step 2 and bmad-quick-dev step 4. Co-authored-by: Claude Fable 5 --- .../4-implementation/bmad-code-review/steps/step-02-review.md | 2 +- .../4-implementation/bmad-quick-dev/step-04-review.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bmm-skills/4-implementation/bmad-code-review/steps/step-02-review.md b/src/bmm-skills/4-implementation/bmad-code-review/steps/step-02-review.md index bbc1f9a82..3767af857 100644 --- a/src/bmm-skills/4-implementation/bmad-code-review/steps/step-02-review.md +++ b/src/bmm-skills/4-implementation/bmad-code-review/steps/step-02-review.md @@ -18,7 +18,7 @@ failed_layers: '' # set at runtime: comma-separated list of layers that failed o 2. Launch parallel subagents without conversation context. If subagents are not available, generate prompt files in `{implementation_artifacts}` — one per reviewer role below — and HALT. Ask the user to run each in a separate session (ideally a different LLM) and paste back the findings. When findings are pasted, resume from this point and proceed to step 3. - - **Blind Hunter** — receives `{diff_output}` only. No spec, no context docs, no project access. Invoke via the `bmad-review-adversarial-general` skill. + - **Blind Hunter** — receives inline `{diff_output}` only. No spec, no context docs, no project access. Invoke via the `bmad-review-adversarial-general` skill. - **Edge Case Hunter** — receives `{diff_output}` and read access to the project. Invoke via the `bmad-review-edge-case-hunter` skill. diff --git a/src/bmm-skills/4-implementation/bmad-quick-dev/step-04-review.md b/src/bmm-skills/4-implementation/bmad-quick-dev/step-04-review.md index 2d96fd25d..3151191d8 100644 --- a/src/bmm-skills/4-implementation/bmad-quick-dev/step-04-review.md +++ b/src/bmm-skills/4-implementation/bmad-quick-dev/step-04-review.md @@ -25,7 +25,7 @@ Do NOT `git add` anything — this is read-only inspection. Launch three subagents without conversation context. If no sub-agents are available, generate three review prompt files in `{implementation_artifacts}` — one per reviewer role below — and HALT. Ask the human to run each in a separate session (ideally a different LLM) and paste back the findings. -- **Blind hunter** — receives `{diff_output}` only. No spec, no context docs, no project access. Invoke via the `bmad-review-adversarial-general` skill. +- **Blind hunter** — receives inline `{diff_output}` only. No spec, no context docs, no project access. Invoke via the `bmad-review-adversarial-general` skill. - **Edge case hunter** — receives `{diff_output}` and read access to the project. Invoke via the `bmad-review-edge-case-hunter` skill. - **Acceptance auditor** — receives `{diff_output}`, `{spec_file}`, and read access to the project. Must also read the docs listed in `{spec_file}` frontmatter `context`. Checks for violations of acceptance criteria, rules, and principles from the spec and context docs. From b9431d6d99e0a70e6f0179634310d1b6fecb861d Mon Sep 17 00:00:00 2001 From: Brian Date: Thu, 11 Jun 2026 08:17:03 -0500 Subject: [PATCH 03/15] Shared canonical memlog script (src/scripts/memlog.py) + bmad-spec as first consumer (#2462) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * bmad-spec: make the memlog canonical, SPEC.md a derived view Replace the bespoke .decision-log.md with the shared memlog script (_bmad/scripts/memlog.py, same location as resolve_customization.py). The append-only memlog becomes the single source of truth; SPEC.md and spec-authored companions are re-derived from it (plus cited sources for raw content) on each run instead of hand-patched. This makes bmad-spec the sole writer of the spec and lets the surrounding steps (PRD, UX, architecture, epics) feed one spec in any order without merge drift. - New "Memory and derivation" section: memlog canonical, SPEC.md a projection, single-writer rule, append/init via the shared script, no status field (terminal moments are event entries). - Operation reads the prior memlog (not the rendered SPEC.md) as the authority on decisions and capability IDs on update. - Conflict-surfacing: live sources/companions that disagree on a field are raised to the user, resolution logged as a new entry. - Rename .decision-log.md -> .memlog.md across SKILL.md and assets. * core: add shared canonical memlog.py in src/scripts Single source-of-truth memlog: append-only, chronological working-memory log for skills. Installs to _bmad/scripts/memlog.py via the existing src/scripts sync (beside resolve_customization.py), so any skill can call it at runtime — bmad-spec is the first consumer. Merges the neutral API (--workspace, free-form --type/--by, generic set) with crash-safe fsync atomic writes. No lifecycle status by design: a memory log records completion as an event entry, never a frontmatter flag. Also accepts --path for callers that hold the file path directly. 30 tests. * bmad-spec: include event in memlog --type list The documented append --type set omitted event while the next line requires --type event for terminal moments. Align the list. * Fix memlog Python floor and exclude tests from install - memlog.py: add 'from __future__ import annotations' so PEP 585/604 hints stay lazy; the script runs on Python 3.8+ instead of crashing below 3.10. Correct the requires-python header to >=3.8. - installer.js: filter tests/, __pycache__/, .pytest_cache/, and *.pyc out of _installSharedScripts so dev-only files never ship to users. --- src/core-skills/bmad-spec/SKILL.md | 30 +- .../bmad-spec/assets/headless-schemas.md | 6 +- .../bmad-spec/assets/spec-template.md | 2 +- src/scripts/memlog.py | 224 +++++++++++++ src/scripts/tests/test_memlog.py | 306 ++++++++++++++++++ tools/installer/core/installer.js | 8 +- 6 files changed, 563 insertions(+), 13 deletions(-) create mode 100644 src/scripts/memlog.py create mode 100644 src/scripts/tests/test_memlog.py diff --git a/src/core-skills/bmad-spec/SKILL.md b/src/core-skills/bmad-spec/SKILL.md index 97baefed3..cdf734528 100644 --- a/src/core-skills/bmad-spec/SKILL.md +++ b/src/core-skills/bmad-spec/SKILL.md @@ -43,15 +43,27 @@ Inside the spec folder: ``` / - SPEC.md ← uppercase, the kernel - .md ← optional, content-typed (e.g. glossary.md) + SPEC.md ← uppercase, the kernel — DERIVED from .memlog.md, never hand-edited + .md ← optional, content-typed (e.g. glossary.md); spec-authored ones are derived too .md - .decision-log.md ← canonical memory for this spec + .memlog.md ← canonical, append-only memory; what SPEC.md is distilled from ``` +## Memory and derivation + +`.memlog.md` is canonical — an append-only, chronological record of every decision, constraint, capability (with its stable `CAP-N`), assumption, open question, and bit of user direction, one line each in the order it happened, never edited or reordered. `SPEC.md` and every spec-authored companion are **derived on each run** from the memlog (the decision-of-record) plus the sources it cites for raw content — never hand-patched. + +Deriving the contract from a living log instead of editing the contract in place is what lets the steps around the spec (PRD, UX, architecture, epics) run in any order and feed the same spec without merge drift: the log only accumulates, the artifact is re-rendered. So the spec is updated *only* by re-deriving it here — bmad-spec is its single writer; a hand-edit to `SPEC.md` from outside is unsupported and is overwritten on the next derive. + +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): + +- `python3 {project-root}/_bmad/scripts/memlog.py init --workspace {spec-folder} --field topic=""` — once, at create. +- `python3 {project-root}/_bmad/scripts/memlog.py append --workspace {spec-folder} --type --text ""` — as each lands. +- Terminal moments (a validation verdict, "spec finalized") are `--type event` entries; the memlog carries no status field. + ## The Operation -Read the input and its ancillary linked materials. If there is no input, follow the no-input branch in **Workspace** (ask or block). If a prior `SPEC.md` exists at the target folder, read it too — the operation becomes an update. Preserve capability IDs; new capabilities get the next unused `CAP-N`; never reuse retired IDs. Otherwise this is a create. +Read the input and its ancillary linked materials. If there is no input, follow the no-input branch in **Workspace** (ask or block). If a prior `.memlog.md` exists at the target folder, read it — the operation becomes an update, and the memlog (not the rendered `SPEC.md`) is the authority on what was decided and on capability IDs. Preserve those IDs; new capabilities get the next unused `CAP-N`; never reuse retired IDs. Otherwise this is a create, and the first move is `memlog.py init`. When the input is structured and pre-sorted (a PRD with an addendum, a GDD, a brief produced by an upstream BMad skill), trust the authored separation: lift kernel-fitting content into SPEC.md, lift overflow into appropriately-named companions. When the input is mixed (a brain dump, a transcript, an RFC, a customer email), do the sorting yourself: walk each claim, apply the three-lens load-bearing test (Spec Law rule 7), and route to the kernel field or a companion. @@ -59,6 +71,8 @@ Distill the input into the five-field kernel using `{workflow.spec_template}` as Write lean from the first pass: every sentence must earn its place. Decoration costs tokens and dilutes downstream readers. +Log each decision, capability, constraint, and accepted change to `.memlog.md` as it is made — that running record is what the render reads. Because the log is append-only, a later entry supersedes an earlier one on the same point while the history stays intact. When two currently-live sources or companions disagree on the same field, or an either/or never got resolved, surface it to the user rather than silently choosing — the resolution is itself a new memlog entry. + If the input is genuinely too thin to distill (e.g. "an app for hikers" with no surrounding context), stop and suggest `bmad-prd` (or sibling ceremony skill). This skill distills; it does not coach. ## Load-bearing @@ -94,7 +108,7 @@ Every spec must satisfy these eight rules. The operation aims for them; the self 5. **Success signal is concrete enough to test or demonstrate against.** "Users love it" doesn't qualify. 6. **Capability IDs are stable and unique.** Never reused, never renumbered. 7. **Preservation.** Every load-bearing source claim lands in SPEC.md or a companion. Wrapper ceremony does not. -8. **Lean prose.** Every sentence carries load-bearing content. Cut decoration, hedges, backstory, throat-clearing. Applies to SPEC.md, companions, and `.decision-log.md`. +8. **Lean prose.** Every sentence carries load-bearing content. Cut decoration, hedges, backstory, throat-clearing. Applies to SPEC.md, companions, and `.memlog.md`. ## Self-Validate @@ -104,7 +118,7 @@ After every create or update, sweep the resulting artifact in **two passes** bef **Pass 2 — Preservation.** Walk the source claim by claim. Confirm each load-bearing claim landed in SPEC.md or a companion. Wrapper-ceremony drops are logged under "Wrapper-only content" so the drop is on the record, not silent. -Append a one-paragraph verdict to `.decision-log.md` covering both passes. In interactive mode, review the verdict with the user. In headless mode, `.decision-log.md` is one of the files returned, so the caller (or its downstream LLM) reads the verdict there. +Record the verdict for each pass to `.memlog.md` (`append --type event`). In interactive mode, review it with the user. In headless mode, `.memlog.md` is one of the files returned, so the caller (or its downstream LLM) reads the verdict there. ## Spec with no change signal @@ -120,10 +134,10 @@ Run `{workflow.on_complete}` if set. ## After Spec is Output -Any update to spec regarding assumptions, open questions, or other changes should be appended to that source's decision log also and offer to update the source. +Any update to the spec — resolved assumptions, answered open questions, other changes — is appended to `.memlog.md` as it happens. When a change overrides something that came from a source input, offer to update that source too, so upstream and the spec don't silently diverge. ## Frontmatter conventions - `companions:` array of `.md` files downstream MUST read alongside SPEC.md to have the full contract. Paths may point inside the spec folder (spec-authored companions like `glossary.md`) or outside it (adopted companions like `../planning-artifacts/ux-designs/ux-foo-bar-2026-05-23/DESIGN.md`). The split between spec-authored and adopted is implicit by path; downstream treats both the same. - `sources:` array of paths to files that were **fully absorbed** into the SPEC, with no remaining downstream value (e.g., a PRD whose every load-bearing claim is now in the kernel). Listed for audit and for bmad-spec to re-read on update. Downstream does NOT read these. Files that downstream still needs to read belong in `companions:`, not here. -- **Do not list** decision logs, README files, organizational artifacts, or any operational record of how upstream skills produced their artifacts. Those are not source content; they are process metadata that downstream consumers don't need. +- **Do not list** the memlog, README files, organizational artifacts, or any operational record of how upstream skills produced their artifacts. Those are not source content; they are process metadata that downstream consumers don't need. diff --git a/src/core-skills/bmad-spec/assets/headless-schemas.md b/src/core-skills/bmad-spec/assets/headless-schemas.md index 096b15803..8e2093bec 100644 --- a/src/core-skills/bmad-spec/assets/headless-schemas.md +++ b/src/core-skills/bmad-spec/assets/headless-schemas.md @@ -1,6 +1,6 @@ # Headless JSON Response -The default invocation is headless: input goes in, JSON comes out. The contract is intentionally tiny — return the outcome and the files touched. Anything else a caller needs is inside those files (SPEC.md, companions, `.decision-log.md`). +The default invocation is headless: input goes in, JSON comes out. The contract is intentionally tiny — return the outcome and the files touched. Anything else a caller needs is inside those files (SPEC.md, companions, `.memlog.md`). ## Success @@ -10,12 +10,12 @@ The default invocation is headless: input goes in, JSON comes out. The contract "files": [ "_bmad-output/specs/spec-quarter-drop/SPEC.md", "_bmad-output/specs/spec-quarter-drop/glossary.md", - "_bmad-output/specs/spec-quarter-drop/.decision-log.md" + "_bmad-output/specs/spec-quarter-drop/.memlog.md" ] } ``` -`files` lists every file written or modified in this run, in any order. The spec folder, kernel filename, decision log location, capabilities, companions, and verdict are all readable from those files; no need to re-encode them in the response. +`files` lists every file written or modified in this run, in any order. The spec folder, kernel filename, memlog location, capabilities, companions, and verdict are all readable from those files; no need to re-encode them in the response. ## Blocked diff --git a/src/core-skills/bmad-spec/assets/spec-template.md b/src/core-skills/bmad-spec/assets/spec-template.md index f8127204c..9c2868be1 100644 --- a/src/core-skills/bmad-spec/assets/spec-template.md +++ b/src/core-skills/bmad-spec/assets/spec-template.md @@ -1,7 +1,7 @@ --- id: SPEC-{slug} companions: [] # files downstream MUST read alongside SPEC.md. Paths may point inside the spec folder (spec-authored) or outside it (adopted from an upstream skill). -sources: [] # files fully absorbed into the SPEC (audit only; downstream does NOT read these). Never decision logs. +sources: [] # files fully absorbed into the SPEC (audit only; downstream does NOT read these). Never the memlog. --- > **Canonical contract.** This SPEC and the files in `companions:` are the complete, preservation-validated contract for what to build, test, and validate. Source documents listed in frontmatter are for traceability only — consult them only if you need narrative rationale or prose color this contract intentionally omits. diff --git a/src/scripts/memlog.py b/src/scripts/memlog.py new file mode 100644 index 000000000..e7f1f245e --- /dev/null +++ b/src/scripts/memlog.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python3 +# /// script +# requires-python = ">=3.8" +# /// +"""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. + +Three invariants make it trustworthy: + + 1. Append-only, chronological. Entries land at the end, in the order they happen. + Nothing is ever inserted backward, reordered, edited, or removed. There is no + edit or delete subcommand by design; history is never rewritten. + 2. Write-only / blind. Every command is an atomic, context-free write and echoes the + new state as one line of 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. + 3. No lifecycle status. A memory log has no "complete" flag. Whether the work is done, + blocked, or paused is itself a fact that happened, so it is recorded as an entry + (e.g. `append --type event --text "session complete"`), never as frontmatter the + log would have to mutate. The chronology stays the single source of truth, and a + resume learns the state by reading the last entries — the same way it learns + everything else. + +Atomicity: every write goes to a temp file, is flushed and fsync'd, then atomically +renamed over the target, so a crash never leaves a half-written entry. + +The file shape (.memlog.md): + + --- + topic: Onboarding flow for a budgeting app + goal: lift week-1 retention + updated: 2026-06-07T14: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? + - (insight) the "scary numbers" risk and the "real numbers" idea are one lever: show real data, pre-categorized + - (direction) optimize for the anxious first-timer, not the power user + - (decision) lead with one pre-categorized account; defer multi-account import + - (event) session complete + +Each entry may carry an optional `--type` — what KIND it is (idea, insight, question, +decision, direction, assumption, gap, note, event, …) — 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 enforce one. + +Commands: + init (--workspace DIR | --path FILE) [--field k=v ...] create the memlog (errors if it exists) + append (--workspace DIR | --path FILE) --text STR [--type T] [--by W] append one entry at the end + set (--workspace DIR | --path FILE) --key K --value V set/replace a descriptive frontmatter field + +Addressing: `--workspace` is the run folder, and the memlog is always {workspace}/.memlog.md. +`--path` points straight at the memlog file instead, for callers that already hold the path. +""" +from __future__ import annotations # keep type-hint syntax lazy so the script runs on 3.8+ + +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 resolve(args) -> Path: + """The memlog file, from either addressing mode: {workspace}/.memlog.md or an explicit --path.""" + return Path(args.path) if args.path else Path(args.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: + """Temp + flush + fsync + atomic rename, so a crash never half-writes an entry.""" + tmp = path.with_suffix(path.suffix + ".tmp") + with open(tmp, "w", encoding="utf-8") as f: + f.write(text) + f.flush() + os.fsync(f.fileno()) + 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, 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), + "entries": entry_count(body), + })) + + +def cmd_init(args) -> int: + path = resolve(args) + 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() + touch(meta) + write_atomic(path, render(meta, "")) + ack(path, "") + return 0 + + +def cmd_append(args) -> int: + path = resolve(args) + 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, body) + return 0 + + +def cmd_set(args) -> int: + path = resolve(args) + meta, body = split(path.read_text(encoding="utf-8")) + meta[args.key] = args.value + touch(meta) + write_atomic(path, render(meta, body)) + ack(path, body) + return 0 + + +def add_target(sp) -> None: + """Every command addresses the memlog the same way: a run folder or an explicit path.""" + g = sp.add_mutually_exclusive_group(required=True) + g.add_argument("--workspace", help="run folder; the memlog is {workspace}/.memlog.md") + g.add_argument("--path", help="explicit memlog file path (alternative to --workspace)") + + +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") + add_target(pi) + 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") + add_target(pa) + 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 descriptive frontmatter field") + add_target(pset) + 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()) diff --git a/src/scripts/tests/test_memlog.py b/src/scripts/tests/test_memlog.py new file mode 100644 index 000000000..b756e7b2d --- /dev/null +++ b/src/scripts/tests/test_memlog.py @@ -0,0 +1,306 @@ +# /// 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, and no +lifecycle status the log would have to mutate. +""" +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 "updated" in meta + assert body.strip() == "" + + +def test_init_has_no_lifecycle_status(ws): + # A memory log carries no "status" flag; completion is an appended entry, not frontmatter. + init(ws) + meta, _ = memlog.split(read(ws)) + assert "status" not in meta + + +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 + + +# --- addressing: --workspace and --path are interchangeable -------------- + +def test_path_addressing_targets_the_file_directly(tmp_path): + target = tmp_path / "run" / ".memlog.md" + assert memlog.main(["init", "--path", str(target), "--field", "topic=T"]) == 0 + assert target.is_file() + assert memlog.main(["append", "--path", str(target), "--text", "an idea", "--type", "idea"]) == 0 + body = memlog.split(target.read_text(encoding="utf-8"))[1] + assert "- (idea) an idea" in body + + +def test_workspace_and_path_resolve_to_same_file(ws): + init(ws) + via_path = str(Path(ws) / MEMLOG) + assert memlog.main(["append", "--path", via_path, "--text", "from path"]) == 0 + assert memlog.main(["append", "--workspace", ws, "--text", "from workspace"]) == 0 + assert entries(ws) == ["- from path", "- from workspace"] + + +def test_target_is_required(ws): + with pytest.raises(SystemExit): + memlog.main(["append", "--text", "orphan"]) # neither --workspace nor --path + + +# --- 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_completion_is_an_entry_not_a_status(ws): + # The documented way to mark a session done: append it. Frontmatter never gains a status. + init(ws) + append(ws, "session complete", entry_type="event") + meta, _ = memlog.split(read(ws)) + assert "status" not in meta + assert entries(ws)[-1] == "- (event) session complete" + + +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 + + +def test_free_vocabulary_is_not_enforced(ws): + # The tool is neutral: any --type the host skill names renders verbatim. + init(ws) + append(ws, "a custom kind", entry_type="crack") + append(ws, "another", entry_type="lock") + body = body_of(ws) + assert "- (crack) a custom kind" in body + assert "- (lock) another" in body + + +# --- set: generic descriptive frontmatter, no lifecycle semantics -------- + +def test_set_adds_field(ws): + init(ws) + memlog.main(["set", "--workspace", ws, "--key", "mode", "--value", "partner"]) + assert memlog.split(read(ws))[0]["mode"] == "partner" + + +def test_set_replaces_field(ws): + init(ws, topic="T", mode="facilitator") + memlog.main(["set", "--workspace", ws, "--key", "mode", "--value", "partner"]) + assert memlog.split(read(ws))[0]["mode"] == "partner" + + +def test_set_preserves_body(ws): + init(ws) + append(ws, "keep me", entry_type="idea") + memlog.main(["set", "--workspace", ws, "--key", "mode", "--value", "partner"]) + meta, body = memlog.split(read(ws)) + assert meta["mode"] == "partner" + assert "- (idea) keep me" in body + + +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 + # 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 entries(ws) == ["- (idea) an idea"] + assert "topic:" not in body # frontmatter never bled into the body + + +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"] + + +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["entries"] == 1 + assert out["memlog"].endswith(MEMLOG) + assert "status" not in out # no lifecycle status + 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 diff --git a/tools/installer/core/installer.js b/tools/installer/core/installer.js index 9347e1f0b..85d47c26e 100644 --- a/tools/installer/core/installer.js +++ b/tools/installer/core/installer.js @@ -630,6 +630,7 @@ class Installer { /** * Sync src/scripts/* → _bmad/scripts/ so shared Python scripts * (e.g. resolve_customization.py) are available at install time. + * Excludes dev-only tests and Python caches so they don't ship to users. * Wipes the destination first so files removed or renamed in source * don't linger and get recorded as installed. Also seeds * _bmad/custom/.gitignore on fresh installs so *.user.toml overrides @@ -643,7 +644,12 @@ class Installer { await fs.remove(paths.scriptsDir); await fs.ensureDir(paths.scriptsDir); - await fs.copy(srcScriptsDir, paths.scriptsDir, { overwrite: true }); + // Ship only the runtime scripts — dev-only tests and Python caches must not land in user projects. + const isInstallable = (srcPath) => { + const base = path.basename(srcPath); + return base !== 'tests' && base !== '__pycache__' && base !== '.pytest_cache' && !base.endsWith('.pyc'); + }; + await fs.copy(srcScriptsDir, paths.scriptsDir, { overwrite: true, filter: isInstallable }); await this._trackFilesRecursive(paths.scriptsDir); const customGitignore = path.join(paths.customDir, '.gitignore'); From fbb48ed711c3381aede1497ca107b97dd2172210 Mon Sep 17 00:00:00 2001 From: Dov Benyomin Sohacheski Date: Thu, 11 Jun 2026 16:29:02 +0300 Subject: [PATCH 04/15] fix: remove empty skill-group dirs left in _bmad after install (#2461) * fix: remove empty skill-group dirs left in _bmad after install Skill cleanup removed each skill's own directory but never pruned the now-empty grouping folders above it (e.g. _bmad/bmm/1-analysis), leaving empty dirs behind after every install. Walk up from each removed skill dir and drop empty parents, stopping at the bmad root. * fix: harden empty-parent pruning boundary and cleanup Use a path-boundary check instead of a string prefix so sibling dirs (e.g. _bmad2) can't match the bmad root, and make the walk best-effort so a dir that vanishes or fills in mid-walk never aborts the install. Move the test fixture cleanup into finally so failures don't leak temp dirs. --------- Co-authored-by: Brian --- test/test-installation-components.js | 45 ++++++++++++++++++++++++++++ tools/installer/core/installer.js | 25 ++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/test/test-installation-components.js b/test/test-installation-components.js index d5d1a6e62..6e015322a 100644 --- a/test/test-installation-components.js +++ b/test/test-installation-components.js @@ -3273,6 +3273,51 @@ async function runTests() { console.log(''); + // ============================================================ + // Test Suite 45: _cleanupSkillDirs prunes empty parent dirs (#empty-bmm-folders) + // ============================================================ + console.log(`${colors.yellow}Test Suite 45: cleanup prunes empty skill-group dirs${colors.reset}\n`); + + let root45; + try { + root45 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-cleanup-test-')); + const bmadDir45 = path.join(root45, '_bmad'); + await fs.ensureDir(path.join(bmadDir45, '_config')); + + // Two skills nested under the same grouping dir (1-analysis), plus a + // module-level file that must survive the cleanup. + await fs.writeFile( + path.join(bmadDir45, '_config', 'skill-manifest.csv'), + [ + 'canonicalId,name,description,module,path', + '"bmad-agent-analyst","bmad-agent-analyst","fixture","bmm","_bmad/bmm/1-analysis/bmad-agent-analyst/SKILL.md"', + '"bmad-research","bmad-research","fixture","bmm","_bmad/bmm/1-analysis/research/bmad-research/SKILL.md"', + '', + ].join('\n'), + ); + await fs.ensureDir(path.join(bmadDir45, 'bmm', '1-analysis', 'bmad-agent-analyst')); + await fs.writeFile(path.join(bmadDir45, 'bmm', '1-analysis', 'bmad-agent-analyst', 'SKILL.md'), 'x'); + await fs.ensureDir(path.join(bmadDir45, 'bmm', '1-analysis', 'research', 'bmad-research')); + await fs.writeFile(path.join(bmadDir45, 'bmm', '1-analysis', 'research', 'bmad-research', 'SKILL.md'), 'x'); + await fs.writeFile(path.join(bmadDir45, 'bmm', 'config.yaml'), 'module: bmm\n'); + + const installer45 = new Installer(); + await installer45._cleanupSkillDirs(bmadDir45); + + assert(!(await fs.pathExists(path.join(bmadDir45, 'bmm', '1-analysis'))), 'empty skill-group dir is pruned after cleanup'); + assert(!(await fs.pathExists(path.join(bmadDir45, 'bmm', '1-analysis', 'research'))), 'empty nested skill-group dir is pruned'); + assert(await fs.pathExists(path.join(bmadDir45, 'bmm', 'config.yaml')), 'module-level files are preserved'); + assert(await fs.pathExists(bmadDir45), 'bmad root is never removed'); + } catch (error) { + console.log(`${colors.red}Test Suite 45 setup failed: ${error.message}${colors.reset}`); + console.log(error.stack); + failed++; + } finally { + if (root45) await fs.remove(root45).catch(() => {}); + } + + console.log(''); + // ============================================================ // Summary // ============================================================ diff --git a/tools/installer/core/installer.js b/tools/installer/core/installer.js index 85d47c26e..9c6c6cb6c 100644 --- a/tools/installer/core/installer.js +++ b/tools/installer/core/installer.js @@ -419,10 +419,35 @@ class Installer { const sourceDir = path.dirname(path.join(bmadDir, relativePath)); if (await fs.pathExists(sourceDir)) { await fs.remove(sourceDir); + await this._removeEmptyParents(path.dirname(sourceDir), bmadDir); } } } + /** + * Remove now-empty parent directories left behind after skill dir cleanup. + * Walks up from dir, stopping at (and never removing) bmadDir. Best-effort: + * a directory that vanishes or fills in mid-walk just ends the walk. + * @param {string} dir - Directory to start walking up from + * @param {string} bmadDir - BMAD installation directory (boundary) + */ + async _removeEmptyParents(dir, bmadDir) { + let current = dir; + while (true) { + // Path-boundary check (not a string prefix, so siblings like _bmad2 don't match). + const rel = path.relative(bmadDir, current); + if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel)) break; + try { + const entries = await fs.readdir(current); + if (entries.length > 0) break; + await fs.rmdir(current); + } catch { + break; + } + current = path.dirname(current); + } + } + async _readSkillManifestRows(bmadDir) { const csvPath = path.join(bmadDir, '_config', 'skill-manifest.csv'); if (!(await fs.pathExists(csvPath))) return []; From 560a2e3a6fbd9087765cb569bb860379117e2249 Mon Sep 17 00:00:00 2001 From: Brian Date: Thu, 11 Jun 2026 21:27:52 -0500 Subject: [PATCH 05/15] feat: installer detects Python version and warns when 3.11+ (tomllib) is missing (#2466) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: installer detects Python version and warns when 3.11+ (tomllib) is missing Several BMAD features need Python at runtime: memlog (3.8+) and the TOML config resolution scripts (3.11+ for stdlib tomllib). Users install into varied environments (Linux, Windows, WSL, Docker) where Python may be missing or too old, and previously only found out via runtime errors. The installer now probes PATH at startup (py -3 / python3 / python) and classifies the result: 3.11+ passes silently with a success line; 3.8-3.10 or missing/too-old Python gets a warning naming exactly which features degrade, plus per-platform install hints. The warning requires an explicit ack — continue (fix later, no reinstall needed) or quit and re-run after installing Python. Warn-don't-block: most of BMAD works without Python, so the install is never refused. In --yes mode the warning logs and continues without prompting. * fix: align Python check with runtime truth (python3) and harden edge cases Review fixes for the installer Python check: - Probe python3 first on all platforms: every runtime call site invokes a literal python3, so only that command vouches for BMAD features. Python found via py/python now gets an explicit mismatch warning instead of a false "all BMAD features supported". - Treat closed/piped stdin as non-interactive (in addition to --yes) so scripted installs no longer silently exit 0 via clack's cancel path. - Retry probes with shell:true on win32 EINVAL (CVE-2024-27980 hardening rejects .bat/.cmd shims like pyenv-win's without a shell). - Add Suite 46 branch tests for checkPythonEnvironment with stubbed detection, prompts, and process.exit. --- test/test-installation-components.js | 138 +++++++++++++++++++ tools/installer/core/python-check.js | 199 +++++++++++++++++++++++++++ tools/installer/ui.js | 10 ++ 3 files changed, 347 insertions(+) create mode 100644 tools/installer/core/python-check.js diff --git a/test/test-installation-components.js b/test/test-installation-components.js index 6e015322a..1317bbbf5 100644 --- a/test/test-installation-components.js +++ b/test/test-installation-components.js @@ -3318,6 +3318,144 @@ async function runTests() { console.log(''); + // ============================================================ + // Test Suite 46: Python environment check (version parsing + classification) + // ============================================================ + console.log(`${colors.yellow}Test Suite 46: python-check version parsing and classification${colors.reset}\n`); + + try { + const { parsePythonVersion, classifyPython, detectPython } = require('../tools/installer/core/python-check'); + + // Version parsing + const v312 = parsePythonVersion('Python 3.12.1'); + assert(v312 && v312.major === 3 && v312.minor === 12 && v312.patch === 1, 'parses "Python 3.12.1"'); + const v311 = parsePythonVersion('Python 3.11.0\n'); + assert(v311 && v311.raw === '3.11.0', 'parses with trailing newline'); + 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(parsePythonVersion('') === null, 'empty output returns null'); + assert(parsePythonVersion('command not found: python3') === null, 'non-version output returns null'); + assert(parsePythonVersion(null) === null, 'null output returns null'); + + // Classification against feature requirements + assert(classifyPython({ major: 3, minor: 11 }) === 'full', '3.11 is full support (tomllib floor)'); + 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'); + + // Detection smoke test — must not throw, and if it finds a Python the + // result must be well-formed. (CI machines may or may not have Python.) + 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 real = { + detectPython: pythonCheck.detectPython, + log: promptsModule.log, + 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 = { + success: async (m) => void seen.success.push(m), + warn: async (m) => void seen.warn.push(m), + info: async (m) => void seen.info.push(m), + error: async () => {}, + }; + 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; + }; + + try { + const v = (major, minor, patch) => ({ major, minor, patch, raw: `${major}.${minor}.${patch}` }); + + // Branch: full support via the runtime command — success, no prompt. + let seen = stub({ command: 'python3', version: v(3, 12, 1), isRuntimeCommand: true }, 'continue'); + let result = await pythonCheck.checkPythonEnvironment(); + assert(result.status === 'full' && seen.success.length === 1, 'full support via python3 logs success'); + assert(seen.select.length === 0 && seen.warn.length === 0, 'full support via python3 skips warning and ack prompt'); + + // 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.8–3.10) — warn + ack, continue returns. + seen = stub({ command: 'python3', version: v(3, 9, 5), isRuntimeCommand: true }, 'continue'); + result = await pythonCheck.checkPythonEnvironment(); + 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'); + + // Branch: no Python, non-interactive — warn + info, never prompts. + 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'); + + // Branch: no Python, interactive, user quits — cancel message + exit 0. + seen = stub(null, 'quit'); + let threw = false; + try { + await pythonCheck.checkPythonEnvironment(); + } catch (error) { + threw = error.message === '__stub_exit__'; + } + assert(threw && seen.exit.length === 1 && seen.exit[0] === 0, 'quit choice exits 0 (user-cancel convention)'); + assert(seen.cancel.length === 1, 'quit choice shows the cancel guidance'); + } finally { + pythonCheck.detectPython = real.detectPython; + promptsModule.log = real.log; + promptsModule.note = real.note; + promptsModule.select = real.select; + promptsModule.cancel = real.cancel; + process.exit = real.exit; + } + } catch (error) { + console.log(`${colors.red}Test Suite 46 setup failed: ${error.message}${colors.reset}`); + console.log(error.stack); + failed++; + } + + console.log(''); + // ============================================================ // Summary // ============================================================ diff --git a/tools/installer/core/python-check.js b/tools/installer/core/python-check.js new file mode 100644 index 000000000..d8539cac3 --- /dev/null +++ b/tools/installer/core/python-check.js @@ -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, +}; diff --git a/tools/installer/ui.js b/tools/installer/ui.js index a107fb0fc..12a295d25 100644 --- a/tools/installer/ui.js +++ b/tools/installer/ui.js @@ -161,6 +161,16 @@ class UI { const messageLoader = new MessageLoader(); await messageLoader.displayStartMessage(); + // Probe the local Python before any other prompts: several BMAD features + // (memlog session memory, TOML config resolution) need Python 3.11+ at + // runtime. Warn-don't-block, but require an explicit ack so the warning + // can't scroll past unseen. The installer runs in the destination + // environment, so probing PATH here tests the right machine. + // Skip the ack when stdin isn't a TTY (CI/Docker/piped): clack's select + // on closed stdin resolves to cancel, which would silently exit 0. + const { checkPythonEnvironment } = require('./core/python-check'); + await checkPythonEnvironment({ nonInteractive: !!options.yes || !process.stdin.isTTY }); + // Parse channel flags (--channel/--all-*/--next=/--pin) once. Warnings // are surfaced immediately so the user sees them before any git ops run. const channelOptions = parseChannelOptions(options); From 242dc6ef759ce252420c7393e2b9683cea9608e1 Mon Sep 17 00:00:00 2001 From: Brian Date: Sun, 14 Jun 2026 15:42:37 -0500 Subject: [PATCH 06/15] bmad-architecture part 1: replace bmad-create-architecture with a lean spine skill (#2467) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Rework solutioning architecture skill into bmad-architecture (spine) - Rename canonical skill bmad-tech-plan -> bmad-architecture; output is ARCHITECTURE-SPINE.md - Convert bmad-create-architecture to a deprecated husk forwarding to bmad-architecture (create intent); strip steps/data/template - Finalize: spine is the default, then offers fuller renderings + a self-contained HTML view; doc_standards polish applies to prose docs only - Reviewer Gate: add adversarial divergence-hunter, on by default at high/regulated/cross-team stakes - Fix references in module-help.csv, bmad-prd, bmad-ux, bmad-agent-architect; reword help entry for when-to-offer - bmad-spec: flag recognized-but-unaddressed domain implications as open_questions * bmad-architecture: extract Finalize/reviewer-menu to references, add grade_spine script - Move the Finalize sequence out of SKILL.md into references/finalize.md - Make references/validate.md own the canonical reviewer menu and prompts - Add scripts/grade_spine.py (+ tests) so validation grade is computed deterministically instead of derived by hand - Tighten SKILL.md spine/memlog framing * bmad-architecture: outcome-driven rewrite + spawned reviewer gate Re-express SKILL.md as goals/outcomes rather than procedure: intent is read from the input (raw idea, large doc, codebase, feature slice, existing spine) instead of a Create/Update/Validate x mode matrix. Restore the counter-default coaching invariants that over-compression had stripped — Guided is the default, load-bearing calls are shown with alternatives and the user chooses, recommend a known starter when the stack is open, investigate brownfield before deciding. Adopt the bmad-prd/product-brief shape: a dedicated Reviewer Gate that always spawns finalize_reviewers as parallel subagents against the spine (lint floor first; ad-hoc lenses scaled to rigor/altitude/criticality), and a numbered Finalize that calls it. Headless runs the full gate non-interactively. Reframe the structural seed as the living source of truth for shape (code owns detail; evolve on shape change; memlog keeps history). Fix template mermaid (C4 -> flowchart, valid one-attr-per-line erDiagram). Ship two default finalize_reviewers (currency/reality check, adversarial divergence hunter). Remove grade_spine.py and the inlined discovery/inputs/validate/finalize refs. * bmad-architecture: review fixes — shared memlog, epic altitude, lint hardening Pre-PR review fixes for the new architecture-spine skill: - Adopt the shared canonical memlog (#2462); drop the vendored copy and the status-flag lifecycle in favor of `event` entries - Wire epic-altitude inheritance: parent-spine load+bind, Inherited Invariants template section, epic-scoped run folder, parent-contradiction lens; state that per-story detail is deferred to bmad-create-story - Add the Update intent; honor a forwarded-activation handoff from the deprecated bmad-create-architecture shim - Rename modes to Coaching path / Fast path (suite-consistent with prd/brief) and sequence the offer as an activation step - Fix the brownfield project-context.md default path ({output_folder} no-op) - lint_spine: line-exact frontmatter, fence-blanking (AD-in-fence + accurate line numbers), map-form key_deps, AD-0 fix; soften template-token severity; add 8 tests - Carve the Reviewer Gate to references/ to stay under the SKILL.md token budget - Template: entities-only ERD, scale-down guidance, softened seed framing; gitignore .memlog.md * bmad-architecture: address PR review (augment + coderabbit) - REF-03: use canonical "invoke the `skill` skill" language for all cross-skill references in SKILL.md (was "route to / offer / hand to / Next:") - lint_spine: scan frontmatter for unfilled template tokens / TBD (paradigm, scope, date were uncatchable in the body-only pass) - lint_spine: guard read_text() so a bad-encoding/unreadable spine returns error JSON and exit 0, honoring the documented contract - reviewer-gate: resolve the "always run" vs "may skip" ambiguity — the gate scales/skips by stakes, but finalize_reviewers always run once it does - tests: +3 (frontmatter token, frontmatter TBD, unreadable spine); use next() * bmad-architecture: align finalize-reviewers wording + headless blocked output - customize.toml: reword finalize_reviewers comment to match reviewer-gate.md — the gate is stakes-gated, but its reviewers always run once it does (coderabbit) - headless.md: state explicitly that a blocked run omits spine/memlog/companions, matching the "omit keys for artifacts not produced" contract (coderabbit) * bmad-architecture: ask deliverable purpose up front, offer presentable renderings, lead next-steps with bmad-spec --- .gitignore | 3 + .../2-plan-workflows/bmad-prd/SKILL.md | 2 +- .../2-plan-workflows/bmad-ux/SKILL.md | 4 +- .../bmad-agent-architect/customize.toml | 4 +- .../3-solutioning/bmad-architecture/SKILL.md | 86 ++++ .../assets/spine-template.md | 129 ++++++ .../bmad-architecture/customize.toml | 100 +++++ .../bmad-architecture/references/headless.md | 26 ++ .../references/reviewer-gate.md | 13 + .../bmad-architecture/scripts/lint_spine.py | 260 ++++++++++++ .../scripts/tests/test_lint_spine.py | 228 +++++++++++ .../bmad-create-architecture/SKILL.md | 76 +--- .../architecture-decision-template.md | 12 - .../data/domain-complexity.csv | 13 - .../data/project-types.csv | 7 - .../steps/step-01-init.md | 153 ------- .../steps/step-01b-continue.md | 173 -------- .../steps/step-02-context.md | 224 ----------- .../steps/step-03-starter.md | 329 --------------- .../steps/step-04-decisions.md | 318 --------------- .../steps/step-05-patterns.md | 359 ----------------- .../steps/step-06-structure.md | 379 ------------------ .../steps/step-07-validation.md | 361 ----------------- .../steps/step-08-complete.md | 82 ---- src/bmm-skills/module-help.csv | 4 +- src/core-skills/bmad-spec/SKILL.md | 2 + 26 files changed, 870 insertions(+), 2477 deletions(-) create mode 100644 src/bmm-skills/3-solutioning/bmad-architecture/SKILL.md create mode 100644 src/bmm-skills/3-solutioning/bmad-architecture/assets/spine-template.md create mode 100644 src/bmm-skills/3-solutioning/bmad-architecture/customize.toml create mode 100644 src/bmm-skills/3-solutioning/bmad-architecture/references/headless.md create mode 100644 src/bmm-skills/3-solutioning/bmad-architecture/references/reviewer-gate.md create mode 100644 src/bmm-skills/3-solutioning/bmad-architecture/scripts/lint_spine.py create mode 100644 src/bmm-skills/3-solutioning/bmad-architecture/scripts/tests/test_lint_spine.py delete mode 100644 src/bmm-skills/3-solutioning/bmad-create-architecture/architecture-decision-template.md delete mode 100644 src/bmm-skills/3-solutioning/bmad-create-architecture/data/domain-complexity.csv delete mode 100644 src/bmm-skills/3-solutioning/bmad-create-architecture/data/project-types.csv delete mode 100644 src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-01-init.md delete mode 100644 src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-01b-continue.md delete mode 100644 src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-02-context.md delete mode 100644 src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-03-starter.md delete mode 100644 src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-04-decisions.md delete mode 100644 src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-05-patterns.md delete mode 100644 src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-06-structure.md delete mode 100644 src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-07-validation.md delete mode 100644 src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-08-complete.md diff --git a/.gitignore b/.gitignore index 1483c0538..99e48d9ab 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,9 @@ design-artifacts/ __pycache__/ .pytest_cache/ +# BMad run artifacts (memlogs are per-run working memory, never committed) +.memlog.md + # System files .DS_Store Thumbs.db diff --git a/src/bmm-skills/2-plan-workflows/bmad-prd/SKILL.md b/src/bmm-skills/2-plan-workflows/bmad-prd/SKILL.md index db005fff7..26a32bd97 100644 --- a/src/bmm-skills/2-plan-workflows/bmad-prd/SKILL.md +++ b/src/bmm-skills/2-plan-workflows/bmad-prd/SKILL.md @@ -88,5 +88,5 @@ Tell the user the sequence in one sentence, then walk it. Polish goes last so 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. 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 to `.decision-log.md`. Share artifact paths. Common next: `bmad-ux`, `bmad-create-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. diff --git a/src/bmm-skills/2-plan-workflows/bmad-ux/SKILL.md b/src/bmm-skills/2-plan-workflows/bmad-ux/SKILL.md index 295cdf75e..b5416fd32 100644 --- a/src/bmm-skills/2-plan-workflows/bmad-ux/SKILL.md +++ b/src/bmm-skills/2-plan-workflows/bmad-ux/SKILL.md @@ -33,7 +33,7 @@ UX may lead, follow, or stand alone. Inherit `sources:` by reference; the spines 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. 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-create-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`. 5. Detect intent: **Create**, **Update**, **Validate**. For Create, before binding a fresh workspace, scan `{workflow.ux_output_path}` for prior in-progress runs (folders matching `{workflow.run_folder_pattern}` whose `DESIGN.md` frontmatter `status` is not `final`) and offer to resume rather than starting over. Run `{workflow.activation_steps_append}`. @@ -87,4 +87,4 @@ Outcomes, in order: - **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. - **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. Share paths. Common next: `bmad-create-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}`. diff --git a/src/bmm-skills/3-solutioning/bmad-agent-architect/customize.toml b/src/bmm-skills/3-solutioning/bmad-agent-architect/customize.toml index 27f940052..468067cd3 100644 --- a/src/bmm-skills/3-solutioning/bmad-agent-architect/customize.toml +++ b/src/bmm-skills/3-solutioning/bmad-agent-architect/customize.toml @@ -56,8 +56,8 @@ principles = [ [[agent.menu]] code = "CA" -description = "Guided workflow to document technical decisions to keep implementation on track" -skill = "bmad-create-architecture" +description = "Produce the architecture spine: the invariants that keep independently-built units consistent" +skill = "bmad-architecture" [[agent.menu]] code = "IR" diff --git a/src/bmm-skills/3-solutioning/bmad-architecture/SKILL.md b/src/bmm-skills/3-solutioning/bmad-architecture/SKILL.md new file mode 100644 index 000000000..551f98602 --- /dev/null +++ b/src/bmm-skills/3-solutioning/bmad-architecture/SKILL.md @@ -0,0 +1,86 @@ +--- +name: bmad-architecture +description: 'Produce the architecture: a lean spine of invariants that keeps everything built from it consistent, projected into whatever format the work needs. Use when the user says "create the architecture", "create technical architecture", "architecture spine", or "create a solution design".' +--- +# BMad Architecture + +## 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. 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: + +> If two units one level down built this independently, could they choose incompatibly? Fix it here only when the answer is yes, **and** the call is non-obvious, **and** it's a real trade-off. Otherwise name it under Deferred and move on. + +Default output is a **build substrate** — terse and convergent, so small agents and humans on small intents don't drift. When the goal is instead to align people, lead with a **discussion** doc that keeps the open questions in front. Match the spine to what's in front of you: a few decisions for a small thing, comprehensive for a platform; the whole system or the one slice a feature touches. + +Record decisions, not rationale (rationale lives in the memlog). Carry shape in diagrams, not prose. Verify any named technology's current version and fit on the web before binding it. + +## How you work + +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. 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 `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 + +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; that's deferred to story time, when you invoke the `bmad-create-story` skill. + +## 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 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): + +- `python3 {project-root}/_bmad/scripts/memlog.py init --workspace {doc_workspace} --field scope="…" --field purpose="…" --field altitude="…"` +- `python3 {project-root}/_bmad/scripts/memlog.py append --workspace {doc_workspace} --type --text "…"` +- A terminal moment (spine finalized, a validation verdict) is an `append --type event` entry — there is no status field to set. + +## Resolution rules + +- Bare paths and `{skill-root}` (e.g. `references/headless.md`) resolve from this skill's installed directory. +- `{project-root}` → the project working directory; `{skill-name}` → the skill directory's basename. +- `{workflow.}` → a merged `customize.toml` field; `{doc_workspace}` → the bound run folder. +- Forward slashes only. Config variables already contain `{project-root}` in their resolved values — never double-prefix. + +## 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`. So a legacy per-project override still reaches the run. + +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. +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. +5. Interactive create: offer the working mode in `{communication_language}` — **Coaching path** (default) or **Fast path** (see *How you work*) — before any drafting; default to Coaching unless the user asks for speed. +6. **Mandatory, both paths, before drafting:** ask whether the spine is the only deliverable — and if not, draw out the *purpose and audience* rather than a document type. "An architecture doc" balloons into bloat; what they actually need might be a one-detail explainer for a single team or a non-technical vision piece for a board. Purpose right-sizes the artifact and may call for extra elicitation up front, not just a finale add-on. + +For a new spine, bind `{doc_workspace}` to `{workflow.spine_output_path}/{workflow.run_folder_pattern}/`, seed `ARCHITECTURE-SPINE.md` from `{workflow.spine_template}`, run `memlog.py init`, and tell the user the path. **At epic altitude, scope the folder to the epic** (set `run_folder_pattern` per `customize.toml`) so per-epic runs don't collide. + +## 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 change nothing. + +## Finalize + +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. 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. +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. +5. **Renderings & polish.** The spine is the build deliverable; with it and the memlog now in place, produce any *additional* human-facing artifact the user needs, scoped to the purpose and audience drawn out up front. The up-front question already flagged whether one's needed; if it wasn't, still offer one here, seeding concrete options: an interactive HTML+SVG deck to walk a team through the architecture and drive discussion, a fuller HTML/md solution design, a C4 set, or a view of how the work splits across teams/epics. Build only what they pick, right-sized to that purpose; apply `{workflow.doc_standards}` polish to that prose only, never to the spine. +6. **External handoffs.** Run `{workflow.external_handoffs}`; surface returned URLs/IDs. Offer to invoke the `bmad-spec` skill to adopt the spine as a companion, keeping `AD` IDs stable so downstream can cite them. +7. **Close.** Set the spine's own frontmatter `status: final`, `updated: {date}`; log a `memlog.py append --type event --text "spine finalized"` (the memlog has no status field). Share paths. Next, **lead with `bmad-spec`** — recommend adopting/refreshing the spine as a spec companion (always the top recommendation when a spec was an input, and a useful next step even when it wasn't), then `bmad-create-epics-and-stories` or — epic altitude — `bmad-create-story`; or invoke `bmad-help` to route. +8. Run `{workflow.on_complete}`. + +## Update + +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 + +The standalone intent — critique an existing spine without changing it. Run the Reviewer Gate (`references/reviewer-gate.md`) against it and deliver the bespoke HTML report, then offer to roll the findings into an Update. (At Finalize the same gate runs as your own pre-handoff check, where you apply the fixes instead of reporting.) diff --git a/src/bmm-skills/3-solutioning/bmad-architecture/assets/spine-template.md b/src/bmm-skills/3-solutioning/bmad-architecture/assets/spine-template.md new file mode 100644 index 000000000..aecd70986 --- /dev/null +++ b/src/bmm-skills/3-solutioning/bmad-architecture/assets/spine-template.md @@ -0,0 +1,129 @@ +--- +name: '{name}' +type: architecture-spine +purpose: build-substrate # build-substrate (default) · discussion · report · deck +altitude: feature # initiative (keeps features) · feature (keeps epics) · epic (keeps stories) +paradigm: '{named design pattern, e.g. hexagonal, layered, pipes-and-filters, actor}' +scope: '{what this spine governs}' +status: draft # draft · final +created: '{date}' +updated: '{date}' +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: [] +companions: [] +--- + +# Architecture Spine — {name} + +> 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 + +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 + +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 | +| --- | --- | --- | +| {AD-n / convention} | {parent spine} | {what it constrains in this scope} | + +## Invariants & Rules + +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} + +- **Binds:** {capability / unit IDs, areas, or `all`} +- **Prevents:** {the divergence this stops} +- **Rule:** {the constraint downstream must follow} + +## Consistency Conventions + +The defaults that bind everything where independent builders would otherwise drift. Cut rows that +don't apply. + +| Concern | Convention | +| --- | --- | +| Naming (entities, files, interfaces, events) | | +| Data & formats (IDs, dates, error shapes, envelopes) | | +| State & cross-cutting (mutation, errors, logging, config, auth) | | + +## Structural Seed + +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}
{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 +{root}/ + {dir}/ # {what lives here} +``` + +## Capability → Architecture Map + +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 | +| --- | --- | --- | +| {CAP-n / area} | {component / module} | {AD-n, convention, paradigm} | + +## Deferred + +Decisions intentionally pushed down, each with the reason it can wait. The half of the contract +that keeps the spine lean. diff --git a/src/bmm-skills/3-solutioning/bmad-architecture/customize.toml b/src/bmm-skills/3-solutioning/bmad-architecture/customize.toml new file mode 100644 index 000000000..3c4e50d0e --- /dev/null +++ b/src/bmm-skills/3-solutioning/bmad-architecture/customize.toml @@ -0,0 +1,100 @@ +# DO NOT EDIT -- overwritten on every update. +# +# Workflow customization surface for bmad-architecture. +# +# Override files (not edited here): +# {project-root}/_bmad/custom/bmad-architecture.toml (team) +# {project-root}/_bmad/custom/bmad-architecture.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). +# Use for pre-flight loads, approved-stack policy checks, etc. +activation_steps_prepend = [] + +# Steps to run after greet but before the workflow begins. +# Use for context-heavy setup that should happen once the user has been acknowledged. +activation_steps_append = [] + +# Persistent facts the workflow keeps in mind for the whole run +# (approved stacks, banned dependencies, platform constraints, compliance guardrails). +# Each entry is either 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 if bmad-generate-project-context produced one — giving the +# architect persistent awareness of the project's tech, domain, and conventions (load-bearing +# for brownfield). Common opt-ins (set in team/user override TOML): +# "Our org is AWS-only -- do not propose GCP or Azure." +# "file:{project-root}/docs/engineering-standards.md" +persistent_facts = [ + "file:{project-root}/**/project-context.md", +] + +# Executed when the workflow completes (after the spine is final and the user has been told). +# String scalar (single instruction) or array of instructions executed in order. Empty for none. +on_complete = "" + +# The architecture spine template. Treated as expert prior knowledge, not a checklist — the LLM +# adapts it to the project, altitude, and domain, and drops sections a project genuinely doesn't +# need. Override the path in team/user TOML to enforce a different spine shape. +spine_template = "assets/spine-template.md" + +# Run folder location. ARCHITECTURE-SPINE.md, its .memlog.md, and any fuller rendering the run +# produces all land inside `{spine_output_path}/{run_folder_pattern}/`. Resume-check scans +# `{spine_output_path}` for prior unfinished runs. +# +# The default pattern fits the common case (one spine per project, at the altitude above epics). +# At EPIC altitude, override run_folder_pattern to carry the epic identity so per-epic runs don't +# collide on the same day — e.g. set it (team/user TOML) to "architecture-epic-{epic_id}", binding +# {epic_id} from the driving spec / the activating payload. Headless callers may instead pass an +# explicit doc_workspace and bypass the pattern entirely. +spine_output_path = "{planning_artifacts}/architecture" +run_folder_pattern = "architecture-{project_name}-{date}" + +# Prose-editorial standards applied at finalize ONLY to a fuller prose document the run produces +# (a discussion report, full architecture doc, or design addendum) — never to the spine or other +# short, structured outputs, which are terse and carry decisions in AD-n blocks and diagrams by +# design. Each entry is a `skill:`, `file:`, or plain-text directive applied before the user sees +# the polished draft. Suggested order: structural passes first, prose mechanics last. Append-only. +doc_standards = [ + "skill:bmad-editorial-review-structure", + "skill:bmad-editorial-review-prose", +] + +# External-source registry. Natural-language directives describing knowledge bases, MCP tools, or +# internal systems the LLM may consult ON DEMAND during the run (not preemptively) — approved-stack +# catalogs, internal platform docs, version registries. Each entry names the tool, the trigger +# condition, and any fields it needs. If a named tool is unavailable at runtime, the LLM falls back +# to standard behavior (e.g. web research) and notes the gap. Empty by default. +# +# Examples (set in team/user override TOML): +# "When choosing a datastore, consult corp:platform_catalog before recommending one." +# "For current library versions, query corp:artifact_registry before web search." +external_sources = [] + +# External-handoff routing applied at Finalize to push outputs beyond local files (Confluence, +# Notion, ticket systems). Each entry names the MCP tool, the destination, and required fields. +# Runs after polish; returned URLs/IDs are surfaced. Unavailable tools are skipped and flagged; +# local files always exist. Empty by default. +external_handoffs = [] + +# --- Finalize reviewers --- +# Extra review lenses spawned as parallel subagents at the validation gate (Finalize and the +# Validate intent), on top of the skill's built-in good-spine checklist and the lint_spine.py +# mechanical floor. The GATE is stakes-gated — a throwaway spine may run it quietly or skip it — +# but whenever the gate runs, every entry here runs with it (the configured floor, never cherry- +# picked); only ad-hoc lenses are optional, and headless never skips the gate. +# +# Entries follow the standard prefix convention: +# "skill:NAME" invoke the named review skill as a subagent against ARCHITECTURE-SPINE.md +# "file:PATH" load the file as a review prompt; spawn an adversarial subagent applying it +# plain text use the text directly as the subagent's review prompt +# +# Resolved on-demand (not at activation). Override TOML may append. +finalize_reviewers = [ + "Verify every committed decision was web-researched or reality-checked rather than asserted from training data: current library/framework versions, that each named technology still exists and fits, and — greenfield — the live defaults of any starter it leans on. Flag anything that could be out of date and wasn't confirmed against the web, the existing project, or the current starter.", + "Attack the spine as an adversary: construct two units one level down that each obey every AD to the letter yet still build incompatibly — clashing shared-data shapes, two owners of one entity, conflicting state-mutation paths. Every pair you find is a hole to close with a new or tightened AD.", +] diff --git a/src/bmm-skills/3-solutioning/bmad-architecture/references/headless.md b/src/bmm-skills/3-solutioning/bmad-architecture/references/headless.md new file mode 100644 index 000000000..bd4d20be1 --- /dev/null +++ b/src/bmm-skills/3-solutioning/bmad-architecture/references/headless.md @@ -0,0 +1,26 @@ +# Headless + +No interactive user: infer everything, ask nothing, but never invent — record inferences as `assumptions[]` and gaps that need a human as `open_questions[]`. Detect headless from a `headless: true` flag, a non-interactive / no-TTY invocation, an activation hook that declares it, or a first message that pre-supplies all inputs and asks for an artifact path back; when ambiguous, default to interactive. + +Drive the run from the payload in the first message — `intent`, `altitude`, `purpose`, the driving input (spec package / PRD / raw intent / brownfield path), a parent spine path at lower altitude, and `doc_workspace` if a specific folder is required. Infer anything absent from the inputs or workspace; don't invent stack, constraints, or scope to fill a gap. You still verify named tech on the web (you can't ask, but you can check) and still drive every write through the shared `{project-root}/_bmad/scripts/memlog.py`. Run the full Reviewer Gate (`references/reviewer-gate.md`) non-interactively: `scripts/lint_spine.py` plus **every `{workflow.finalize_reviewers}` lens as a parallel subagent** (and any ad-hoc lens the spine's criticality warrants). Headless skips only the human picking from the menu — never the reviewers themselves; apply the clear fixes and record anything unresolved in `open_questions[]`. For a true authority collision, list it in `conflicts_with_prior_decisions[]`. For the Validate intent, always write the report to `{doc_workspace}` and add `"offer_to_update": true`. If intent stays ambiguous after inference, halt blocked. + +End with JSON only, omitting keys for artifacts not produced — the shape below is the fully-produced (`complete`) case; a `blocked` run produces no spine, so it omits `spine`, `memlog`, and `companions` entirely (see the note under the block): + +```json +{ + "status": "complete | partial | blocked", + "intent": "create | update | validate", + "altitude": "initiative | feature | epic", + "purpose": "build-substrate | discussion", + "doc_workspace": "", + "spine": "{doc_workspace}/ARCHITECTURE-SPINE.md", + "memlog": "{doc_workspace}/.memlog.md", + "companions": [], + "assumptions": [], + "open_questions": [], + "conflicts_with_prior_decisions": [], + "reason": "" +} +``` + +`complete` stands alone · `partial` (spine produced, but `open_questions[]` non-empty or critical inputs inferred) means review before downstream use · `blocked` means no spine produced — return only `status`, `intent`, `reason`, and `doc_workspace` (if bound), omitting `spine`, `memlog`, `companions`, and the artifact arrays that don't exist. diff --git a/src/bmm-skills/3-solutioning/bmad-architecture/references/reviewer-gate.md b/src/bmm-skills/3-solutioning/bmad-architecture/references/reviewer-gate.md new file mode 100644 index 000000000..729844c46 --- /dev/null +++ b/src/bmm-skills/3-solutioning/bmad-architecture/references/reviewer-gate.md @@ -0,0 +1,13 @@ +# Reviewer Gate + +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: `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.) + +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 2–5 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; 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. diff --git a/src/bmm-skills/3-solutioning/bmad-architecture/scripts/lint_spine.py b/src/bmm-skills/3-solutioning/bmad-architecture/scripts/lint_spine.py new file mode 100644 index 000000000..88fa3e06c --- /dev/null +++ b/src/bmm-skills/3-solutioning/bmad-architecture/scripts/lint_spine.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python3 +# /// script +# requires-python = ">=3.10" +# /// +"""lint-spine — the mechanical half of spine decision-integrity, done deterministically. + +LLMs miscount IDs and miss literal placeholders; a grep does not. This linter owns the +checks a script does better than a prompt, and leaves the semantic half (is each Rule +actually enforceable? does the boundary make sense?) to the rubric walker. + +It reads ARCHITECTURE-SPINE.md from a workspace and reports, as compact JSON on stdout: + + - placeholder literal TBD / TODO / "similar to AD-n" / unfilled {template-token} + - ad_id duplicate or non-monotonic AD-n identifiers + - ad_fields an AD-n block missing Binds / Prevents / Rule + - version_pin a frontmatter key_deps entry with no @version + +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 +with the real file. Reported lines are absolute file lines (frontmatter offset added). Exit +code is always 0 — findings travel in the JSON; the caller (Reviewer Gate / rubric walker) +decides what to do with them. +""" +from __future__ import annotations + +import argparse +import json +import re +import sys +from pathlib import Path + +SPINE = "ARCHITECTURE-SPINE.md" + +AD_HEADING = re.compile(r"^#{2,4}\s*AD-(\d+)\b(.*)$", re.MULTILINE) +HEADING = re.compile(r"^#{1,6}\s", re.MULTILINE) +FENCE = re.compile(r"```.*?```", re.DOTALL) +PLACEHOLDER_WORD = re.compile(r"\b(TBD|TODO|FIXME|XXX)\b") +SIMILAR_TO = re.compile(r"similar to AD-\d+", re.IGNORECASE) +TEMPLATE_TOKEN = re.compile(r"\{[a-z_][a-z0-9_ /.-]*\}") + + +def split_frontmatter(text: str) -> tuple[str, str, int]: + """Return (frontmatter, body, body_line_offset). + + Frontmatter is the content between the first two lines that are *exactly* `---` + (line-exact, like memlog.split — a `---` inside a value or a body thematic break never + truncates it). body_line_offset is the number of file lines before the body begins, so a + body-relative line number plus the offset gives the absolute file line. Absent frontmatter + → ('', text, 0).""" + lines = text.split("\n") + if lines and lines[0] == "---": + for i in range(1, len(lines)): + if lines[i] == "---": + fm = "\n".join(lines[1:i]) + body = "\n".join(lines[i + 1:]) + return fm, body, i + 1 + return "", text, 0 + + +def blank_fences(text: str) -> str: + """Replace each fenced block with the same number of newlines, so scanning skips fenced + content while every line number outside the fence stays put.""" + return FENCE.sub(lambda m: "\n" * m.group(0).count("\n"), text) + + +def line_of(text: str, idx: int) -> int: + return text.count("\n", 0, idx) + 1 + + +def find_placeholders(body: str, offset: int) -> list[dict]: + findings: list[dict] = [] + scan = blank_fences(body) + # (regex, label, severity) — TBD/TODO and dangling cross-refs are unambiguous; a bare + # {template-token} can be legitimate brace prose, so it is flagged low ("possible") to keep + # the mechanical pass near-zero false-positive rather than train reviewers to ignore it. + for rx, label, severity in ( + (PLACEHOLDER_WORD, "placeholder marker", "high"), + (SIMILAR_TO, "unresolved cross-reference", "high"), + (TEMPLATE_TOKEN, "possible unfilled template token (verify)", "low"), + ): + for m in rx.finditer(scan): + findings.append({ + "category": "placeholder", + "severity": severity, + "detail": f"{label}: {m.group(0)!r}", + "location": f"{SPINE} (line {offset + line_of(scan, m.start())})", + }) + return findings + + +def find_frontmatter_placeholders(frontmatter: str) -> list[dict]: + """Catch unfilled tokens left in frontmatter (e.g. paradigm/scope/date) — part of the + spine contract, but outside the body that find_placeholders scans.""" + findings: list[dict] = [] + for rx, label, severity in ( + (PLACEHOLDER_WORD, "placeholder marker", "high"), + (TEMPLATE_TOKEN, "possible unfilled template token (verify)", "low"), + ): + for m in rx.finditer(frontmatter): + findings.append({ + "category": "placeholder", + "severity": severity, + "detail": f"frontmatter {label}: {m.group(0)!r}", + "location": f"{SPINE} frontmatter (line {1 + line_of(frontmatter, m.start())})", + }) + return findings + + +def find_ad_issues(body: str, offset: int) -> list[dict]: + findings: list[dict] = [] + scan = blank_fences(body) # AD headings shown inside a code fence are not live ADs + matches = list(AD_HEADING.finditer(scan)) + seen: dict[int, int] = {} + prev: int | None = None + for m in matches: + num = int(m.group(1)) + file_line = offset + line_of(scan, m.start()) + loc = f"{SPINE} AD-{num} (line {file_line})" + if num in seen: + findings.append({ + "category": "ad_id", + "severity": "high", + "detail": f"AD-{num} id reused (also at line {seen[num]})", + "location": loc, + }) + else: + seen[num] = file_line + if prev is not None and num <= prev: + findings.append({ + "category": "ad_id", + "severity": "high", + "detail": f"AD-{num} is non-monotonic (follows AD-{prev}); ids must ascend and never renumber", + "location": loc, + }) + prev = num if prev is None else max(prev, num) + + # block text = from this heading to the next heading of any level + start = m.end() + nxt = HEADING.search(scan, start) + block = scan[start:nxt.start()] if nxt else scan[start:] + low = block.lower() + missing = [f for f in ("binds", "prevents", "rule") if f not in low] + if missing: + findings.append({ + "category": "ad_fields", + "severity": "high", + "detail": f"AD-{num} missing required field(s): {', '.join(missing)}", + "location": loc, + }) + return findings + + +def find_unpinned_deps(frontmatter: str) -> list[dict]: + findings: list[dict] = [] + lines = frontmatter.splitlines() + in_key_deps = False + key_indent = 0 + for raw in lines: + stripped = raw.strip() + if not stripped or stripped.startswith("#"): + continue + 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 + if in_key_deps: + if indent <= key_indent and not stripped.startswith("-"): + in_key_deps = False + continue + if stripped.startswith("-"): + # block-sequence form: `- name@version` + _check_dep(_strip_comment(stripped[1:]).strip().strip("'\""), findings) + else: + # map form: `name: version` — pinned iff a non-empty value is present + mm = re.match(r"([^:]+):\s*(.*)$", stripped) + if mm: + name = mm.group(1).strip().strip("'\"") + val = _strip_comment(mm.group(2)).strip().strip("'\"") + if name and not val: + findings.append({ + "category": "version_pin", + "severity": "medium", + "detail": f"key_deps entry {name!r} has no version pin", + "location": f"{SPINE} frontmatter stack.key_deps", + }) + return findings + + +def _strip_comment(s: str) -> str: + """Drop a trailing YAML ` # comment`, leaving an inline `name@1.2` intact.""" + return re.sub(r"(^|\s)#.*$", "", s) + + +def _check_dep(item: str, findings: list[dict]) -> None: + if not item or item.startswith("#"): + 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: + frontmatter, body, offset = split_frontmatter(text) + findings: list[dict] = [] + findings += find_frontmatter_placeholders(frontmatter) + findings += find_placeholders(body, offset) + findings += find_ad_issues(body, offset) + findings += find_unpinned_deps(frontmatter) + counts: dict[str, int] = {} + for f in findings: + counts[f["severity"]] = counts.get(f["severity"], 0) + 1 + return { + "ok": len(findings) == 0, + "spine": SPINE, + "total_findings": len(findings), + "by_severity": counts, + "findings": findings, + } + + +def main(argv: list[str] | None = None) -> int: + ap = argparse.ArgumentParser(description="Lint an architecture spine for mechanical integrity.") + ap.add_argument("--workspace", required=True, help="run folder containing ARCHITECTURE-SPINE.md") + ap.add_argument("-o", "--output", help="write JSON here instead of stdout") + args = ap.parse_args(argv) + + spine_path = Path(args.workspace) / SPINE + if not spine_path.exists(): + result = {"ok": False, "error": f"{spine_path} not found", "findings": [], "total_findings": 0} + else: + try: + text = spine_path.read_text(encoding="utf-8") + except (OSError, UnicodeDecodeError) as e: + # honor the "exit code is always 0" contract: a read/decode failure travels in JSON + result = {"ok": False, "error": f"could not read {spine_path}: {e}", "findings": [], "total_findings": 0} + else: + result = lint(text) + + out = json.dumps(result, indent=2) + if args.output: + Path(args.output).write_text(out + "\n", encoding="utf-8") + else: + print(out) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/bmm-skills/3-solutioning/bmad-architecture/scripts/tests/test_lint_spine.py b/src/bmm-skills/3-solutioning/bmad-architecture/scripts/tests/test_lint_spine.py new file mode 100644 index 000000000..220bb42e9 --- /dev/null +++ b/src/bmm-skills/3-solutioning/bmad-architecture/scripts/tests/test_lint_spine.py @@ -0,0 +1,228 @@ +# /// script +# requires-python = ">=3.10" +# dependencies = ["pytest>=8.0"] +# /// +"""Tests for lint_spine.py. Run: uv run --with pytest pytest scripts/tests/test_lint_spine.py + +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, +AD-n blocks missing required fields, and unpinned dependency versions. +""" +import importlib.util +import json +import re +import sys +from pathlib import Path + +import pytest + +_SPEC = importlib.util.spec_from_file_location( + "lint_spine", Path(__file__).resolve().parent.parent / "lint_spine.py" +) +lint_spine = importlib.util.module_from_spec(_SPEC) +sys.modules["lint_spine"] = lint_spine +_SPEC.loader.exec_module(lint_spine) + + +CLEAN = """--- +name: 'Demo' +stack: + key_deps: + - fastapi@0.115 + - pydantic@2.9 +--- + +## Invariants & Rules + +### AD-1 — single write path + +- **Binds:** all +- **Prevents:** divergent mutation +- **Rule:** state changes only through the command bus + +### AD-2 — layered deps `[ADOPTED]` + +- **Binds:** all +- **Prevents:** import cycles +- **Rule:** ui -> app -> domain, never backward + +```mermaid +flowchart LR + A --> B{decision} +``` +""" + + +def cats(result): + return sorted(f["category"] for f in result["findings"]) + + +def test_clean_spine_passes(): + result = lint_spine.lint(CLEAN) + assert result["ok"] is True + assert result["total_findings"] == 0 + + +def test_mermaid_braces_not_flagged(): + # the {decision} node lives in a fenced block and must not read as a template token + result = lint_spine.lint(CLEAN) + assert "placeholder" not in cats(result) + + +def test_placeholder_markers_caught(): + text = CLEAN.replace("the command bus", "TBD") + result = lint_spine.lint(text) + assert "placeholder" in cats(result) + + +def test_similar_to_caught(): + text = CLEAN.replace("import cycles", "similar to AD-1") + result = lint_spine.lint(text) + assert any("cross-reference" in f["detail"] for f in result["findings"]) + + +def test_unfilled_template_token_caught(): + text = CLEAN.replace("single write path", "{decision}") + result = lint_spine.lint(text) + assert any(f["category"] == "placeholder" for f in result["findings"]) + + +def test_duplicate_ad_id_caught(): + text = CLEAN.replace("### AD-2 — layered deps `[ADOPTED]`", "### AD-1 — layered deps") + result = lint_spine.lint(text) + assert "ad_id" in cats(result) + + +def test_non_monotonic_ad_id_caught(): + text = CLEAN.replace("### AD-2 — layered deps `[ADOPTED]`", "### AD-5 — layered deps").replace( + "### AD-1 — single write path", "### AD-9 — single write path" + ) + result = lint_spine.lint(text) + assert any("non-monotonic" in f["detail"] for f in result["findings"]) + + +def test_missing_field_caught(): + text = CLEAN.replace("- **Rule:** state changes only through the command bus\n", "") + result = lint_spine.lint(text) + assert any(f["category"] == "ad_fields" and "rule" in f["detail"] for f in result["findings"]) + + +def test_unpinned_dep_caught(): + text = CLEAN.replace("- fastapi@0.115", "- fastapi") + result = lint_spine.lint(text) + assert "version_pin" in cats(result) + + +def test_inline_key_deps_unpinned(): + text = CLEAN.replace(" key_deps:\n - fastapi@0.115\n - pydantic@2.9", " key_deps: [fastapi, redis@7]") + result = lint_spine.lint(text) + pins = [f for f in result["findings"] if f["category"] == "version_pin"] + assert len(pins) == 1 and "fastapi" in pins[0]["detail"] + + +def test_empty_key_deps_ok(): + text = CLEAN.replace(" key_deps:\n - fastapi@0.115\n - pydantic@2.9", " key_deps: []") + result = lint_spine.lint(text) + assert "version_pin" not in cats(result) + + +def test_yaml_comments_not_parsed_as_deps(): + # a SEED comment on the key_deps line must not read as an unpinned dependency + text = CLEAN.replace( + " key_deps:\n - fastapi@0.115\n - pydantic@2.9", + " key_deps: # SEED — verified current 2026-06\n - fastapi@0.115 # web framework", + ) + result = lint_spine.lint(text) + assert "version_pin" not in cats(result) + + +def test_template_token_is_low_severity(): + # a bare {token} can be legitimate brace prose; it is flagged, but low (not high) so the + # mechanical pass stays near-zero false-positive + text = CLEAN.replace("single write path", "{decision}") + result = lint_spine.lint(text) + toks = [f for f in result["findings"] if f["category"] == "placeholder" and "template token" in f["detail"]] + assert toks and all(f["severity"] == "low" for f in toks) + + +def test_no_frontmatter_body_still_scanned(): + text = "## Invariants\n\n### AD-1 — x\n\n- **Binds:** all\n- **Prevents:** drift\n- **Rule:** TBD\n" + result = lint_spine.lint(text) + assert "placeholder" in cats(result) # TBD caught even with no frontmatter + + +def test_frontmatter_value_with_dashes_not_truncated(): + # a value containing '---' must not be read as the closing fence (line-exact close) + text = "---\nscope: 'phase 1 --- phase 2'\nstack:\n key_deps:\n - fastapi\n---\n\n## Invariants\n" + result = lint_spine.lint(text) + assert any(f["category"] == "version_pin" for f in result["findings"]) # read past the inline --- + + +def test_ad_heading_in_fence_not_counted(): + text = ( + "---\nname: 'x'\n---\n\n" + "### AD-1 — real\n\n- **Binds:** all\n- **Prevents:** drift\n- **Rule:** do x\n\n" + "## Docs\n\n```text\n### AD-2 — illustrative only, no fields\n```\n" + ) + result = lint_spine.lint(text) + assert result["ok"] is True # the fenced AD-2 is not a live AD → no ad_fields/ad_id finding + + +def test_map_form_key_deps_unpinned_caught(): + text = "---\nstack:\n key_deps:\n fastapi: '0.115'\n redis:\n---\n\n## Invariants\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_map_form_key_deps_pinned_ok(): + text = "---\nstack:\n key_deps:\n fastapi: '0.115'\n---\n\n## Invariants\n" + result = lint_spine.lint(text) + assert "version_pin" not in cats(result) + + +def test_placeholder_line_number_is_absolute(): + # a TBD after a multi-line fence reports its real file line (fence blanked, not collapsed) + text = ( + "---\nname: 'x'\n---\n\n" + "## A\n\n" + "```text\nf1\nf2\nf3\n```\n\n" + "TBD here\n" + ) + result = lint_spine.lint(text) + ph = next(f for f in result["findings"] if "TBD" in f["detail"]) + n = int(re.search(r"line (\d+)", ph["location"]).group(1)) + assert n == 13 + + +def test_missing_spine_file_reports_error(tmp_path, capsys): + rc = lint_spine.main(["--workspace", str(tmp_path)]) + out = json.loads(capsys.readouterr().out) + assert rc == 0 and out["ok"] is False and "not found" in out["error"] + + +def test_frontmatter_unfilled_token_caught(): + # an unfilled {scope}/{paradigm}/{date} in frontmatter is part of the contract and must lint + text = "---\nname: 'x'\nscope: '{what this spine governs}'\n---\n\n## Invariants\n" + result = lint_spine.lint(text) + fm = [f for f in result["findings"] if f["category"] == "placeholder" and "frontmatter" in f["detail"]] + assert fm and any("template token" in f["detail"] for f in fm) + + +def test_frontmatter_tbd_caught(): + text = "---\nname: 'x'\nstatus: TBD\n---\n\n## Invariants\n" + result = lint_spine.lint(text) + assert any(f["category"] == "placeholder" and "frontmatter" in f["detail"] and "TBD" in f["detail"] + for f in result["findings"]) + + +def test_unreadable_spine_returns_error_not_crash(tmp_path, capsys): + # a spine that exists but can't be UTF-8 decoded must yield error JSON + exit 0, not a traceback + (tmp_path / lint_spine.SPINE).write_bytes(b"\xff\xfe bad bytes not utf-8") + rc = lint_spine.main(["--workspace", str(tmp_path)]) + out = json.loads(capsys.readouterr().out) + assert rc == 0 and out["ok"] is False and "could not read" in out["error"] + + +if __name__ == "__main__": + sys.exit(pytest.main([__file__, "-q"])) diff --git a/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md b/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md index e7f024ed2..b1a77e043 100644 --- a/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md +++ b/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md @@ -1,74 +1,30 @@ --- name: bmad-create-architecture -description: 'Create architecture solution design decisions for AI agent consistency. Use when the user says "lets create architecture" or "create technical architecture" or "create a solution design"' +description: 'DEPRECATED — consolidated into bmad-architecture create intent - this skill will be removed in v7 in favor of `bmad-architecture`.' --- -# Architecture Workflow +# DEPRECATED — forwards to bmad-architecture (create intent) -**Goal:** Create comprehensive architecture decisions through collaborative step-by-step discovery that ensures AI agents implement consistently. - -**Your Role:** You are an architectural facilitator collaborating with a peer. This is a partnership, not a client-vendor relationship. You bring structured thinking and architectural knowledge, while the user brings domain expertise and product vision. Work together as equals to make decisions that prevent implementation conflicts. - -## Conventions - -- Bare paths (e.g. `steps/step-01-init.md`) resolve from the skill root. -- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives). -- `{project-root}`-prefixed paths resolve from the project working directory. -- `{skill-name}` resolves to the skill directory's basename. - -## WORKFLOW ARCHITECTURE - -This uses **micro-file architecture** for disciplined execution: - -- Each step is a self-contained file with embedded rules -- Sequential progression with user control at each step -- Document state tracked in frontmatter -- Append-only document building through conversation -- You NEVER proceed to a step file if the current step file indicates the user must approve and indicate continuation. +This skill was consolidated into `bmad-architecture`. It is retained as a thin compatibility shim so existing invocations by name and `_bmad/custom/bmad-create-architecture.toml` override files keep working. New work should invoke `bmad-architecture` directly — it detects create / update / validate intent from the conversation. ## On Activation -### Step 1: Resolve the Workflow Block +1. Resolve customization: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. This picks up any `{project-root}/_bmad/custom/bmad-create-architecture.toml` and `bmad-create-architecture.user.toml` overrides for the legacy fields (`activation_steps_prepend`, `activation_steps_append`, `persistent_facts`, `on_complete`). -Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow` +2. Load `{project-root}/_bmad/bmm/config.yaml` (and `config.user.yaml` if present) to resolve `{user_name}` and `{communication_language}`. -**If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver: +3. Emit a deprecation notice to the user in `{communication_language}`: -1. `{skill-root}/customize.toml` — defaults -2. `{project-root}/_bmad/custom/{skill-name}.toml` — team overrides -3. `{project-root}/_bmad/custom/{skill-name}.user.toml` — personal overrides + > Notice: `bmad-create-architecture` is deprecated and will be removed in a future release. It now forwards to `bmad-architecture` with create intent. To silence this notice and access the full new customization surface (`spine_template`, `spine_output_path`, `run_folder_pattern`, `doc_standards`, `external_sources`, `external_handoffs`, `finalize_reviewers`), migrate `_bmad/custom/bmad-create-architecture.toml` to `_bmad/custom/bmad-architecture.toml` and invoke `bmad-architecture` directly next time. Customization fields that were in this version still remain in the new version and will be respected if present in `_bmad/custom/bmad-architecture.toml`, but the new version also supports additional fields that you can take advantage of by migrating. -Any missing file is skipped. Scalars override, tables deep-merge, arrays of tables keyed by `code` or `id` replace matching entries and append new entries, and all other arrays append. +4. Invoke `bmad-architecture` with the following context. Pass these as the activating context so `bmad-architecture` honors them instead of resolving its own customization from scratch: -### Step 2: Execute Prepend Steps + - **Intent:** `create` — skip `bmad-architecture`'s usual intent detection step. + - **Pre-resolved legacy customization** — use these in place of resolving from `bmad-architecture`'s own `customize.toml` for the four legacy fields. For everything else (`spine_template`, `spine_output_path`, `run_folder_pattern`, `doc_standards`, `external_sources`, `external_handoffs`, `finalize_reviewers`), use `bmad-architecture`'s own defaults and overrides as normal: + - `activation_steps_prepend` = the resolved value from step 1 + - `activation_steps_append` = the resolved value from step 1 + - `persistent_facts` = the resolved value from step 1 + - `on_complete` = the resolved value from step 1 + - **Original user input:** forward whatever the user said when invoking this skill verbatim. -Execute each entry in `{workflow.activation_steps_prepend}` in order before proceeding. - -### Step 3: Load Persistent Facts - -Treat every entry in `{workflow.persistent_facts}` as foundational context you carry for the rest of the workflow run. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts. All other entries are facts verbatim. - -### Step 4: Load Config - -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: -- Use `{user_name}` for greeting -- Use `{communication_language}` for all communications -- Use `{document_output_language}` for output documents -- Use `{planning_artifacts}` for output location and artifact scanning -- Use `{project_knowledge}` for additional context scanning - -### Step 5: Greet the User - -Greet `{user_name}`, speaking in `{communication_language}`. - -### Step 6: Execute Append Steps - -Execute each entry in `{workflow.activation_steps_append}` in order. - -Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed. - -## Execution - -Read fully and follow: `./steps/step-01-init.md` to begin the workflow. - -**Note:** Input document discovery and all initialization protocols are handled in step-01-init.md. + `bmad-architecture` takes the workflow from here. Do not execute any further steps in this shim. diff --git a/src/bmm-skills/3-solutioning/bmad-create-architecture/architecture-decision-template.md b/src/bmm-skills/3-solutioning/bmad-create-architecture/architecture-decision-template.md deleted file mode 100644 index 51ac3d6ff..000000000 --- a/src/bmm-skills/3-solutioning/bmad-create-architecture/architecture-decision-template.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -stepsCompleted: [] -inputDocuments: [] -workflowType: 'architecture' -project_name: '{{project_name}}' -user_name: '{{user_name}}' -date: '{{date}}' ---- - -# Architecture Decision Document - -_This document builds collaboratively through step-by-step discovery. Sections are appended as we work through each architectural decision together._ diff --git a/src/bmm-skills/3-solutioning/bmad-create-architecture/data/domain-complexity.csv b/src/bmm-skills/3-solutioning/bmad-create-architecture/data/domain-complexity.csv deleted file mode 100644 index d619659ef..000000000 --- a/src/bmm-skills/3-solutioning/bmad-create-architecture/data/domain-complexity.csv +++ /dev/null @@ -1,13 +0,0 @@ -domain,signals,complexity_level,suggested_workflow,web_searches -e_commerce,"shopping,cart,checkout,payment,products,store",medium,standard,"ecommerce architecture patterns, payment processing, inventory management" -fintech,"banking,payment,trading,finance,money,investment",high,enhanced,"financial security, PCI compliance, trading algorithms, fraud detection" -healthcare,"medical,diagnostic,clinical,patient,hospital,health",high,enhanced,"HIPAA compliance, medical data security, FDA regulations, health tech" -social,"social network,community,users,friends,posts,sharing",high,advanced,"social graph algorithms, feed ranking, notification systems, privacy" -education,"learning,course,student,teacher,training,academic",medium,standard,"LMS architecture, progress tracking, assessment systems, video streaming" -productivity,"productivity,workflow,tasks,management,business,tools",medium,standard,"collaboration patterns, real-time editing, notification systems, integration" -media,"content,media,video,audio,streaming,broadcast",high,advanced,"CDN architecture, video encoding, streaming protocols, content delivery" -iot,"IoT,sensors,devices,embedded,smart,connected",high,advanced,"device communication, real-time data processing, edge computing, security" -government,"government,civic,public,admin,policy,regulation",high,enhanced,"accessibility standards, security clearance, data privacy, audit trails" -process_control,"industrial automation,process control,PLC,SCADA,DCS,HMI,operational technology,control system,cyberphysical,MES,instrumentation,I&C,P&ID",high,advanced,"industrial process control architecture, SCADA system design, OT cybersecurity architecture, real-time control systems" -building_automation,"building automation,BAS,BMS,HVAC,smart building,fire alarm,fire protection,fire suppression,life safety,elevator,DDC,access control,sequence of operations,commissioning",high,advanced,"building automation architecture, BACnet integration patterns, smart building design, building management system security" -gaming,"game,gaming,multiplayer,real-time,interactive,entertainment",high,advanced,"real-time multiplayer, game engine architecture, matchmaking, leaderboards" \ No newline at end of file diff --git a/src/bmm-skills/3-solutioning/bmad-create-architecture/data/project-types.csv b/src/bmm-skills/3-solutioning/bmad-create-architecture/data/project-types.csv deleted file mode 100644 index 3733748e9..000000000 --- a/src/bmm-skills/3-solutioning/bmad-create-architecture/data/project-types.csv +++ /dev/null @@ -1,7 +0,0 @@ -project_type,detection_signals,description,typical_starters -web_app,"website,web application,browser,frontend,UI,interface",Web-based applications running in browsers,Next.js, Vite, Remix -mobile_app,"mobile,iOS,Android,app,smartphone,tablet",Native mobile applications,React Native, Expo, Flutter -api_backend,"API,REST,GraphQL,backend,service,microservice",Backend services and APIs,NestJS, Express, Fastify -full_stack,"full-stack,complete,web+mobile,frontend+backend",Applications with both frontend and backend,T3 App, RedwoodJS, Blitz -cli_tool,"CLI,command line,terminal,console,tool",Command-line interface tools,oclif, Commander, Caporal -desktop_app,"desktop,Electron,Tauri,native app,macOS,Windows",Desktop applications,Electron, Tauri, Flutter Desktop \ No newline at end of file diff --git a/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-01-init.md b/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-01-init.md deleted file mode 100644 index c2933dfb8..000000000 --- a/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-01-init.md +++ /dev/null @@ -1,153 +0,0 @@ -# Step 1: Architecture Workflow Initialization - -## MANDATORY EXECUTION RULES (READ FIRST): - -- 🛑 NEVER generate content without user input -- 📖 CRITICAL: ALWAYS read the complete step file before taking any action - partial understanding leads to incomplete decisions -- 🔄 CRITICAL: When loading next step with 'C', ensure the entire file is read and understood before proceeding -- ✅ ALWAYS treat this as collaborative discovery between architectural peers -- 📋 YOU ARE A FACILITATOR, not a content generator -- 💬 FOCUS on initialization and setup only - don't look ahead to future steps -- 🚪 DETECT existing workflow state and handle continuation properly -- ⚠️ ABSOLUTELY NO TIME ESTIMATES - AI development speed has fundamentally changed -- ✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}` - -## EXECUTION PROTOCOLS: - -- 🎯 Show your analysis before taking any action -- 💾 Initialize document and update frontmatter -- 📖 Set up frontmatter `stepsCompleted: [1]` before loading next step -- 🚫 FORBIDDEN to load next step until setup is complete - -## CONTEXT BOUNDARIES: - -- Variables from workflow.md are available in memory -- Previous context = what's in output document + frontmatter -- Don't assume knowledge from other steps -- Input document discovery happens in this step - -## YOUR TASK: - -Initialize the Architecture workflow by detecting continuation state, discovering input documents, and setting up the document for collaborative architectural decision making. - -## INITIALIZATION SEQUENCE: - -### 1. Check for Existing Workflow - -First, check if the output document already exists: - -- Look for existing {planning_artifacts}/`*architecture*.md` -- If exists, read the complete file(s) including frontmatter -- If not exists, this is a fresh workflow - -### 2. Handle Continuation (If Document Exists) - -If the document exists and has frontmatter with `stepsCompleted`: - -- **STOP here** and load `./step-01b-continue.md` immediately -- Do not proceed with any initialization tasks -- Let step-01b handle the continuation logic - -### 3. Fresh Workflow Setup (If No Document) - -If no document exists or no `stepsCompleted` in frontmatter: - -#### A. Input Document Discovery - -Discover and load context documents using smart discovery. Documents can be in the following locations: -- {planning_artifacts}/** -- {output_folder}/** -- {project_knowledge}/** -- {project-root}/docs/** - -Also - when searching - documents can be a single markdown file, or a folder with an index and multiple files. For Example, if searching for `*foo*.md` and not found, also search for a folder called *foo*/index.md (which indicates sharded content) - -Try to discover the following: -- Product Brief (`*brief*.md`) -- Product Requirements Document (`*prd*.md`) -- UX Design (`*ux-design*.md`) and other -- Research Documents (`*research*.md`) -- Project Documentation (generally multiple documents might be found for this in the `{project_knowledge}` or `{project-root}/docs` folder.) -- Project Context (`**/project-context.md`) - -Confirm what you have found with the user, along with asking if the user wants to provide anything else. Only after this confirmation will you proceed to follow the loading rules - -**Loading Rules:** - -- Load ALL discovered files completely that the user confirmed or provided (no offset/limit) -- If there is a project context, whatever is relevant should try to be biased in the remainder of this whole workflow process -- For sharded folders, load ALL files to get complete picture, using the index first to potentially know the potential of each document -- index.md is a guide to what's relevant whenever available -- Track all successfully loaded files in frontmatter `inputDocuments` array - -#### B. Validate Required Inputs - -Before proceeding, verify we have the essential inputs: - -**PRD Validation:** - -- If no PRD found: "Architecture requires a PRD to work from. Please run the PRD workflow first or provide the PRD file path." -- Do NOT proceed without PRD - -**Other Input that might exist:** - -- UX Spec: "Provides UI/UX architectural requirements" - -#### C. Create Initial Document - -Copy the template from `../architecture-decision-template.md` to `{planning_artifacts}/architecture.md` - -#### D. Complete Initialization and Report - -Complete setup and report to user: - -**Document Setup:** - -- Created: `{planning_artifacts}/architecture.md` from template -- Initialized frontmatter with workflow state - -**Input Documents Discovered:** -Report what was found: -"Welcome {{user_name}}! I've set up your Architecture workspace for {{project_name}}. - -**Documents Found:** - -- PRD: {number of PRD files loaded or "None found - REQUIRED"} -- UX Design: {number of UX files loaded or "None found"} -- Research: {number of research files loaded or "None found"} -- Project docs: {number of project files loaded or "None found"} -- Project context: {project_context_rules count of rules for AI agents found} - -**Files loaded:** {list of specific file names or "No additional documents found"} - -Ready to begin architectural decision making. Do you have any other documents you'd like me to include? - -[C] Continue to project context analysis - -## SUCCESS METRICS: - -✅ Existing workflow detected and handed off to step-01b correctly -✅ Fresh workflow initialized with template and frontmatter -✅ Input documents discovered and loaded using sharded-first logic -✅ All discovered files tracked in frontmatter `inputDocuments` -✅ PRD requirement validated and communicated -✅ User confirmed document setup and can proceed - -## FAILURE MODES: - -❌ Proceeding with fresh initialization when existing workflow exists -❌ Not updating frontmatter with discovered input documents -❌ Creating document without proper template -❌ Not checking sharded folders first before whole files -❌ Not reporting what documents were found to user -❌ Proceeding without validating PRD requirement - -❌ **CRITICAL**: Reading only partial step file - leads to incomplete understanding and poor decisions -❌ **CRITICAL**: Proceeding with 'C' without fully reading and understanding the next step file -❌ **CRITICAL**: Making decisions without complete understanding of step requirements and protocols - -## NEXT STEP: - -After user selects [C] to continue, only after ensuring all the template output has been created, then load `./step-02-context.md` to analyze the project context and begin architectural decision making. - -Remember: Do NOT proceed to step-02 until user explicitly selects [C] from the menu and setup is confirmed! diff --git a/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-01b-continue.md b/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-01b-continue.md deleted file mode 100644 index 977896afc..000000000 --- a/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-01b-continue.md +++ /dev/null @@ -1,173 +0,0 @@ -# Step 1b: Workflow Continuation Handler - -## MANDATORY EXECUTION RULES (READ FIRST): - -- 🛑 NEVER generate content without user input - -- 📖 CRITICAL: ALWAYS read the complete step file before taking any action - partial understanding leads to incomplete decisions -- 🔄 CRITICAL: When loading next step with 'C', ensure the entire file is read and understood before proceeding -- ✅ ALWAYS treat this as collaborative discovery between architectural peers -- 📋 YOU ARE A FACILITATOR, not a content generator -- 💬 FOCUS on understanding current state and getting user confirmation -- 🚪 HANDLE workflow resumption smoothly and transparently -- ⚠️ ABSOLUTELY NO TIME ESTIMATES - AI development speed has fundamentally changed -- ✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}` - -## EXECUTION PROTOCOLS: - -- 🎯 Show your analysis before taking any action -- 📖 Read existing document completely to understand current state -- 💾 Update frontmatter to reflect continuation -- 🚫 FORBIDDEN to proceed to next step without user confirmation - -## CONTEXT BOUNDARIES: - -- Existing document and frontmatter are available -- Input documents already loaded should be in frontmatter `inputDocuments` -- Steps already completed are in `stepsCompleted` array -- Focus on understanding where we left off - -## YOUR TASK: - -Handle workflow continuation by analyzing existing work and guiding the user to resume at the appropriate step. - -## CONTINUATION SEQUENCE: - -### 1. Analyze Current Document State - -Read the existing architecture document completely and analyze: - -**Frontmatter Analysis:** - -- `stepsCompleted`: What steps have been done -- `inputDocuments`: What documents were loaded -- `lastStep`: Last step that was executed -- `project_name`, `user_name`, `date`: Basic context - -**Content Analysis:** - -- What sections exist in the document -- What architectural decisions have been made -- What appears incomplete or in progress -- Any TODOs or placeholders remaining - -### 2. Present Continuation Summary - -Show the user their current progress: - -"Welcome back {{user_name}}! I found your Architecture work for {{project_name}}. - -**Current Progress:** - -- Steps completed: {{stepsCompleted list}} -- Last step worked on: Step {{lastStep}} -- Input documents loaded: {{number of inputDocuments}} files - -**Document Sections Found:** -{list all H2/H3 sections found in the document} - -{if_incomplete_sections} -**Incomplete Areas:** - -- {areas that appear incomplete or have placeholders} - {/if_incomplete_sections} - -**What would you like to do?** -[R] Resume from where we left off -[C] Continue to next logical step -[O] Overview of all remaining steps -[X] Start over (will overwrite existing work) -" - -### 3. Handle User Choice - -#### If 'R' (Resume from where we left off): - -- Identify the next step based on `stepsCompleted` -- Load the appropriate step file to continue -- Example: If `stepsCompleted: [1, 2, 3]`, load `./step-04-decisions.md` - -#### If 'C' (Continue to next logical step): - -- Analyze the document content to determine logical next step -- May need to review content quality and completeness -- If content seems complete for current step, advance to next -- If content seems incomplete, suggest staying on current step - -#### If 'O' (Overview of all remaining steps): - -- Provide brief description of all remaining steps -- Let user choose which step to work on -- Don't assume sequential progression is always best - -#### If 'X' (Start over): - -- Confirm: "This will delete all existing architectural decisions. Are you sure? (y/n)" -- If confirmed: Delete existing document and read fully and follow: `./step-01-init.md` -- If not confirmed: Return to continuation menu - -### 4. Navigate to Selected Step - -After user makes choice: - -**Load the selected step file:** - -- Update frontmatter `lastStep` to reflect current navigation -- Execute the selected step file -- Let that step handle the detailed continuation logic - -**State Preservation:** - -- Maintain all existing content in the document -- Keep `stepsCompleted` accurate -- Track the resumption in workflow status - -### 5. Special Continuation Cases - -#### If `stepsCompleted` is empty but document has content: - -- This suggests an interrupted workflow -- Ask user: "I see the document has content but no steps are marked as complete. Should I analyze what's here and set the appropriate step status?" - -#### If document appears corrupted or incomplete: - -- Ask user: "The document seems incomplete. Would you like me to try to recover what's here, or would you prefer to start fresh?" - -#### If document is complete but workflow not marked as done: - -- Ask user: "The architecture looks complete! Should I mark this workflow as finished, or is there more you'd like to work on?" - -## SUCCESS METRICS: - -✅ Existing document state properly analyzed and understood -✅ User presented with clear continuation options -✅ User choice handled appropriately and transparently -✅ Workflow state preserved and updated correctly -✅ Navigation to appropriate step handled smoothly - -## FAILURE MODES: - -❌ Not reading the complete existing document before making suggestions -❌ Losing track of what steps were actually completed -❌ Automatically proceeding without user confirmation of next steps -❌ Not checking for incomplete or placeholder content -❌ Losing existing document content during resumption - -❌ **CRITICAL**: Reading only partial step file - leads to incomplete understanding and poor decisions -❌ **CRITICAL**: Proceeding with 'C' without fully reading and understanding the next step file -❌ **CRITICAL**: Making decisions without complete understanding of step requirements and protocols - -## NEXT STEP: - -After user selects their continuation option, load the appropriate step file based on their choice. The step file will handle the detailed work from that point forward. - -Valid step files to load: -- `./step-02-context.md` -- `./step-03-starter.md` -- `./step-04-decisions.md` -- `./step-05-patterns.md` -- `./step-06-structure.md` -- `./step-07-validation.md` -- `./step-08-complete.md` - -Remember: The goal is smooth, transparent resumption that respects the work already done while giving the user control over how to proceed. diff --git a/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-02-context.md b/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-02-context.md deleted file mode 100644 index 96cb5c4e1..000000000 --- a/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-02-context.md +++ /dev/null @@ -1,224 +0,0 @@ -# Step 2: Project Context Analysis - -## MANDATORY EXECUTION RULES (READ FIRST): - -- 🛑 NEVER generate content without user input - -- 📖 CRITICAL: ALWAYS read the complete step file before taking any action - partial understanding leads to incomplete decisions -- 🔄 CRITICAL: When loading next step with 'C', ensure the entire file is read and understood before proceeding -- ✅ ALWAYS treat this as collaborative discovery between architectural peers -- 📋 YOU ARE A FACILITATOR, not a content generator -- 💬 FOCUS on understanding project scope and requirements for architecture -- 🎯 ANALYZE loaded documents, don't assume or generate requirements -- ⚠️ ABSOLUTELY NO TIME ESTIMATES - AI development speed has fundamentally changed -- ✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}` - -## EXECUTION PROTOCOLS: - -- 🎯 Show your analysis before taking any action -- ⚠️ Present A/P/C menu after generating project context analysis -- 💾 ONLY save when user chooses C (Continue) -- 📖 Update frontmatter `stepsCompleted: [1, 2]` before loading next step -- 🚫 FORBIDDEN to load next step until C is selected - -## COLLABORATION MENUS (A/P/C): - -This step will generate content and present choices: - -- **A (Advanced Elicitation)**: Use discovery protocols to develop deeper insights about project context and architectural implications -- **P (Party Mode)**: Bring multiple perspectives to analyze project requirements from different architectural angles -- **C (Continue)**: Save the content to the document and proceed to next step - -## PROTOCOL INTEGRATION: - -- When 'A' selected: Invoke the `bmad-advanced-elicitation` skill -- When 'P' selected: Invoke the `bmad-party-mode` skill -- PROTOCOLS always return to display this step's A/P/C menu after the A or P have completed -- User accepts/rejects protocol changes before proceeding - -## CONTEXT BOUNDARIES: - -- Current document and frontmatter from step 1 are available -- Input documents already loaded are in memory (PRD, epics, UX spec, etc.) -- Focus on architectural implications of requirements -- No technology decisions yet - pure analysis phase - -## YOUR TASK: - -Fully read and Analyze the loaded project documents to understand architectural scope, requirements, and constraints before beginning decision making. - -## CONTEXT ANALYSIS SEQUENCE: - -### 1. Review Project Requirements - -**From PRD Analysis:** - -- Extract and analyze Functional Requirements (FRs) -- Identify Non-Functional Requirements (NFRs) like performance, security, compliance -- Note any technical constraints or dependencies mentioned -- Count and categorize requirements to understand project scale - -**From Epics/Stories (if available):** - -- Map epic structure and user stories to architectural components -- Extract acceptance criteria for technical implications -- Identify cross-cutting concerns that span multiple epics -- Estimate story complexity for architectural planning - -**From UX Design (if available):** - -- Extract architectural implications from UX requirements: - - Component complexity (simple forms vs rich interactions) - - Animation/transition requirements - - Real-time update needs (live data, collaborative features) - - Platform-specific UI requirements - - Accessibility standards (WCAG compliance level) - - Responsive design breakpoints - - Offline capability requirements - - Performance expectations (load times, interaction responsiveness) - -### 2. Project Scale Assessment - -Calculate and present project complexity: - -**Complexity Indicators:** - -- Real-time features requirements -- Multi-tenancy needs -- Regulatory compliance requirements -- Integration complexity -- User interaction complexity -- Data complexity and volume - -### 3. Reflect Understanding - -Present your analysis back to user for validation: - -"I'm reviewing your project documentation for {{project_name}}. - -{if_epics_loaded}I see {{epic_count}} epics with {{story_count}} total stories.{/if_epics_loaded} -{if_no_epics}I found {{fr_count}} functional requirements organized into {{fr_category_list}}.{/if_no_epics} -{if_ux_loaded}I also found your UX specification which defines the user experience requirements.{/if_ux_loaded} - -**Key architectural aspects I notice:** - -- [Summarize core functionality from FRs] -- [Note critical NFRs that will shape architecture] -- {if_ux_loaded}[Note UX complexity and technical requirements]{/if_ux_loaded} -- [Identify unique technical challenges or constraints] -- [Highlight any regulatory or compliance requirements] - -**Scale indicators:** - -- Project complexity appears to be: [low/medium/high/enterprise] -- Primary technical domain: [web/mobile/api/backend/full-stack/etc] -- Cross-cutting concerns identified: [list major ones] - -This analysis will help me guide you through the architectural decisions needed to ensure AI agents implement this consistently. - -Does this match your understanding of the project scope and requirements?" - -### 4. Generate Project Context Content - -Prepare the content to append to the document: - -#### Content Structure: - -```markdown -## Project Context Analysis - -### Requirements Overview - -**Functional Requirements:** -{{analysis of FRs and what they mean architecturally}} - -**Non-Functional Requirements:** -{{NFRs that will drive architectural decisions}} - -**Scale & Complexity:** -{{project_scale_assessment}} - -- Primary domain: {{technical_domain}} -- Complexity level: {{complexity_level}} -- Estimated architectural components: {{component_count}} - -### Technical Constraints & Dependencies - -{{known_constraints_dependencies}} - -### Cross-Cutting Concerns Identified - -{{concerns_that_will_affect_multiple_components}} -``` - -### 5. Present Content and Menu - -Show the generated content and present choices: - -"I've drafted the Project Context Analysis based on your requirements. This sets the foundation for our architectural decisions. - -**Here's what I'll add to the document:** - -[Show the complete markdown content from step 4] - -**What would you like to do?** -[A] Advanced Elicitation - Let's dive deeper into architectural implications -[P] Party Mode - Bring different perspectives to analyze requirements -[C] Continue - Save this analysis and begin architectural decisions" - -### 6. Handle Menu Selection - -#### If 'A' (Advanced Elicitation): - -- Invoke the `bmad-advanced-elicitation` skill with the current context analysis -- Process the enhanced architectural insights that come back -- Ask user: "Accept these enhancements to the project context analysis? (y/n)" -- If yes: Update content with improvements, then return to A/P/C menu -- If no: Keep original content, then return to A/P/C menu - -#### If 'P' (Party Mode): - -- Invoke the `bmad-party-mode` skill with the current project context -- Process the collaborative improvements to architectural understanding -- Ask user: "Accept these changes to the project context analysis? (y/n)" -- If yes: Update content with improvements, then return to A/P/C menu -- If no: Keep original content, then return to A/P/C menu - -#### If 'C' (Continue): - -- Append the final content to `{planning_artifacts}/architecture.md` -- Update frontmatter: `stepsCompleted: [1, 2]` -- Load `./step-03-starter.md` - -## APPEND TO DOCUMENT: - -When user selects 'C', append the content directly to the document using the structure from step 4. - -## SUCCESS METRICS: - -✅ All input documents thoroughly analyzed for architectural implications -✅ Project scope and complexity clearly assessed and validated -✅ Technical constraints and dependencies identified -✅ Cross-cutting concerns mapped for architectural planning -✅ User confirmation of project understanding -✅ A/P/C menu presented and handled correctly -✅ Content properly appended to document when C selected - -## FAILURE MODES: - -❌ Skimming documents without deep architectural analysis -❌ Missing or misinterpreting critical NFRs -❌ Not validating project understanding with user -❌ Underestimating complexity indicators -❌ Generating content without real analysis of loaded documents -❌ Not presenting A/P/C menu after content generation - -❌ **CRITICAL**: Reading only partial step file - leads to incomplete understanding and poor decisions -❌ **CRITICAL**: Proceeding with 'C' without fully reading and understanding the next step file -❌ **CRITICAL**: Making decisions without complete understanding of step requirements and protocols - -## NEXT STEP: - -After user selects 'C' and content is saved to document, load `./step-03-starter.md` to evaluate starter template options. - -Remember: Do NOT proceed to step-03 until user explicitly selects 'C' from the A/P/C menu and content is saved! diff --git a/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-03-starter.md b/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-03-starter.md deleted file mode 100644 index 339092a17..000000000 --- a/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-03-starter.md +++ /dev/null @@ -1,329 +0,0 @@ -# Step 3: Starter Template Evaluation - -## MANDATORY EXECUTION RULES (READ FIRST): - -- 🛑 NEVER generate content without user input -- ✅ ALWAYS treat this as collaborative discovery between architectural peers -- 📋 YOU ARE A FACILITATOR, not a content generator -- 💬 FOCUS on evaluating starter template options with current versions -- 🌐 ALWAYS search the web to verify current versions - NEVER trust hardcoded versions -- ⚠️ ABSOLUTELY NO TIME ESTIMATES - AI development speed has fundamentally changed -- 📖 CRITICAL: ALWAYS read the complete step file before taking any action - partial understanding leads to incomplete architecture -- 🔄 CRITICAL: When loading next step with 'C', ensure the entire file is read and understood before proceeding -- ✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}` - -## EXECUTION PROTOCOLS: - -- 🎯 Show your analysis before taking any action -- 🌐 Search the web to verify current versions and options -- ⚠️ Present A/P/C menu after generating starter template analysis -- 💾 ONLY save when user chooses C (Continue) -- 📖 Update frontmatter `stepsCompleted: [1, 2, 3]` before loading next step -- 🚫 FORBIDDEN to load next step until C is selected - -## COLLABORATION MENUS (A/P/C): - -This step will generate content and present choices: - -- **A (Advanced Elicitation)**: Use discovery protocols to explore unconventional starter options or custom approaches -- **P (Party Mode)**: Bring multiple perspectives to evaluate starter trade-offs for different use cases -- **C (Continue)**: Save the content to the document and proceed to next step - -## PROTOCOL INTEGRATION: - -- When 'A' selected: Invoke the `bmad-advanced-elicitation` skill -- When 'P' selected: Invoke the `bmad-party-mode` skill -- PROTOCOLS always return to display this step's A/P/C menu after the A or P have completed -- User accepts/rejects protocol changes before proceeding - -## CONTEXT BOUNDARIES: - -- Project context from step 2 is available and complete -- Project context file from step-01 may contain technical preferences -- No architectural decisions made yet - evaluating foundations -- Focus on technical preferences discovery and starter evaluation -- Consider project requirements and existing preferences when evaluating options - -## YOUR TASK: - -Discover technical preferences and evaluate starter template options, leveraging existing technical preferences and establishing solid architectural foundations. - -## STARTER EVALUATION SEQUENCE: - -### 0. Check Technical Preferences & Context - -**Check Project Context for Existing Technical Preferences:** -"Before we dive into starter templates, let me check if you have any technical preferences already documented. - -{{if_project_context_exists}} -I found some technical rules in your project context file: -{{extracted_technical_preferences_from_project_context}} - -**Project Context Technical Rules Found:** - -- Languages/Frameworks: {{languages_frameworks_from_context}} -- Tools & Libraries: {{tools_from_context}} -- Development Patterns: {{patterns_from_context}} -- Platform Preferences: {{platforms_from_context}} - -{{else}} -No existing technical preferences found in project context file. We'll establish your technical preferences now. -{{/if_project_context}}" - -**Discover User Technical Preferences:** -"Based on your project context, let's discuss your technical preferences: - -{{primary_technology_category}} Preferences: - -- **Languages**: Do you have preferences between TypeScript/JavaScript, Python, Go, Rust, etc.? -- **Frameworks**: Any existing familiarity or preferences (React, Vue, Angular, Next.js, etc.)? -- **Databases**: Any preferences or existing infrastructure (PostgreSQL, MongoDB, MySQL, etc.)? - -**Development Experience:** - -- What's your team's experience level with different technologies? -- Are there any technologies you want to learn vs. what you're comfortable with? - -**Platform/Deployment Preferences:** - -- Cloud provider preferences (AWS, Vercel, Railway, etc.)? -- Container preferences (Docker, Serverless, Traditional)? - -**Integrations:** - -- Any existing systems or APIs you need to integrate with? -- Third-party services you plan to use (payment, authentication, analytics, etc.)? - -These preferences will help me recommend the most suitable starter templates and guide our architectural decisions." - -### 1. Identify Primary Technology Domain - -Based on project context analysis and technical preferences, identify the primary technology stack: - -- **Web application** → Look for Next.js, Vite, Remix, SvelteKit starters -- **Mobile app** → Look for React Native, Expo, Flutter starters -- **API/Backend** → Look for NestJS, Express, Fastify, Supabase starters -- **CLI tool** → Look for CLI framework starters (oclif, commander, etc.) -- **Full-stack** → Look for T3, RedwoodJS, Blitz, Next.js starters -- **Desktop** → Look for Electron, Tauri starters - -### 2. UX Requirements Consideration - -If UX specification was loaded, consider UX requirements when selecting starter: - -- **Rich animations** → Framer Motion compatible starter -- **Complex forms** → React Hook Form included starter -- **Real-time features** → Socket.io or WebSocket ready starter -- **Design system** → Storybook-enabled starter -- **Offline capability** → Service worker or PWA configured starter - -### 3. Research Current Starter Options - -Search the web to find current, maintained starter templates: - -``` -Search the web: "{{primary_technology}} starter template CLI create command latest" -Search the web: "{{primary_technology}} boilerplate generator latest options" -Search the web: "{{primary_technology}} production-ready starter best practices" -``` - -### 4. Investigate Top Starter Options - -For each promising starter found, investigate details: - -``` -Search the web: "{{starter_name}} default setup technologies included latest" -Search the web: "{{starter_name}} project structure file organization" -Search the web: "{{starter_name}} production deployment capabilities" -Search the web: "{{starter_name}} recent updates maintenance status" -``` - -### 5. Analyze What Each Starter Provides - -For each viable starter option, document: - -**Technology Decisions Made:** - -- Language/TypeScript configuration -- Styling solution (CSS, Tailwind, Styled Components, etc.) -- Testing framework setup -- Linting/Formatting configuration -- Build tooling and optimization -- Project structure and organization - -**Architectural Patterns Established:** - -- Code organization patterns -- Component structure conventions -- API layering approach -- State management setup -- Routing patterns -- Environment configuration - -**Development Experience Features:** - -- Hot reloading and development server -- TypeScript configuration -- Debugging setup -- Testing infrastructure -- Documentation generation - -### 6. Present Starter Options - -Based on user skill level and project needs: - -**For Expert Users:** -"Found {{starter_name}} which provides: -{{quick_decision_list_of_key_decisions}} - -This would establish our base architecture with these technical decisions already made. Use it?" - -**For Intermediate Users:** -"I found {{starter_name}}, which is a well-maintained starter for {{project_type}} projects. - -It makes these architectural decisions for us: -{{decision_list_with_explanations}} - -This gives us a solid foundation following current best practices. Should we use it?" - -**For Beginner Users:** -"I found {{starter_name}}, which is like a pre-built foundation for your project. - -Think of it like buying a prefab house frame instead of cutting each board yourself. - -It makes these decisions for us: -{{friendly_explanation_of_decisions}} - -This is a great starting point that follows best practices and saves us from making dozens of small technical choices. Should we use it?" - -### 7. Get Current CLI Commands - -If user shows interest in a starter, get the exact current commands: - -``` -Search the web: "{{starter_name}} CLI command options flags latest" -Search the web: "{{starter_name}} create new project command examples" -``` - -### 8. Generate Starter Template Content - -Prepare the content to append to the document: - -#### Content Structure: - -````markdown -## Starter Template Evaluation - -### Primary Technology Domain - -{{identified_domain}} based on project requirements analysis - -### Starter Options Considered - -{{analysis_of_evaluated_starters}} - -### Selected Starter: {{starter_name}} - -**Rationale for Selection:** -{{why_this_starter_was_chosen}} - -**Initialization Command:** - -```bash -{{full_starter_command_with_options}} -``` - -**Architectural Decisions Provided by Starter:** - -**Language & Runtime:** -{{language_typescript_setup}} - -**Styling Solution:** -{{styling_solution_configuration}} - -**Build Tooling:** -{{build_tools_and_optimization}} - -**Testing Framework:** -{{testing_setup_and_configuration}} - -**Code Organization:** -{{project_structure_and_patterns}} - -**Development Experience:** -{{development_tools_and_workflow}} - -**Note:** Project initialization using this command should be the first implementation story. - -```` - -### 9. Present Content and Menu - -Show the generated content and present choices: - -"I've analyzed starter template options for {{project_type}} projects. - -**Here's what I'll add to the document:** - -[Show the complete markdown content from step 8] - -**What would you like to do?** -[A] Advanced Elicitation - Explore custom approaches or unconventional starters -[P] Party Mode - Evaluate trade-offs from different perspectives -[C] Continue - Save this decision and move to architectural decisions" - -### 10. Handle Menu Selection - -#### If 'A' (Advanced Elicitation): - -- Invoke the `bmad-advanced-elicitation` skill with current starter analysis -- Process enhanced insights about starter options or custom approaches -- Ask user: "Accept these changes to the starter template evaluation? (y/n)" -- If yes: Update content, then return to A/P/C menu -- If no: Keep original content, then return to A/P/C menu - -#### If 'P' (Party Mode): - -- Invoke the `bmad-party-mode` skill with starter evaluation context -- Process collaborative insights about starter trade-offs -- Ask user: "Accept these changes to the starter template evaluation? (y/n)" -- If yes: Update content, then return to A/P/C menu -- If no: Keep original content, then return to A/P/C menu - -#### If 'C' (Continue): - -- Append the final content to `{planning_artifacts}/architecture.md` -- Update frontmatter: `stepsCompleted: [1, 2, 3]` -- Load `./step-04-decisions.md` - -## APPEND TO DOCUMENT: - -When user selects 'C', append the content directly to the document using the structure from step 8. - -## SUCCESS METRICS: - -✅ Primary technology domain correctly identified from project context -✅ Current, maintained starter templates researched and evaluated -✅ All versions verified using web search, not hardcoded -✅ Architectural implications of starter choice clearly documented -✅ User provided with clear rationale for starter selection -✅ A/P/C menu presented and handled correctly -✅ Content properly appended to document when C selected - -## FAILURE MODES: - -❌ Not verifying current versions with web search -❌ Ignoring UX requirements when evaluating starters -❌ Not documenting what architectural decisions the starter makes -❌ Failing to consider maintenance status of starter templates -❌ Not providing clear rationale for starter selection -❌ Not presenting A/P/C menu after content generation -❌ **CRITICAL**: Reading only partial step file - leads to incomplete understanding and poor decisions -❌ **CRITICAL**: Proceeding with 'C' without fully reading and understanding the next step file -❌ **CRITICAL**: Making decisions without complete understanding of step requirements and protocols - -## NEXT STEP: - -After user selects 'C' and content is saved to document, load `./step-04-decisions.md` to begin making specific architectural decisions. - -Remember: Do NOT proceed to step-04 until user explicitly selects 'C' from the A/P/C menu and content is saved! diff --git a/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-04-decisions.md b/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-04-decisions.md deleted file mode 100644 index 061b69a7e..000000000 --- a/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-04-decisions.md +++ /dev/null @@ -1,318 +0,0 @@ -# Step 4: Core Architectural Decisions - -## MANDATORY EXECUTION RULES (READ FIRST): - -- 🛑 NEVER generate content without user input - -- 📖 CRITICAL: ALWAYS read the complete step file before taking any action - partial understanding leads to incomplete decisions -- 🔄 CRITICAL: When loading next step with 'C', ensure the entire file is read and understood before proceeding -- ✅ ALWAYS treat this as collaborative discovery between architectural peers -- 📋 YOU ARE A FACILITATOR, not a content generator -- 💬 FOCUS on making critical architectural decisions collaboratively -- 🌐 ALWAYS search the web to verify current technology versions -- ⚠️ ABSOLUTELY NO TIME ESTIMATES - AI development speed has fundamentally changed -- ✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}` - -## EXECUTION PROTOCOLS: - -- 🎯 Show your analysis before taking any action -- 🌐 Search the web to verify technology versions and options -- ⚠️ Present A/P/C menu after each major decision category -- 💾 ONLY save when user chooses C (Continue) -- 📖 Update frontmatter `stepsCompleted: [1, 2, 3, 4]` before loading next step -- 🚫 FORBIDDEN to load next step until C is selected - -## COLLABORATION MENUS (A/P/C): - -This step will generate content and present choices for each decision category: - -- **A (Advanced Elicitation)**: Use discovery protocols to explore innovative approaches to specific decisions -- **P (Party Mode)**: Bring multiple perspectives to evaluate decision trade-offs -- **C (Continue)**: Save the current decisions and proceed to next decision category - -## PROTOCOL INTEGRATION: - -- When 'A' selected: Invoke the `bmad-advanced-elicitation` skill -- When 'P' selected: Invoke the `bmad-party-mode` skill -- PROTOCOLS always return to display this step's A/P/C menu after the A or P have completed -- User accepts/rejects protocol changes before proceeding - -## CONTEXT BOUNDARIES: - -- Project context from step 2 is available -- Starter template choice from step 3 is available -- Project context file may contain technical preferences and rules -- Technical preferences discovered in step 3 are available -- Focus on decisions not already made by starter template or existing preferences -- Collaborative decision making, not recommendations - -## YOUR TASK: - -Facilitate collaborative architectural decision making, leveraging existing technical preferences and starter template decisions, focusing on remaining choices critical to the project's success. - -## DECISION MAKING SEQUENCE: - -### 1. Load Decision Framework & Check Existing Preferences - -**Review Technical Preferences from Step 3:** -"Based on our technical preferences discussion in step 3, let's build on those foundations: - -**Your Technical Preferences:** -{{user_technical_preferences_from_step_3}} - -**Starter Template Decisions:** -{{starter_template_decisions}} - -**Project Context Technical Rules:** -{{project_context_technical_rules}}" - -**Identify Remaining Decisions:** -Based on technical preferences, starter template choice, and project context, identify remaining critical decisions: - -**Already Decided (Don't re-decide these):** - -- {{starter_template_decisions}} -- {{user_technology_preferences}} -- {{project_context_technical_rules}} - -**Critical Decisions:** Must be decided before implementation can proceed -**Important Decisions:** Shape the architecture significantly -**Nice-to-Have:** Can be deferred if needed - -### 2. Decision Categories by Priority - -#### Category 1: Data Architecture - -- Database choice (if not determined by starter) -- Data modeling approach -- Data validation strategy -- Migration approach -- Caching strategy - -#### Category 2: Authentication & Security - -- Authentication method -- Authorization patterns -- Security middleware -- Data encryption approach -- API security strategy - -#### Category 3: API & Communication - -- API design patterns (REST, GraphQL, etc.) -- API documentation approach -- Error handling standards -- Rate limiting strategy -- Communication between services - -#### Category 4: Frontend Architecture (if applicable) - -- State management approach -- Component architecture -- Routing strategy -- Performance optimization -- Bundle optimization - -#### Category 5: Infrastructure & Deployment - -- Hosting strategy -- CI/CD pipeline approach -- Environment configuration -- Monitoring and logging -- Scaling strategy - -### 3. Facilitate Each Decision Category - -For each category, facilitate collaborative decision making: - -**Present the Decision:** -Based on user skill level and project context: - -**Expert Mode:** -"{{Decision_Category}}: {{Specific_Decision}} - -Options: {{concise_option_list_with_tradeoffs}} - -What's your preference for this decision?" - -**Intermediate Mode:** -"Next decision: {{Human_Friendly_Category}} - -We need to choose {{Specific_Decision}}. - -Common options: -{{option_list_with_brief_explanations}} - -For your project, I'd lean toward {{recommendation}} because {{reason}}. What are your thoughts?" - -**Beginner Mode:** -"Let's talk about {{Human_Friendly_Category}}. - -{{Educational_Context_About_Why_This_Matters}} - -Think of it like {{real_world_analogy}}. - -Your main options: -{{friendly_options_with_pros_cons}} - -My suggestion: {{recommendation}} -This is good for you because {{beginner_friendly_reason}}. - -What feels right to you?" - -**Verify Technology Versions:** -If decision involves specific technology: - -``` -Search the web: "{{technology}} latest stable version" -Search the web: "{{technology}} current LTS version" -Search the web: "{{technology}} production readiness" -``` - -**Get User Input:** -"What's your preference? (or 'explain more' for details)" - -**Handle User Response:** - -- If user wants more info: Provide deeper explanation -- If user has preference: Discuss implications and record decision -- If user wants alternatives: Explore other options - -**Record the Decision:** - -- Category: {{category}} -- Decision: {{user_choice}} -- Version: {{verified_version_if_applicable}} -- Rationale: {{user_reasoning_or_default}} -- Affects: {{components_or_epics}} -- Provided by Starter: {{yes_if_from_starter}} - -### 4. Check for Cascading Implications - -After each major decision, identify related decisions: - -"This choice means we'll also need to decide: - -- {{related_decision_1}} -- {{related_decision_2}}" - -### 5. Generate Decisions Content - -After facilitating all decision categories, prepare the content to append: - -#### Content Structure: - -```markdown -## Core Architectural Decisions - -### Decision Priority Analysis - -**Critical Decisions (Block Implementation):** -{{critical_decisions_made}} - -**Important Decisions (Shape Architecture):** -{{important_decisions_made}} - -**Deferred Decisions (Post-MVP):** -{{decisions_deferred_with_rationale}} - -### Data Architecture - -{{data_related_decisions_with_versions_and_rationale}} - -### Authentication & Security - -{{security_related_decisions_with_versions_and_rationale}} - -### API & Communication Patterns - -{{api_related_decisions_with_versions_and_rationale}} - -### Frontend Architecture - -{{frontend_related_decisions_with_versions_and_rationale}} - -### Infrastructure & Deployment - -{{infrastructure_related_decisions_with_versions_and_rationale}} - -### Decision Impact Analysis - -**Implementation Sequence:** -{{ordered_list_of_decisions_for_implementation}} - -**Cross-Component Dependencies:** -{{how_decisions_affect_each_other}} -``` - -### 6. Present Content and Menu - -Show the generated decisions content and present choices: - -"I've documented all the core architectural decisions we've made together. - -**Here's what I'll add to the document:** - -[Show the complete markdown content from step 5] - -**What would you like to do?** -[A] Advanced Elicitation - Explore innovative approaches to any specific decisions -[P] Party Mode - Review decisions from multiple perspectives -[C] Continue - Save these decisions and move to implementation patterns" - -### 7. Handle Menu Selection - -#### If 'A' (Advanced Elicitation): - -- Invoke the `bmad-advanced-elicitation` skill with specific decision categories -- Process enhanced insights about particular decisions -- Ask user: "Accept these enhancements to the architectural decisions? (y/n)" -- If yes: Update content, then return to A/P/C menu -- If no: Keep original content, then return to A/P/C menu - -#### If 'P' (Party Mode): - -- Invoke the `bmad-party-mode` skill with architectural decisions context -- Process collaborative insights about decision trade-offs -- Ask user: "Accept these changes to the architectural decisions? (y/n)" -- If yes: Update content, then return to A/P/C menu -- If no: Keep original content, then return to A/P/C menu - -#### If 'C' (Continue): - -- Append the final content to `{planning_artifacts}/architecture.md` -- Update frontmatter: `stepsCompleted: [1, 2, 3, 4]` -- Load `./step-05-patterns.md` - -## APPEND TO DOCUMENT: - -When user selects 'C', append the content directly to the document using the structure from step 5. - -## SUCCESS METRICS: - -✅ All critical architectural decisions made collaboratively -✅ Technology versions verified using web search -✅ Decision rationale clearly documented -✅ Cascading implications identified and addressed -✅ User provided appropriate level of explanation for skill level -✅ A/P/C menu presented and handled correctly for each category -✅ Content properly appended to document when C selected - -## FAILURE MODES: - -❌ Making recommendations instead of facilitating decisions -❌ Not verifying technology versions with web search -❌ Missing cascading implications between decisions -❌ Not adapting explanations to user skill level -❌ Forgetting to document decisions made by starter template -❌ Not presenting A/P/C menu after content generation - -❌ **CRITICAL**: Reading only partial step file - leads to incomplete understanding and poor decisions -❌ **CRITICAL**: Proceeding with 'C' without fully reading and understanding the next step file -❌ **CRITICAL**: Making decisions without complete understanding of step requirements and protocols - -## NEXT STEP: - -After user selects 'C' and content is saved to document, load `./step-05-patterns.md` to define implementation patterns that ensure consistency across AI agents. - -Remember: Do NOT proceed to step-05 until user explicitly selects 'C' from the A/P/C menu and content is saved! diff --git a/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-05-patterns.md b/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-05-patterns.md deleted file mode 100644 index 6fa446d95..000000000 --- a/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-05-patterns.md +++ /dev/null @@ -1,359 +0,0 @@ -# Step 5: Implementation Patterns & Consistency Rules - -## MANDATORY EXECUTION RULES (READ FIRST): - -- 🛑 NEVER generate content without user input - -- 📖 CRITICAL: ALWAYS read the complete step file before taking any action - partial understanding leads to incomplete decisions -- 🔄 CRITICAL: When loading next step with 'C', ensure the entire file is read and understood before proceeding -- ✅ ALWAYS treat this as collaborative discovery between architectural peers -- 📋 YOU ARE A FACILITATOR, not a content generator -- 💬 FOCUS on patterns that prevent AI agent implementation conflicts -- 🎯 EMPHASIZE what agents could decide DIFFERENTLY if not specified -- ⚠️ ABSOLUTELY NO TIME ESTIMATES - AI development speed has fundamentally changed -- ✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}` - -## EXECUTION PROTOCOLS: - -- 🎯 Show your analysis before taking any action -- 🎯 Focus on consistency, not implementation details -- ⚠️ Present A/P/C menu after generating patterns content -- 💾 ONLY save when user chooses C (Continue) -- 📖 Update frontmatter `stepsCompleted: [1, 2, 3, 4, 5]` before loading next step -- 🚫 FORBIDDEN to load next step until C is selected - -## COLLABORATION MENUS (A/P/C): - -This step will generate content and present choices: - -- **A (Advanced Elicitation)**: Use discovery protocols to develop comprehensive consistency patterns -- **P (Party Mode)**: Bring multiple perspectives to identify potential conflict points -- **C (Continue)**: Save the patterns and proceed to project structure - -## PROTOCOL INTEGRATION: - -- When 'A' selected: Invoke the `bmad-advanced-elicitation` skill -- When 'P' selected: Invoke the `bmad-party-mode` skill -- PROTOCOLS always return to display this step's A/P/C menu after the A or P have completed -- User accepts/rejects protocol changes before proceeding - -## CONTEXT BOUNDARIES: - -- Core architectural decisions from step 4 are complete -- Technology stack is decided and versions are verified -- Focus on HOW agents should implement, not WHAT they should implement -- Consider what could vary between different AI agents - -## YOUR TASK: - -Define implementation patterns and consistency rules that ensure multiple AI agents write compatible, consistent code that works together seamlessly. - -## PATTERNS DEFINITION SEQUENCE: - -### 1. Identify Potential Conflict Points - -Based on the chosen technology stack and decisions, identify where AI agents could make different choices: - -**Naming Conflicts:** - -- Database table/column naming conventions -- API endpoint naming patterns -- File and directory naming -- Component/function/variable naming -- Route parameter formats - -**Structural Conflicts:** - -- Where tests are located -- How components are organized -- Where utilities and helpers go -- Configuration file organization -- Static asset organization - -**Format Conflicts:** - -- API response wrapper formats -- Error response structures -- Date/time formats in APIs and UI -- JSON field naming conventions -- API status code usage - -**Communication Conflicts:** - -- Event naming conventions -- Event payload structures -- State update patterns -- Action naming conventions -- Logging formats and levels - -**Process Conflicts:** - -- Loading state handling -- Error recovery patterns -- Retry implementation approaches -- Authentication flow patterns -- Validation timing and methods - -### 2. Facilitate Pattern Decisions - -For each conflict category, facilitate collaborative pattern definition: - -**Present the Conflict Point:** -"Given that we're using {{tech_stack}}, different AI agents might handle {{conflict_area}} differently. - -For example, one agent might name database tables 'users' while another uses 'Users' - this would cause conflicts. - -We need to establish consistent patterns that all agents follow." - -**Show Options and Trade-offs:** -"Common approaches for {{pattern_category}}: - -1. {{option_1}} - {{pros_and_cons}} -2. {{option_2}} - {{pros_and_cons}} -3. {{option_3}} - {{pros_and_cons}} - -Which approach makes the most sense for our project?" - -**Get User Decision:** -"What's your preference for this pattern? (or discuss the trade-offs more)" - -### 3. Define Pattern Categories - -#### Naming Patterns - -**Database Naming:** - -- Table naming: users, Users, or user? -- Column naming: user_id or userId? -- Foreign key format: user_id or fk_user? -- Index naming: idx_users_email or users_email_index? - -**API Naming:** - -- REST endpoint naming: /users or /user? Plural or singular? -- Route parameter format: :id or {id}? -- Query parameter naming: user_id or userId? -- Header naming conventions: X-Custom-Header or Custom-Header? - -**Code Naming:** - -- Component naming: UserCard or user-card? -- File naming: UserCard.tsx or user-card.tsx? -- Function naming: getUserData or get_user_data? -- Variable naming: userId or user_id? - -#### Structure Patterns - -**Project Organization:** - -- Where do tests live? **tests**/ or \*.test.ts co-located? -- How are components organized? By feature or by type? -- Where do shared utilities go? -- How are services and repositories organized? - -**File Structure:** - -- Config file locations and naming -- Static asset organization -- Documentation placement -- Environment file organization - -#### Format Patterns - -**API Formats:** - -- API response wrapper? {data: ..., error: ...} or direct response? -- Error format? {message, code} or {error: {type, detail}}? -- Date format in JSON? ISO strings or timestamps? -- Success response structure? - -**Data Formats:** - -- JSON field naming: snake_case or camelCase? -- Boolean representations: true/false or 1/0? -- Null handling patterns -- Array vs object for single items - -#### Communication Patterns - -**Event Systems:** - -- Event naming convention: user.created or UserCreated? -- Event payload structure standards -- Event versioning approach -- Async event handling patterns - -**State Management:** - -- State update patterns: immutable updates or direct mutation? -- Action naming conventions -- Selector patterns -- State organization principles - -#### Process Patterns - -**Error Handling:** - -- Global error handling approach -- Error boundary patterns -- User-facing error message format -- Logging vs user error distinction - -**Loading States:** - -- Loading state naming conventions -- Global vs local loading states -- Loading state persistence -- Loading UI patterns - -### 4. Generate Patterns Content - -Prepare the content to append to the document: - -#### Content Structure: - -```markdown -## Implementation Patterns & Consistency Rules - -### Pattern Categories Defined - -**Critical Conflict Points Identified:** -{{number_of_potential_conflicts}} areas where AI agents could make different choices - -### Naming Patterns - -**Database Naming Conventions:** -{{database_naming_rules_with_examples}} - -**API Naming Conventions:** -{{api_naming_rules_with_examples}} - -**Code Naming Conventions:** -{{code_naming_rules_with_examples}} - -### Structure Patterns - -**Project Organization:** -{{project_structure_rules_with_examples}} - -**File Structure Patterns:** -{{file_organization_rules_with_examples}} - -### Format Patterns - -**API Response Formats:** -{{api_response_structure_rules}} - -**Data Exchange Formats:** -{{data_format_rules_with_examples}} - -### Communication Patterns - -**Event System Patterns:** -{{event_naming_and_structure_rules}} - -**State Management Patterns:** -{{state_update_and_organization_rules}} - -### Process Patterns - -**Error Handling Patterns:** -{{consistent_error_handling_approaches}} - -**Loading State Patterns:** -{{loading_state_management_rules}} - -### Enforcement Guidelines - -**All AI Agents MUST:** - -- {{mandatory_pattern_1}} -- {{mandatory_pattern_2}} -- {{mandatory_pattern_3}} - -**Pattern Enforcement:** - -- How to verify patterns are followed -- Where to document pattern violations -- Process for updating patterns - -### Pattern Examples - -**Good Examples:** -{{concrete_examples_of_correct_pattern_usage}} - -**Anti-Patterns:** -{{examples_of_what_to_avoid}} -``` - -### 5. Present Content and Menu - -Show the generated patterns content and present choices: - -"I've documented implementation patterns that will prevent conflicts between AI agents working on this project. - -**Here's what I'll add to the document:** - -[Show the complete markdown content from step 4] - -**What would you like to do?** -[A] Advanced Elicitation - Explore additional consistency patterns -[P] Party Mode - Review patterns from different implementation perspectives -[C] Continue - Save these patterns and move to project structure" - -### 6. Handle Menu Selection - -#### If 'A' (Advanced Elicitation): - -- Invoke the `bmad-advanced-elicitation` skill with current patterns -- Process enhanced consistency rules that come back -- Ask user: "Accept these additional pattern refinements? (y/n)" -- If yes: Update content, then return to A/P/C menu -- If no: Keep original content, then return to A/P/C menu - -#### If 'P' (Party Mode): - -- Invoke the `bmad-party-mode` skill with implementation patterns context -- Process collaborative insights about potential conflicts -- Ask user: "Accept these changes to the implementation patterns? (y/n)" -- If yes: Update content, then return to A/P/C menu -- If no: Keep original content, then return to A/P/C menu - -#### If 'C' (Continue): - -- Append the final content to `{planning_artifacts}/architecture.md` -- Update frontmatter: `stepsCompleted: [1, 2, 3, 4, 5]` -- Load `./step-06-structure.md` - -## APPEND TO DOCUMENT: - -When user selects 'C', append the content directly to the document using the structure from step 4. - -## SUCCESS METRICS: - -✅ All potential AI agent conflict points identified and addressed -✅ Comprehensive patterns defined for naming, structure, and communication -✅ Concrete examples provided for each pattern -✅ Enforcement guidelines clearly documented -✅ User collaborated on pattern decisions rather than receiving recommendations -✅ A/P/C menu presented and handled correctly -✅ Content properly appended to document when C selected - -## FAILURE MODES: - -❌ Missing potential conflict points that could cause agent conflicts -❌ Being too prescriptive about implementation details instead of focusing on consistency -❌ Not providing concrete examples for each pattern -❌ Failing to address cross-cutting concerns like error handling -❌ Not considering the chosen technology stack when defining patterns -❌ Not presenting A/P/C menu after content generation - -❌ **CRITICAL**: Reading only partial step file - leads to incomplete understanding and poor decisions -❌ **CRITICAL**: Proceeding with 'C' without fully reading and understanding the next step file -❌ **CRITICAL**: Making decisions without complete understanding of step requirements and protocols - -## NEXT STEP: - -After user selects 'C' and content is saved to document, load `./step-06-structure.md` to define the complete project structure. - -Remember: Do NOT proceed to step-06 until user explicitly selects 'C' from the A/P/C menu and content is saved! diff --git a/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-06-structure.md b/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-06-structure.md deleted file mode 100644 index 195abafc2..000000000 --- a/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-06-structure.md +++ /dev/null @@ -1,379 +0,0 @@ -# Step 6: Project Structure & Boundaries - -## MANDATORY EXECUTION RULES (READ FIRST): - -- 🛑 NEVER generate content without user input - -- 📖 CRITICAL: ALWAYS read the complete step file before taking any action - partial understanding leads to incomplete decisions -- 🔄 CRITICAL: When loading next step with 'C', ensure the entire file is read and understood before proceeding -- ✅ ALWAYS treat this as collaborative discovery between architectural peers -- 📋 YOU ARE A FACILITATOR, not a content generator -- 💬 FOCUS on defining complete project structure and clear boundaries -- 🗺️ MAP requirements/epics to architectural components -- ⚠️ ABSOLUTELY NO TIME ESTIMATES - AI development speed has fundamentally changed -- ✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}` - -## EXECUTION PROTOCOLS: - -- 🎯 Show your analysis before taking any action -- 🗺️ Create complete project tree, not generic placeholders -- ⚠️ Present A/P/C menu after generating project structure -- 💾 ONLY save when user chooses C (Continue) -- 📖 Update frontmatter `stepsCompleted: [1, 2, 3, 4, 5, 6]` before loading next step -- 🚫 FORBIDDEN to load next step until C is selected - -## COLLABORATION MENUS (A/P/C): - -This step will generate content and present choices: - -- **A (Advanced Elicitation)**: Use discovery protocols to explore innovative project organization approaches -- **P (Party Mode)**: Bring multiple perspectives to evaluate project structure trade-offs -- **C (Continue)**: Save the project structure and proceed to validation - -## PROTOCOL INTEGRATION: - -- When 'A' selected: Invoke the `bmad-advanced-elicitation` skill -- When 'P' selected: Invoke the `bmad-party-mode` skill -- PROTOCOLS always return to display this step's A/P/C menu after the A or P have completed -- User accepts/rejects protocol changes before proceeding - -## CONTEXT BOUNDARIES: - -- All previous architectural decisions are complete -- Implementation patterns and consistency rules are defined -- Focus on physical project structure and component boundaries -- Map requirements to specific files and directories - -## YOUR TASK: - -Define the complete project structure and architectural boundaries based on all decisions made, creating a concrete implementation guide for AI agents. - -## PROJECT STRUCTURE SEQUENCE: - -### 1. Analyze Requirements Mapping - -Map project requirements to architectural components: - -**From Epics (if available):** -"Epic: {{epic_name}} → Lives in {{module/directory/service}}" - -- User stories within the epic -- Cross-epic dependencies -- Shared components needed - -**From FR Categories (if no epics):** -"FR Category: {{fr_category_name}} → Lives in {{module/directory/service}}" - -- Related functional requirements -- Shared functionality across categories -- Integration points between categories - -### 2. Define Project Directory Structure - -Based on technology stack and patterns, create the complete project structure: - -**Root Configuration Files:** - -- Package management files (package.json, requirements.txt, etc.) -- Build and development configuration -- Environment configuration files -- CI/CD pipeline files -- Documentation files - -**Source Code Organization:** - -- Application entry points -- Core application structure -- Feature/module organization -- Shared utilities and libraries -- Configuration and environment files - -**Test Organization:** - -- Unit test locations and structure -- Integration test organization -- End-to-end test structure -- Test utilities and fixtures - -**Build and Distribution:** - -- Build output directories -- Distribution files -- Static assets -- Documentation build - -### 3. Define Integration Boundaries - -Map how components communicate and where boundaries exist: - -**API Boundaries:** - -- External API endpoints -- Internal service boundaries -- Authentication and authorization boundaries -- Data access layer boundaries - -**Component Boundaries:** - -- Frontend component communication patterns -- State management boundaries -- Service communication patterns -- Event-driven integration points - -**Data Boundaries:** - -- Database schema boundaries -- Data access patterns -- Caching boundaries -- External data integration points - -### 4. Create Complete Project Tree - -Generate a comprehensive directory structure showing all files and directories: - -**Technology-Specific Structure Examples:** - -**Next.js Full-Stack:** - -``` -project-name/ -├── README.md -├── package.json -├── next.config.js -├── tailwind.config.js -├── tsconfig.json -├── .env.local -├── .env.example -├── .gitignore -├── .github/ -│ └── workflows/ -│ └── ci.yml -├── src/ -│ ├── app/ -│ │ ├── globals.css -│ │ ├── layout.tsx -│ │ └── page.tsx -│ ├── components/ -│ │ ├── ui/ -│ │ ├── forms/ -│ │ └── features/ -│ ├── lib/ -│ │ ├── db.ts -│ │ ├── auth.ts -│ │ └── utils.ts -│ ├── types/ -│ └── middleware.ts -├── prisma/ -│ ├── schema.prisma -│ └── migrations/ -├── tests/ -│ ├── __mocks__/ -│ ├── components/ -│ └── e2e/ -└── public/ - └── assets/ -``` - -**API Backend (NestJS):** - -``` -project-name/ -├── package.json -├── nest-cli.json -├── tsconfig.json -├── .env -├── .env.example -├── .gitignore -├── README.md -├── src/ -│ ├── main.ts -│ ├── app.module.ts -│ ├── config/ -│ ├── modules/ -│ │ ├── auth/ -│ │ ├── users/ -│ │ └── common/ -│ ├── services/ -│ ├── repositories/ -│ ├── decorators/ -│ ├── pipes/ -│ ├── guards/ -│ └── interceptors/ -├── test/ -│ ├── unit/ -│ ├── integration/ -│ └── e2e/ -├── prisma/ -│ ├── schema.prisma -│ └── migrations/ -└── docker-compose.yml -``` - -### 5. Map Requirements to Structure - -Create explicit mapping from project requirements to specific files/directories: - -**Epic/Feature Mapping:** -"Epic: User Management - -- Components: src/components/features/users/ -- Services: src/services/users/ -- API Routes: src/app/api/users/ -- Database: prisma/migrations/_*users*_ -- Tests: tests/features/users/" - -**Cross-Cutting Concerns:** -"Authentication System - -- Components: src/components/auth/ -- Services: src/services/auth/ -- Middleware: src/middleware/auth.ts -- Guards: src/guards/auth.guard.ts -- Tests: tests/auth/" - -### 6. Generate Structure Content - -Prepare the content to append to the document: - -#### Content Structure: - -```markdown -## Project Structure & Boundaries - -### Complete Project Directory Structure -``` - -{{complete_project_tree_with_all_files_and_directories}} - -``` - -### Architectural Boundaries - -**API Boundaries:** -{{api_boundary_definitions_and_endpoints}} - -**Component Boundaries:** -{{component_communication_patterns_and_boundaries}} - -**Service Boundaries:** -{{service_integration_patterns_and_boundaries}} - -**Data Boundaries:** -{{data_access_patterns_and_boundaries}} - -### Requirements to Structure Mapping - -**Feature/Epic Mapping:** -{{mapping_of_epics_or_features_to_specific_directories}} - -**Cross-Cutting Concerns:** -{{mapping_of_shared_functionality_to_locations}} - -### Integration Points - -**Internal Communication:** -{{how_components_within_the_project_communicate}} - -**External Integrations:** -{{third_party_service_integration_points}} - -**Data Flow:** -{{how_data_flows_through_the_architecture}} - -### File Organization Patterns - -**Configuration Files:** -{{where_and_how_config_files_are_organized}} - -**Source Organization:** -{{how_source_code_is_structured_and_organized}} - -**Test Organization:** -{{how_tests_are_structured_and_organized}} - -**Asset Organization:** -{{how_static_and_dynamic_assets_are_organized}} - -### Development Workflow Integration - -**Development Server Structure:** -{{how_the_project_is organized_for_development}} - -**Build Process Structure:** -{{how_the_build_process_uses_the_project_structure}} - -**Deployment Structure:** -{{how_the_project_structure_supports_deployment}} -``` - -### 7. Present Content and Menu - -Show the generated project structure content and present choices: - -"I've created a complete project structure based on all our architectural decisions. - -**Here's what I'll add to the document:** - -[Show the complete markdown content from step 6] - -**What would you like to do?** -[A] Advanced Elicitation - Explore innovative project organization approaches -[P] Party Mode - Review structure from different development perspectives -[C] Continue - Save this structure and move to architecture validation" - -### 8. Handle Menu Selection - -#### If 'A' (Advanced Elicitation): - -- Invoke the `bmad-advanced-elicitation` skill with current project structure -- Process enhanced organizational insights that come back -- Ask user: "Accept these changes to the project structure? (y/n)" -- If yes: Update content, then return to A/P/C menu -- If no: Keep original content, then return to A/P/C menu - -#### If 'P' (Party Mode): - -- Invoke the `bmad-party-mode` skill with project structure context -- Process collaborative insights about organization trade-offs -- Ask user: "Accept these changes to the project structure? (y/n)" -- If yes: Update content, then return to A/P/C menu -- If no: Keep original content, then return to A/P/C menu - -#### If 'C' (Continue): - -- Append the final content to `{planning_artifacts}/architecture.md` -- Update frontmatter: `stepsCompleted: [1, 2, 3, 4, 5, 6]` -- Load `./step-07-validation.md` - -## APPEND TO DOCUMENT: - -When user selects 'C', append the content directly to the document using the structure from step 6. - -## SUCCESS METRICS: - -✅ Complete project tree defined with all files and directories -✅ All architectural boundaries clearly documented -✅ Requirements/epics mapped to specific locations -✅ Integration points and communication patterns defined -✅ Project structure aligned with chosen technology stack -✅ A/P/C menu presented and handled correctly -✅ Content properly appended to document when C selected - -## FAILURE MODES: - -❌ Creating generic placeholder structure instead of specific, complete tree -❌ Not mapping requirements to specific files and directories -❌ Missing important integration boundaries -❌ Not considering the chosen technology stack in structure design -❌ Not defining how components communicate across boundaries -❌ Not presenting A/P/C menu after content generation - -❌ **CRITICAL**: Reading only partial step file - leads to incomplete understanding and poor decisions -❌ **CRITICAL**: Proceeding with 'C' without fully reading and understanding the next step file -❌ **CRITICAL**: Making decisions without complete understanding of step requirements and protocols - -## NEXT STEP: - -After user selects 'C' and content is saved to document, load `./step-07-validation.md` to validate architectural coherence and completeness. - -Remember: Do NOT proceed to step-07 until user explicitly selects 'C' from the A/P/C menu and content is saved! diff --git a/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-07-validation.md b/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-07-validation.md deleted file mode 100644 index 246071a6a..000000000 --- a/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-07-validation.md +++ /dev/null @@ -1,361 +0,0 @@ -# Step 7: Architecture Validation & Completion - -## MANDATORY EXECUTION RULES (READ FIRST): - -- 🛑 NEVER generate content without user input - -- 📖 CRITICAL: ALWAYS read the complete step file before taking any action - partial understanding leads to incomplete decisions -- 🔄 CRITICAL: When loading next step with 'C', ensure the entire file is read and understood before proceeding -- ✅ ALWAYS treat this as collaborative discovery between architectural peers -- 📋 YOU ARE A FACILITATOR, not a content generator -- 💬 FOCUS on validating architectural coherence and completeness -- ✅ VALIDATE all requirements are covered by architectural decisions -- ⚠️ ABSOLUTELY NO TIME ESTIMATES - AI development speed has fundamentally changed -- ✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}` - -## EXECUTION PROTOCOLS: - -- 🎯 Show your analysis before taking any action -- ✅ Run comprehensive validation checks on the complete architecture -- ⚠️ Present A/P/C menu after generating validation results -- 💾 ONLY save when user chooses C (Continue) -- 📖 Update frontmatter `stepsCompleted: [1, 2, 3, 4, 5, 6, 7]` before loading next step -- 🚫 FORBIDDEN to load next step until C is selected - -## COLLABORATION MENUS (A/P/C): - -This step will generate content and present choices: - -- **A (Advanced Elicitation)**: Use discovery protocols to address complex architectural issues found during validation -- **P (Party Mode)**: Bring multiple perspectives to resolve validation concerns -- **C (Continue)**: Save the validation results and complete the architecture - -## PROTOCOL INTEGRATION: - -- When 'A' selected: Invoke the `bmad-advanced-elicitation` skill -- When 'P' selected: Invoke the `bmad-party-mode` skill -- PROTOCOLS always return to display this step's A/P/C menu after the A or P have completed -- User accepts/rejects protocol changes before proceeding - -## CONTEXT BOUNDARIES: - -- Complete architecture document with all sections is available -- All architectural decisions, patterns, and structure are defined -- Focus on validation, gap analysis, and coherence checking -- Prepare for handoff to implementation phase - -## YOUR TASK: - -Validate the complete architecture for coherence, completeness, and readiness to guide AI agents through consistent implementation. - -## VALIDATION SEQUENCE: - -### 1. Coherence Validation - -Check that all architectural decisions work together: - -**Decision Compatibility:** - -- Do all technology choices work together without conflicts? -- Are all versions compatible with each other? -- Do patterns align with technology choices? -- Are there any contradictory decisions? - -**Pattern Consistency:** - -- Do implementation patterns support the architectural decisions? -- Are naming conventions consistent across all areas? -- Do structure patterns align with technology stack? -- Are communication patterns coherent? - -**Structure Alignment:** - -- Does the project structure support all architectural decisions? -- Are boundaries properly defined and respected? -- Does the structure enable the chosen patterns? -- Are integration points properly structured? - -### 2. Requirements Coverage Validation - -Verify all project requirements are architecturally supported: - -**From Epics (if available):** - -- Does every epic have architectural support? -- Are all user stories implementable with these decisions? -- Are cross-epic dependencies handled architecturally? -- Are there any gaps in epic coverage? - -**From FR Categories (if no epics):** - -- Does every functional requirement have architectural support? -- Are all FR categories fully covered by architectural decisions? -- Are cross-cutting FRs properly addressed? -- Are there any missing architectural capabilities? - -**Non-Functional Requirements:** - -- Are performance requirements addressed architecturally? -- Are security requirements fully covered? -- Are scalability considerations properly handled? -- Are compliance requirements architecturally supported? - -### 3. Implementation Readiness Validation - -Assess if AI agents can implement consistently: - -**Decision Completeness:** - -- Are all critical decisions documented with versions? -- Are implementation patterns comprehensive enough? -- Are consistency rules clear and enforceable? -- Are examples provided for all major patterns? - -**Structure Completeness:** - -- Is the project structure complete and specific? -- Are all files and directories defined? -- Are integration points clearly specified? -- Are component boundaries well-defined? - -**Pattern Completeness:** - -- Are all potential conflict points addressed? -- Are naming conventions comprehensive? -- Are communication patterns fully specified? -- Are process patterns (error handling, etc.) complete? - -### 4. Gap Analysis - -Identify and document any missing elements: - -**Critical Gaps:** - -- Missing architectural decisions that block implementation -- Incomplete patterns that could cause conflicts -- Missing structural elements needed for development -- Undefined integration points - -**Important Gaps:** - -- Areas that need more detailed specification -- Patterns that could be more comprehensive -- Documentation that would help implementation -- Examples that would clarify complex decisions - -**Nice-to-Have Gaps:** - -- Additional patterns that would be helpful -- Supplementary documentation -- Tooling recommendations -- Development workflow optimizations - -### 5. Address Validation Issues - -For any issues found, facilitate resolution: - -**Critical Issues:** -"I found some issues that need to be addressed before implementation: - -{{critical_issue_description}} - -These could cause implementation problems. How would you like to resolve this?" - -**Important Issues:** -"I noticed a few areas that could be improved: - -{{important_issue_description}} - -These aren't blocking, but addressing them would make implementation smoother. Should we work on these?" - -**Minor Issues:** -"Here are some minor suggestions for improvement: - -{{minor_issue_description}} - -These are optional refinements. Would you like to address any of these?" - -### 6. Generate Validation Content - -Prepare the content to append to the document: - -#### Content Structure: - -```markdown -## Architecture Validation Results - -### Coherence Validation ✅ - -**Decision Compatibility:** -{{assessment_of_how_all_decisions_work_together}} - -**Pattern Consistency:** -{{verification_that_patterns_support_decisions}} - -**Structure Alignment:** -{{confirmation_that_structure_supports_architecture}} - -### Requirements Coverage Validation ✅ - -**Epic/Feature Coverage:** -{{verification_that_all_epics_or_features_are_supported}} - -**Functional Requirements Coverage:** -{{confirmation_that_all_FRs_are_architecturally_supported}} - -**Non-Functional Requirements Coverage:** -{{verification_that_NFRs_are_addressed}} - -### Implementation Readiness Validation ✅ - -**Decision Completeness:** -{{assessment_of_decision_documentation_completeness}} - -**Structure Completeness:** -{{evaluation_of_project_structure_completeness}} - -**Pattern Completeness:** -{{verification_of_implementation_patterns_completeness}} - -### Gap Analysis Results - -{{gap_analysis_findings_with_priority_levels}} - -### Validation Issues Addressed - -{{description_of_any_issues_found_and_resolutions}} - -### Architecture Completeness Checklist - -Mark each item `[x]` only if validation confirms it; leave `[ ]` if it is missing, partial, or unverified. Any unchecked item must be reflected in the Gap Analysis above and in the Overall Status below. - -**Requirements Analysis** - -- [ ] Project context thoroughly analyzed -- [ ] Scale and complexity assessed -- [ ] Technical constraints identified -- [ ] Cross-cutting concerns mapped - -**Architectural Decisions** - -- [ ] Critical decisions documented with versions -- [ ] Technology stack fully specified -- [ ] Integration patterns defined -- [ ] Performance considerations addressed - -**Implementation Patterns** - -- [ ] Naming conventions established -- [ ] Structure patterns defined -- [ ] Communication patterns specified -- [ ] Process patterns documented - -**Project Structure** - -- [ ] Complete directory structure defined -- [ ] Component boundaries established -- [ ] Integration points mapped -- [ ] Requirements to structure mapping complete - -### Architecture Readiness Assessment - -**Overall Status:** {{READY FOR IMPLEMENTATION | READY WITH MINOR GAPS | NOT READY}} (choose READY FOR IMPLEMENTATION only when all 16 checklist items are `[x]` and no Critical Gaps remain; choose NOT READY when any Critical Gap is open or any Requirements Analysis or Architectural Decisions item is unchecked; otherwise READY WITH MINOR GAPS) - -**Confidence Level:** {{high/medium/low}} based on validation results - -**Key Strengths:** -{{list_of_architecture_strengths}} - -**Areas for Future Enhancement:** -{{areas_that_could_be_improved_later}} - -### Implementation Handoff - -**AI Agent Guidelines:** - -- Follow all architectural decisions exactly as documented -- Use implementation patterns consistently across all components -- Respect project structure and boundaries -- Refer to this document for all architectural questions - -**First Implementation Priority:** -{{starter_template_command_or_first_architectural_step}} -``` - -### 7. Present Content and Menu - -Show the validation results and present choices: - -"I've completed a comprehensive validation of your architecture. - -**Validation Summary:** - -- ✅ Coherence: All decisions work together -- ✅ Coverage: All requirements are supported -- ✅ Readiness: AI agents can implement consistently - -**Here's what I'll add to complete the architecture document:** - -[Show the complete markdown content from step 6] - -**What would you like to do?** -[A] Advanced Elicitation - Address any complex architectural concerns -[P] Party Mode - Review validation from different implementation perspectives -[C] Continue - Complete the architecture and finish workflow - -### 8. Handle Menu Selection - -#### If 'A' (Advanced Elicitation): - -- Invoke the `bmad-advanced-elicitation` skill with validation issues -- Process enhanced solutions for complex concerns -- Ask user: "Accept these architectural improvements? (y/n)" -- If yes: Update content, then return to A/P/C menu -- If no: Keep original content, then return to A/P/C menu - -#### If 'P' (Party Mode): - -- Invoke the `bmad-party-mode` skill with validation context -- Process collaborative insights on implementation readiness -- Ask user: "Accept these changes to the validation results? (y/n)" -- If yes: Update content, then return to A/P/C menu -- If no: Keep original content, then return to A/P/C menu - -#### If 'C' (Continue): - -- Append the final content to `{planning_artifacts}/architecture.md` -- Update frontmatter: `stepsCompleted: [1, 2, 3, 4, 5, 6, 7]` -- Load `./step-08-complete.md` - -## APPEND TO DOCUMENT: - -When user selects 'C', append the content directly to the document using the structure from step 6. - -## SUCCESS METRICS: - -✅ All architectural decisions validated for coherence -✅ Complete requirements coverage verified -✅ Implementation readiness confirmed -✅ All gaps identified and addressed -✅ Comprehensive validation checklist completed -✅ A/P/C menu presented and handled correctly -✅ Content properly appended to document when C selected - -## FAILURE MODES: - -❌ Skipping validation of decision compatibility -❌ Not verifying all requirements are architecturally supported -❌ Missing potential implementation conflicts -❌ Not addressing gaps found during validation -❌ Providing incomplete validation checklist -❌ Not presenting A/P/C menu after content generation - -❌ **CRITICAL**: Reading only partial step file - leads to incomplete understanding and poor decisions -❌ **CRITICAL**: Proceeding with 'C' without fully reading and understanding the next step file -❌ **CRITICAL**: Making decisions without complete understanding of step requirements and protocols - -## NEXT STEP: - -After user selects 'C' and content is saved to document, load `./step-08-complete.md` to complete the workflow and provide implementation guidance. - -Remember: Do NOT proceed to step-08 until user explicitly selects 'C' from the A/P/C menu and content is saved! diff --git a/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-08-complete.md b/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-08-complete.md deleted file mode 100644 index 5aaab087e..000000000 --- a/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-08-complete.md +++ /dev/null @@ -1,82 +0,0 @@ -# Step 8: Architecture Completion & Handoff - -## MANDATORY EXECUTION RULES (READ FIRST): - -- 🛑 NEVER generate content without user input - -- 📖 CRITICAL: ALWAYS read the complete step file before taking any action - partial understanding leads to incomplete decisions -- ✅ ALWAYS treat this as collaborative completion between architectural peers -- 📋 YOU ARE A FACILITATOR, not a content generator -- 💬 FOCUS on successful workflow completion and implementation handoff -- 🎯 PROVIDE clear next steps for implementation phase -- ⚠️ ABSOLUTELY NO TIME ESTIMATES - AI development speed has fundamentally changed -- ✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}` - -## EXECUTION PROTOCOLS: - -- 🎯 Show your analysis before taking any action -- 🎯 Present completion summary and implementation guidance -- 📖 Update frontmatter with final workflow state -- 🚫 THIS IS THE FINAL STEP IN THIS WORKFLOW - -## YOUR TASK: - -Complete the architecture workflow, provide a comprehensive completion summary, and guide the user to the next phase of their project development. - -## COMPLETION SEQUENCE: - -### 1. Congratulate the User on Completion - -Both you and the User completed something amazing here - give a summary of what you achieved together and really congratulate the user on a job well done. - -### 2. Update the created document's frontmatter - -```yaml -stepsCompleted: [1, 2, 3, 4, 5, 6, 7, 8] -workflowType: 'architecture' -lastStep: 8 -status: 'complete' -completedAt: '{{current_date}}' -``` - -### 3. Next Steps Guidance - -Architecture complete. Invoke the `bmad-help` skill. - -Upon Completion of task output: offer to answer any questions about the Architecture Document. - - -## SUCCESS METRICS: - -✅ Complete architecture document delivered with all sections -✅ All architectural decisions documented and validated -✅ Implementation patterns and consistency rules finalized -✅ Project structure complete with all files and directories -✅ User provided with clear next steps and implementation guidance -✅ Workflow status properly updated -✅ User collaboration maintained throughout completion process - -## FAILURE MODES: - -❌ Not providing clear implementation guidance -❌ Missing final validation of document completeness -❌ Not updating workflow status appropriately -❌ Failing to celebrate the successful completion -❌ Not providing specific next steps for the user -❌ Rushing completion without proper summary - -❌ **CRITICAL**: Reading only partial step file - leads to incomplete understanding and poor decisions -❌ **CRITICAL**: Proceeding with 'C' without fully reading and understanding the next step file -❌ **CRITICAL**: Making decisions without complete understanding of step requirements and protocols - -## WORKFLOW COMPLETE: - -This is the final step of the Architecture workflow. The user now has a complete, validated architecture document ready for AI agent implementation. - -The architecture will serve as the single source of truth for all technical decisions, ensuring consistent implementation across the entire project development lifecycle. - -## On Complete - -Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete` - -If the resolved `workflow.on_complete` is non-empty, follow it as the final terminal instruction before exiting. diff --git a/src/bmm-skills/module-help.csv b/src/bmm-skills/module-help.csv index eb2d51c1c..d0f17494f 100644 --- a/src/bmm-skills/module-help.csv +++ b/src/bmm-skills/module-help.csv @@ -17,8 +17,8 @@ BMad Method,bmad-product-brief,Create Brief,CB,An expert guided experience to na BMad Method,bmad-prfaq,PRFAQ Challenge,WB,Working Backwards guided experience to forge and stress-test your product concept to ensure you have a great product that users will love and need through the PRFAQ gauntlet to determine feasibility and alignment with user needs. alternative to product brief.,,-H,1-analysis,,,false,planning_artifacts,prfaq document BMad Method,bmad-prd,Create Edit and Review PRD,PRD,"Facilitated PRD workflow — create a new PRD via coached discovery, update an existing one against a change signal, or validate a finished PRD against a checklist with an HTML findings report.",,,2-planning,bmad-product-brief,,true,planning_artifacts,prd BMad Method,bmad-ux,Create UX,CU,"Guidance through realizing the plan for your UX, strongly recommended if a UI is a primary piece of the proposed project.",,,2-planning,bmad-prd,,false,planning_artifacts,ux design -BMad Method,bmad-create-architecture,Create Architecture,CA,Guided workflow to document technical decisions.,,,3-solutioning,,,true,planning_artifacts,architecture -BMad Method,bmad-create-epics-and-stories,Create Epics and Stories,CE,,,,3-solutioning,bmad-create-architecture,,true,planning_artifacts,epics and stories +BMad Method,bmad-architecture,Architecture,CA,Offer once requirements exist (a PRD or spec; plus UX if present) and the user is ready to move from what to how. Also offer any time independently-built parts risk diverging. Produces the architecture spine: the invariants that keep features epics and stories consistent. Comes before epics and stories and scales from a quick spine to a full architecture (brownfield: ratifies the existing codebase).,,,3-solutioning,,,true,planning_artifacts,architecture +BMad Method,bmad-create-epics-and-stories,Create Epics and Stories,CE,,,,3-solutioning,bmad-architecture,,true,planning_artifacts,epics and stories BMad Method,bmad-check-implementation-readiness,Check Implementation Readiness,IR,Ensure PRD UX Architecture and Epics Stories are aligned.,,,3-solutioning,bmad-create-epics-and-stories,,true,planning_artifacts,readiness report BMad Method,bmad-sprint-planning,Sprint Planning,SP,Kicks off implementation by producing a plan the implementation agents will follow in sequence for every story.,,,4-implementation,,,true,implementation_artifacts,sprint status BMad Method,bmad-sprint-status,Sprint Status,SS,Anytime: Summarize sprint status and route to next workflow.,,,4-implementation,bmad-sprint-planning,,false,, diff --git a/src/core-skills/bmad-spec/SKILL.md b/src/core-skills/bmad-spec/SKILL.md index cdf734528..73360f127 100644 --- a/src/core-skills/bmad-spec/SKILL.md +++ b/src/core-skills/bmad-spec/SKILL.md @@ -69,6 +69,8 @@ When the input is structured and pre-sorted (a PRD with an addendum, a GDD, a br Distill the input into the five-field kernel using `{workflow.spec_template}` as the skeleton. When input is rich, extract directly — no elicitation. When input is sparse, choose: **express** (best-effort distill, every gap becomes an `open_questions[]` entry) or **guided** (walk the five fields with the user one at a time). Headless defaults to express and logs the choice. Interactive asks. +A recognized domain implication the input leaves unaddressed *is* such a gap — name it as an `open_questions[]` entry (healthcare input silent on PHI/HIPAA, payments silent on PCI, control systems silent on fail-safe) and move on. Flag it; never invent the answer or coach toward it. If these dominate, the input is too thin — suggest `bmad-prd`. + Write lean from the first pass: every sentence must earn its place. Decoration costs tokens and dilutes downstream readers. Log each decision, capability, constraint, and accepted change to `.memlog.md` as it is made — that running record is what the render reads. Because the log is append-only, a later entry supersedes an earlier one on the same point while the history stays intact. When two currently-live sources or companions disagree on the same field, or an either/or never got resolved, surface it to the user rather than silently choosing — the resolution is itself a new memlog entry. From 2417f0048daa9b668239423789640fb7212850e7 Mon Sep 17 00:00:00 2001 From: Brian Date: Wed, 17 Jun 2026 16:00:52 -0500 Subject: [PATCH 07/15] bmad-architecture: lean directives, breadth coverage, redesigned spine template (#2475) * bmad-architecture: breadth coverage + lean directives; reviewer reports to subfolder - Inline forward-readiness (inherit upstream silently; thin input -> suggest bmad-spec or hybrid-capture) and brownfield (ratify, don't re-tell) directives in place of a standalone spine-checklist - Use {workflow.persistent_facts} instead of hardcoded project-context.md - Reviewer gate writes per-reviewer reports to reviews/ subfolder so the deliverable folder stays clean - Require breadth coverage at distill and in the gate rubric: every altitude-owned dimension decided/deferred/open, flagging the operational/environmental envelope a domain-focused draft skips - Trim repeated directives and human-facing justification prose * Redesign spine template and move stack pinning to a body table Rework spine-template.md so it stops forcing fixed structure: guidance moves into single-line HTML comments (stripped at distill), the always-two diagrams and empty-mermaid render bugs are gone, and the structural-seed framing opens up so the operational/environmental envelope isn't skipped. Stack moves from nested frontmatter into a ## Stack | Name | Version | table. lint_spine.py drops the frontmatter dep check for find_unpinned_stack, which parses the Stack table and flags real-name/blank-version rows while skipping {token} skeletons. Tests reworked to match; 24 passing. SKILL.md Finalize tightened to act-then-strip template comments and sweep altitude-owned breadth. * Harden find_unpinned_stack: blank fences, locate columns, looser heading Address PR review (CodeRabbit + Augment): find_unpinned_stack scanned raw body, so pipe-rows or ## headings inside a fenced block could be misread as live Stack content and misreport version_pin. Now blanks fences first, like find_placeholders and find_ad_issues, honoring the linter's fences-are-non-live contract. Also locate both Name and Version columns from the header (a reordered table now pairs name to version correctly) and match the heading on a word boundary (## Stack & Versions still counts). Add regression tests for fenced rows, fenced headings, renamed heading, and reordered columns (28 passing). Reword stale 'unpinned deps' / 'unpinned dependency versions' to 'unpinned Stack versions' to match the body-table model. --- .../3-solutioning/bmad-architecture/SKILL.md | 21 ++-- .../assets/spine-template.md | 92 ++++----------- .../references/reviewer-gate.md | 6 +- .../bmad-architecture/scripts/lint_spine.py | 107 +++++++++--------- .../scripts/tests/test_lint_spine.py | 88 ++++++++++---- 5 files changed, 151 insertions(+), 163 deletions(-) diff --git a/src/bmm-skills/3-solutioning/bmad-architecture/SKILL.md b/src/bmm-skills/3-solutioning/bmad-architecture/SKILL.md index 551f98602..61a49ee8a 100644 --- a/src/bmm-skills/3-solutioning/bmad-architecture/SKILL.md +++ b/src/bmm-skills/3-solutioning/bmad-architecture/SKILL.md @@ -6,7 +6,7 @@ description: 'Produce the architecture: a lean spine of invariants that keeps ev ## 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. 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. +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. One test decides what belongs: @@ -18,17 +18,17 @@ Record decisions, not rationale (rationale lives in the memlog). Carry shape in ## How you work -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. +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. -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. +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. -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. +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. ## 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. +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. -**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. +**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. ## How a run works @@ -38,7 +38,6 @@ Writes go through the shared script (don't read the file back except on resume): - `python3 {project-root}/_bmad/scripts/memlog.py init --workspace {doc_workspace} --field scope="…" --field purpose="…" --field altitude="…"` - `python3 {project-root}/_bmad/scripts/memlog.py append --workspace {doc_workspace} --type --text "…"` -- A terminal moment (spine finalized, a validation verdict) is an `append --type event` entry — there is no status field to set. ## Resolution rules @@ -49,7 +48,7 @@ Writes go through the shared script (don't read the file back except on resume): ## 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`. So a legacy per-project override still reaches the run. +**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`. 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. @@ -62,13 +61,13 @@ For a new spine, bind `{doc_workspace}` to `{workflow.spine_output_path}/{workfl ## 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 change nothing. +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. ## Finalize 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. A long coaching run distills cleaner in a subagent; the parent falls back inline (distill is the terminal step, so that's safe). +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. 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. 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. @@ -79,7 +78,7 @@ Walk the sequence; reviewer fixes land before polish. ## Update -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. +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. ## Validate diff --git a/src/bmm-skills/3-solutioning/bmad-architecture/assets/spine-template.md b/src/bmm-skills/3-solutioning/bmad-architecture/assets/spine-template.md index aecd70986..56329f483 100644 --- a/src/bmm-skills/3-solutioning/bmad-architecture/assets/spine-template.md +++ b/src/bmm-skills/3-solutioning/bmad-architecture/assets/spine-template.md @@ -8,106 +8,58 @@ scope: '{what this spine governs}' status: draft # draft · final created: '{date}' updated: '{date}' -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) +binds: [] # capability / unit IDs governed (from the driving spec; at epic altitude, also the inherited parent AD ids) sources: [] companions: [] --- # Architecture Spine — {name} -> 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 -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 -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 | | --- | --- | --- | -| {AD-n / convention} | {parent spine} | {what it constrains in this scope} | +| {AD-id / convention} | {parent spine} | {what it constrains in this scope} | ## Invariants & Rules -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} -- **Binds:** {capability / unit IDs, areas, or `all`} +- **Binds:** {capability / unit ids / fr/nfr's, areas, or `all`} - **Prevents:** {the divergence this stops} - **Rule:** {the constraint downstream must follow} ## Consistency Conventions -The defaults that bind everything where independent builders would otherwise drift. Cut rows that -don't apply. + | Concern | Convention | | --- | --- | | 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) | | +## Stack + + + +| Name | Version | +| --- | --- | +| {language / framework / key dep / platform / chain} | {pinned version} | + ## Structural Seed -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}
{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 {root}/ @@ -116,14 +68,12 @@ erDiagram ## Capability → Architecture Map -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 | | --- | --- | --- | -| {CAP-n / area} | {component / module} | {AD-n, convention, paradigm} | +| {CAP-id / area} | {component / module} | {AD-id, convention, paradigm} | ## Deferred -Decisions intentionally pushed down, each with the reason it can wait. The half of the contract -that keeps the spine lean. + diff --git a/src/bmm-skills/3-solutioning/bmad-architecture/references/reviewer-gate.md b/src/bmm-skills/3-solutioning/bmad-architecture/references/reviewer-gate.md index 729844c46..175676413 100644 --- a/src/bmm-skills/3-solutioning/bmad-architecture/references/reviewer-gate.md +++ b/src/bmm-skills/3-solutioning/bmad-architecture/references/reviewer-gate.md @@ -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. -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. +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 Stack versions), 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.) -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 2–5 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}/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 2–5 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; and if a parent spine is inherited, no new `AD` weakens or contradicts an inherited one. +**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. 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. diff --git a/src/bmm-skills/3-solutioning/bmad-architecture/scripts/lint_spine.py b/src/bmm-skills/3-solutioning/bmad-architecture/scripts/lint_spine.py index 88fa3e06c..d583a528d 100644 --- a/src/bmm-skills/3-solutioning/bmad-architecture/scripts/lint_spine.py +++ b/src/bmm-skills/3-solutioning/bmad-architecture/scripts/lint_spine.py @@ -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} - ad_id duplicate or non-monotonic AD-n identifiers - ad_fields an AD-n block missing Binds / Prevents / Rule - - version_pin a frontmatter key_deps entry with no @version + - version_pin a ## Stack table row with no version 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 @@ -150,65 +150,62 @@ def find_ad_issues(body: str, offset: int) -> list[dict]: return findings -def find_unpinned_deps(frontmatter: str) -> list[dict]: +def find_unpinned_stack(body: str, offset: int) -> 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] = [] - lines = frontmatter.splitlines() - in_key_deps = False - key_indent = 0 - for raw in lines: - stripped = raw.strip() - if not stripped or stripped.startswith("#"): + in_stack = False + header_seen = False + name_idx, ver_idx = 0, 1 + scan = blank_fences(body) + for i, raw in enumerate(scan.splitlines()): + if HEADING.match(raw): + in_stack = re.match(r"^##\s+Stack\b", raw) is not None + header_seen = False + name_idx, ver_idx = 0, 1 continue - 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 + if not in_stack or not raw.lstrip().startswith("|"): continue - if in_key_deps: - if indent <= key_indent and not stripped.startswith("-"): - in_key_deps = False - continue - if stripped.startswith("-"): - # block-sequence form: `- name@version` - _check_dep(_strip_comment(stripped[1:]).strip().strip("'\""), findings) - else: - # map form: `name: version` — pinned iff a non-empty value is present - mm = re.match(r"([^:]+):\s*(.*)$", stripped) - if mm: - name = mm.group(1).strip().strip("'\"") - val = _strip_comment(mm.group(2)).strip().strip("'\"") - if name and not val: - findings.append({ - "category": "version_pin", - "severity": "medium", - "detail": f"key_deps entry {name!r} has no version pin", - "location": f"{SPINE} frontmatter stack.key_deps", - }) + if set(raw.strip()) <= set("|-: "): + continue # separator row + cells = _table_cells(raw) + if not header_seen: + header_seen = True + for j, c in enumerate(cells): + if c.lower() == "name": + name_idx = j + elif c.lower() == "version": + ver_idx = j + continue + name = cells[name_idx] if len(cells) > name_idx else "" + version = cells[ver_idx] if len(cells) > ver_idx else "" + if not name or TEMPLATE_TOKEN.search(name): + continue + if not version or TEMPLATE_TOKEN.search(version): + findings.append({ + "category": "version_pin", + "severity": "medium", + "detail": f"Stack entry {name!r} has no version", + "location": f"{SPINE} (line {offset + i + 1})", + }) return findings -def _strip_comment(s: str) -> str: - """Drop a trailing YAML ` # comment`, leaving an inline `name@1.2` intact.""" - return re.sub(r"(^|\s)#.*$", "", s) - - -def _check_dep(item: str, findings: list[dict]) -> None: - if not item or item.startswith("#"): - 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 _table_cells(row: str) -> list[str]: + """Split a markdown table row into trimmed cells, dropping the leading/trailing pipe.""" + s = row.strip() + if s.startswith("|"): + s = s[1:] + if s.endswith("|"): + s = s[:-1] + return [c.strip() for c in s.split("|")] def lint(text: str) -> dict: @@ -217,7 +214,7 @@ def lint(text: str) -> dict: findings += find_frontmatter_placeholders(frontmatter) findings += find_placeholders(body, offset) findings += find_ad_issues(body, offset) - findings += find_unpinned_deps(frontmatter) + findings += find_unpinned_stack(body, offset) counts: dict[str, int] = {} for f in findings: counts[f["severity"]] = counts.get(f["severity"], 0) + 1 diff --git a/src/bmm-skills/3-solutioning/bmad-architecture/scripts/tests/test_lint_spine.py b/src/bmm-skills/3-solutioning/bmad-architecture/scripts/tests/test_lint_spine.py index 220bb42e9..55cf7482d 100644 --- a/src/bmm-skills/3-solutioning/bmad-architecture/scripts/tests/test_lint_spine.py +++ b/src/bmm-skills/3-solutioning/bmad-architecture/scripts/tests/test_lint_spine.py @@ -6,7 +6,7 @@ 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, -AD-n blocks missing required fields, and unpinned dependency versions. +AD-n blocks missing required fields, and unpinned Stack versions. """ import importlib.util import json @@ -26,10 +26,6 @@ _SPEC.loader.exec_module(lint_spine) CLEAN = """--- name: 'Demo' -stack: - key_deps: - - fastapi@0.115 - - pydantic@2.9 --- ## Invariants & Rules @@ -50,6 +46,13 @@ stack: flowchart LR A --> B{decision} ``` + +## Stack + +| Name | Version | +| --- | --- | +| fastapi | 0.115 | +| pydantic | 2.9 | """ @@ -108,30 +111,32 @@ def test_missing_field_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) assert "version_pin" in cats(result) -def test_inline_key_deps_unpinned(): - text = CLEAN.replace(" key_deps:\n - fastapi@0.115\n - pydantic@2.9", " key_deps: [fastapi, redis@7]") +def test_placeholder_version_caught(): + text = CLEAN.replace("| fastapi | 0.115 |", "| fastapi | {pin} |") result = lint_spine.lint(text) - pins = [f for f in result["findings"] if f["category"] == "version_pin"] - assert len(pins) == 1 and "fastapi" in pins[0]["detail"] + assert any(f["category"] == "version_pin" and "fastapi" in f["detail"] for f in result["findings"]) -def test_empty_key_deps_ok(): - text = CLEAN.replace(" key_deps:\n - fastapi@0.115\n - pydantic@2.9", " key_deps: []") +def test_no_stack_section_ok(): + text = CLEAN.split("## Stack")[0] result = lint_spine.lint(text) assert "version_pin" not in cats(result) -def test_yaml_comments_not_parsed_as_deps(): - # a SEED comment on the key_deps line must not read as an unpinned dependency - text = CLEAN.replace( - " key_deps:\n - fastapi@0.115\n - pydantic@2.9", - " key_deps: # SEED — verified current 2026-06\n - fastapi@0.115 # web framework", - ) +def test_stack_skeleton_row_not_version_pinned(): + # a leftover {token} name is the placeholder pass's job, not a double-reported version_pin + text = CLEAN.replace("| fastapi | 0.115 |", "| {language / framework} | {pinned version} |") + result = lint_spine.lint(text) + assert "version_pin" not in cats(result) + + +def test_stack_html_comment_not_parsed_as_row(): + text = CLEAN.replace("## Stack\n", "## Stack\n\n\n") result = lint_spine.lint(text) assert "version_pin" not in cats(result) @@ -153,7 +158,8 @@ def test_no_frontmatter_body_still_scanned(): def test_frontmatter_value_with_dashes_not_truncated(): # a value containing '---' must not be read as the closing fence (line-exact close) - text = "---\nscope: 'phase 1 --- phase 2'\nstack:\n key_deps:\n - fastapi\n---\n\n## Invariants\n" + text = ("---\nname: 'x'\nscope: 'phase 1 --- phase 2'\n---\n\n" + "## Stack\n\n| Name | Version |\n| --- | --- |\n| fastapi | |\n") result = lint_spine.lint(text) assert any(f["category"] == "version_pin" for f in result["findings"]) # read past the inline --- @@ -168,19 +174,55 @@ 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 -def test_map_form_key_deps_unpinned_caught(): - text = "---\nstack:\n key_deps:\n fastapi: '0.115'\n redis:\n---\n\n## Invariants\n" +def test_stack_table_flags_only_the_unpinned_row(): + text = ("---\nname: 'x'\n---\n\n## Stack\n\n| Name | Version |\n| --- | --- |\n" + "| fastapi | 0.115 |\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_map_form_key_deps_pinned_ok(): - text = "---\nstack:\n key_deps:\n fastapi: '0.115'\n---\n\n## Invariants\n" +def test_stack_table_all_pinned_ok(): + text = ("---\nname: 'x'\n---\n\n## Stack\n\n| Name | Version |\n| --- | --- |\n" + "| fastapi | 0.115 |\n") result = lint_spine.lint(text) 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(): # a TBD after a multi-line fence reports its real file line (fence blanked, not collapsed) text = ( From 5bcc235cdb3ef5eb9e4403ee3479577f8091dbe9 Mon Sep 17 00:00:00 2001 From: Davor Racic Date: Thu, 18 Jun 2026 05:19:18 +0200 Subject: [PATCH 08/15] fix(installer): guard WSL installs from Windows Node (#2470) --- test/test-installation-components.js | 119 +++++++++++++++++++++++++ tools/installer/commands/install.js | 3 + tools/installer/core/wsl-node-check.js | 109 ++++++++++++++++++++++ 3 files changed, 231 insertions(+) create mode 100644 tools/installer/core/wsl-node-check.js diff --git a/test/test-installation-components.js b/test/test-installation-components.js index 1317bbbf5..f511b4376 100644 --- a/test/test-installation-components.js +++ b/test/test-installation-components.js @@ -3456,6 +3456,125 @@ async function runTests() { console.log(''); + // ============================================================ + // Test Suite 47: WSL shell using Windows Node guard + // ============================================================ + console.log(`${colors.yellow}Test Suite 47: WSL Windows Node guard${colors.reset}\n`); + + try { + const wslNodeCheck = require('../tools/installer/core/wsl-node-check'); + + let detection = wslNodeCheck.detectWindowsNodeFromWsl({ + platform: 'win32', + 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; + try { + await wslNodeCheck.checkWindowsNodeFromWsl(); + } catch (error) { + threw = error.message === '__stub_exit__'; + } + assert(threw && seen.exit[0] === 1, 'guard exits with code 1 when Windows Node is launched from WSL'); + assert(seen.errors[0].includes('Windows Node.js was launched from a WSL shell'), 'guard logs the mismatch explanation'); + } finally { + wslNodeCheck.detectWindowsNodeFromWsl = real.detectWindowsNodeFromWsl; + promptsModule.log = real.log; + process.exit = real.exit; + } + } catch (error) { + console.log(`${colors.red}Test Suite 47 setup failed: ${error.message}${colors.reset}`); + console.log(error.stack); + failed++; + } + + console.log(''); + // ============================================================ // Summary // ============================================================ diff --git a/tools/installer/commands/install.js b/tools/installer/commands/install.js index 1dfe6fb70..42f6213de 100644 --- a/tools/installer/commands/install.js +++ b/tools/installer/commands/install.js @@ -75,6 +75,9 @@ module.exports = { return; } + const { checkWindowsNodeFromWsl } = require('../core/wsl-node-check'); + await checkWindowsNodeFromWsl(); + // Set debug flag as environment variable for all components if (options.debug) { process.env.BMAD_DEBUG_MANIFEST = 'true'; diff --git a/tools/installer/core/wsl-node-check.js b/tools/installer/core/wsl-node-check.js new file mode 100644 index 000000000..261ebf2d7 --- /dev/null +++ b/tools/installer/core/wsl-node-check.js @@ -0,0 +1,109 @@ +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, +}; From 07d34fb43a97162bb1643746e5dde39489637550 Mon Sep 17 00:00:00 2001 From: SLUR <125234156+slur16105@users.noreply.github.com> Date: Thu, 18 Jun 2026 12:28:07 +0900 Subject: [PATCH 09/15] fix: adjust nav height for dual announcement banners (#2473) --- website/src/styles/custom.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/styles/custom.css b/website/src/styles/custom.css index 6ab5b2ee5..23b0dfb30 100644 --- a/website/src/styles/custom.css +++ b/website/src/styles/custom.css @@ -15,7 +15,7 @@ ============================================ */ :root { --ai-banner-height: 2.75rem; - --sl-nav-height: 6.25rem; /* Base nav height (~3.5rem) + banner height (2.75rem) */ + --sl-nav-height: 9rem; /* Base nav (3.5rem) + two announcement banners (2.75rem each) */ /* Full-width content - override Starlight's default 45rem/67.5rem */ --sl-content-width: 65rem; From cfafc89cf6c82de7cb539012a85ef1814db4ec88 Mon Sep 17 00:00:00 2001 From: Dov Benyomin Sohacheski Date: Thu, 18 Jun 2026 06:34:20 +0300 Subject: [PATCH 10/15] feat(skills): track retrospective action items in sprint-status (#2465) Retrospective Step 12 appends agreed action items to a new action_items section in sprint-status.yaml and updates the previous epic's entries from the Step 4 follow-through. Sprint-status parses the section, validates its statuses, and surfaces open items in the summary and data mode. Sprint-planning preserves the section when regenerating the file. --- .../bmad-retrospective/SKILL.md | 15 +++++++++++++ .../bmad-sprint-planning/SKILL.md | 21 ++++++++++++++++++- .../bmad-sprint-planning/checklist.md | 3 ++- .../sprint-status-template.yaml | 13 ++++++++++++ .../bmad-sprint-status/SKILL.md | 13 ++++++++++++ 5 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md b/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md index 07aec498a..46998b6b2 100644 --- a/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md @@ -350,6 +350,7 @@ Amelia (Developer): "I found our retrospectives from Epic {{prev_epic_num}}. Let **Action Item Follow-Through:** - 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 - Mark each action item: ✅ Completed, ⏳ In Progress, ❌ Not Addressed @@ -1403,6 +1404,19 @@ Amelia (Developer): "See you all when prep work is done. Meeting adjourned!" Find development_status key "epic-{{epic_number}}-retrospective" Verify current status (typically "optional" or "pending") Update development_status["epic-{{epic_number}}-retrospective"] = "done" +Append each Epic {{epic_number}} action item to the action_items section, creating the section after development_status if missing. One entry per item: + +```yaml +action_items: + - epic: {{epic_number}} + action: "{{action_description}}" + owner: "{{owner}}" + status: open +``` + +Quote action and owner values so punctuation (e.g., "#") cannot break YAML parsing + +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) Update last_updated field to current date Save file, preserving ALL comments and structure including STATUS DEFINITIONS @@ -1412,6 +1426,7 @@ Amelia (Developer): "See you all when prep work is done. Meeting adjourned!" Retrospective key: epic-{{epic_number}}-retrospective Status: {{previous_status}} → done +Action items recorded: {{action_count}} diff --git a/src/bmm-skills/4-implementation/bmad-sprint-planning/SKILL.md b/src/bmm-skills/4-implementation/bmad-sprint-planning/SKILL.md index dd7bfa55b..c56f9091b 100644 --- a/src/bmm-skills/4-implementation/bmad-sprint-planning/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-sprint-planning/SKILL.md @@ -151,6 +151,7 @@ development_status: - 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`) +- If existing `{status_file}` has an `action_items` section, carry it over unchanged **Status Flow Reference:** @@ -194,12 +195,18 @@ development_status: # - optional: Can be completed but not required # - 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: # =============== # - Epic transitions to 'in-progress' automatically when first story is created # - Stories can be worked in parallel if team capacity allows # - 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) +# - Retrospective appends its action items to action_items; sprint-status surfaces open ones generated: { date } last_updated: { date } @@ -215,6 +222,7 @@ development_status: Write the complete sprint status YAML to {status_file} CRITICAL: Metadata appears TWICE - once as comments (#) for documentation, once as YAML key:value fields for parsing Ensure all items are ordered: epic, its stories, its retrospective, next epic... +If the existing file had an action_items section, write it back unchanged after development_status @@ -223,7 +231,8 @@ development_status: - [ ] Every epic in epic files appears in {status_file} - [ ] Every story in epic files appears in {status_file} - [ ] Every epic has a corresponding retrospective entry -- [ ] No items in {status_file} that don't exist in epic files +- [ ] No development_status 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) - [ ] File is valid YAML syntax @@ -291,6 +300,16 @@ optional ↔ done - **optional**: Ready to be conducted but not required - **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 1. **Epic Activation**: Mark epic as `in-progress` when starting work on its first story diff --git a/src/bmm-skills/4-implementation/bmad-sprint-planning/checklist.md b/src/bmm-skills/4-implementation/bmad-sprint-planning/checklist.md index 7c20b1f37..2ec5045bc 100644 --- a/src/bmm-skills/4-implementation/bmad-sprint-planning/checklist.md +++ b/src/bmm-skills/4-implementation/bmad-sprint-planning/checklist.md @@ -7,7 +7,8 @@ - [ ] 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 epic has a corresponding retrospective entry -- [ ] No items in sprint-status.yaml that don't exist in epic files +- [ ] No development_status items in sprint-status.yaml that don't exist in epic files +- [ ] action_items section (if it existed) carried over unchanged ### Parsing Verification diff --git a/src/bmm-skills/4-implementation/bmad-sprint-planning/sprint-status-template.yaml b/src/bmm-skills/4-implementation/bmad-sprint-planning/sprint-status-template.yaml index d454f930c..8b91ff748 100644 --- a/src/bmm-skills/4-implementation/bmad-sprint-planning/sprint-status-template.yaml +++ b/src/bmm-skills/4-implementation/bmad-sprint-planning/sprint-status-template.yaml @@ -26,11 +26,17 @@ # - optional: Can be completed but not required # - 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: # =============== # - 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 # - 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): @@ -54,3 +60,10 @@ development_status: 2-2-chat-interface: backlog 2-3-llm-integration: backlog 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 diff --git a/src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md b/src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md index cad4f0df0..0e060a684 100644 --- a/src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md @@ -112,12 +112,14 @@ Run `/bmad:bmm:workflows:sprint-planning` to generate it, then rerun sprint-stat Map legacy epic status "contexted" → "in-progress" Count epic statuses: backlog, in-progress, done Count retrospective statuses: optional, done + Parse action_items list if present. Set open_action_items = entries with status "open" or "in-progress" Validate all statuses against known values: - Valid story statuses: backlog, ready-for-dev, in-progress, review, done, drafted (legacy) - Valid epic statuses: backlog, in-progress, done, contexted (legacy) - Valid retrospective statuses: optional, done +- Valid action item statuses: open, in-progress, done @@ -132,6 +134,7 @@ Run `/bmad:bmm:workflows:sprint-planning` to generate it, then rerun sprint-stat - Stories: backlog, ready-for-dev, in-progress, review, done - Epics: backlog, in-progress, done - Retrospectives: optional, done +- Action items: open, in-progress, done How should these be corrected? {{#each invalid_entries}} @@ -181,6 +184,14 @@ 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}}) +{{#if open_action_items}} +**Open Action Items:** +{{#each open_action_items}} + +- {{action}} — {{status}} (epic {{epic}}, owner: {{owner}}) + {{/each}} + {{/if}} + {{#if risks}} **Risks:** {{#each risks}} @@ -243,6 +254,7 @@ If the command targets a story, set `story_key={{next_story_id}}` when prompted. epic_backlog = {{epic_backlog}} epic_in_progress = {{epic_in_progress}} epic_done = {{epic_done}} + open_action_items = {{open_action_items}} risks = {{risks}} Return to caller @@ -283,6 +295,7 @@ 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) - Epics: backlog, in-progress, done (legacy: contexted) - Retrospectives: optional, done +- Action items (if present): open, in-progress, done is_valid = false error = "Invalid status values: {{invalid_entries}}" From 606ad6063baa74916bea98ef26da6253216a2dd0 Mon Sep 17 00:00:00 2001 From: Loic Duong <34233006+loicduong@users.noreply.github.com> Date: Thu, 18 Jun 2026 10:35:00 +0700 Subject: [PATCH 11/15] fix: update Claude marketplace metadata (#2457) * fix: update marketplace metadata * fix: update marketplace metadata * fix: include architecture skill in marketplace --- .claude-plugin/marketplace.json | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index e30519d15..d610d1a97 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -13,13 +13,14 @@ "name": "bmad-pro-skills", "source": "./", "description": "Next level skills for power users — advanced prompting techniques, agent management, and more.", - "version": "6.6.0", + "version": "6.8.0", "author": { "name": "Brian (BMad) Madison" }, "skills": [ "./src/core-skills/bmad-help", "./src/core-skills/bmad-brainstorming", + "./src/core-skills/bmad-customize", "./src/core-skills/bmad-spec", "./src/core-skills/bmad-party-mode", "./src/core-skills/bmad-shard-doc", @@ -35,12 +36,13 @@ "name": "bmad-method-lifecycle", "source": "./", "description": "Full-lifecycle AI development framework — agents and workflows for product analysis, planning, architecture, and implementation.", - "version": "6.6.0", + "version": "6.8.0", "author": { "name": "Brian (BMad) Madison" }, "skills": [ "./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-tech-writer", "./src/bmm-skills/1-analysis/bmad-document-project", @@ -49,18 +51,22 @@ "./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-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-edit-prd", "./src/bmm-skills/2-plan-workflows/bmad-validate-prd", - "./src/bmm-skills/2-plan-workflows/bmad-create-ux-design", + "./src/bmm-skills/2-plan-workflows/bmad-ux", "./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-check-implementation-readiness", "./src/bmm-skills/3-solutioning/bmad-create-epics-and-stories", "./src/bmm-skills/3-solutioning/bmad-generate-project-context", "./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-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-status", "./src/bmm-skills/4-implementation/bmad-code-review", From 9d5739d9920bfe892e683a750e388996306082fd Mon Sep 17 00:00:00 2001 From: Brian Date: Thu, 18 Jun 2026 00:57:26 -0500 Subject: [PATCH 12/15] Party Mode: configurable custom parties, run modes, and a rewritten explainer (#2479) * Add configurable parties to bmad-party-mode Party mode gains a customize.toml config surface and a guided authoring flow, while the out-of-the-box default room is unchanged. - customize.toml: party_members (custom personas), party_groups (named rooms with an optional freeform `scene`), default_party, and party_mode (auto/session/subagent/agent-team). Universal hooks wired (activation steps, persistent_facts, on_complete). - Roster model: collective = installed agents + custom members (the pool, summonable by name). Default room stays installed-only so customs never crowd it. Groups curate subsets; open-cast groups (no members) are cast from the scene on the fly. - scripts/resolve_party.py: lazy roster resolver (installed-only default, group menu by name, one group's detail on demand, alias/override merge) + unit tests. - references/create-party.md: create/edit parties, distill personas from data for focus groups, persist ad-hoc casts; writes overrides via bmad-customize. - Ships a "Code Review Crew" group (5 adversarial review lenses), available via --party but absent from the default room. * Rework party-mode modes + rewrite the docs explainer Skill: - How It Runs is now a compact router; one mode active per session, runtime --mode wins over the customize default, all degrade to session - Default mode is `session`; `auto`/`subagent`/`agent-team` carved to references/mode-*.md, loaded only when that mode is active - Add references/mode-auto.md (spawn-vs-voice rubric), mode-subagent.md, mode-agent-team.md - resolve_party.py fallback default auto -> session - customize.toml: party_mode default session; trim duplicated mode gloss - Trim restated script-contract prose; collapse "Following the User's Lead"; add scene/persona-binding and web-search rules; offer an HTML keepsake at wrap-up Docs: - Full rewrite of docs/explanation/party-mode.md: the four modes, custom parties (personas, scenes, shapes), the shipped Code Review Crew as one example beside the module-based default, launch examples, party ideas, and the multi-group bonus tip * Keep party energy up and route the keepsake to a config output dir - SKILL.md: add "Keep It Feeling Like a Party" guidance so the room stays fun and engaging and doesn't drift into Q&A or a report - Keepsake now writes to {workflow.output_dir}; step 2 resolves {output_folder} and {date} - customize.toml: add output_dir = {output_folder}/party-mode, overridable in team/user TOML (matches the bmad-brainstorming output pattern) --- docs/explanation/party-mode.md | 161 ++++++++--- src/core-skills/bmad-party-mode/SKILL.md | 74 ++--- .../bmad-party-mode/customize.toml | 150 ++++++++++ .../references/create-party.md | 65 +++++ .../references/mode-agent-team.md | 11 + .../bmad-party-mode/references/mode-auto.md | 13 + .../references/mode-subagent.md | 19 ++ .../bmad-party-mode/scripts/resolve_party.py | 267 ++++++++++++++++++ .../scripts/tests/test-resolve_party.py | 138 +++++++++ 9 files changed, 822 insertions(+), 76 deletions(-) create mode 100644 src/core-skills/bmad-party-mode/customize.toml create mode 100644 src/core-skills/bmad-party-mode/references/create-party.md create mode 100644 src/core-skills/bmad-party-mode/references/mode-agent-team.md create mode 100644 src/core-skills/bmad-party-mode/references/mode-auto.md create mode 100644 src/core-skills/bmad-party-mode/references/mode-subagent.md create mode 100644 src/core-skills/bmad-party-mode/scripts/resolve_party.py create mode 100644 src/core-skills/bmad-party-mode/scripts/tests/test-resolve_party.py diff --git a/docs/explanation/party-mode.md b/docs/explanation/party-mode.md index b44afac24..4d3483fc8 100644 --- a/docs/explanation/party-mode.md +++ b/docs/explanation/party-mode.md @@ -1,59 +1,140 @@ --- title: "Party Mode" -description: Multi-agent collaboration - get all your AI agents in one conversation +description: Get your AI agents in one conversation — run them, build your own cast, and choose how independently they think sidebar: order: 11 --- -Get all your AI agents in one conversation. +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, and how to build your own cast of personas instead of using the installed agents. ## What is Party Mode? -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. +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. -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. +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. **Good for:** -- Big decisions with tradeoffs -- Brainstorming sessions -- Post-mortems when things go wrong -- Sprint retrospectives and planning +- Decisions with real tradeoffs +- Brainstorming and "what are we missing?" +- Post-mortems and retrospectives +- Pressure-testing a plan before you commit -## Examples +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. -### Calling Out Bad Architecture +:::note[Example] +**You:** Monolith or microservices for the MVP? -**You:** "Okay team, our last sprint was a disaster. The authentication service went down three times. Who screwed this up?" +**Architect:** Start monolith. Microservices add operating cost you don't need at a thousand users. -**Architect:** "The design was sound - distributed authentication with proper fallbacks. If implementation deviated from the spec..." +**PM:** Agreed. Time to market matters more than scaling we can't prove yet. -**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." - -**TEA:** "And I should've caught this in integration tests. The test scenarios didn't cover concurrent invalidation." - -### Creative Brainstorming - -**You:** "How do we make onboarding feel magical instead of boring?" - -**UX Designer:** "Start with progressive disclosure - reveal features as users need them, not all at once in a tutorial." - -**Storyteller:** "What if onboarding was a story? Each step reveals a character's journey - the user IS the hero." - -**Game Designer:** "Building on that - what if the first 'quest' is actually solving a real user problem? They learn by doing something valuable." - -### Technical Decision - -**You:** "Monolith or microservices for MVP?" - -**Architect:** "Start monolith. Microservices add complexity you don't need at 1000 users." - -**PM:** "Agree. Time to market matters more than theoretical scalability." - -**Dev:** "Monolith with clear module boundaries. We can extract services later if needed." - -:::tip[Better Decisions] -Better decisions through diverse perspectives. Welcome to party mode. +**Dev:** Monolith, but with clean module boundaries so we can split a service out later without a rewrite. +::: + +## Starting a party + +Invoke the skill and say what you want; it works out whether you mean to run a party or build one. + +| Goal | Type this | +| --- | --- | +| 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`) | +| 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" | +| Create or add a party | "party mode, create a new party" | +| Edit an existing party | "party mode, edit the writers' room" | +| Customize the skill | `/bmad-customize bmad-party-mode` | + +## How a party runs + +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. + +| Mode | What it does | Reach for it when | +| --- | --- | --- | +| `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. | +| `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. | + +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. +::: + +## 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. ::: diff --git a/src/core-skills/bmad-party-mode/SKILL.md b/src/core-skills/bmad-party-mode/SKILL.md index 862ac2a22..bb291701b 100644 --- a/src/core-skills/bmad-party-mode/SKILL.md +++ b/src/core-skills/bmad-party-mode/SKILL.md @@ -1,11 +1,13 @@ --- name: bmad-party-mode -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.' +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.' --- # Party Mode -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. +Run a round-table 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. + +**Two intents.** Usually the user wants to *run* a party — that's everything below. If instead they want to *create or configure* one — invent a cast, add a persona, distill customer data into a focus-group panel, set a default, or **edit an existing custom party** (retune a member, add someone to a group) — load `references/create-party.md` and follow it. Detect which from how they invoke the skill; when it's unclear, ask. Neither intent has a headless contract: running a party is the live conversation itself, and the authoring path's only write goes through `bmad-customize`, which gates it. ## What "Good" Feels Like @@ -16,60 +18,60 @@ Run a roundtable where BMAD agents talk to each other, and to the user, like a r If a round comes back feeling like four essays stapled together, you missed the objective. Tighten it the next round. +## Conventions + +- Bare paths (e.g. `references/create-party.md`) resolve from `{skill-root}`, where `customize.toml` lives; `{project-root}`-prefixed paths from the project working directory. + ## Setup -1. Load `{project-root}/_bmad/core/config.yaml`: greet with `{user_name}`, speak in `{communication_language}`. -2. Resolve the roster: - ```bash - python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root} --key agents - ``` - Each entry is keyed by `code` and carries `name`, `title`, `icon`, `description`, `module`, and `team`. -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. -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. +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 its defaults. Then run each `{workflow.activation_steps_prepend}` entry, and hold each `{workflow.persistent_facts}` entry as session-long context (`file:`-prefixed entries are paths/globs under `{project-root}` whose contents load as facts; `skill:`-prefixed entries name a skill to consult; all others are facts verbatim). +2. Load `{project-root}/_bmad/core/config.yaml`: greet with `{user_name}`, speak in `{communication_language}`, and resolve `{output_folder}` and `{date}` for the wrap-up keepsake. +3. **Resolve the active roster:** `python3 {skill-root}/scripts/resolve_party.py --project-root {project-root} --skill {skill-root}`. It returns the active group's full member detail (the `{workflow.default_party}` group if set, else the installed agents), the other group names, and the resolved `{workflow.party_mode}`. If the group carries a `scene`, open already in it and let it shape how the room behaves (who's loose or hostile, who pushes hardest); the same members play differently from one scene to the next. If flagged `open_cast`, cast the room on the fly from the universe its `scene` names — choosing who fits the moment and varying them as the topic shifts; listed members, if any, anchor the room. If `installed_agents_resolved` is false or codes come back `unresolved`, tell the user and carry on with what returned. +4. **Roster overrides:** + - If the invocation names a cast or characters inline (e.g. "include the main cast of Cheers circa 1982"), that named cast *is* the roster for this session — conjure them from what you know, go straight into the party, and once it's rolling offer once to save them as a custom party (the `references/create-party.md` write path), without stalling. Ephemeral; this path skips the script. + - A runtime `--party ` (alias `--group `) overrides any configured `default_party`: run `resolve_party.py --party ` for that group's full detail. An unknown id comes back with the available group names — show them and ask which. + - Run `resolve_party.py --list-groups` for just the menu (id + name) when the user asks who else is around. + - Mid-session the same levers apply: the user can switch rooms ("switch to the writers' room") — re-run `resolve_party.py --party `, set the new group's `scene`, and carry the thread over so the new faces react to where things stand — or summon any member of the *collective* (installed agents plus your custom `party_members`) by name, even one not in the current room. +5. Welcome the user and show who's in the room (icon, name, one-line role). If other groups exist, you may note they can switch rooms. Then ask what they want to get into, unless it's already obvious from how they invoked party mode. + +Then run each `{workflow.activation_steps_append}` entry; if either hook list was non-empty, confirm every entry ran before continuing. + +**Hold this the whole run:** it's theater of the mind, so set the stage and play it straight — never break the fourth wall about the mechanism (no "you have 4 agents in the room", no "I'm orchestrating a party"). Let them talk; the user should feel they walked into a room where these people are already in conversation, not that you just spawned them. ## How It Runs -**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. +Use `{workflow.party_mode}` for the session unless the user passed `--mode ` (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. -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. +- **`session`** — voice every persona inline, one mind behind every voice. The floor every other mode degrades to; needs no extra instructions. +- **`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`. +- **`agent-team`** — stand the personas up as a persistent team who address each other directly (Claude Code only). Load `references/mode-agent-team.md`. -**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. - -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 2–3 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 `), use it for everyone. +**Voicing the room** (every mode presents this way). Pick 2–3 personas whose perspective fits the moment and let them talk directly, in character; vary who shows up round to round so it isn't the same voices every time. Each turn opens with `{icon} **{name}:**`, and turns run back to back so it reads as one exchange. Don't summarize, blend, or narrate what a persona "would" say — let them say it. ## 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. +Present one exchange, not a row of answers aimed at the user. The hard rule: never change what an agent argued — add staging and connective tissue, but don't invent positions, soften a stance, or put words in a persona's mouth. Weave delivery, preserve substance; it still reads like that specific character, quirks and speech patterns and all. -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. +## Always Holds -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. +- **Scene and persona are binding.** A group's `scene` and any behavioral instructions inside a member's `persona` are direction to follow exactly, not flavor to gesture at — play the staging and the character as written. When you spawn or stand up agents, carry both into their brief. +- **Search when you're past your cutoff.** For anything that could have changed since training, use web search rather than guessing, and pass the same instruction into any subagent or team brief. ## 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. +The user steers — whatever they raise, serve the conversation: 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. +- **A flat turn?** Don't retry it — move on; the user will ask for more if they want it. + +## Keep It Feeling Like a Party + +It is your goal to keep party mode feeling like a party, a good party. fun, engaging, simulating, insightful, or whatever the user came for. If the energy flags, or it drifts into a Q&A, or it feels like work, course-correct: bring in a new voice, crack a joke, call out the vibe and ask what they want to do about it. Inject some randomness and unexpectedness occasionally. Don't let it become a report. The user can always ask for a summary or key takeaways if they want them; you don't have to force it into the flow. Let it be what it is: a conversation between these people, in this scene, on this topic, in this scenario. ## Wrapping Up -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. +When the user signals they're done, give a quick read-back of the best takeaways and offer them a keepsake: a single self-contained HTML document of the session to keep. If they want it, make it genuinely nice rather than a transcript dump — lay the conversation out by persona (their icons, names, voice), and reach for inline SVG and light animation where it lifts the piece. Write it as a standalone `.html` into `{workflow.output_dir}/` (a `{date}`-stamped, topic-named file), or wherever they ask. Then run `{workflow.on_complete}` if non-empty (a string scalar is one instruction, an array is a sequence run in order) and drop back to normal mode. Read the room; don't wait for a magic word. \ No newline at end of file diff --git a/src/core-skills/bmad-party-mode/customize.toml b/src/core-skills/bmad-party-mode/customize.toml new file mode 100644 index 000000000..3fcaa6675 --- /dev/null +++ b/src/core-skills/bmad-party-mode/customize.toml @@ -0,0 +1,150 @@ +# 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 ` 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 ` 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" + +# 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 ` +# (alias `--group `) 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. +# +# 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"] +# +# [[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." +# --------------------------------------------------------------------------- + +[[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"] diff --git a/src/core-skills/bmad-party-mode/references/create-party.md b/src/core-skills/bmad-party-mode/references/create-party.md new file mode 100644 index 000000000..feeaa1deb --- /dev/null +++ b/src/core-skills/bmad-party-mode/references/create-party.md @@ -0,0 +1,65 @@ +# 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`, and `members` (codes). `members` is optional: leave it off for an open-cast room whose `scene` names a pool the model casts from on the fly. +- `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 default question, 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: `python3 {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. + +## 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 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 — `python3 {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, 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 ` to summon the group, or that it's now the default if they set it. diff --git a/src/core-skills/bmad-party-mode/references/mode-agent-team.md b/src/core-skills/bmad-party-mode/references/mode-agent-team.md new file mode 100644 index 000000000..821cd3042 --- /dev/null +++ b/src/core-skills/bmad-party-mode/references/mode-agent-team.md @@ -0,0 +1,11 @@ +# 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 ` pin overrides it for everyone. diff --git a/src/core-skills/bmad-party-mode/references/mode-auto.md b/src/core-skills/bmad-party-mode/references/mode-auto.md new file mode 100644 index 000000000..f718221c8 --- /dev/null +++ b/src/core-skills/bmad-party-mode/references/mode-auto.md @@ -0,0 +1,13 @@ +# 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. diff --git a/src/core-skills/bmad-party-mode/references/mode-subagent.md b/src/core-skills/bmad-party-mode/references/mode-subagent.md new file mode 100644 index 000000000..b063fb8c7 --- /dev/null +++ b/src/core-skills/bmad-party-mode/references/mode-subagent.md @@ -0,0 +1,19 @@ +# 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 ` pin overrides it for everyone. diff --git a/src/core-skills/bmad-party-mode/scripts/resolve_party.py b/src/core-skills/bmad-party-mode/scripts/resolve_party.py new file mode 100644 index 000000000..bcca64af4 --- /dev/null +++ b/src/core-skills/bmad-party-mode/scripts/resolve_party.py @@ -0,0 +1,267 @@ +#!/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 — 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} + 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" + + # 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]}) + _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() diff --git a/src/core-skills/bmad-party-mode/scripts/tests/test-resolve_party.py b/src/core-skills/bmad-party-mode/scripts/tests/test-resolve_party.py new file mode 100644 index 000000000..58c50a985 --- /dev/null +++ b/src/core-skills/bmad-party-mode/scripts/tests/test-resolve_party.py @@ -0,0 +1,138 @@ +#!/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") + + +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() From b0b1796227a042af1580460dc6a75f00a0707482 Mon Sep 17 00:00:00 2001 From: Brian Date: Sat, 20 Jun 2026 17:44:21 -0500 Subject: [PATCH 13/15] feat(party-mode): persistent per-party memory (#2484) * feat(party-mode): add persistent per-party memory Each party now keeps a succinct, append-only memlog (the memlog standard) under {memory_dir}//, so a room remembers prior sessions and opens in character carrying them forward. - Memory accrues live: capture memorable beats as they land, with a floor so an abandoned session still leaves a trace; wrap-up is a top-up. - Read distills via a reader subagent that returns only current standing state (latest dynamic per pair, open threads, recent callbacks) so the raw log never enters the party context. - Writes are silent and fail safe: a missing or erroring memlog.py is skipped without breaking the fiction. - New customize knobs: party_memory (on by default) and memory_dir. Keyed per party (group id, or `installed` for the default room); ad-hoc casts stay ephemeral. On-disk compaction is left to a future memlog.py pass. * refactor(party-mode): standard structure, per-group memory, keep on-the-fly cast - Restructure SKILL.md to the standard skill shape (intro -> Conventions -> On Activation -> content); consolidate all performance rules into one "Keep It Feeling Like a Party" section. SKILL.md ~500 tokens lighter. - Per-group `memory` flag: global party_memory now governs only the default room; resolve_party.py resolves memory_enabled per active roster (default room -> party_memory, named group -> own flag), with tests. - On-the-fly characters are captured as memlog entries during a session; at wrap-up the room offers to save them into the party via bmad-customize. - Memory mechanics consolidated into references/party-memory.md; SKILL.md step 5 just routes to it. - Docs updated. * docs(party-mode): fix open-cast lock-down claim and python3->uv run in create-party --- .gitignore | 2 + docs/explanation/party-mode.md | 12 ++- src/core-skills/bmad-party-mode/SKILL.md | 85 +++++++------------ .../bmad-party-mode/customize.toml | 25 ++++++ .../references/create-party.md | 15 ++-- .../references/party-memory.md | 51 +++++++++++ .../bmad-party-mode/scripts/resolve_party.py | 9 +- .../scripts/tests/test-resolve_party.py | 8 ++ 8 files changed, 147 insertions(+), 60 deletions(-) create mode 100644 src/core-skills/bmad-party-mode/references/party-memory.md diff --git a/.gitignore b/.gitignore index 99e48d9ab..b903b294a 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,8 @@ CLAUDE.local.md .claude/settings.local.json .junie/ .agents/ +.analysis/ + z*/ !docs/zh-cn/ diff --git a/docs/explanation/party-mode.md b/docs/explanation/party-mode.md index 4d3483fc8..baa2e4505 100644 --- a/docs/explanation/party-mode.md +++ b/docs/explanation/party-mode.md @@ -5,7 +5,7 @@ sidebar: 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, and how to build your own cast of personas instead of using the installed agents. +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. ## What is Party Mode? @@ -131,6 +131,16 @@ Whichever mode is running, the orchestrator presents the result as one conversat 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. diff --git a/src/core-skills/bmad-party-mode/SKILL.md b/src/core-skills/bmad-party-mode/SKILL.md index bb291701b..e1cf3c59a 100644 --- a/src/core-skills/bmad-party-mode/SKILL.md +++ b/src/core-skills/bmad-party-mode/SKILL.md @@ -5,38 +5,38 @@ description: 'Orchestrates lively group discussions between installed BMAD agent # Party Mode -Run a round-table 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. - -**Two intents.** Usually the user wants to *run* a party — that's everything below. If instead they want to *create or configure* one — invent a cast, add a persona, distill customer data into a focus-group panel, set a default, or **edit an existing custom party** (retune a member, add someone to a group) — load `references/create-party.md` and follow it. Detect which from how they invoke the skill; when it's unclear, ask. Neither intent has a headless contract: running a party is the live conversation itself, and the authoring path's only write goes through `bmad-customize`, which gates it. - -## What "Good" Feels Like - -- **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. -- **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. -- **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. -- **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. - -If a round comes back feeling like four essays stapled together, you missed the objective. Tighten it the next round. +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. ## Conventions -- Bare paths (e.g. `references/create-party.md`) resolve from `{skill-root}`, where `customize.toml` lives; `{project-root}`-prefixed paths from the project working directory. +- **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.}` resolves to `customize.toml`'s `[workflow]` table (overrides win). +- **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. +- **File roles:** a party's memory is the per-party memlog at `{workflow.memory_dir}//.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). +- **Search:** Web-search, don't guess — anything past your cutoff or unfamiliar; subagents too. -## Setup +## On Activation -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 its defaults. Then run each `{workflow.activation_steps_prepend}` entry, and hold each `{workflow.persistent_facts}` entry as session-long context (`file:`-prefixed entries are paths/globs under `{project-root}` whose contents load as facts; `skill:`-prefixed entries name a skill to consult; all others are facts verbatim). -2. Load `{project-root}/_bmad/core/config.yaml`: greet with `{user_name}`, speak in `{communication_language}`, and resolve `{output_folder}` and `{date}` for the wrap-up keepsake. -3. **Resolve the active roster:** `python3 {skill-root}/scripts/resolve_party.py --project-root {project-root} --skill {skill-root}`. It returns the active group's full member detail (the `{workflow.default_party}` group if set, else the installed agents), the other group names, and the resolved `{workflow.party_mode}`. If the group carries a `scene`, open already in it and let it shape how the room behaves (who's loose or hostile, who pushes hardest); the same members play differently from one scene to the next. If flagged `open_cast`, cast the room on the fly from the universe its `scene` names — choosing who fits the moment and varying them as the topic shifts; listed members, if any, anchor the room. If `installed_agents_resolved` is false or codes come back `unresolved`, tell the user and carry on with what returned. -4. **Roster overrides:** - - If the invocation names a cast or characters inline (e.g. "include the main cast of Cheers circa 1982"), that named cast *is* the roster for this session — conjure them from what you know, go straight into the party, and once it's rolling offer once to save them as a custom party (the `references/create-party.md` write path), without stalling. Ephemeral; this path skips the script. - - A runtime `--party ` (alias `--group `) overrides any configured `default_party`: run `resolve_party.py --party ` for that group's full detail. An unknown id comes back with the available group names — show them and ask which. - - Run `resolve_party.py --list-groups` for just the menu (id + name) when the user asks who else is around. - - Mid-session the same levers apply: the user can switch rooms ("switch to the writers' room") — re-run `resolve_party.py --party `, set the new group's `scene`, and carry the thread over so the new faces react to where things stand — or summon any member of the *collective* (installed agents plus your custom `party_members`) by name, even one not in the current room. -5. Welcome the user and show who's in the room (icon, name, one-line role). If other groups exist, you may note they can switch rooms. Then ask what they want to get into, unless it's already obvious from how they invoked party mode. +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). +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 ` (alias `--group `) 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 ` 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. -Then 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 -**Hold this the whole run:** it's theater of the mind, so set the stage and play it straight — never break the fourth wall about the mechanism (no "you have 4 agents in the room", no "I'm orchestrating a party"). Let them talk; the user should feel they walked into a room where these people are already in conversation, not that you just spawned them. +This is the bar — strive for every one of these, every round. It's the difference between a party and a panel: + +- **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. +- **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. +- **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. +- **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 @@ -44,34 +44,15 @@ Use `{workflow.party_mode}` for the session unless the user passed `--mode /.memlog.md`, where `` 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 = "" @@ -130,17 +146,25 @@ persona = "Counters the perfectionists so the room isn't a pile-on. 'Does this a # 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]] @@ -148,3 +172,4 @@ 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 diff --git a/src/core-skills/bmad-party-mode/references/create-party.md b/src/core-skills/bmad-party-mode/references/create-party.md index feeaa1deb..a0f33340e 100644 --- a/src/core-skills/bmad-party-mode/references/create-party.md +++ b/src/core-skills/bmad-party-mode/references/create-party.md @@ -7,7 +7,7 @@ A guided authoring flow that turns an idea — a themed cast, a one-off persona, 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`, and `members` (codes). `members` is optional: leave it off for an open-cast room whose `scene` names a pool the model casts from on the fly. +- `[[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. @@ -26,11 +26,15 @@ Open by understanding what they're building. Three common shapes — stay open, 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 default question, and go straight to the write. +**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: `python3 {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. +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) @@ -54,12 +58,13 @@ Keep pushing for specificity. "Skeptical CFO" is a placeholder; "won't approve a ## 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 — `python3 {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. +**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, 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. +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 ` to summon the group, or that it's now the default if they set it. diff --git a/src/core-skills/bmad-party-mode/references/party-memory.md b/src/core-skills/bmad-party-mode/references/party-memory.md new file mode 100644 index 000000000..78244d2c6 --- /dev/null +++ b/src/core-skills/bmad-party-mode/references/party-memory.md @@ -0,0 +1,51 @@ +# 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 (" 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 \ + --text "" +``` + +Add `--by ` 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. \ No newline at end of file diff --git a/src/core-skills/bmad-party-mode/scripts/resolve_party.py b/src/core-skills/bmad-party-mode/scripts/resolve_party.py index bcca64af4..abee93cf3 100644 --- a/src/core-skills/bmad-party-mode/scripts/resolve_party.py +++ b/src/core-skills/bmad-party-mode/scripts/resolve_party.py @@ -197,7 +197,8 @@ def group_detail(g, collective, index): 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} + "members": members, "unresolved": unresolved, + "memory_enabled": bool(g.get("memory", False))} if g.get("scene"): detail["scene"] = g["scene"] if not raw_members: @@ -220,6 +221,9 @@ def main(): 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: @@ -252,7 +256,8 @@ def main(): # 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]}) + "members": [collective[c] for c in installed_codes], + "memory_enabled": party_memory}) _emit(result) diff --git a/src/core-skills/bmad-party-mode/scripts/tests/test-resolve_party.py b/src/core-skills/bmad-party-mode/scripts/tests/test-resolve_party.py index 58c50a985..43aaa90c7 100644 --- a/src/core-skills/bmad-party-mode/scripts/tests/test-resolve_party.py +++ b/src/core-skills/bmad-party-mode/scripts/tests/test-resolve_party.py @@ -113,6 +113,14 @@ class TestGroupDetail(unittest.TestCase): 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.""" From cd8ac7e9aa54782f3f584b601edce6020f8fd110 Mon Sep 17 00:00:00 2001 From: Brian Date: Sat, 20 Jun 2026 17:47:12 -0500 Subject: [PATCH 14/15] bmm: standardize memlog usage across skills (#2483) * bmm: standardize memlog usage across skills - Point all memlog writes at the canonical core script (uv {project-root}/_bmad/scripts/memlog.py) in bmad-spec, bmad-brainstorming, and bmad-architecture; drop the python3/{skill-root} invocations and remove the bundled memlog.py copy from bmad-brainstorming. - Migrate bmad-prd, bmad-ux, and bmad-product-brief off the hand-authored decision-log.md onto the memlog standard: .memlog.md written only via memlog.py (init/append), distilled-toward not authored, no lifecycle status; rename the headless decision_log JSON key to memlog. - Fix bmad-spec capability rendering: nested bullets so intent/success break onto their own lines instead of collapsing into one blob. - Update EN + FR docs (getting-started, workflow-map, core-tools) to reference .memlog.md. - Remove the bmad-product-brief eval suite (to be replaced with a new format). * bmm: invoke scripts with 'uv run' instead of python3/bare uv Bare 'uv {path}.py' does not execute (uv treats the path as a subcommand and errors); only 'uv run {path}' runs the script. Fixes the broken bare-uv memlog form shipped earlier in this branch and converts python3 script calls to 'uv run' across the 6 touched skills (memlog.py, resolve_customization.py, brain.py, lint_spine.py). Inline 'python3 -c' one-liners and .py shebangs are left as-is. * bmm: address PR review on memlog standardization - Delete orphaned bmad-brainstorming/scripts/tests/test_memlog.py: it imported the bundled memlog.py removed in this PR (ModuleNotFoundError on collection) and its unique tests asserted the now-removed status-lifecycle behavior. The canonical src/scripts/tests/test_memlog.py is the corrected superset, so no coverage is lost. - Make runnable memlog command examples self-contained with the full 'uv run {project-root}/_bmad/scripts/memlog.py ... --workspace {doc_workspace}' form across bmad-brainstorming (converge/finalize/headless), bmad-prd, and bmad-ux. Terse checklist back-references left short by design. - bmad-product-brief Update: init .memlog.md if missing (legacy/pre-standard briefs), matching bmad-prd and bmad-ux; fix the --type override invocation. --- docs/fr/reference/core-tools.md | 4 +- docs/fr/reference/workflow-map.md | 4 +- docs/fr/tutorials/getting-started.md | 2 +- docs/reference/core-tools.md | 4 +- docs/reference/workflow-map.md | 6 +- docs/tutorials/getting-started.md | 2 +- .../bmm-skills/bmad-product-brief/evals.json | 237 ---------------- .../files/branfield-memo.md | 46 --- .../files/forkbird-brief/addendum.md | 40 --- .../files/forkbird-brief/brief.md | 56 ---- .../files/forkbird-brief/decision-log.md | 27 -- .../files/meridian-mobility-report.md | 116 -------- .../files/mossridge-brief/addendum.md | 41 --- .../files/mossridge-brief/brief.md | 57 ---- .../files/mossridge-brief/decision-log.md | 29 -- .../files/pantry-bridge-interviews.md | 90 ------ .../bmad-product-brief/files/q2-brainstorm.md | 101 ------- .../bmad-product-brief/triggers.json | 18 -- .../1-analysis/bmad-product-brief/SKILL.md | 16 +- .../2-plan-workflows/bmad-prd/SKILL.md | 14 +- .../bmad-prd/assets/headless-schemas.md | 4 +- .../2-plan-workflows/bmad-prd/customize.toml | 2 +- .../bmad-prd/references/headless.md | 2 +- .../bmad-prd/references/validate.md | 2 +- .../2-plan-workflows/bmad-ux/SKILL.md | 14 +- .../bmad-ux/assets/design-directions.md | 2 +- .../bmad-ux/assets/headless-schemas.md | 4 +- .../bmad-ux/assets/key-screens.md | 8 +- .../2-plan-workflows/bmad-ux/customize.toml | 2 +- .../bmad-ux/references/creative-tools.md | 2 +- .../bmad-ux/references/headless.md | 2 +- .../bmad-ux/references/validate.md | 4 +- .../3-solutioning/bmad-architecture/SKILL.md | 8 +- .../references/reviewer-gate.md | 2 +- src/core-skills/bmad-brainstorming/SKILL.md | 18 +- .../bmad-brainstorming/references/converge.md | 2 +- .../bmad-brainstorming/references/finalize.md | 2 +- .../bmad-brainstorming/references/headless.md | 8 +- .../references/in-chat-techniques.md | 2 +- .../references/mode-autonomous.md | 2 +- .../bmad-brainstorming/scripts/memlog.py | 202 ------------- .../scripts/tests/test_memlog.py | 265 ------------------ src/core-skills/bmad-spec/SKILL.md | 6 +- .../bmad-spec/assets/spec-template.md | 6 +- 44 files changed, 77 insertions(+), 1404 deletions(-) delete mode 100644 evals/bmm-skills/bmad-product-brief/evals.json delete mode 100644 evals/bmm-skills/bmad-product-brief/files/branfield-memo.md delete mode 100644 evals/bmm-skills/bmad-product-brief/files/forkbird-brief/addendum.md delete mode 100644 evals/bmm-skills/bmad-product-brief/files/forkbird-brief/brief.md delete mode 100644 evals/bmm-skills/bmad-product-brief/files/forkbird-brief/decision-log.md delete mode 100644 evals/bmm-skills/bmad-product-brief/files/meridian-mobility-report.md delete mode 100644 evals/bmm-skills/bmad-product-brief/files/mossridge-brief/addendum.md delete mode 100644 evals/bmm-skills/bmad-product-brief/files/mossridge-brief/brief.md delete mode 100644 evals/bmm-skills/bmad-product-brief/files/mossridge-brief/decision-log.md delete mode 100644 evals/bmm-skills/bmad-product-brief/files/pantry-bridge-interviews.md delete mode 100644 evals/bmm-skills/bmad-product-brief/files/q2-brainstorm.md delete mode 100644 evals/bmm-skills/bmad-product-brief/triggers.json delete mode 100644 src/core-skills/bmad-brainstorming/scripts/memlog.py delete mode 100644 src/core-skills/bmad-brainstorming/scripts/tests/test_memlog.py diff --git a/docs/fr/reference/core-tools.md b/docs/fr/reference/core-tools.md index da173fa58..76f5801d5 100644 --- a/docs/fr/reference/core-tools.md +++ b/docs/fr/reference/core-tools.md @@ -113,7 +113,7 @@ La magie se produit dans les idées 50–100. Le workflow encourage la générat 1. Lit l’entrée et tout document annexe lié 2. Distille en un noyau à cinq champs via un modèle configurable ; redirige l’excé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) -4. Écrit `SPEC.md`, les compagnons associés, et un `.decision-log.md` sous `{output_folder}/specs/spec-{slug}/` +4. Écrit `SPEC.md`, les compagnons associés, et un `.memlog.md` sous `{output_folder}/specs/spec-{slug}/` La loi Spec impose huit règles : les capacités expriment à la fois l’intention 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 l’inten - `slug` (optionnel) — Requis uniquement lorsque l’entrée est succincte et qu’aucun 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 d’en créer une nouvelle -**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. +**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. :::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 lorsqu’elles ont besoin d’exprimer une intention sous forme de contrat canonique ou de proposer des mises à jour. diff --git a/docs/fr/reference/workflow-map.md b/docs/fr/reference/workflow-map.md index 86592af5b..d5cb910f5 100644 --- a/docs/fr/reference/workflow-map.md +++ b/docs/fr/reference/workflow-map.md @@ -47,13 +47,13 @@ Définissez ce qu’il faut construire et pour qui. | 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`, `decision-log.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`, `.memlog.md` ; Validation : `validation-report.html` + `.md` | | `bmad-ux` | Concevez l’expérience utilisateur (lorsque l’UX compte) | `DESIGN.md`, `EXPERIENCE.md` | :::tip[Trois intentions en un seul skill] `bmad-prd` couvre l’intégralité du cycle de vie du PRD. Précisez votre intention lors de l’appel, 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 `decision-log.md` +- **Créer** — nouveau PRD à partir de zéro via une découverte accompagnée ; produit `prd.md`, `addendum.md` et `.memlog.md` - **Mettre à jour** — réconcilie un PRD existant avec un signal de changement, en mettant en évidence les conflits avant d’appliquer les modifications - **Valider** — évalue un PRD à l’aide d’une liste de contrôle configurable et produit un rapport de constats structuré au format HTML ::: diff --git a/docs/fr/tutorials/getting-started.md b/docs/fr/tutorials/getting-started.md index c77d63f0a..65eb2b5a5 100644 --- a/docs/fr/tutorials/getting-started.md +++ b/docs/fr/tutorials/getting-started.md @@ -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 :** 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`, `decision-log.md` +2. Résultat : `prd.md`, `addendum.md`, `.memlog.md` :::note[Intentions de `bmad-prd`] diff --git a/docs/reference/core-tools.md b/docs/reference/core-tools.md index d2625d190..d7250b9cd 100644 --- a/docs/reference/core-tools.md +++ b/docs/reference/core-tools.md @@ -113,7 +113,7 @@ The magic happens in ideas 50–100. The workflow encourages generating 100+ ide 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. 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 `.decision-log.md` under `{output_folder}/specs/spec-{slug}/`. +4. Writes `SPEC.md`, sibling companions, and a `.memlog.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. @@ -123,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. - `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 `.decision-log.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 `.memlog.md`. Headless callers receive a JSON response with the result status and the list of files written or modified. :::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. diff --git a/docs/reference/workflow-map.md b/docs/reference/workflow-map.md index 6d71a3a1f..2155f260d 100644 --- a/docs/reference/workflow-map.md +++ b/docs/reference/workflow-map.md @@ -46,13 +46,13 @@ Define what to build and for whom. | Workflow | Purpose | Produces | |-------------------------|-------------------------------------------------------------------------------------|---------------------------------------------------| -| `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`, `.decision-log.md` | +| `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-ux` | Design user experience (when UX matters) — DESIGN.md (visual) + EXPERIENCE.md (behavioral) spine pair | `DESIGN.md`, `EXPERIENCE.md`, `.memlog.md` | :::tip[Three intents in one skill] `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 `decision-log.md` +- **Create** — new PRD from scratch via coached discovery; produces `prd.md`, `addendum.md`, and `.memlog.md` - **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 ::: diff --git a/docs/tutorials/getting-started.md b/docs/tutorials/getting-started.md index 869de2529..fd3b65d9d 100644 --- a/docs/tutorials/getting-started.md +++ b/docs/tutorials/getting-started.md @@ -148,7 +148,7 @@ All workflows in this phase are optional. [**Not sure which to use?**](../explan **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 -2. Output: `prd.md`, `addendum.md`, `decision-log.md` +2. Output: `prd.md`, `addendum.md`, `.memlog.md` :::note[`bmad-prd` intents] diff --git a/evals/bmm-skills/bmad-product-brief/evals.json b/evals/bmm-skills/bmad-product-brief/evals.json deleted file mode 100644 index 2c70b3376..000000000 --- a/evals/bmm-skills/bmad-product-brief/evals.json +++ /dev/null @@ -1,237 +0,0 @@ -{ - "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)" - ] - } - ] -} diff --git a/evals/bmm-skills/bmad-product-brief/files/branfield-memo.md b/evals/bmm-skills/bmad-product-brief/files/branfield-memo.md deleted file mode 100644 index 0836d9d91..000000000 --- a/evals/bmm-skills/bmad-product-brief/files/branfield-memo.md +++ /dev/null @@ -1,46 +0,0 @@ -# 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. diff --git a/evals/bmm-skills/bmad-product-brief/files/forkbird-brief/addendum.md b/evals/bmm-skills/bmad-product-brief/files/forkbird-brief/addendum.md deleted file mode 100644 index e5fd867c0..000000000 --- a/evals/bmm-skills/bmad-product-brief/files/forkbird-brief/addendum.md +++ /dev/null @@ -1,40 +0,0 @@ -# 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 4–6 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. diff --git a/evals/bmm-skills/bmad-product-brief/files/forkbird-brief/brief.md b/evals/bmm-skills/bmad-product-brief/files/forkbird-brief/brief.md deleted file mode 100644 index 81c5fc5c1..000000000 --- a/evals/bmm-skills/bmad-product-brief/files/forkbird-brief/brief.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -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 28–45 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 15–30% 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 diff --git a/evals/bmm-skills/bmad-product-brief/files/forkbird-brief/decision-log.md b/evals/bmm-skills/bmad-product-brief/files/forkbird-brief/decision-log.md deleted file mode 100644 index d7bbb7e97..000000000 --- a/evals/bmm-skills/bmad-product-brief/files/forkbird-brief/decision-log.md +++ /dev/null @@ -1,27 +0,0 @@ -# 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. diff --git a/evals/bmm-skills/bmad-product-brief/files/meridian-mobility-report.md b/evals/bmm-skills/bmad-product-brief/files/meridian-mobility-report.md deleted file mode 100644 index 0f9de8838..000000000 --- a/evals/bmm-skills/bmad-product-brief/files/meridian-mobility-report.md +++ /dev/null @@ -1,116 +0,0 @@ -# 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.* diff --git a/evals/bmm-skills/bmad-product-brief/files/mossridge-brief/addendum.md b/evals/bmm-skills/bmad-product-brief/files/mossridge-brief/addendum.md deleted file mode 100644 index 9fdbf7236..000000000 --- a/evals/bmm-skills/bmad-product-brief/files/mossridge-brief/addendum.md +++ /dev/null @@ -1,41 +0,0 @@ -# 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. diff --git a/evals/bmm-skills/bmad-product-brief/files/mossridge-brief/brief.md b/evals/bmm-skills/bmad-product-brief/files/mossridge-brief/brief.md deleted file mode 100644 index ad5fc4761..000000000 --- a/evals/bmm-skills/bmad-product-brief/files/mossridge-brief/brief.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -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:** Tuesday–Saturday 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 4–6 active volunteers needed at any given time, with a roster of 12–15 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. diff --git a/evals/bmm-skills/bmad-product-brief/files/mossridge-brief/decision-log.md b/evals/bmm-skills/bmad-product-brief/files/mossridge-brief/decision-log.md deleted file mode 100644 index 7965b1ac6..000000000 --- a/evals/bmm-skills/bmad-product-brief/files/mossridge-brief/decision-log.md +++ /dev/null @@ -1,29 +0,0 @@ -# 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. diff --git a/evals/bmm-skills/bmad-product-brief/files/pantry-bridge-interviews.md b/evals/bmm-skills/bmad-product-brief/files/pantry-bridge-interviews.md deleted file mode 100644 index 20f011297..000000000 --- a/evals/bmm-skills/bmad-product-brief/files/pantry-bridge-interviews.md +++ /dev/null @@ -1,90 +0,0 @@ -# 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. diff --git a/evals/bmm-skills/bmad-product-brief/files/q2-brainstorm.md b/evals/bmm-skills/bmad-product-brief/files/q2-brainstorm.md deleted file mode 100644 index e04e45773..000000000 --- a/evals/bmm-skills/bmad-product-brief/files/q2-brainstorm.md +++ /dev/null @@ -1,101 +0,0 @@ -# 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. diff --git a/evals/bmm-skills/bmad-product-brief/triggers.json b/evals/bmm-skills/bmad-product-brief/triggers.json deleted file mode 100644 index b933f0769..000000000 --- a/evals/bmm-skills/bmad-product-brief/triggers.json +++ /dev/null @@ -1,18 +0,0 @@ -[ - { "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 } -] diff --git a/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md b/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md index ec06f0a3d..ad40bf72c 100644 --- a/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md +++ b/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md @@ -15,7 +15,7 @@ At the opening greeting, let the user know they can invoke `bmad-party-mode` for ## On Activation -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. +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. 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. 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 -**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. +**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=""`. 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, `.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. +**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 ""`, 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, `.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. +**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. ## Headless Mode @@ -44,7 +44,7 @@ When invoked headless, do not ask. Complete the intent using what is provided, w "intent": "create", "brief": "{doc_workspace}/brief.md", "addendum": "{doc_workspace}/addendum.md", - "decision_log": "{doc_workspace}/.decision-log.md", + "memlog": "{doc_workspace}/.memlog.md", "open_questions": [], "external_handoffs": [ {"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 - **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`, `.decision-log.md`) exists on disk and the user knows the path. -- **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. +- **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. +- **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 --text ""` (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. - **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. - **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 -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. +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. 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. 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. diff --git a/src/bmm-skills/2-plan-workflows/bmad-prd/SKILL.md b/src/bmm-skills/2-plan-workflows/bmad-prd/SKILL.md index 26a32bd97..6ebfeab3f 100644 --- a/src/bmm-skills/2-plan-workflows/bmad-prd/SKILL.md +++ b/src/bmm-skills/2-plan-workflows/bmad-prd/SKILL.md @@ -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. - `{workflow.}` resolves to fields in `customize.toml`'s `[workflow]` table (overrides win per BMad merge rules). - `{doc_workspace}` is the bound run folder. -- **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. +- **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 --text ""` (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. ## On Activation -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. +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. 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. 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 -**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`. +**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=""` 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, `.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`. +**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 ""` per recovered decision) before continuing. Surface conflicts with prior decisions before applying. Then `## Finalize`. **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. -1. **Decision log audit.** Walk `.decision-log.md` with the user; each entry captured in PRD, in addendum, or set aside. +1. **Memlog audit.** Walk `.memlog.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. 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 to `.decision-log.md`. 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 via `memlog.py append`. 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. 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 to `.decision-log.md`. 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 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. 8. Run `{workflow.on_complete}` if non-empty. diff --git a/src/bmm-skills/2-plan-workflows/bmad-prd/assets/headless-schemas.md b/src/bmm-skills/2-plan-workflows/bmad-prd/assets/headless-schemas.md index 82c53e6f9..89d5b6c15 100644 --- a/src/bmm-skills/2-plan-workflows/bmad-prd/assets/headless-schemas.md +++ b/src/bmm-skills/2-plan-workflows/bmad-prd/assets/headless-schemas.md @@ -18,7 +18,7 @@ Every headless run ends with one of these payloads. Omit keys for artifacts not "intent": "create", "prd": "{doc_workspace}/prd.md", "addendum": "{doc_workspace}/addendum.md", - "decision_log": "{doc_workspace}/.decision-log.md", + "memlog": "{doc_workspace}/.memlog.md", "open_questions": [], "assumptions": [], "external_handoffs": [ @@ -34,7 +34,7 @@ Every headless run ends with one of these payloads. Omit keys for artifacts not "status": "complete", "intent": "update", "prd": "{doc_workspace}/prd.md", - "decision_log": "{doc_workspace}/.decision-log.md", + "memlog": "{doc_workspace}/.memlog.md", "changes_summary": "1-3 sentences describing what changed and why", "conflicts_with_prior_decisions": [], "open_questions": [], diff --git a/src/bmm-skills/2-plan-workflows/bmad-prd/customize.toml b/src/bmm-skills/2-plan-workflows/bmad-prd/customize.toml index 21f297974..77515bd27 100644 --- a/src/bmm-skills/2-plan-workflows/bmad-prd/customize.toml +++ b/src/bmm-skills/2-plan-workflows/bmad-prd/customize.toml @@ -60,7 +60,7 @@ validation_checklist_template = "assets/prd-validation-checklist.md" # collapse — no JS. validation_report_template = "assets/validation-report-template.html" -# Run folder location. The PRD, optional addendum, decision log, and optional +# Run folder location. The PRD, optional addendum, memlog, and optional # validation report all land inside `{prd_output_path}/{run_folder_pattern}/`. # Resume-check scans `{prd_output_path}` for prior unfinished runs. prd_output_path = "{planning_artifacts}/prds" diff --git a/src/bmm-skills/2-plan-workflows/bmad-prd/references/headless.md b/src/bmm-skills/2-plan-workflows/bmad-prd/references/headless.md index 4ea4f24d7..2f5a168a0 100644 --- a/src/bmm-skills/2-plan-workflows/bmad-prd/references/headless.md +++ b/src/bmm-skills/2-plan-workflows/bmad-prd/references/headless.md @@ -34,6 +34,6 @@ End with the JSON response (full schemas with examples in `assets/headless-schem ## Mode-specific overrides -**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. +**Update.** Apply the change, log it via `uv run {project-root}/_bmad/scripts/memlog.py append --workspace {doc_workspace} --type change --text ""`, 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. diff --git a/src/bmm-skills/2-plan-workflows/bmad-prd/references/validate.md b/src/bmm-skills/2-plan-workflows/bmad-prd/references/validate.md index 6b303814f..f9bb8cd68 100644 --- a/src/bmm-skills/2-plan-workflows/bmad-prd/references/validate.md +++ b/src/bmm-skills/2-plan-workflows/bmad-prd/references/validate.md @@ -4,7 +4,7 @@ The Validate intent playbook. Standalone — this intent critiques an existing P ## Orient -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. +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. ## Run the Reviewer Gate diff --git a/src/bmm-skills/2-plan-workflows/bmad-ux/SKILL.md b/src/bmm-skills/2-plan-workflows/bmad-ux/SKILL.md index b5416fd32..b441b2196 100644 --- a/src/bmm-skills/2-plan-workflows/bmad-ux/SKILL.md +++ b/src/bmm-skills/2-plan-workflows/bmad-ux/SKILL.md @@ -30,7 +30,7 @@ UX may lead, follow, or stand alone. Inherit `sources:` by reference; the spines ## On Activation -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. +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. 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. 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 -**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. +**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=""`; create `DESIGN.md` (frontmatter only) and `EXPERIENCE.md` (frontmatter only). Run Discovery → Finalize. -**Update.** Read spines + log + sources. Create the log if missing — this update is entry one. Surface conflicts with prior decisions. Run 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. **Validate.** See `references/validate.md`. ## Discovery -**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. +**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 --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. **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: -- **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. +- **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. - **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. -- **Open items triaged.** Open Questions, `[ASSUMPTION]`, `[NOTE FOR UX]`. Phase-blockers one at a time; non-blockers → log. +- **Open items triaged.** Open Questions, `[ASSUMPTION]`, `[NOTE FOR UX]`. Phase-blockers one at a time; non-blockers → `memlog.py append`. - **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. - **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. 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 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}`. diff --git a/src/bmm-skills/2-plan-workflows/bmad-ux/assets/design-directions.md b/src/bmm-skills/2-plan-workflows/bmad-ux/assets/design-directions.md index a21fb3a93..289b270a6 100644 --- a/src/bmm-skills/2-plan-workflows/bmad-ux/assets/design-directions.md +++ b/src/bmm-skills/2-plan-workflows/bmad-ux/assets/design-directions.md @@ -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). -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 `