Compare commits

..

No commits in common. "9ca0316674b7284fede4324d335e30a55b0d5406" and "c46502f64049797ba1f04bcc18c5f6ca219a250a" have entirely different histories.

17 changed files with 225 additions and 263 deletions

View File

@ -1,197 +0,0 @@
# BMAD PRD Purpose
**The PRD is the top of the required funnel that feeds all subsequent product development work in rhw BMad Method.**
---
## What is a BMAD PRD?
A dual-audience document serving:
1. **Human Product Managers and builders** - Vision, strategy, stakeholder communication
2. **LLM Downstream Consumption** - UX Design → Architecture → Epics → Development AI Agents
Each successive document becomes more AI-tailored and granular.
---
## Core Philosophy: Information Density
**High Signal-to-Noise Ratio**
Every sentence must carry information weight. LLMs consume precise, dense content efficiently.
**Anti-Patterns (Eliminate These):**
- ❌ "The system will allow users to..." → ✅ "Users can..."
- ❌ "It is important to note that..." → ✅ State the fact directly
- ❌ "In order to..." → ✅ "To..."
- ❌ Conversational filler and padding → ✅ Direct, concise statements
**Goal:** Maximum information per word. Zero fluff.
---
## The Traceability Chain
**PRD starts the chain:**
```
Vision → Success Criteria → User Journeys → Functional Requirements → (future: User Stories)
```
**In the PRD, establish:**
- Vision → Success Criteria alignment
- Success Criteria → User Journey coverage
- User Journey → Functional Requirement mapping
- All requirements traceable to user needs
**Why:** Each downstream artifact (UX, Architecture, Epics, Stories) must trace back to documented user needs and business objectives. This chain ensures we build the right thing.
---
## What Makes Great Functional Requirements?
### FRs are Capabilities, Not Implementation
**Good FR:** "Users can reset their password via email link"
**Bad FR:** "System sends JWT via email and validates with database" (implementation leakage)
**Good FR:** "Dashboard loads in under 2 seconds for 95th percentile"
**Bad FR:** "Fast loading time" (subjective, unmeasurable)
### SMART Quality Criteria
**Specific:** Clear, precisely defined capability
**Measurable:** Quantifiable with test criteria
**Attainable:** Realistic within constraints
**Relevant:** Aligns with business objectives
**Traceable:** Links to source (executive summary or user journey)
### FR Anti-Patterns
**Subjective Adjectives:**
- ❌ "easy to use", "intuitive", "user-friendly", "fast", "responsive"
- ✅ Use metrics: "completes task in under 3 clicks", "loads in under 2 seconds"
**Implementation Leakage:**
- ❌ Technology names, specific libraries, implementation details
- ✅ Focus on capability and measurable outcomes
**Vague Quantifiers:**
- ❌ "multiple users", "several options", "various formats"
- ✅ "up to 100 concurrent users", "3-5 options", "PDF, DOCX, TXT formats"
**Missing Test Criteria:**
- ❌ "The system shall provide notifications"
- ✅ "The system shall send email notifications within 30 seconds of trigger event"
---
## What Makes Great Non-Functional Requirements?
### NFRs Must Be Measurable
**Template:**
```
"The system shall [metric] [condition] [measurement method]"
```
**Examples:**
- ✅ "The system shall respond to API requests in under 200ms for 95th percentile as measured by APM monitoring"
- ✅ "The system shall maintain 99.9% uptime during business hours as measured by cloud provider SLA"
- ✅ "The system shall support 10,000 concurrent users as measured by load testing"
### NFR Anti-Patterns
**Unmeasurable Claims:**
- ❌ "The system shall be scalable" → ✅ "The system shall handle 10x load growth through horizontal scaling"
- ❌ "High availability required" → ✅ "99.9% uptime as measured by cloud provider SLA"
**Missing Context:**
- ❌ "Response time under 1 second" → ✅ "API response time under 1 second for 95th percentile under normal load"
---
## Domain-Specific Requirements
**Auto-Detect and Enforce Based on Project Context**
Certain industries have mandatory requirements that must be present:
- **Healthcare:** HIPAA Privacy & Security Rules, PHI encryption, audit logging, MFA
- **Fintech:** PCI-DSS Level 1, AML/KYC compliance, SOX controls, financial audit trails
- **GovTech:** NIST framework, Section 508 accessibility (WCAG 2.1 AA), FedRAMP, data residency
- **E-Commerce:** PCI-DSS for payments, inventory accuracy, tax calculation by jurisdiction
**Why:** Missing these requirements in the PRD means they'll be missed in architecture and implementation, creating expensive rework. During PRD creation there is a step to cover this - during validation we want to make sure it was covered. For this purpose steps will utilize a domain-complexity.csv and project-types.csv.
---
## Document Structure (Markdown, Human-Readable)
### Required Sections
1. **Executive Summary** - Vision, differentiator, target users
2. **Success Criteria** - Measurable outcomes (SMART)
3. **Product Scope** - MVP, Growth, Vision phases
4. **User Journeys** - Comprehensive coverage
5. **Domain Requirements** - Industry-specific compliance (if applicable)
6. **Innovation Analysis** - Competitive differentiation (if applicable)
7. **Project-Type Requirements** - Platform-specific needs
8. **Functional Requirements** - Capability contract (FRs)
9. **Non-Functional Requirements** - Quality attributes (NFRs)
### Formatting for Dual Consumption
**For Humans:**
- Clear, professional language
- Logical flow from vision to requirements
- Easy for stakeholders to review and approve
**For LLMs:**
- ## Level 2 headers for all main sections (enables extraction)
- Consistent structure and patterns
- Precise, testable language
- High information density
---
## Downstream Impact
**How the PRD Feeds Next Artifacts:**
**UX Design:**
- User journeys → interaction flows
- FRs → design requirements
- Success criteria → UX metrics
**Architecture:**
- FRs → system capabilities
- NFRs → architecture decisions
- Domain requirements → compliance architecture
- Project-type requirements → platform choices
**Epics & Stories (created after architecture):**
- FRs → user stories (1 FR could map to 1-3 stories potentially)
- Acceptance criteria → story acceptance tests
- Priority → sprint sequencing
- Traceability → stories map back to vision
**Development AI Agents:**
- Precise requirements → implementation clarity
- Test criteria → automated test generation
- Domain requirements → compliance enforcement
- Measurable NFRs → performance targets
---
## Summary: What Makes a Great BMAD PRD?
**High Information Density** - Every sentence carries weight, zero fluff
**Measurable Requirements** - All FRs and NFRs are testable with specific criteria
**Clear Traceability** - Each requirement links to user need and business objective
**Domain Awareness** - Industry-specific requirements auto-detected and included
**Zero Anti-Patterns** - No subjective adjectives, implementation leakage, or vague quantifiers
**Dual Audience Optimized** - Human-readable AND LLM-consumable
**Markdown Format** - Professional, clean, accessible to all stakeholders
---
**Remember:** The PRD is the foundation. Quality here ripples through every subsequent phase. A dense, precise, well-traced PRD makes UX design, architecture, epic breakdown, and AI development dramatically more effective.

View File

@ -1,6 +1,6 @@
---
# File references (ONLY variables used in this step)
prdPurpose: '../data/prd-purpose.md'
prdPurpose: '{project-root}/_bmad/bmm-skills/2-plan-workflows/bmad-create-prd/data/prd-purpose.md'
---
# Step E-1: Discovery & Understanding

View File

@ -1,7 +1,7 @@
---
# File references (ONLY variables used in this step)
prdFile: '{prd_file_path}'
prdPurpose: '../data/prd-purpose.md'
prdPurpose: '{project-root}/_bmad/bmm-skills/2-plan-workflows/bmad-create-prd/data/prd-purpose.md'
---
# Step E-1B: Legacy PRD Conversion Assessment

View File

@ -2,7 +2,7 @@
# File references (ONLY variables used in this step)
prdFile: '{prd_file_path}'
validationReport: '{validation_report_path}' # If provided
prdPurpose: '../data/prd-purpose.md'
prdPurpose: '{project-root}/_bmad/bmm-skills/2-plan-workflows/bmad-create-prd/data/prd-purpose.md'
---
# Step E-2: Deep Review & Analysis

View File

@ -1,7 +1,7 @@
---
# File references (ONLY variables used in this step)
prdFile: '{prd_file_path}'
prdPurpose: '../data/prd-purpose.md'
prdPurpose: '{project-root}/_bmad/bmm-skills/2-plan-workflows/bmad-create-prd/data/prd-purpose.md'
---
# Step E-3: Edit & Update

View File

@ -1,6 +1,7 @@
---
# File references (ONLY variables used in this step)
prdFile: '{prd_file_path}'
validationWorkflow: '{project-root}/_bmad/bmm-skills/2-plan-workflows/bmad-validate-prd/steps-v/step-v-01-discovery.md'
---
# Step E-4: Complete & Validate
@ -116,7 +117,8 @@ Display:
- Display: "This will run all 13 validation checks on the updated PRD."
- Display: "Preparing to validate: {prd_file_path}"
- Display: "**Proceeding to validation...**"
- Invoke the `bmad-validate-prd` skill to run the complete validation workflow
- Read fully and follow: {validationWorkflow} (steps-v/step-v-01-discovery.md)
- Note: This hands off to the validation workflow which will run its complete 13-step process
- **IF E (Edit More):**
- Display: "**Additional Edits**"

View File

@ -1,4 +1,5 @@
---
wipFile: '{implementation_artifacts}/spec-wip.md'
deferred_work_file: '{implementation_artifacts}/deferred-work.md'
spec_file: '' # set at runtime for both routes before leaving this step
---
@ -20,7 +21,7 @@ Before listing artifacts or prompting the user, check whether you already know t
1. Explicit argument
Did the user pass a specific file path, spec name, or clear instruction this message?
- If it points to a file that matches the spec template (has `status` frontmatter with a recognized value: draft, ready-for-dev, in-progress, in-review, or done) → set `spec_file` and **EARLY EXIT** to the appropriate step (step-02 for draft, step-03 for ready/in-progress, step-04 for review). For `done`, ingest as context and proceed to INSTRUCTIONS — do not resume.
- If it points to a file that matches the spec template (has `status` frontmatter with a recognized value: ready-for-dev, in-progress, or in-review) → set `spec_file` and **EARLY EXIT** to the appropriate step (step-03 for ready/in-progress, step-04 for review).
- Anything else (intent files, external docs, plans, descriptions) → ingest it as starting intent and proceed to INSTRUCTIONS. Do not attempt to infer a workflow state from it.
2. Recent conversation
@ -28,8 +29,8 @@ Before listing artifacts or prompting the user, check whether you already know t
Use the same routing as above.
3. Otherwise — scan artifacts and ask
- Active specs (`draft`, `ready-for-dev`, `in-progress`, `in-review`) in `{implementation_artifacts}`? → List them and HALT. Ask user which to resume (or `[N]` for new).
- If `draft` selected: Set `spec_file`. **EARLY EXIT**`./step-02-plan.md` (resume planning from the draft)
- `{wipFile}` exists? → Offer resume or archive.
- Active specs (`ready-for-dev`, `in-progress`, `in-review`) in `{implementation_artifacts}`? → List them and HALT. Ask user which to resume (or `[N]` for new).
- If `ready-for-dev` or `in-progress` selected: Set `spec_file`. **EARLY EXIT**`./step-03-implement.md`
- If `in-review` selected: Set `spec_file`. **EARLY EXIT**`./step-04-review.md`
- Unformatted spec or intent file lacking `status` frontmatter? → Suggest treating its contents as the starting intent. Do NOT attempt to infer a state and resume it.
@ -64,7 +65,7 @@ Never ask extra questions if you already understand what the user intends.
- On **K**: Proceed as-is.
5. Route — choose exactly one:
Derive a valid kebab-case slug from the clarified intent. If the intent references a tracking identifier (story number, issue number, ticket ID), lead the slug with it (e.g. `3-2-digest-delivery`, `gh-47-fix-auth`). If `{implementation_artifacts}/spec-{slug}.md` already exists: if its status is `draft`, treat it as the same work and resume it (set `spec_file` to that path, **EARLY EXIT**`./step-02-plan.md`); otherwise append `-2`, `-3`, etc. Set `spec_file` = `{implementation_artifacts}/spec-{slug}.md`.
Derive a valid kebab-case slug from the clarified intent. If the intent references a tracking identifier (story number, issue number, ticket ID), lead the slug with it (e.g. `3-2-digest-delivery`, `gh-47-fix-auth`). If `{implementation_artifacts}/spec-{slug}.md` already exists, append `-2`, `-3`, etc. Set `spec_file` = `{implementation_artifacts}/spec-{slug}.md`.
**a) One-shot** — zero blast radius: no plausible path by which this change causes unintended consequences elsewhere. Clear intent, no architectural decisions.

View File

@ -1,4 +1,5 @@
---
wipFile: '{implementation_artifacts}/spec-wip.md'
deferred_work_file: '{implementation_artifacts}/deferred-work.md'
---
@ -11,12 +12,11 @@ deferred_work_file: '{implementation_artifacts}/deferred-work.md'
## INSTRUCTIONS
1. Draft resume check. If `{spec_file}` exists with `status: draft`, read it and capture the verbatim `<frozen-after-approval>...</frozen-after-approval>` block as `preserved_intent`. Otherwise `preserved_intent` is empty.
2. Investigate codebase. _Isolate deep exploration in sub-agents/tasks where available. To prevent context snowballing, instruct subagents to give you distilled summaries only._
3. Read `./spec-template.md` fully. Fill it out based on the intent and investigation. If `{preserved_intent}` is non-empty, substitute it for the `<frozen-after-approval>` block in your filled spec before writing. Write the result to `{spec_file}`.
4. Self-review against READY FOR DEVELOPMENT standard.
5. If intent gaps exist, do not fantasize, do not leave open questions, HALT and ask the human.
6. Token count check (see SCOPE STANDARD). If spec exceeds 1600 tokens:
1. Investigate codebase. _Isolate deep exploration in sub-agents/tasks where available. To prevent context snowballing, instruct subagents to give you distilled summaries only._
2. Read `./spec-template.md` fully. Fill it out based on the intent and investigation, and write the result to `{wipFile}`.
3. Self-review against READY FOR DEVELOPMENT standard.
4. If intent gaps exist, do not fantasize, do not leave open questions, HALT and ask the human.
5. Token count check (see SCOPE STANDARD). If spec exceeds 1600 tokens:
- Show user the token count.
- HALT and ask human: `[S] Split — carve off secondary goals` | `[K] Keep full spec — accept the risks`
- On **S**: Propose the split — name each secondary goal. Append deferred goals to `{deferred_work_file}`. Rewrite the current spec to cover only the main goal — do not surgically carve sections out; regenerate the spec for the narrowed scope. Continue to checkpoint.
@ -26,7 +26,7 @@ deferred_work_file: '{implementation_artifacts}/deferred-work.md'
Present summary. If token count exceeded 1600 and user chose [K], include the token count and explain why it may be a problem. HALT and ask human: `[A] Approve` | `[E] Edit`
- **A**: Set status `ready-for-dev` in `{spec_file}`. Everything inside `<frozen-after-approval>` is now locked — only the human can change it. Display the finalized spec path to the user as a CWD-relative path (no leading `/`) so it is clickable in the terminal. → Step 3.
- **A**: Rename `{wipFile}` to `{spec_file}`, set status `ready-for-dev`. Everything inside `<frozen-after-approval>` is now locked — only the human can change it. Display the finalized spec path to the user as a CWD-relative path (no leading `/`) so it is clickable in the terminal. → Step 3.
- **E**: Apply changes, then return to CHECKPOINT 1.

View File

@ -1,5 +1,6 @@
---
deferred_work_file: '{implementation_artifacts}/deferred-work.md'
spec_file: '' # set by step-01 before entering this step
---
# Step One-Shot: Implement, Review, Present

View File

@ -70,6 +70,10 @@ Load and read full config from `{main_config}` and resolve:
YOU MUST ALWAYS SPEAK OUTPUT in your Agent communication style with the config `{communication_language}`.
### 2. First Step Execution
### 2. Paths
- `wipFile` = `{implementation_artifacts}/spec-wip.md`
### 3. First Step Execution
Read fully and follow: `./step-01-clarify-and-route.md` to begin the workflow.

View File

@ -0,0 +1,154 @@
/**
* install_to_bmad Flag Design Contract Tests
*
* Unit tests against the functions that implement the install_to_bmad flag.
* These nail down the 4 core design decisions:
*
* 1. true/omitted skill stays in _bmad/ (default behavior)
* 2. false skill removed from _bmad/ after IDE install
* 3. No platform no cleanup runs (cleanup lives in installVerbatimSkills)
* 4. Mixed flags each skill evaluated independently
*
* Usage: node test/test-install-to-bmad.js
*/
const path = require('node:path');
const os = require('node:os');
const fs = require('fs-extra');
const { loadSkillManifest, getInstallToBmad } = require('../tools/installer/ide/shared/skill-manifest');
// ANSI colors
const colors = {
reset: '\u001B[0m',
green: '\u001B[32m',
red: '\u001B[31m',
yellow: '\u001B[33m',
cyan: '\u001B[36m',
dim: '\u001B[2m',
};
let passed = 0;
let failed = 0;
function assert(condition, testName, errorMessage = '') {
if (condition) {
console.log(`${colors.green}${colors.reset} ${testName}`);
passed++;
} else {
console.log(`${colors.red}${colors.reset} ${testName}`);
if (errorMessage) {
console.log(` ${colors.dim}${errorMessage}${colors.reset}`);
}
failed++;
}
}
async function runTests() {
console.log(`${colors.cyan}========================================`);
console.log('install_to_bmad — Design Contract Tests');
console.log(`========================================${colors.reset}\n`);
// ============================================================
// 1. true/omitted → getInstallToBmad returns true (keep in _bmad/)
// ============================================================
console.log(`${colors.yellow}Design decision 1: true or omitted → skill stays in _bmad/${colors.reset}\n`);
// Null manifest (no bmad-skill-manifest.yaml) → true
assert(getInstallToBmad(null, 'workflow.md') === true, 'null manifest defaults to true');
// Single-entry, flag omitted → true
assert(
getInstallToBmad({ __single: { type: 'skill' } }, 'workflow.md') === true,
'single-entry manifest with flag omitted defaults to true',
);
// Single-entry, explicit true → true
assert(
getInstallToBmad({ __single: { type: 'skill', install_to_bmad: true } }, 'workflow.md') === true,
'single-entry manifest with explicit true returns true',
);
console.log('');
// ============================================================
// 2. false → getInstallToBmad returns false (remove from _bmad/)
// ============================================================
console.log(`${colors.yellow}Design decision 2: false → skill removed from _bmad/${colors.reset}\n`);
// Single-entry, explicit false → false
assert(
getInstallToBmad({ __single: { type: 'skill', install_to_bmad: false } }, 'workflow.md') === false,
'single-entry manifest with explicit false returns false',
);
// loadSkillManifest round-trip: YAML with false is preserved through load
{
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-itb-'));
await fs.writeFile(path.join(tmpDir, 'bmad-skill-manifest.yaml'), 'type: skill\ninstall_to_bmad: false\n');
const loaded = await loadSkillManifest(tmpDir);
assert(getInstallToBmad(loaded, 'workflow.md') === false, 'loadSkillManifest preserves install_to_bmad: false through round-trip');
await fs.remove(tmpDir);
}
console.log('');
// ============================================================
// 3. No platform → cleanup only runs inside installVerbatimSkills
// (This is a design invariant: getInstallToBmad is only consulted
// during IDE install. Without a platform, the flag has no effect.)
// ============================================================
console.log(`${colors.yellow}Design decision 3: flag is a per-skill property, not a pipeline gate${colors.reset}\n`);
// The flag value is stored but doesn't trigger any side effects by itself.
// Cleanup is driven by reading the CSV column inside installVerbatimSkills.
// We verify the flag is just data — getInstallToBmad doesn't touch the filesystem.
{
const manifest = { __single: { type: 'skill', install_to_bmad: false } };
const result = getInstallToBmad(manifest, 'workflow.md');
assert(typeof result === 'boolean', 'getInstallToBmad returns a boolean (pure data, no side effects)');
assert(result === false, 'false value is faithfully returned for consumer to act on');
}
console.log('');
// ============================================================
// 4. Mixed flags → each skill evaluated independently
// ============================================================
console.log(`${colors.yellow}Design decision 4: mixed flags — each skill independent${colors.reset}\n`);
// Multi-entry manifest: different files can have different flags
{
const manifest = {
'workflow.md': { type: 'skill', install_to_bmad: false },
'other.md': { type: 'skill', install_to_bmad: true },
};
assert(getInstallToBmad(manifest, 'workflow.md') === false, 'multi-entry: workflow.md with false returns false');
assert(getInstallToBmad(manifest, 'other.md') === true, 'multi-entry: other.md with true returns true');
assert(getInstallToBmad(manifest, 'unknown.md') === true, 'multi-entry: unknown file defaults to true');
}
console.log('');
// ============================================================
// Summary
// ============================================================
console.log(`${colors.cyan}========================================`);
console.log('Results:');
console.log(` Passed: ${colors.green}${passed}${colors.reset}`);
console.log(` Failed: ${colors.red}${failed}${colors.reset}`);
console.log(`========================================${colors.reset}\n`);
if (failed === 0) {
console.log(`${colors.green}All install_to_bmad contract tests passed!${colors.reset}\n`);
process.exit(0);
} else {
console.log(`${colors.red}Some install_to_bmad contract tests failed${colors.reset}\n`);
process.exit(1);
}
}
runTests().catch((error) => {
console.error(`${colors.red}Test runner failed:${colors.reset}`, error.message);
console.error(error.stack);
process.exit(1);
});

View File

@ -59,8 +59,8 @@ async function createTestBmadFixture() {
await fs.writeFile(
path.join(fixtureDir, '_config', 'skill-manifest.csv'),
[
'canonicalId,name,description,module,path',
'"bmad-master","bmad-master","Minimal test agent fixture","core","_bmad/core/bmad-master/SKILL.md"',
'canonicalId,name,description,module,path,install_to_bmad',
'"bmad-master","bmad-master","Minimal test agent fixture","core","_bmad/core/bmad-master/SKILL.md","true"',
'',
].join('\n'),
);
@ -103,8 +103,8 @@ async function createSkillCollisionFixture() {
await fs.writeFile(
path.join(configDir, 'skill-manifest.csv'),
[
'canonicalId,name,description,module,path',
'"bmad-help","bmad-help","Native help skill","core","_bmad/core/tasks/bmad-help/SKILL.md"',
'canonicalId,name,description,module,path,install_to_bmad',
'"bmad-help","bmad-help","Native help skill","core","_bmad/core/tasks/bmad-help/SKILL.md","true"',
'',
].join('\n'),
);
@ -1306,7 +1306,7 @@ async function runTests() {
const existingCsv27 = await fs.readFile(path.join(configDir27, 'skill-manifest.csv'), 'utf8');
await fs.writeFile(
path.join(configDir27, 'skill-manifest.csv'),
existingCsv27.trimEnd() + '\n"bmad-architect","bmad-architect","Architect","bmm","_bmad/bmm/agents/bmad-architect/SKILL.md"\n',
existingCsv27.trimEnd() + '\n"bmad-architect","bmad-architect","Architect","bmm","_bmad/bmm/agents/bmad-architect/SKILL.md","true"\n',
);
// Run Claude Code setup (which triggers cleanup then install)

View File

@ -132,10 +132,6 @@ class Installer {
await this._setupIdes(config, allModules, paths, addResult, previousSkillIds);
// Skills are now in IDE directories — remove redundant copies from _bmad/.
// Also cleans up skill dirs left by older installer versions.
await this._cleanupSkillDirs(paths.bmadDir);
const restoreResult = await this._restoreUserFiles(paths, updateState);
// Render consolidated summary
@ -417,33 +413,6 @@ class Installer {
}
}
/**
* Remove skill directories from _bmad/ after IDE installation.
* Skills are self-contained in IDE directories, so _bmad/ only needs
* module-level files (config.yaml, _config/, etc.).
* Also cleans up skill dirs left by older installer versions.
* @param {string} bmadDir - BMAD installation directory
*/
async _cleanupSkillDirs(bmadDir) {
const csv = require('csv-parse/sync');
const csvPath = path.join(bmadDir, '_config', 'skill-manifest.csv');
if (!(await fs.pathExists(csvPath))) return;
const csvContent = await fs.readFile(csvPath, 'utf8');
const records = csv.parse(csvContent, { columns: true, skip_empty_lines: true });
const bmadFolderName = path.basename(bmadDir);
const bmadPrefix = bmadFolderName + '/';
for (const record of records) {
if (!record.path) continue;
const relativePath = record.path.startsWith(bmadPrefix) ? record.path.slice(bmadPrefix.length) : record.path;
const sourceDir = path.dirname(path.join(bmadDir, relativePath));
if (await fs.pathExists(sourceDir)) {
await fs.remove(sourceDir);
}
}
}
/**
* Restore custom and modified files that were backed up before the update.
* No-op for fresh installs (updateState is null).

View File

@ -9,6 +9,7 @@ const {
loadSkillManifest: loadSkillManifestShared,
getCanonicalId: getCanonicalIdShared,
getArtifactType: getArtifactTypeShared,
getInstallToBmad: getInstallToBmadShared,
} = require('../ide/shared/skill-manifest');
// Load package.json for version info
@ -41,6 +42,11 @@ class ManifestGenerator {
return getArtifactTypeShared(manifest, filename);
}
/** Delegate to shared skill-manifest module */
getInstallToBmad(manifest, filename) {
return getInstallToBmadShared(manifest, filename);
}
/**
* Clean text for CSV output by normalizing whitespace.
* Note: Quote escaping is handled by escapeCsv() at write time.
@ -121,7 +127,7 @@ class ManifestGenerator {
* Recursively walk a module directory tree, collecting native SKILL.md entrypoints.
* A directory is discovered as a skill when it contains a SKILL.md file with
* valid name/description frontmatter (name must match directory name).
* Manifest YAML is loaded only when present for agent metadata.
* Manifest YAML is loaded only when present for install_to_bmad and agent metadata.
* Populates this.skills[] and this.skillClaimedDirs (Set of absolute paths).
*/
async collectSkills() {
@ -150,7 +156,7 @@ class ManifestGenerator {
const skillMeta = await this.parseSkillMd(skillMdPath, dir, dirName, debug);
if (skillMeta) {
// Load manifest when present (for agent metadata)
// Load manifest when present (for install_to_bmad and agent metadata)
const manifest = await this.loadSkillManifest(dir);
const artifactType = this.getArtifactType(manifest, skillFile);
@ -176,6 +182,7 @@ class ManifestGenerator {
module: moduleName,
path: installPath,
canonicalId,
install_to_bmad: this.getInstallToBmad(manifest, skillFile),
});
// Add to files list
@ -465,7 +472,7 @@ class ManifestGenerator {
const csvPath = path.join(cfgDir, 'skill-manifest.csv');
const escapeCsv = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`;
let csvContent = 'canonicalId,name,description,module,path\n';
let csvContent = 'canonicalId,name,description,module,path,install_to_bmad\n';
for (const skill of this.skills) {
const row = [
@ -474,6 +481,7 @@ class ManifestGenerator {
escapeCsv(skill.description),
escapeCsv(skill.module),
escapeCsv(skill.path),
escapeCsv(skill.install_to_bmad),
].join(',');
csvContent += row + '\n';
}

View File

@ -183,6 +183,18 @@ class ConfigDrivenIdeSetup {
count++;
}
// Post-install cleanup: remove _bmad/ directories for skills with install_to_bmad === "false"
for (const record of records) {
if (record.install_to_bmad === 'false') {
const relativePath = record.path.startsWith(bmadPrefix) ? record.path.slice(bmadPrefix.length) : record.path;
const sourceFile = path.join(bmadDir, relativePath);
const sourceDir = path.dirname(sourceFile);
if (await fs.pathExists(sourceDir)) {
await fs.remove(sourceDir);
}
}
}
return count;
}

View File

@ -54,4 +54,19 @@ function getArtifactType(manifest, filename) {
return null;
}
module.exports = { loadSkillManifest, getCanonicalId, getArtifactType };
/**
* Get the install_to_bmad flag for a specific file from a loaded skill manifest.
* @param {Object|null} manifest - Loaded manifest (from loadSkillManifest)
* @param {string} filename - Source filename to look up
* @returns {boolean} install_to_bmad value (defaults to true)
*/
function getInstallToBmad(manifest, filename) {
if (!manifest) return true;
// Single-entry manifest applies to all files in the directory
if (manifest.__single) return manifest.__single.install_to_bmad !== false;
// Multi-entry: look up by filename directly
if (manifest[filename]) return manifest[filename].install_to_bmad !== false;
return true;
}
module.exports = { loadSkillManifest, getCanonicalId, getArtifactType, getInstallToBmad };

View File

@ -156,15 +156,8 @@ function mapInstalledToSource(refPath) {
// Skip install-only paths (generated at install time, not in source)
if (isInstallOnly(cleaned)) return null;
// Map installed module names to their source directory names
// _bmad/core/ → src/core-skills/, _bmad/bmm/ → src/bmm-skills/
if (cleaned.startsWith('core/')) {
return path.join(SRC_DIR, 'core-skills', cleaned.slice('core/'.length));
}
if (cleaned.startsWith('bmm/')) {
return path.join(SRC_DIR, 'bmm-skills', cleaned.slice('bmm/'.length));
}
if (cleaned.startsWith('utility/')) {
// core/, bmm/, and utility/ are directly under src/
if (cleaned.startsWith('core/') || cleaned.startsWith('bmm/') || cleaned.startsWith('utility/')) {
return path.join(SRC_DIR, cleaned);
}