refactor(install): make edit-prd self-contained and remove install_to_bmad
Give bmad-edit-prd its own copy of prd-purpose.md and replace the cross-skill validation workflow reference with a skill invocation, so all three PRD skills are fully self-contained. With no remaining consumers, remove the install_to_bmad flag from manifests, CSV output, the post-install cleanup loop, and the dedicated test file.
This commit is contained in:
parent
3e590bb186
commit
5d01549ea2
|
|
@ -1,3 +0,0 @@
|
||||||
# Cross-referenced by bmad-edit-prd for prd-purpose.md and data files.
|
|
||||||
# Must remain in _bmad/ until those references are refactored.
|
|
||||||
install_to_bmad: true
|
|
||||||
|
|
@ -0,0 +1,197 @@
|
||||||
|
# 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.
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
# File references (ONLY variables used in this step)
|
# File references (ONLY variables used in this step)
|
||||||
prdPurpose: '{project-root}/_bmad/bmm/2-plan-workflows/bmad-create-prd/data/prd-purpose.md'
|
prdPurpose: '../data/prd-purpose.md'
|
||||||
---
|
---
|
||||||
|
|
||||||
# Step E-1: Discovery & Understanding
|
# Step E-1: Discovery & Understanding
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
# File references (ONLY variables used in this step)
|
# File references (ONLY variables used in this step)
|
||||||
prdFile: '{prd_file_path}'
|
prdFile: '{prd_file_path}'
|
||||||
prdPurpose: '{project-root}/_bmad/bmm/2-plan-workflows/bmad-create-prd/data/prd-purpose.md'
|
prdPurpose: '../data/prd-purpose.md'
|
||||||
---
|
---
|
||||||
|
|
||||||
# Step E-1B: Legacy PRD Conversion Assessment
|
# Step E-1B: Legacy PRD Conversion Assessment
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# File references (ONLY variables used in this step)
|
# File references (ONLY variables used in this step)
|
||||||
prdFile: '{prd_file_path}'
|
prdFile: '{prd_file_path}'
|
||||||
validationReport: '{validation_report_path}' # If provided
|
validationReport: '{validation_report_path}' # If provided
|
||||||
prdPurpose: '{project-root}/_bmad/bmm/2-plan-workflows/bmad-create-prd/data/prd-purpose.md'
|
prdPurpose: '../data/prd-purpose.md'
|
||||||
---
|
---
|
||||||
|
|
||||||
# Step E-2: Deep Review & Analysis
|
# Step E-2: Deep Review & Analysis
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
# File references (ONLY variables used in this step)
|
# File references (ONLY variables used in this step)
|
||||||
prdFile: '{prd_file_path}'
|
prdFile: '{prd_file_path}'
|
||||||
prdPurpose: '{project-root}/_bmad/bmm/2-plan-workflows/bmad-create-prd/data/prd-purpose.md'
|
prdPurpose: '../data/prd-purpose.md'
|
||||||
---
|
---
|
||||||
|
|
||||||
# Step E-3: Edit & Update
|
# Step E-3: Edit & Update
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
---
|
---
|
||||||
# File references (ONLY variables used in this step)
|
# File references (ONLY variables used in this step)
|
||||||
prdFile: '{prd_file_path}'
|
prdFile: '{prd_file_path}'
|
||||||
validationWorkflow: '{project-root}/_bmad/bmm/2-plan-workflows/bmad-validate-prd/steps-v/step-v-01-discovery.md'
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Step E-4: Complete & Validate
|
# Step E-4: Complete & Validate
|
||||||
|
|
@ -117,8 +116,7 @@ Display:
|
||||||
- Display: "This will run all 13 validation checks on the updated PRD."
|
- Display: "This will run all 13 validation checks on the updated PRD."
|
||||||
- Display: "Preparing to validate: {prd_file_path}"
|
- Display: "Preparing to validate: {prd_file_path}"
|
||||||
- Display: "**Proceeding to validation...**"
|
- Display: "**Proceeding to validation...**"
|
||||||
- Read fully and follow: {validationWorkflow} (steps-v/step-v-01-discovery.md)
|
- Invoke the `bmad-validate-prd` skill to run the complete validation workflow
|
||||||
- Note: This hands off to the validation workflow which will run its complete 13-step process
|
|
||||||
|
|
||||||
- **IF E (Edit More):**
|
- **IF E (Edit More):**
|
||||||
- Display: "**Additional Edits**"
|
- Display: "**Additional Edits**"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
# Cross-referenced by bmad-edit-prd for validation workflow steps.
|
|
||||||
# Must remain in _bmad/ until those references are refactored.
|
|
||||||
install_to_bmad: true
|
|
||||||
|
|
@ -1,157 +0,0 @@
|
||||||
/**
|
|
||||||
* 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. omitted → skill removed from _bmad/ (default behavior)
|
|
||||||
* 2. true → skill stays in _bmad/ (explicit opt-in)
|
|
||||||
* 2b. 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. omitted → getInstallToBmad returns false (remove from _bmad/)
|
|
||||||
// Skills are self-contained in IDE directories, so the default
|
|
||||||
// is to clean up from _bmad/ after IDE install.
|
|
||||||
// ============================================================
|
|
||||||
console.log(`${colors.yellow}Design decision 1: omitted → skill removed from _bmad/ (default)${colors.reset}\n`);
|
|
||||||
|
|
||||||
// Null manifest (no bmad-skill-manifest.yaml) → false
|
|
||||||
assert(getInstallToBmad(null, 'workflow.md') === false, 'null manifest defaults to false');
|
|
||||||
|
|
||||||
// Single-entry, flag omitted → false
|
|
||||||
assert(
|
|
||||||
getInstallToBmad({ __single: { type: 'skill' } }, 'workflow.md') === false,
|
|
||||||
'single-entry manifest with flag omitted defaults to false',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Single-entry, explicit true → true (opt-in to keep in _bmad/)
|
|
||||||
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') === false, 'multi-entry: unknown file defaults to false');
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
@ -59,8 +59,8 @@ async function createTestBmadFixture() {
|
||||||
await fs.writeFile(
|
await fs.writeFile(
|
||||||
path.join(fixtureDir, '_config', 'skill-manifest.csv'),
|
path.join(fixtureDir, '_config', 'skill-manifest.csv'),
|
||||||
[
|
[
|
||||||
'canonicalId,name,description,module,path,install_to_bmad',
|
'canonicalId,name,description,module,path',
|
||||||
'"bmad-master","bmad-master","Minimal test agent fixture","core","_bmad/core/bmad-master/SKILL.md","true"',
|
'"bmad-master","bmad-master","Minimal test agent fixture","core","_bmad/core/bmad-master/SKILL.md"',
|
||||||
'',
|
'',
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
);
|
);
|
||||||
|
|
@ -103,8 +103,8 @@ async function createSkillCollisionFixture() {
|
||||||
await fs.writeFile(
|
await fs.writeFile(
|
||||||
path.join(configDir, 'skill-manifest.csv'),
|
path.join(configDir, 'skill-manifest.csv'),
|
||||||
[
|
[
|
||||||
'canonicalId,name,description,module,path,install_to_bmad',
|
'canonicalId,name,description,module,path',
|
||||||
'"bmad-help","bmad-help","Native help skill","core","_bmad/core/tasks/bmad-help/SKILL.md","true"',
|
'"bmad-help","bmad-help","Native help skill","core","_bmad/core/tasks/bmad-help/SKILL.md"',
|
||||||
'',
|
'',
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ const {
|
||||||
loadSkillManifest: loadSkillManifestShared,
|
loadSkillManifest: loadSkillManifestShared,
|
||||||
getCanonicalId: getCanonicalIdShared,
|
getCanonicalId: getCanonicalIdShared,
|
||||||
getArtifactType: getArtifactTypeShared,
|
getArtifactType: getArtifactTypeShared,
|
||||||
getInstallToBmad: getInstallToBmadShared,
|
|
||||||
} = require('../ide/shared/skill-manifest');
|
} = require('../ide/shared/skill-manifest');
|
||||||
|
|
||||||
// Load package.json for version info
|
// Load package.json for version info
|
||||||
|
|
@ -42,11 +41,6 @@ class ManifestGenerator {
|
||||||
return getArtifactTypeShared(manifest, filename);
|
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.
|
* Clean text for CSV output by normalizing whitespace.
|
||||||
* Note: Quote escaping is handled by escapeCsv() at write time.
|
* Note: Quote escaping is handled by escapeCsv() at write time.
|
||||||
|
|
@ -127,7 +121,7 @@ class ManifestGenerator {
|
||||||
* Recursively walk a module directory tree, collecting native SKILL.md entrypoints.
|
* 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
|
* A directory is discovered as a skill when it contains a SKILL.md file with
|
||||||
* valid name/description frontmatter (name must match directory name).
|
* valid name/description frontmatter (name must match directory name).
|
||||||
* Manifest YAML is loaded only when present — for install_to_bmad and agent metadata.
|
* Manifest YAML is loaded only when present — for agent metadata.
|
||||||
* Populates this.skills[] and this.skillClaimedDirs (Set of absolute paths).
|
* Populates this.skills[] and this.skillClaimedDirs (Set of absolute paths).
|
||||||
*/
|
*/
|
||||||
async collectSkills() {
|
async collectSkills() {
|
||||||
|
|
@ -156,7 +150,7 @@ class ManifestGenerator {
|
||||||
const skillMeta = await this.parseSkillMd(skillMdPath, dir, dirName, debug);
|
const skillMeta = await this.parseSkillMd(skillMdPath, dir, dirName, debug);
|
||||||
|
|
||||||
if (skillMeta) {
|
if (skillMeta) {
|
||||||
// Load manifest when present (for install_to_bmad and agent metadata)
|
// Load manifest when present (for agent metadata)
|
||||||
const manifest = await this.loadSkillManifest(dir);
|
const manifest = await this.loadSkillManifest(dir);
|
||||||
const artifactType = this.getArtifactType(manifest, skillFile);
|
const artifactType = this.getArtifactType(manifest, skillFile);
|
||||||
|
|
||||||
|
|
@ -182,7 +176,6 @@ class ManifestGenerator {
|
||||||
module: moduleName,
|
module: moduleName,
|
||||||
path: installPath,
|
path: installPath,
|
||||||
canonicalId,
|
canonicalId,
|
||||||
install_to_bmad: this.getInstallToBmad(manifest, skillFile),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add to files list
|
// Add to files list
|
||||||
|
|
@ -472,7 +465,7 @@ class ManifestGenerator {
|
||||||
const csvPath = path.join(cfgDir, 'skill-manifest.csv');
|
const csvPath = path.join(cfgDir, 'skill-manifest.csv');
|
||||||
const escapeCsv = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`;
|
const escapeCsv = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`;
|
||||||
|
|
||||||
let csvContent = 'canonicalId,name,description,module,path,install_to_bmad\n';
|
let csvContent = 'canonicalId,name,description,module,path\n';
|
||||||
|
|
||||||
for (const skill of this.skills) {
|
for (const skill of this.skills) {
|
||||||
const row = [
|
const row = [
|
||||||
|
|
@ -481,7 +474,6 @@ class ManifestGenerator {
|
||||||
escapeCsv(skill.description),
|
escapeCsv(skill.description),
|
||||||
escapeCsv(skill.module),
|
escapeCsv(skill.module),
|
||||||
escapeCsv(skill.path),
|
escapeCsv(skill.path),
|
||||||
escapeCsv(skill.install_to_bmad),
|
|
||||||
].join(',');
|
].join(',');
|
||||||
csvContent += row + '\n';
|
csvContent += row + '\n';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -183,18 +183,6 @@ class ConfigDrivenIdeSetup {
|
||||||
count++;
|
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;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,22 +54,4 @@ function getArtifactType(manifest, filename) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
module.exports = { loadSkillManifest, getCanonicalId, getArtifactType };
|
||||||
* Get the install_to_bmad flag for a specific file from a loaded skill manifest.
|
|
||||||
* Skills are self-contained in their IDE skill directories (.claude/skills/, etc.),
|
|
||||||
* so the default is false — skill content is removed from _bmad/ after IDE install.
|
|
||||||
* Set install_to_bmad: true in bmad-skill-manifest.yaml to opt a skill back in.
|
|
||||||
* @param {Object|null} manifest - Loaded manifest (from loadSkillManifest)
|
|
||||||
* @param {string} filename - Source filename to look up
|
|
||||||
* @returns {boolean} install_to_bmad value (defaults to false)
|
|
||||||
*/
|
|
||||||
function getInstallToBmad(manifest, filename) {
|
|
||||||
if (!manifest) return false;
|
|
||||||
// Single-entry manifest applies to all files in the directory
|
|
||||||
if (manifest.__single) return manifest.__single.install_to_bmad === true;
|
|
||||||
// Multi-entry: look up by filename directly
|
|
||||||
if (manifest[filename]) return manifest[filename].install_to_bmad === true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { loadSkillManifest, getCanonicalId, getArtifactType, getInstallToBmad };
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue