feat(skills): workflow customization pilot + resolver and installer fixes

Workflow customization:
- bmad-product-brief adopts the customize.yaml pattern with new standard
  keys (activation_steps_prepend, activation_steps_append, skill_end)
  that apply to any skill type, not just workflows.
- SKILL.md resolves customization as the first activation step, executes
  prepend items immediately, retains append for after greeting, and
  re-resolves skill_end after Stage 5 (Finalize).
- Added {skill-root} to the Conventions block.
- Normalized all sub-prompt path references to bare-from-skill-root
  (../agents/ -> agents/, sibling filenames -> prompts/<file>).

Metadata:
- Added "DO NOT EDIT -- overwritten on every update." header to all 6
  agent customize.yaml files.

Resolver:
- find_project_root now walks from skill_dir first, then falls back to
  cwd. Nested-workspace setups where an ancestor of cwd has an unrelated
  _bmad/ would previously bind the resolver to the wrong project.

Installer:
- Added 'memory' to the nonModuleDirs sets at all three filter sites so
  sidecar-generated _bmad/memory/<agent>/ folders aren't treated as
  modules and don't receive a generated config.yaml.
- detectCustomFiles now skips the entire _memory/ and memory/ subtrees
  generically, replacing the old v6.1-specific -sidecar substring check.
  Agent runtime state is never flagged as custom/modified noise on update.
This commit is contained in:
Brian Madison 2026-04-18 22:04:58 -05:00
parent 8cd157864e
commit 4d5842c8c7
15 changed files with 63 additions and 61 deletions

View File

@ -1,3 +1,5 @@
# DO NOT EDIT -- overwritten on every update.
agent: agent:
metadata: metadata:
name: Mary name: Mary
@ -42,7 +44,7 @@ agent:
skill: bmad-technical-research skill: bmad-technical-research
- code: CB - code: CB
description: "Create or update product briefs through guided or autonomous discovery" description: "Create or update product briefs through guided or autonomous discovery"
skill: bmad-product-brief-preview skill: bmad-product-brief
- code: WB - code: WB
description: "Working Backwards PRFAQ challenge — forge and stress-test product concepts" description: "Working Backwards PRFAQ challenge — forge and stress-test product concepts"
skill: bmad-prfaq skill: bmad-prfaq

View File

@ -1,3 +1,5 @@
# DO NOT EDIT -- overwritten on every update.
agent: agent:
metadata: metadata:
name: Paige name: Paige

View File

@ -5,13 +5,6 @@ description: Create or update product briefs through guided or autonomous discov
# Create Product Brief # Create Product Brief
## Conventions
- Bare paths (e.g. `prompts/finalize.md`) resolve from the skill root.
- `{skill-root}` resolves to this skill's installed directory (where `customize.yaml` lives).
- `{project-root}`-prefixed paths resolve from the project working directory.
- `{skill-name}` resolves to the skill directory's basename.
## Overview ## Overview
This skill helps you create compelling product briefs through collaborative discovery, intelligent artifact analysis, and web research. Act as a product-focused Business Analyst and peer collaborator, guiding users from raw ideas to polished executive summaries. Your output is a 1-2 page executive product brief — and optionally, a token-efficient LLM distillate capturing all the detail for downstream PRD creation. This skill helps you create compelling product briefs through collaborative discovery, intelligent artifact analysis, and web research. Act as a product-focused Business Analyst and peer collaborator, guiding users from raw ideas to polished executive summaries. Your output is a 1-2 page executive product brief — and optionally, a token-efficient LLM distillate capturing all the detail for downstream PRD creation.
@ -20,6 +13,13 @@ The user is the domain expert. You bring structured thinking, facilitation, mark
**Design rationale:** We always understand intent before scanning artifacts — without knowing what the brief is about, scanning documents is noise, not signal. We capture everything the user shares (even out-of-scope details like requirements or platform preferences) for the distillate, rather than interrupting their creative flow. **Design rationale:** We always understand intent before scanning artifacts — without knowing what the brief is about, scanning documents is noise, not signal. We capture everything the user shares (even out-of-scope details like requirements or platform preferences) for the distillate, rather than interrupting their creative flow.
## Conventions
- Bare paths (e.g. `prompts/finalize.md`) resolve from the skill root.
- `{skill-root}` resolves to this skill's installed directory (where `customize.yaml` lives).
- `{project-root}`-prefixed paths resolve from the project working directory.
- `{skill-name}` resolves to the skill directory's basename.
## Activation Mode Detection ## Activation Mode Detection
Check activation context immediately: Check activation context immediately:
@ -39,23 +39,25 @@ Check activation context immediately:
1. **Resolve customization** 1. **Resolve customization**
Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key inject --key additional_resources` Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key activation_steps_prepend --key activation_steps_append`
**If the script fails**, resolve yourself from `customize.yaml`, with `{project-root}/_bmad/custom/{skill-name}.yaml` overriding, and `{skill-name}.user.yaml` overriding both (any missing file is skipped). **If the script fails**, resolve yourself from `customize.yaml`, with `{project-root}/_bmad/custom/{skill-name}.yaml` overriding, and `{skill-name}.user.yaml` overriding both (any missing file is skipped).
- **Inject before** — If `inject.before` resolved to a non-empty value, prepend it to your active instructions and follow it. - Execute each item in `activation_steps_prepend` in order before proceeding.
- **Available resources** — Note the `additional_resources` list. Do not read these files now; they are available for subsequent prompts to reference when needed. - Retain `activation_steps_append` — you will execute it after step 3.
2. Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:: 2. Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
- Use `{user_name}` for greeting - Use `{user_name}` for greeting
- Use `{communication_language}` for all communications - Use `{communication_language}` for all communications
- Use `{document_output_language}` for output documents - Use `{document_output_language}` for output documents
- Use `{planning_artifacts}` for output location and artifact scanning - Use `{planning_artifacts}` for output location and artifact scanning
- Use `{project_knowledge}` for additional context scanning - Use `{project_knowledge}` for additional context scanning
3. **Greet user** as `{user_name}`, speaking in `{communication_language}`. 3. **Greet user if you have not already** by `{user_name}`, speaking in `{communication_language}`.
4. **Stage 1: Understand Intent** (handled here in SKILL.md) 4. Execute each retained `activation_steps_append` item in order.
5. **Stage 1: Understand Intent** (handled here in SKILL.md)
### Stage 1: Understand Intent ### Stage 1: Understand Intent
@ -97,10 +99,3 @@ Check activation context immediately:
| 4 | Draft & Review | Draft brief, fan out review subagents | `prompts/draft-and-review.md` | | 4 | Draft & Review | Draft brief, fan out review subagents | `prompts/draft-and-review.md` |
| 5 | Finalize | Polish, output, offer distillate | `prompts/finalize.md` | | 5 | Finalize | Polish, output, offer distillate | `prompts/finalize.md` |
## Post-Workflow Customization
After Stage 5 (Finalize) completes and before declaring the workflow done, resolve `inject.after`:
Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key inject.after`
If resolved `inject.after` is non-empty, append it to your active instructions and follow it.

View File

@ -1,20 +1,6 @@
# Customization surface for bmad-product-brief.
# DO NOT EDIT -- overwritten on every update. # DO NOT EDIT -- overwritten on every update.
#
# To customize, create one of the following and include only the fields
# you want to change (unmentioned fields inherit from this file):
# {project-root}/_bmad/custom/bmad-product-brief.yaml (team, committed)
# {project-root}/_bmad/custom/bmad-product-brief.user.yaml (personal, gitignored)
# Additional resource files loaded into workflow context on activation. # Standard customizations for all workflow skills
# Paths may use {project-root}. Agent notes the list on activation activation_steps_prepend: []
# but does not preload the files; subsequent prompts reference them as needed. activation_steps_append: []
# Arrays replace atomically -- include the full list you want. skill_end: ""
additional_resources: []
# Injected prompts woven into the workflow's context.
# before: loaded before the workflow begins
# after: loaded after the workflow completes (pre-finalize)
inject:
before: ""
after: ""

View File

@ -12,9 +12,9 @@ Now that you know what the brief is about, fan out subagents in parallel to gath
**Launch in parallel:** **Launch in parallel:**
1. **Artifact Analyzer** (`../agents/artifact-analyzer.md`) — Scans `{planning_artifacts}` and `{project_knowledge}` for relevant documents. Also scans any specific paths the user provided. Returns structured synthesis of what it found. 1. **Artifact Analyzer** (`agents/artifact-analyzer.md`) — Scans `{planning_artifacts}` and `{project_knowledge}` for relevant documents. Also scans any specific paths the user provided. Returns structured synthesis of what it found.
2. **Web Researcher** (`../agents/web-researcher.md`) — Searches for competitive landscape, market context, trends, and relevant industry data. Returns structured findings scoped to the product domain. 2. **Web Researcher** (`agents/web-researcher.md`) — Searches for competitive landscape, market context, trends, and relevant industry data. Returns structured findings scoped to the product domain.
### Graceful Degradation ### Graceful Degradation
@ -38,20 +38,20 @@ Once subagent results return (or inline scanning completes):
- Highlight anything surprising or worth discussing - Highlight anything surprising or worth discussing
- Share the gaps you've identified - Share the gaps you've identified
- Ask: "Anything else you'd like to add, or shall we move on to filling in the details?" - Ask: "Anything else you'd like to add, or shall we move on to filling in the details?"
- Route to `guided-elicitation.md` - Route to `prompts/guided-elicitation.md`
**Yolo mode:** **Yolo mode:**
- Absorb all findings silently - Absorb all findings silently
- Skip directly to `draft-and-review.md` — you have enough to draft - Skip directly to `prompts/draft-and-review.md` — you have enough to draft
- The user will refine later - The user will refine later
**Headless mode:** **Headless mode:**
- Absorb all findings - Absorb all findings
- Skip directly to `draft-and-review.md` - Skip directly to `prompts/draft-and-review.md`
- No interaction - No interaction
## Stage Complete ## Stage Complete
This stage is complete when subagent results (or inline scanning fallback) have returned and findings are merged with user context. Route per mode: This stage is complete when subagent results (or inline scanning fallback) have returned and findings are merged with user context. Route per mode:
- **Guided**`guided-elicitation.md` - **Guided**`prompts/guided-elicitation.md`
- **Yolo / Headless**`draft-and-review.md` - **Yolo / Headless**`prompts/draft-and-review.md`

View File

@ -8,7 +8,7 @@
## Step 1: Draft the Executive Brief ## Step 1: Draft the Executive Brief
Use `../resources/brief-template.md` as a guide — adapt structure to fit the product's story. Use `resources/brief-template.md` as a guide — adapt structure to fit the product's story.
**Writing principles:** **Writing principles:**
- **Executive audience** — persuasive, clear, concise. 1-2 pages. - **Executive audience** — persuasive, clear, concise. 1-2 pages.
@ -36,9 +36,9 @@ Before showing the draft to the user, run it through multiple review lenses in p
**Launch in parallel:** **Launch in parallel:**
1. **Skeptic Reviewer** (`../agents/skeptic-reviewer.md`) — "What's missing? What assumptions are untested? What could go wrong? Where is the brief vague or hand-wavy?" 1. **Skeptic Reviewer** (`agents/skeptic-reviewer.md`) — "What's missing? What assumptions are untested? What could go wrong? Where is the brief vague or hand-wavy?"
2. **Opportunity Reviewer** (`../agents/opportunity-reviewer.md`) — "What adjacent value propositions are being missed? What market angles or partnerships could strengthen this? What's underemphasized?" 2. **Opportunity Reviewer** (`agents/opportunity-reviewer.md`) — "What adjacent value propositions are being missed? What market angles or partnerships could strengthen this? What's underemphasized?"
3. **Contextual Reviewer** — You (the main agent) pick the most useful third lens based on THIS specific product. Choose the lens that addresses the SINGLE BIGGEST RISK that the skeptic and opportunity reviewers won't naturally catch. Examples: 3. **Contextual Reviewer** — You (the main agent) pick the most useful third lens based on THIS specific product. Choose the lens that addresses the SINGLE BIGGEST RISK that the skeptic and opportunity reviewers won't naturally catch. Examples:
- For healthtech: "Regulatory and compliance risk reviewer" - For healthtech: "Regulatory and compliance risk reviewer"
@ -65,7 +65,7 @@ After all reviews complete:
## Step 4: Present to User ## Step 4: Present to User
**Headless mode:** Skip to `finalize.md` — no user interaction. Save the improved draft directly. **Headless mode:** Skip to `prompts/finalize.md` — no user interaction. Save the improved draft directly.
**Yolo and Guided modes:** **Yolo and Guided modes:**
@ -83,4 +83,4 @@ Present reviewer findings with brief rationale, then offer: "Want me to dig into
## Stage Complete ## Stage Complete
This stage is complete when: (a) the draft has been reviewed by all three lenses and improvements integrated, AND either (autonomous) save and route directly, or (guided/yolo) the user is satisfied. Route to `finalize.md`. This stage is complete when: (a) the draft has been reviewed by all three lenses and improvements integrated, AND either (autonomous) save and route directly, or (guided/yolo) the user is satisfied. Route to `prompts/finalize.md`.

View File

@ -72,4 +72,6 @@ purpose: "Token-efficient context for downstream PRD creation"
## Stage Complete ## Stage Complete
This is the terminal stage. After delivering the completion message and file paths, the workflow is done. If the user requests further revisions, loop back to `draft-and-review.md`. Otherwise, exit. Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key skill_end`
If resolved `skill_end` is non-empty follow it as the final terminal stage. After delivering the completion message and file paths, the workflow is done. If the user requests further revisions, loop back to `prompts/draft-and-review.md`. Otherwise, exit.

View File

@ -5,7 +5,7 @@
**Goal:** Fill the gaps in what you know. By now you have the user's brain dump, artifact analysis, and web research. This stage is about smart, targeted questioning — not rote section-by-section interrogation. **Goal:** Fill the gaps in what you know. By now you have the user's brain dump, artifact analysis, and web research. This stage is about smart, targeted questioning — not rote section-by-section interrogation.
**Skip this stage entirely in Yolo and Autonomous modes** — go directly to `draft-and-review.md`. **Skip this stage entirely in Yolo and Autonomous modes** — go directly to `prompts/draft-and-review.md`.
## Approach ## Approach
@ -67,4 +67,4 @@ If the user is providing complete, confident answers and you have solid coverage
## Stage Complete ## Stage Complete
This stage is complete when sufficient substance exists to draft a compelling brief and the user confirms readiness. Route to `draft-and-review.md`. This stage is complete when sufficient substance exists to draft a compelling brief and the user confirms readiness. Route to `prompts/draft-and-review.md`.

View File

@ -1,3 +1,5 @@
# DO NOT EDIT -- overwritten on every update.
agent: agent:
metadata: metadata:
name: John name: John

View File

@ -1,3 +1,5 @@
# DO NOT EDIT -- overwritten on every update.
agent: agent:
metadata: metadata:
name: Sally name: Sally

View File

@ -1,3 +1,5 @@
# DO NOT EDIT -- overwritten on every update.
agent: agent:
metadata: metadata:
name: Winston name: Winston

View File

@ -1,3 +1,5 @@
# DO NOT EDIT -- overwritten on every update.
agent: agent:
metadata: metadata:
name: Amelia name: Amelia

View File

@ -216,7 +216,11 @@ def main():
defaults = load_yaml(defaults_path, required=True) defaults = load_yaml(defaults_path, required=True)
project_root = find_project_root(Path.cwd()) or find_project_root(skill_dir) # Prefer the project that contains this skill. Only fall back to cwd if
# the skill isn't inside a recognizable project tree (unusual but possible
# for standalone skills invoked directly). Using cwd first is unsafe when
# an ancestor of cwd happens to have a stray _bmad/ from another project.
project_root = find_project_root(skill_dir) or find_project_root(Path.cwd())
team = {} team = {}
user = {} user = {}

View File

@ -718,8 +718,11 @@ class Installer {
const customFiles = []; const customFiles = [];
const modifiedFiles = []; const modifiedFiles = [];
// Memory is always in _bmad/_memory // Memory subtrees (v6.1: _bmad/_memory, current: _bmad/memory) hold
const bmadMemoryPath = '_memory'; // per-user runtime data generated by agents with sidecars. These files
// aren't installer-managed and must never be reported as "custom" or
// "modified" — they're user state, not user overrides.
const bmadMemoryPaths = ['_memory', 'memory'];
// Check if the manifest has hashes - if not, we can't detect modifications // Check if the manifest has hashes - if not, we can't detect modifications
let manifestHasHashes = false; let manifestHasHashes = false;
@ -785,7 +788,7 @@ class Installer {
continue; continue;
} }
if (relativePath.startsWith(bmadMemoryPath + '/') && path.dirname(relativePath).includes('-sidecar')) { if (bmadMemoryPaths.some((mp) => relativePath === mp || relativePath.startsWith(mp + '/'))) {
continue; continue;
} }
@ -836,7 +839,7 @@ class Installer {
// Get all installed module directories // Get all installed module directories
const entries = await fs.readdir(bmadDir, { withFileTypes: true }); const entries = await fs.readdir(bmadDir, { withFileTypes: true });
const nonModuleDirs = new Set(['_config', '_memory', 'docs', 'scripts', 'custom']); const nonModuleDirs = new Set(['_config', '_memory', 'memory', 'docs', 'scripts', 'custom']);
const installedModules = entries.filter((entry) => entry.isDirectory() && !nonModuleDirs.has(entry.name)).map((entry) => entry.name); const installedModules = entries.filter((entry) => entry.isDirectory() && !nonModuleDirs.has(entry.name)).map((entry) => entry.name);
// Generate config.yaml for each installed module // Generate config.yaml for each installed module
@ -963,7 +966,7 @@ class Installer {
// Get all installed module directories // Get all installed module directories
const entries = await fs.readdir(bmadDir, { withFileTypes: true }); const entries = await fs.readdir(bmadDir, { withFileTypes: true });
const nonModuleDirs = new Set(['_config', '_memory', 'docs', 'scripts', 'custom']); const nonModuleDirs = new Set(['_config', '_memory', 'memory', 'docs', 'scripts', 'custom']);
const installedModules = entries.filter((entry) => entry.isDirectory() && !nonModuleDirs.has(entry.name)).map((entry) => entry.name); const installedModules = entries.filter((entry) => entry.isDirectory() && !nonModuleDirs.has(entry.name)).map((entry) => entry.name);
// Add core module to scan (it's installed at root level as _config, but we check src/core-skills) // Add core module to scan (it's installed at root level as _config, but we check src/core-skills)

View File

@ -820,7 +820,7 @@ class OfficialModules {
let foundAny = false; let foundAny = false;
const entries = await fs.readdir(bmadDir, { withFileTypes: true }); const entries = await fs.readdir(bmadDir, { withFileTypes: true });
const nonModuleDirs = new Set(['_config', '_memory', 'docs', 'scripts', 'custom']); const nonModuleDirs = new Set(['_config', '_memory', 'memory', 'docs', 'scripts', 'custom']);
for (const entry of entries) { for (const entry of entries) {
if (entry.isDirectory()) { if (entry.isDirectory()) {
if (nonModuleDirs.has(entry.name)) { if (nonModuleDirs.has(entry.name)) {