Compare commits
18 Commits
4637b65527
...
610aca0641
| Author | SHA1 | Date |
|---|---|---|
|
|
610aca0641 | |
|
|
888b960793 | |
|
|
bce48d8139 | |
|
|
32c2724561 | |
|
|
2771dd76b8 | |
|
|
a12d5d03b5 | |
|
|
75ec4aa504 | |
|
|
7b4875be79 | |
|
|
c57506464f | |
|
|
861716fbe3 | |
|
|
0ba809c3e8 | |
|
|
42aa184074 | |
|
|
5a5ade333a | |
|
|
7f7ce8c5e3 | |
|
|
5e1149dc14 | |
|
|
df9a7f9b67 | |
|
|
9cd362d2d8 | |
|
|
e64cef80b6 |
|
|
@ -37,7 +37,7 @@ permissions:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
if: github.event_name != 'workflow_dispatch' || github.ref == 'refs/heads/main'
|
if: github.repository == 'bmad-code-org/BMAD-METHOD' && (github.event_name != 'workflow_dispatch' || github.ref == 'refs/heads/main')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ name: Quality & Validation
|
||||||
# - Schema validation (YAML structure)
|
# - Schema validation (YAML structure)
|
||||||
# - Agent schema tests (fixture-based validation)
|
# - Agent schema tests (fixture-based validation)
|
||||||
# - Installation component tests (compilation)
|
# - Installation component tests (compilation)
|
||||||
|
# - fs wrapper tests (native fs replacement)
|
||||||
# - Bundle validation (web bundle integrity)
|
# - Bundle validation (web bundle integrity)
|
||||||
|
|
||||||
"on":
|
"on":
|
||||||
|
|
@ -112,5 +113,8 @@ jobs:
|
||||||
- name: Test agent compilation components
|
- name: Test agent compilation components
|
||||||
run: npm run test:install
|
run: npm run test:install
|
||||||
|
|
||||||
|
- name: Test fs wrapper
|
||||||
|
run: npm run test:fs
|
||||||
|
|
||||||
- name: Validate file references
|
- name: Validate file references
|
||||||
run: npm run validate:refs
|
run: npm run validate:refs
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Development & Testing
|
||||||
|
test/
|
||||||
|
.husky/
|
||||||
|
.github/
|
||||||
|
.vscode/
|
||||||
|
.augment/
|
||||||
|
coverage/
|
||||||
|
test-output/
|
||||||
|
|
||||||
|
# Documentation site (users access docs online)
|
||||||
|
docs/
|
||||||
|
website/
|
||||||
|
|
||||||
|
# Configuration files (development only)
|
||||||
|
.coderabbit.yaml
|
||||||
|
.markdownlint-cli2.yaml
|
||||||
|
.prettierignore
|
||||||
|
.nvmrc
|
||||||
|
eslint.config.mjs
|
||||||
|
prettier.config.mjs
|
||||||
|
|
||||||
|
# Build tools (not needed at runtime)
|
||||||
|
tools/build-docs.mjs
|
||||||
|
tools/fix-doc-links.js
|
||||||
|
tools/validate-doc-links.js
|
||||||
|
tools/validate-file-refs.js
|
||||||
|
tools/validate-agent-schema.js
|
||||||
|
|
||||||
|
# Images (branding/marketing only)
|
||||||
|
banner-bmad-method.png
|
||||||
|
Wordmark.png
|
||||||
|
|
||||||
|
# Repository metadata
|
||||||
|
CONTRIBUTING.md
|
||||||
|
CONTRIBUTORS.md
|
||||||
|
SECURITY.md
|
||||||
|
TRADEMARK.md
|
||||||
|
CHANGELOG.md
|
||||||
|
CNAME
|
||||||
|
CODE_OF_CONDUCT.md
|
||||||
|
|
@ -26,3 +26,33 @@ This page lists the default BMM (Agile suite) agents that install with BMad Meth
|
||||||
| Quick Flow Solo Dev (Barry) | `bmad-master` | `QS`, `QD`, `CR` | Quick Spec, Quick Dev, Code Review |
|
| Quick Flow Solo Dev (Barry) | `bmad-master` | `QS`, `QD`, `CR` | Quick Spec, Quick Dev, Code Review |
|
||||||
| UX Designer (Sally) | `bmad-ux-designer` | `CU` | Create UX Design |
|
| UX Designer (Sally) | `bmad-ux-designer` | `CU` | Create UX Design |
|
||||||
| Technical Writer (Paige) | `bmad-tech-writer` | `DP`, `WD`, `US`, `MG`, `VD`, `EC` | Document Project, Write Document, Update Standards, Mermaid Generate, Validate Doc, Explain Concept |
|
| Technical Writer (Paige) | `bmad-tech-writer` | `DP`, `WD`, `US`, `MG`, `VD`, `EC` | Document Project, Write Document, Update Standards, Mermaid Generate, Validate Doc, Explain Concept |
|
||||||
|
|
||||||
|
## Trigger Types
|
||||||
|
|
||||||
|
Agent menu triggers use two different invocation types. Knowing which type a trigger uses helps you provide the right input.
|
||||||
|
|
||||||
|
### Workflow triggers (no arguments needed)
|
||||||
|
|
||||||
|
Most triggers load a structured workflow file. Type the trigger code and the agent starts the workflow, prompting you for input at each step.
|
||||||
|
|
||||||
|
Examples: `CP` (Create PRD), `DS` (Dev Story), `CA` (Create Architecture), `QS` (Quick Spec)
|
||||||
|
|
||||||
|
### Conversational triggers (arguments required)
|
||||||
|
|
||||||
|
Some triggers start a free-form conversation instead of a structured workflow. These expect you to describe what you need alongside the trigger code.
|
||||||
|
|
||||||
|
| Agent | Trigger | What to provide |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Technical Writer (Paige) | `WD` | Description of the document to write |
|
||||||
|
| Technical Writer (Paige) | `US` | Preferences or conventions to add to standards |
|
||||||
|
| Technical Writer (Paige) | `MG` | Diagram description and type (sequence, flowchart, etc.) |
|
||||||
|
| Technical Writer (Paige) | `VD` | Document to validate and focus areas |
|
||||||
|
| Technical Writer (Paige) | `EC` | Concept name to explain |
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```text
|
||||||
|
WD Write a deployment guide for our Docker setup
|
||||||
|
MG Create a sequence diagram showing the auth flow
|
||||||
|
EC Explain how the module system works
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -95,11 +95,11 @@ TEA also supports P0-P3 risk-based prioritization and optional integrations with
|
||||||
|
|
||||||
## How Testing Fits into Workflows
|
## How Testing Fits into Workflows
|
||||||
|
|
||||||
Quinn's Automate workflow appears in Phase 4 (Implementation) of the BMad Method workflow map. A typical sequence:
|
Quinn's Automate workflow appears in Phase 4 (Implementation) of the BMad Method workflow map. It is designed to run **after a full epic is complete** — once all stories in an epic have been implemented and code-reviewed. A typical sequence:
|
||||||
|
|
||||||
1. Implement a story with the Dev workflow (`DS`)
|
1. For each story in the epic: implement with Dev (`DS`), then validate with Code Review (`CR`)
|
||||||
2. Generate tests with Quinn (`QA`) or TEA's Automate workflow
|
2. After the epic is complete: generate tests with Quinn (`QA`) or TEA's Automate workflow
|
||||||
3. Validate implementation with Code Review (`CR`)
|
3. Run retrospective (`bmad-retrospective`) to capture lessons learned
|
||||||
|
|
||||||
Quinn works directly from source code without loading planning documents (PRD, architecture). TEA workflows can integrate with upstream planning artifacts for traceability.
|
Quinn works directly from source code without loading planning documents (PRD, architecture). TEA workflows can integrate with upstream planning artifacts for traceability.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
"commander": "^14.0.0",
|
"commander": "^14.0.0",
|
||||||
"csv-parse": "^6.1.0",
|
"csv-parse": "^6.1.0",
|
||||||
"fs-extra": "^11.3.0",
|
|
||||||
"glob": "^11.0.3",
|
"glob": "^11.0.3",
|
||||||
"ignore": "^7.0.5",
|
"ignore": "^7.0.5",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
|
|
@ -6996,20 +6995,6 @@
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fs-extra": {
|
|
||||||
"version": "11.3.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz",
|
|
||||||
"integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"graceful-fs": "^4.2.0",
|
|
||||||
"jsonfile": "^6.0.1",
|
|
||||||
"universalify": "^2.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/fs.realpath": {
|
"node_modules/fs.realpath": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||||
|
|
@ -7227,6 +7212,7 @@
|
||||||
"version": "4.2.11",
|
"version": "4.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/h3": {
|
"node_modules/h3": {
|
||||||
|
|
@ -9066,18 +9052,6 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/jsonfile": {
|
|
||||||
"version": "6.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
|
|
||||||
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"universalify": "^2.0.0"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"graceful-fs": "^4.1.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/katex": {
|
"node_modules/katex": {
|
||||||
"version": "0.16.28",
|
"version": "0.16.28",
|
||||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.28.tgz",
|
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.28.tgz",
|
||||||
|
|
@ -13607,15 +13581,6 @@
|
||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/universalify": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/unrs-resolver": {
|
"node_modules/unrs-resolver": {
|
||||||
"version": "1.11.1",
|
"version": "1.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,9 @@
|
||||||
"lint:md": "markdownlint-cli2 \"**/*.md\"",
|
"lint:md": "markdownlint-cli2 \"**/*.md\"",
|
||||||
"prepare": "command -v husky >/dev/null 2>&1 && husky || exit 0",
|
"prepare": "command -v husky >/dev/null 2>&1 && husky || exit 0",
|
||||||
"rebundle": "node tools/cli/bundlers/bundle-web.js rebundle",
|
"rebundle": "node tools/cli/bundlers/bundle-web.js rebundle",
|
||||||
"test": "npm run test:schemas && npm run test:refs && npm run test:install && npm run validate:schemas && npm run lint && npm run lint:md && npm run format:check",
|
"test": "npm run test:schemas && npm run test:refs && npm run test:install && npm run test:fs && npm run validate:schemas && npm run lint && npm run lint:md && npm run format:check",
|
||||||
"test:coverage": "c8 --reporter=text --reporter=html npm run test:schemas",
|
"test:coverage": "c8 --reporter=text --reporter=html npm run test:schemas",
|
||||||
|
"test:fs": "node test/test-fs-wrapper.js",
|
||||||
"test:install": "node test/test-installation-components.js",
|
"test:install": "node test/test-installation-components.js",
|
||||||
"test:refs": "node test/test-file-refs-csv.js",
|
"test:refs": "node test/test-file-refs-csv.js",
|
||||||
"test:schemas": "node test/test-agent-schema.js",
|
"test:schemas": "node test/test-agent-schema.js",
|
||||||
|
|
@ -71,7 +72,6 @@
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
"commander": "^14.0.0",
|
"commander": "^14.0.0",
|
||||||
"csv-parse": "^6.1.0",
|
"csv-parse": "^6.1.0",
|
||||||
"fs-extra": "^11.3.0",
|
|
||||||
"glob": "^11.0.3",
|
"glob": "^11.0.3",
|
||||||
"ignore": "^7.0.5",
|
"ignore": "^7.0.5",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ outputFile: '{planning_artifacts}/product-brief-{{project_name}}-{{date}}.md'
|
||||||
|
|
||||||
# Task References
|
# Task References
|
||||||
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
||||||
partyModeWorkflow: '{project-root}/_bmad/core/workflows/party-mode/workflow.md'
|
partyModeWorkflow: '{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md'
|
||||||
---
|
---
|
||||||
|
|
||||||
# Step 2: Product Vision Discovery
|
# Step 2: Product Vision Discovery
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ outputFile: '{planning_artifacts}/product-brief-{{project_name}}-{{date}}.md'
|
||||||
|
|
||||||
# Task References
|
# Task References
|
||||||
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
||||||
partyModeWorkflow: '{project-root}/_bmad/core/workflows/party-mode/workflow.md'
|
partyModeWorkflow: '{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md'
|
||||||
---
|
---
|
||||||
|
|
||||||
# Step 3: Target Users Discovery
|
# Step 3: Target Users Discovery
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ outputFile: '{planning_artifacts}/product-brief-{{project_name}}-{{date}}.md'
|
||||||
|
|
||||||
# Task References
|
# Task References
|
||||||
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
||||||
partyModeWorkflow: '{project-root}/_bmad/core/workflows/party-mode/workflow.md'
|
partyModeWorkflow: '{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md'
|
||||||
---
|
---
|
||||||
|
|
||||||
# Step 4: Success Metrics Definition
|
# Step 4: Success Metrics Definition
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ outputFile: '{planning_artifacts}/product-brief-{{project_name}}-{{date}}.md'
|
||||||
|
|
||||||
# Task References
|
# Task References
|
||||||
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
||||||
partyModeWorkflow: '{project-root}/_bmad/core/workflows/party-mode/workflow.md'
|
partyModeWorkflow: '{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md'
|
||||||
---
|
---
|
||||||
|
|
||||||
# Step 5: MVP Scope Definition
|
# Step 5: MVP Scope Definition
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ domainComplexityCSV: '../data/domain-complexity.csv'
|
||||||
|
|
||||||
# Task References
|
# Task References
|
||||||
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
||||||
partyModeWorkflow: '{project-root}/_bmad/core/workflows/party-mode/workflow.md'
|
partyModeWorkflow: '{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md'
|
||||||
---
|
---
|
||||||
|
|
||||||
# Step 2: Project Discovery
|
# Step 2: Project Discovery
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ outputFile: '{planning_artifacts}/prd.md'
|
||||||
|
|
||||||
# Task References
|
# Task References
|
||||||
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
||||||
partyModeWorkflow: '{project-root}/_bmad/core/workflows/party-mode/workflow.md'
|
partyModeWorkflow: '{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md'
|
||||||
---
|
---
|
||||||
|
|
||||||
# Step 2b: Product Vision Discovery
|
# Step 2b: Product Vision Discovery
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ outputFile: '{planning_artifacts}/prd.md'
|
||||||
|
|
||||||
# Task References
|
# Task References
|
||||||
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
||||||
partyModeWorkflow: '{project-root}/_bmad/core/workflows/party-mode/workflow.md'
|
partyModeWorkflow: '{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md'
|
||||||
---
|
---
|
||||||
|
|
||||||
# Step 2c: Executive Summary Generation
|
# Step 2c: Executive Summary Generation
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ outputFile: '{planning_artifacts}/prd.md'
|
||||||
|
|
||||||
# Task References
|
# Task References
|
||||||
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
||||||
partyModeWorkflow: '{project-root}/_bmad/core/workflows/party-mode/workflow.md'
|
partyModeWorkflow: '{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md'
|
||||||
---
|
---
|
||||||
|
|
||||||
# Step 3: Success Criteria Definition
|
# Step 3: Success Criteria Definition
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ outputFile: '{planning_artifacts}/prd.md'
|
||||||
|
|
||||||
# Task References
|
# Task References
|
||||||
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
||||||
partyModeWorkflow: '{project-root}/_bmad/core/workflows/party-mode/workflow.md'
|
partyModeWorkflow: '{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md'
|
||||||
---
|
---
|
||||||
|
|
||||||
# Step 4: User Journey Mapping
|
# Step 4: User Journey Mapping
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ domainComplexityCSV: '{project-root}/_bmad/bmm/workflows/2-plan-workflows/create
|
||||||
|
|
||||||
# Task References
|
# Task References
|
||||||
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
||||||
partyModeWorkflow: '{project-root}/_bmad/core/workflows/party-mode/workflow.md'
|
partyModeWorkflow: '{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md'
|
||||||
---
|
---
|
||||||
|
|
||||||
# Step 5: Domain-Specific Requirements (Optional)
|
# Step 5: Domain-Specific Requirements (Optional)
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ projectTypesCSV: '{project-root}/_bmad/bmm/workflows/2-plan-workflows/create-prd
|
||||||
|
|
||||||
# Task References
|
# Task References
|
||||||
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
||||||
partyModeWorkflow: '{project-root}/_bmad/core/workflows/party-mode/workflow.md'
|
partyModeWorkflow: '{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md'
|
||||||
---
|
---
|
||||||
|
|
||||||
# Step 6: Innovation Discovery
|
# Step 6: Innovation Discovery
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ projectTypesCSV: '../data/project-types.csv'
|
||||||
|
|
||||||
# Task References
|
# Task References
|
||||||
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
||||||
partyModeWorkflow: '{project-root}/_bmad/core/workflows/party-mode/workflow.md'
|
partyModeWorkflow: '{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md'
|
||||||
---
|
---
|
||||||
|
|
||||||
# Step 7: Project-Type Deep Dive
|
# Step 7: Project-Type Deep Dive
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ outputFile: '{planning_artifacts}/prd.md'
|
||||||
|
|
||||||
# Task References
|
# Task References
|
||||||
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
||||||
partyModeWorkflow: '{project-root}/_bmad/core/workflows/party-mode/workflow.md'
|
partyModeWorkflow: '{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md'
|
||||||
---
|
---
|
||||||
|
|
||||||
# Step 8: Scoping Exercise - MVP & Future Features
|
# Step 8: Scoping Exercise - MVP & Future Features
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ outputFile: '{planning_artifacts}/prd.md'
|
||||||
|
|
||||||
# Task References
|
# Task References
|
||||||
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
||||||
partyModeWorkflow: '{project-root}/_bmad/core/workflows/party-mode/workflow.md'
|
partyModeWorkflow: '{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md'
|
||||||
---
|
---
|
||||||
|
|
||||||
# Step 9: Functional Requirements Synthesis
|
# Step 9: Functional Requirements Synthesis
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ outputFile: '{planning_artifacts}/prd.md'
|
||||||
|
|
||||||
# Task References
|
# Task References
|
||||||
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
||||||
partyModeWorkflow: '{project-root}/_bmad/core/workflows/party-mode/workflow.md'
|
partyModeWorkflow: '{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md'
|
||||||
---
|
---
|
||||||
|
|
||||||
# Step 10: Non-Functional Requirements
|
# Step 10: Non-Functional Requirements
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ purposeFile: '{project-root}/_bmad/bmm/workflows/2-plan-workflows/create-prd/dat
|
||||||
|
|
||||||
# Task References
|
# Task References
|
||||||
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
||||||
partyModeWorkflow: '{project-root}/_bmad/core/workflows/party-mode/workflow.md'
|
partyModeWorkflow: '{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md'
|
||||||
---
|
---
|
||||||
|
|
||||||
# Step 11: Document Polish
|
# Step 11: Document Polish
|
||||||
|
|
@ -99,6 +99,22 @@ Review the entire document with PRD purpose principles in mind:
|
||||||
- Are technical terms used appropriately?
|
- Are technical terms used appropriately?
|
||||||
- Would stakeholders find this easy to understand?
|
- Would stakeholders find this easy to understand?
|
||||||
|
|
||||||
|
### 2b. Brainstorming Reconciliation (if brainstorming input exists)
|
||||||
|
|
||||||
|
**Check the PRD frontmatter `inputDocuments` for any brainstorming document** (e.g., `brainstorming-session*.md`, `brainstorming-report.md`). If a brainstorming document was used as input:
|
||||||
|
|
||||||
|
1. **Load the brainstorming document** and extract all distinct ideas, themes, and recommendations
|
||||||
|
2. **Cross-reference against the PRD** — for each brainstorming idea, check if it landed in any PRD section (requirements, success criteria, user journeys, scope, etc.)
|
||||||
|
3. **Identify dropped ideas** — ideas from brainstorming that do not appear anywhere in the PRD. Pay special attention to:
|
||||||
|
- Tone, personality, and interaction design ideas (these are most commonly lost)
|
||||||
|
- Design philosophy and coaching approach ideas
|
||||||
|
- "What should this feel like" ideas (UX feel, not just UX function)
|
||||||
|
- Qualitative/soft ideas that don't map cleanly to functional requirements
|
||||||
|
4. **Present findings to user**: "These brainstorming ideas did not make it into the PRD: [list]. Should any be incorporated?"
|
||||||
|
5. **If user wants to incorporate dropped ideas**: Add them to the most appropriate PRD section (success criteria, non-functional requirements, or a new section if needed)
|
||||||
|
|
||||||
|
**Why this matters**: Brainstorming documents are often long, and the PRD's structured template has an implicit bias toward concrete/structural ideas. Soft ideas (tone, philosophy, interaction feel) frequently get silently dropped because they don't map cleanly to FR/NFR format.
|
||||||
|
|
||||||
### 3. Optimization Actions
|
### 3. Optimization Actions
|
||||||
|
|
||||||
Make targeted improvements:
|
Make targeted improvements:
|
||||||
|
|
@ -193,6 +209,7 @@ When user selects 'C', replace the entire document content with the polished ver
|
||||||
✅ User's voice and intent preserved
|
✅ User's voice and intent preserved
|
||||||
✅ Document is more readable and professional
|
✅ Document is more readable and professional
|
||||||
✅ A/P/C menu presented and handled correctly
|
✅ A/P/C menu presented and handled correctly
|
||||||
|
✅ Brainstorming reconciliation completed (if brainstorming input exists)
|
||||||
✅ Polished document saved when C selected
|
✅ Polished document saved when C selected
|
||||||
|
|
||||||
## FAILURE MODES:
|
## FAILURE MODES:
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ description: 'Discovery & Understanding - Understand what user wants to edit and
|
||||||
altStepFile: './step-e-01b-legacy-conversion.md'
|
altStepFile: './step-e-01b-legacy-conversion.md'
|
||||||
prdPurpose: '{project-root}/_bmad/bmm/workflows/2-plan-workflows/create-prd/data/prd-purpose.md'
|
prdPurpose: '{project-root}/_bmad/bmm/workflows/2-plan-workflows/create-prd/data/prd-purpose.md'
|
||||||
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
||||||
partyModeWorkflow: '{project-root}/_bmad/core/workflows/party-mode/workflow.md'
|
partyModeWorkflow: '{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md'
|
||||||
---
|
---
|
||||||
|
|
||||||
# Step E-1: Discovery & Understanding
|
# Step E-1: Discovery & Understanding
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ description: 'Document Discovery & Confirmation - Handle fresh context validatio
|
||||||
# File references (ONLY variables used in this step)
|
# File references (ONLY variables used in this step)
|
||||||
nextStepFile: './step-v-02-format-detection.md'
|
nextStepFile: './step-v-02-format-detection.md'
|
||||||
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
||||||
partyModeWorkflow: '{project-root}/_bmad/core/workflows/party-mode/workflow.md'
|
partyModeWorkflow: '{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md'
|
||||||
prdPurpose: '../data/prd-purpose.md'
|
prdPurpose: '../data/prd-purpose.md'
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ This step will generate content and present choices:
|
||||||
## PROTOCOL INTEGRATION:
|
## PROTOCOL INTEGRATION:
|
||||||
|
|
||||||
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
||||||
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md
|
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md
|
||||||
- PROTOCOLS always return to this step's A/P/C menu
|
- PROTOCOLS always return to this step's A/P/C menu
|
||||||
- User accepts/rejects protocol changes before proceeding
|
- User accepts/rejects protocol changes before proceeding
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ This step will generate content and present choices:
|
||||||
## PROTOCOL INTEGRATION:
|
## PROTOCOL INTEGRATION:
|
||||||
|
|
||||||
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
||||||
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md
|
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md
|
||||||
- PROTOCOLS always return to this step's A/P/C menu
|
- PROTOCOLS always return to this step's A/P/C menu
|
||||||
- User accepts/rejects protocol changes before proceeding
|
- User accepts/rejects protocol changes before proceeding
|
||||||
|
|
||||||
|
|
@ -169,7 +169,7 @@ Show the generated core experience content and present choices:
|
||||||
|
|
||||||
#### If 'P' (Party Mode):
|
#### If 'P' (Party Mode):
|
||||||
|
|
||||||
- Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md with the current core experience definition
|
- Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md with the current core experience definition
|
||||||
- Process the collaborative experience improvements that come back
|
- Process the collaborative experience improvements that come back
|
||||||
- Ask user: "Accept these changes to the core experience definition? (y/n)"
|
- Ask user: "Accept these changes to the core experience definition? (y/n)"
|
||||||
- If yes: Update content with improvements, then return to A/P/C menu
|
- If yes: Update content with improvements, then return to A/P/C menu
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ This step will generate content and present choices:
|
||||||
## PROTOCOL INTEGRATION:
|
## PROTOCOL INTEGRATION:
|
||||||
|
|
||||||
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
||||||
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md
|
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md
|
||||||
- PROTOCOLS always return to this step's A/P/C menu
|
- PROTOCOLS always return to this step's A/P/C menu
|
||||||
- User accepts/rejects protocol changes before proceeding
|
- User accepts/rejects protocol changes before proceeding
|
||||||
|
|
||||||
|
|
@ -172,7 +172,7 @@ Show the generated emotional response content and present choices:
|
||||||
|
|
||||||
#### If 'P' (Party Mode):
|
#### If 'P' (Party Mode):
|
||||||
|
|
||||||
- Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md with the current emotional response definition
|
- Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md with the current emotional response definition
|
||||||
- Process the collaborative emotional insights that come back
|
- Process the collaborative emotional insights that come back
|
||||||
- Ask user: "Accept these changes to the emotional response definition? (y/n)"
|
- Ask user: "Accept these changes to the emotional response definition? (y/n)"
|
||||||
- If yes: Update content with improvements, then return to A/P/C menu
|
- If yes: Update content with improvements, then return to A/P/C menu
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ This step will generate content and present choices:
|
||||||
## PROTOCOL INTEGRATION:
|
## PROTOCOL INTEGRATION:
|
||||||
|
|
||||||
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
||||||
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md
|
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md
|
||||||
- PROTOCOLS always return to this step's A/P/C menu
|
- PROTOCOLS always return to this step's A/P/C menu
|
||||||
- User accepts/rejects protocol changes before proceeding
|
- User accepts/rejects protocol changes before proceeding
|
||||||
|
|
||||||
|
|
@ -187,7 +187,7 @@ Show the generated inspiration analysis content and present choices:
|
||||||
|
|
||||||
#### If 'P' (Party Mode):
|
#### If 'P' (Party Mode):
|
||||||
|
|
||||||
- Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md with the current inspiration analysis
|
- Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md with the current inspiration analysis
|
||||||
- Process the collaborative pattern insights that come back
|
- Process the collaborative pattern insights that come back
|
||||||
- Ask user: "Accept these changes to the inspiration analysis? (y/n)"
|
- Ask user: "Accept these changes to the inspiration analysis? (y/n)"
|
||||||
- If yes: Update content with improvements, then return to A/P/C menu
|
- If yes: Update content with improvements, then return to A/P/C menu
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ This step will generate content and present choices:
|
||||||
## PROTOCOL INTEGRATION:
|
## PROTOCOL INTEGRATION:
|
||||||
|
|
||||||
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
||||||
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md
|
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md
|
||||||
- PROTOCOLS always return to this step's A/P/C menu
|
- PROTOCOLS always return to this step's A/P/C menu
|
||||||
- User accepts/rejects protocol changes before proceeding
|
- User accepts/rejects protocol changes before proceeding
|
||||||
|
|
||||||
|
|
@ -205,7 +205,7 @@ Show the generated design system content and present choices:
|
||||||
|
|
||||||
#### If 'P' (Party Mode):
|
#### If 'P' (Party Mode):
|
||||||
|
|
||||||
- Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md with the current design system choice
|
- Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md with the current design system choice
|
||||||
- Process the collaborative design system insights that come back
|
- Process the collaborative design system insights that come back
|
||||||
- Ask user: "Accept these changes to the design system decision? (y/n)"
|
- Ask user: "Accept these changes to the design system decision? (y/n)"
|
||||||
- If yes: Update content with improvements, then return to A/P/C menu
|
- If yes: Update content with improvements, then return to A/P/C menu
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ This step will generate content and present choices:
|
||||||
## PROTOCOL INTEGRATION:
|
## PROTOCOL INTEGRATION:
|
||||||
|
|
||||||
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
||||||
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md
|
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md
|
||||||
- PROTOCOLS always return to this step's A/P/C menu
|
- PROTOCOLS always return to this step's A/P/C menu
|
||||||
- User accepts/rejects protocol changes before proceeding
|
- User accepts/rejects protocol changes before proceeding
|
||||||
|
|
||||||
|
|
@ -207,7 +207,7 @@ Show the generated defining experience content and present choices:
|
||||||
|
|
||||||
#### If 'P' (Party Mode):
|
#### If 'P' (Party Mode):
|
||||||
|
|
||||||
- Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md with the current defining experience
|
- Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md with the current defining experience
|
||||||
- Process the collaborative experience insights that come back
|
- Process the collaborative experience insights that come back
|
||||||
- Ask user: "Accept these changes to the defining experience? (y/n)"
|
- Ask user: "Accept these changes to the defining experience? (y/n)"
|
||||||
- If yes: Update content with improvements, then return to A/P/C menu
|
- If yes: Update content with improvements, then return to A/P/C menu
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ This step will generate content and present choices:
|
||||||
## PROTOCOL INTEGRATION:
|
## PROTOCOL INTEGRATION:
|
||||||
|
|
||||||
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
||||||
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md
|
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md
|
||||||
- PROTOCOLS always return to this step's A/P/C menu
|
- PROTOCOLS always return to this step's A/P/C menu
|
||||||
- User accepts/rejects protocol changes before proceeding
|
- User accepts/rejects protocol changes before proceeding
|
||||||
|
|
||||||
|
|
@ -177,7 +177,7 @@ Show the generated visual foundation content and present choices:
|
||||||
|
|
||||||
#### If 'P' (Party Mode):
|
#### If 'P' (Party Mode):
|
||||||
|
|
||||||
- Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md with the current visual foundation
|
- Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md with the current visual foundation
|
||||||
- Process the collaborative visual insights that come back
|
- Process the collaborative visual insights that come back
|
||||||
- Ask user: "Accept these changes to the visual foundation? (y/n)"
|
- Ask user: "Accept these changes to the visual foundation? (y/n)"
|
||||||
- If yes: Update content with improvements, then return to A/P/C menu
|
- If yes: Update content with improvements, then return to A/P/C menu
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ This step will generate content and present choices:
|
||||||
## PROTOCOL INTEGRATION:
|
## PROTOCOL INTEGRATION:
|
||||||
|
|
||||||
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
||||||
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md
|
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md
|
||||||
- PROTOCOLS always return to this step's A/P/C menu
|
- PROTOCOLS always return to this step's A/P/C menu
|
||||||
- User accepts/rejects protocol changes before proceeding
|
- User accepts/rejects protocol changes before proceeding
|
||||||
|
|
||||||
|
|
@ -177,7 +177,7 @@ Show the generated design direction content and present choices:
|
||||||
|
|
||||||
#### If 'P' (Party Mode):
|
#### If 'P' (Party Mode):
|
||||||
|
|
||||||
- Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md with the current design direction
|
- Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md with the current design direction
|
||||||
- Process the collaborative design insights that come back
|
- Process the collaborative design insights that come back
|
||||||
- Ask user: "Accept these changes to the design direction? (y/n)"
|
- Ask user: "Accept these changes to the design direction? (y/n)"
|
||||||
- If yes: Update content with improvements, then return to A/P/C menu
|
- If yes: Update content with improvements, then return to A/P/C menu
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ This step will generate content and present choices:
|
||||||
## PROTOCOL INTEGRATION:
|
## PROTOCOL INTEGRATION:
|
||||||
|
|
||||||
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
||||||
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md
|
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md
|
||||||
- PROTOCOLS always return to this step's A/P/C menu
|
- PROTOCOLS always return to this step's A/P/C menu
|
||||||
- User accepts/rejects protocol changes before proceeding
|
- User accepts/rejects protocol changes before proceeding
|
||||||
|
|
||||||
|
|
@ -195,7 +195,7 @@ Show the generated user journey content and present choices:
|
||||||
|
|
||||||
#### If 'P' (Party Mode):
|
#### If 'P' (Party Mode):
|
||||||
|
|
||||||
- Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md with the current user journeys
|
- Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md with the current user journeys
|
||||||
- Process the collaborative journey insights that come back
|
- Process the collaborative journey insights that come back
|
||||||
- Ask user: "Accept these changes to the user journeys? (y/n)"
|
- Ask user: "Accept these changes to the user journeys? (y/n)"
|
||||||
- If yes: Update content with improvements, then return to A/P/C menu
|
- If yes: Update content with improvements, then return to A/P/C menu
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ This step will generate content and present choices:
|
||||||
## PROTOCOL INTEGRATION:
|
## PROTOCOL INTEGRATION:
|
||||||
|
|
||||||
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
||||||
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md
|
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md
|
||||||
- PROTOCOLS always return to this step's A/P/C menu
|
- PROTOCOLS always return to this step's A/P/C menu
|
||||||
- User accepts/rejects protocol changes before proceeding
|
- User accepts/rejects protocol changes before proceeding
|
||||||
|
|
||||||
|
|
@ -201,7 +201,7 @@ Show the generated component strategy content and present choices:
|
||||||
|
|
||||||
#### If 'P' (Party Mode):
|
#### If 'P' (Party Mode):
|
||||||
|
|
||||||
- Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md with the current component strategy
|
- Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md with the current component strategy
|
||||||
- Process the collaborative component insights that come back
|
- Process the collaborative component insights that come back
|
||||||
- Ask user: "Accept these changes to the component strategy? (y/n)"
|
- Ask user: "Accept these changes to the component strategy? (y/n)"
|
||||||
- If yes: Update content with improvements, then return to A/P/C menu
|
- If yes: Update content with improvements, then return to A/P/C menu
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ This step will generate content and present choices:
|
||||||
## PROTOCOL INTEGRATION:
|
## PROTOCOL INTEGRATION:
|
||||||
|
|
||||||
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
||||||
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md
|
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md
|
||||||
- PROTOCOLS always return to this step's A/P/C menu
|
- PROTOCOLS always return to this step's A/P/C menu
|
||||||
- User accepts/rejects protocol changes before proceeding
|
- User accepts/rejects protocol changes before proceeding
|
||||||
|
|
||||||
|
|
@ -190,7 +190,7 @@ Show the generated UX patterns content and present choices:
|
||||||
|
|
||||||
#### If 'P' (Party Mode):
|
#### If 'P' (Party Mode):
|
||||||
|
|
||||||
- Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md with the current UX patterns
|
- Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md with the current UX patterns
|
||||||
- Process the collaborative pattern insights that come back
|
- Process the collaborative pattern insights that come back
|
||||||
- Ask user: "Accept these changes to the UX patterns? (y/n)"
|
- Ask user: "Accept these changes to the UX patterns? (y/n)"
|
||||||
- If yes: Update content with improvements, then return to A/P/C menu
|
- If yes: Update content with improvements, then return to A/P/C menu
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ This step will generate content and present choices:
|
||||||
## PROTOCOL INTEGRATION:
|
## PROTOCOL INTEGRATION:
|
||||||
|
|
||||||
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
||||||
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md
|
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md
|
||||||
- PROTOCOLS always return to this step's A/P/C menu
|
- PROTOCOLS always return to this step's A/P/C menu
|
||||||
- User accepts/rejects protocol changes before proceeding
|
- User accepts/rejects protocol changes before proceeding
|
||||||
|
|
||||||
|
|
@ -217,7 +217,7 @@ Show the generated responsive and accessibility content and present choices:
|
||||||
|
|
||||||
#### If 'P' (Party Mode):
|
#### If 'P' (Party Mode):
|
||||||
|
|
||||||
- Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md with the current responsive/accessibility strategy
|
- Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md with the current responsive/accessibility strategy
|
||||||
- Process the collaborative insights that come back
|
- Process the collaborative insights that come back
|
||||||
- Ask user: "Accept these changes to the responsive/accessibility strategy? (y/n)"
|
- Ask user: "Accept these changes to the responsive/accessibility strategy? (y/n)"
|
||||||
- If yes: Update content with improvements, then return to A/P/C menu
|
- If yes: Update content with improvements, then return to A/P/C menu
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ This step will generate content and present choices:
|
||||||
## PROTOCOL INTEGRATION:
|
## PROTOCOL INTEGRATION:
|
||||||
|
|
||||||
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
||||||
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md
|
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md
|
||||||
- PROTOCOLS always return to display this step's A/P/C menu after the A or P have completed
|
- 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
|
- User accepts/rejects protocol changes before proceeding
|
||||||
|
|
||||||
|
|
@ -178,7 +178,7 @@ Show the generated content and present choices:
|
||||||
|
|
||||||
#### If 'P' (Party Mode):
|
#### If 'P' (Party Mode):
|
||||||
|
|
||||||
- Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md with the current project context
|
- Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md with the current project context
|
||||||
- Process the collaborative improvements to architectural understanding
|
- Process the collaborative improvements to architectural understanding
|
||||||
- Ask user: "Accept these changes to the project context analysis? (y/n)"
|
- 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 yes: Update content with improvements, then return to A/P/C menu
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ This step will generate content and present choices:
|
||||||
## PROTOCOL INTEGRATION:
|
## PROTOCOL INTEGRATION:
|
||||||
|
|
||||||
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
||||||
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md
|
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md
|
||||||
- PROTOCOLS always return to display this step's A/P/C menu after the A or P have completed
|
- 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
|
- User accepts/rejects protocol changes before proceeding
|
||||||
|
|
||||||
|
|
@ -284,7 +284,7 @@ Show the generated content and present choices:
|
||||||
|
|
||||||
#### If 'P' (Party Mode):
|
#### If 'P' (Party Mode):
|
||||||
|
|
||||||
- Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md with starter evaluation context
|
- Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md with starter evaluation context
|
||||||
- Process collaborative insights about starter trade-offs
|
- Process collaborative insights about starter trade-offs
|
||||||
- Ask user: "Accept these changes to the starter template evaluation? (y/n)"
|
- Ask user: "Accept these changes to the starter template evaluation? (y/n)"
|
||||||
- If yes: Update content, then return to A/P/C menu
|
- If yes: Update content, then return to A/P/C menu
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ This step will generate content and present choices for each decision category:
|
||||||
## PROTOCOL INTEGRATION:
|
## PROTOCOL INTEGRATION:
|
||||||
|
|
||||||
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
||||||
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md
|
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md
|
||||||
- PROTOCOLS always return to display this step's A/P/C menu after the A or P have completed
|
- 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
|
- User accepts/rejects protocol changes before proceeding
|
||||||
|
|
||||||
|
|
@ -272,7 +272,7 @@ Show the generated decisions content and present choices:
|
||||||
|
|
||||||
#### If 'P' (Party Mode):
|
#### If 'P' (Party Mode):
|
||||||
|
|
||||||
- Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md with architectural decisions context
|
- Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md with architectural decisions context
|
||||||
- Process collaborative insights about decision trade-offs
|
- Process collaborative insights about decision trade-offs
|
||||||
- Ask user: "Accept these changes to the architectural decisions? (y/n)"
|
- Ask user: "Accept these changes to the architectural decisions? (y/n)"
|
||||||
- If yes: Update content, then return to A/P/C menu
|
- If yes: Update content, then return to A/P/C menu
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ This step will generate content and present choices:
|
||||||
## PROTOCOL INTEGRATION:
|
## PROTOCOL INTEGRATION:
|
||||||
|
|
||||||
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
||||||
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md
|
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md
|
||||||
- PROTOCOLS always return to display this step's A/P/C menu after the A or P have completed
|
- 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
|
- User accepts/rejects protocol changes before proceeding
|
||||||
|
|
||||||
|
|
@ -313,7 +313,7 @@ Show the generated patterns content and present choices:
|
||||||
|
|
||||||
#### If 'P' (Party Mode):
|
#### If 'P' (Party Mode):
|
||||||
|
|
||||||
- Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md with implementation patterns context
|
- Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md with implementation patterns context
|
||||||
- Process collaborative insights about potential conflicts
|
- Process collaborative insights about potential conflicts
|
||||||
- Ask user: "Accept these changes to the implementation patterns? (y/n)"
|
- Ask user: "Accept these changes to the implementation patterns? (y/n)"
|
||||||
- If yes: Update content, then return to A/P/C menu
|
- If yes: Update content, then return to A/P/C menu
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ This step will generate content and present choices:
|
||||||
## PROTOCOL INTEGRATION:
|
## PROTOCOL INTEGRATION:
|
||||||
|
|
||||||
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
||||||
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md
|
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md
|
||||||
- PROTOCOLS always return to display this step's A/P/C menu after the A or P have completed
|
- 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
|
- User accepts/rejects protocol changes before proceeding
|
||||||
|
|
||||||
|
|
@ -333,7 +333,7 @@ Show the generated project structure content and present choices:
|
||||||
|
|
||||||
#### If 'P' (Party Mode):
|
#### If 'P' (Party Mode):
|
||||||
|
|
||||||
- Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md with project structure context
|
- Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md with project structure context
|
||||||
- Process collaborative insights about organization trade-offs
|
- Process collaborative insights about organization trade-offs
|
||||||
- Ask user: "Accept these changes to the project structure? (y/n)"
|
- Ask user: "Accept these changes to the project structure? (y/n)"
|
||||||
- If yes: Update content, then return to A/P/C menu
|
- If yes: Update content, then return to A/P/C menu
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ This step will generate content and present choices:
|
||||||
## PROTOCOL INTEGRATION:
|
## PROTOCOL INTEGRATION:
|
||||||
|
|
||||||
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
- When 'A' selected: Read fully and follow: {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
||||||
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md
|
- When 'P' selected: Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md
|
||||||
- PROTOCOLS always return to display this step's A/P/C menu after the A or P have completed
|
- 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
|
- User accepts/rejects protocol changes before proceeding
|
||||||
|
|
||||||
|
|
@ -313,7 +313,7 @@ Show the validation results and present choices:
|
||||||
|
|
||||||
#### If 'P' (Party Mode):
|
#### If 'P' (Party Mode):
|
||||||
|
|
||||||
- Read fully and follow: {project-root}/_bmad/core/workflows/party-mode/workflow.md with validation context
|
- Read fully and follow: {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md with validation context
|
||||||
- Process collaborative insights on implementation readiness
|
- Process collaborative insights on implementation readiness
|
||||||
- Ask user: "Accept these changes to the validation results? (y/n)"
|
- Ask user: "Accept these changes to the validation results? (y/n)"
|
||||||
- If yes: Update content, then return to A/P/C menu
|
- If yes: Update content, then return to A/P/C menu
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ epicsTemplate: '{workflow_path}/templates/epics-template.md'
|
||||||
|
|
||||||
# Task References
|
# Task References
|
||||||
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
||||||
partyModeWorkflow: '{project-root}/_bmad/core/workflows/party-mode/workflow.md'
|
partyModeWorkflow: '{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md'
|
||||||
|
|
||||||
# Template References
|
# Template References
|
||||||
epicsTemplate: '{workflow_path}/templates/epics-template.md'
|
epicsTemplate: '{workflow_path}/templates/epics-template.md'
|
||||||
|
|
@ -154,20 +154,31 @@ Review the Architecture document for technical requirements that impact epic and
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6. Extract Additional Requirements from UX (if exists)
|
### 6. Extract UX Design Requirements (if UX document exists)
|
||||||
|
|
||||||
Review the UX document for requirements that affect epic and story creation:
|
**IMPORTANT**: The UX Design Specification is a first-class input document, not supplementary material. Requirements from the UX spec must be extracted with the same rigor as PRD functional requirements.
|
||||||
|
|
||||||
|
Read the FULL UX Design document and extract ALL actionable work items:
|
||||||
|
|
||||||
**Look for:**
|
**Look for:**
|
||||||
|
|
||||||
- Responsive design requirements
|
- **Design token work**: Color systems, spacing scales, typography tokens that need implementation or consolidation
|
||||||
- Accessibility requirements
|
- **Component proposals**: Reusable UI components identified in the UX spec (e.g., ConfirmActions, StatusMessage, EmptyState, FocusIndicator)
|
||||||
- Browser/device compatibility
|
- **Visual standardization**: Semantic CSS classes, consistent color palette usage, design pattern consolidation
|
||||||
- User interaction patterns that need implementation
|
- **Accessibility requirements**: Contrast audit fixes, ARIA patterns, keyboard navigation, screen reader support
|
||||||
- Animation or transition requirements
|
- **Responsive design requirements**: Breakpoints, layout adaptations, mobile-specific interactions
|
||||||
- Error handling UX requirements
|
- **Interaction patterns**: Animations, transitions, loading states, error handling UX
|
||||||
|
- **Browser/device compatibility**: Target platforms, progressive enhancement requirements
|
||||||
|
|
||||||
**Add these to Additional Requirements list.**
|
**Format UX Design Requirements as a SEPARATE section (not merged into Additional Requirements):**
|
||||||
|
|
||||||
|
```
|
||||||
|
UX-DR1: [Actionable UX design requirement with clear implementation scope]
|
||||||
|
UX-DR2: [Actionable UX design requirement with clear implementation scope]
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
**🚨 CRITICAL**: Do NOT reduce UX requirements to vague summaries. Each UX-DR must be specific enough to generate a story with testable acceptance criteria. If the UX spec identifies 6 reusable components, list all 6 — not "create reusable components."
|
||||||
|
|
||||||
### 7. Load and Initialize Template
|
### 7. Load and Initialize Template
|
||||||
|
|
||||||
|
|
@ -178,7 +189,8 @@ Load {epicsTemplate} and initialize {outputFile}:
|
||||||
3. Replace placeholder sections with extracted requirements:
|
3. Replace placeholder sections with extracted requirements:
|
||||||
- {{fr_list}} → extracted FRs
|
- {{fr_list}} → extracted FRs
|
||||||
- {{nfr_list}} → extracted NFRs
|
- {{nfr_list}} → extracted NFRs
|
||||||
- {{additional_requirements}} → extracted additional requirements
|
- {{additional_requirements}} → extracted additional requirements (from Architecture)
|
||||||
|
- {{ux_design_requirements}} → extracted UX Design Requirements (if UX document exists)
|
||||||
4. Leave {{requirements_coverage_map}} and {{epics_list}} as placeholders for now
|
4. Leave {{requirements_coverage_map}} and {{epics_list}} as placeholders for now
|
||||||
|
|
||||||
### 8. Present Extracted Requirements
|
### 8. Present Extracted Requirements
|
||||||
|
|
@ -197,12 +209,17 @@ Display to user:
|
||||||
- Display key NFRs
|
- Display key NFRs
|
||||||
- Ask if any constraints were missed
|
- Ask if any constraints were missed
|
||||||
|
|
||||||
**Additional Requirements:**
|
**Additional Requirements (Architecture):**
|
||||||
|
|
||||||
- Summarize technical requirements from Architecture
|
- Summarize technical requirements from Architecture
|
||||||
- Summarize UX requirements (if applicable)
|
|
||||||
- Verify completeness
|
- Verify completeness
|
||||||
|
|
||||||
|
**UX Design Requirements (if applicable):**
|
||||||
|
|
||||||
|
- Show count of UX-DRs found
|
||||||
|
- Display key UX Design requirements (design tokens, components, accessibility)
|
||||||
|
- Verify each UX-DR is specific enough for story creation
|
||||||
|
|
||||||
### 9. Get User Confirmation
|
### 9. Get User Confirmation
|
||||||
|
|
||||||
Ask: "Do these extracted requirements accurately represent what needs to be built? Any additions or corrections?"
|
Ask: "Do these extracted requirements accurately represent what needs to be built? Any additions or corrections?"
|
||||||
|
|
@ -216,6 +233,7 @@ After extraction and confirmation, update {outputFile} with:
|
||||||
- Complete FR list in {{fr_list}} section
|
- Complete FR list in {{fr_list}} section
|
||||||
- Complete NFR list in {{nfr_list}} section
|
- Complete NFR list in {{nfr_list}} section
|
||||||
- All additional requirements in {{additional_requirements}} section
|
- All additional requirements in {{additional_requirements}} section
|
||||||
|
- UX Design requirements in {{ux_design_requirements}} section (if UX document exists)
|
||||||
|
|
||||||
### 10. Present MENU OPTIONS
|
### 10. Present MENU OPTIONS
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ outputFile: '{planning_artifacts}/epics.md'
|
||||||
|
|
||||||
# Task References
|
# Task References
|
||||||
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
||||||
partyModeWorkflow: '{project-root}/_bmad/core/workflows/party-mode/workflow.md'
|
partyModeWorkflow: '{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md'
|
||||||
|
|
||||||
# Template References
|
# Template References
|
||||||
epicsTemplate: '{workflow_path}/templates/epics-template.md'
|
epicsTemplate: '{workflow_path}/templates/epics-template.md'
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ outputFile: '{planning_artifacts}/epics.md'
|
||||||
|
|
||||||
# Task References
|
# Task References
|
||||||
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
||||||
partyModeWorkflow: '{project-root}/_bmad/core/workflows/party-mode/workflow.md'
|
partyModeWorkflow: '{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md'
|
||||||
|
|
||||||
# Template References
|
# Template References
|
||||||
epicsTemplate: '{workflow_path}/templates/epics-template.md'
|
epicsTemplate: '{workflow_path}/templates/epics-template.md'
|
||||||
|
|
@ -66,9 +66,11 @@ Load {outputFile} and review:
|
||||||
|
|
||||||
- Approved epics_list from Step 2
|
- Approved epics_list from Step 2
|
||||||
- FR coverage map
|
- FR coverage map
|
||||||
- All requirements (FRs, NFRs, additional)
|
- All requirements (FRs, NFRs, additional, **UX Design requirements if present**)
|
||||||
- Template structure at the end of the document
|
- Template structure at the end of the document
|
||||||
|
|
||||||
|
**UX Design Integration**: If UX Design Requirements (UX-DRs) were extracted in Step 1, ensure they are visible during story creation. UX-DRs must be covered by stories — either within existing epics (e.g., accessibility fixes for a feature epic) or in a dedicated "Design System / UX Polish" epic.
|
||||||
|
|
||||||
### 2. Explain Story Creation Approach
|
### 2. Explain Story Creation Approach
|
||||||
|
|
||||||
**STORY CREATION GUIDELINES:**
|
**STORY CREATION GUIDELINES:**
|
||||||
|
|
@ -146,6 +148,7 @@ Display:
|
||||||
- Epic goal statement
|
- Epic goal statement
|
||||||
- FRs covered by this epic
|
- FRs covered by this epic
|
||||||
- Any NFRs or additional requirements relevant
|
- Any NFRs or additional requirements relevant
|
||||||
|
- Any UX Design Requirements (UX-DRs) relevant to this epic
|
||||||
|
|
||||||
#### B. Story Breakdown
|
#### B. Story Breakdown
|
||||||
|
|
||||||
|
|
@ -207,6 +210,7 @@ After all epics and stories are generated:
|
||||||
- Verify the document follows template structure exactly
|
- Verify the document follows template structure exactly
|
||||||
- Ensure all placeholders are replaced
|
- Ensure all placeholders are replaced
|
||||||
- Confirm all FRs are covered
|
- Confirm all FRs are covered
|
||||||
|
- **Confirm all UX Design Requirements (UX-DRs) are covered by at least one story** (if UX document was an input)
|
||||||
- Check formatting consistency
|
- Check formatting consistency
|
||||||
|
|
||||||
## TEMPLATE STRUCTURE COMPLIANCE:
|
## TEMPLATE STRUCTURE COMPLIANCE:
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ outputFile: '{planning_artifacts}/epics.md'
|
||||||
|
|
||||||
# Task References
|
# Task References
|
||||||
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
advancedElicitationTask: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
||||||
partyModeWorkflow: '{project-root}/_bmad/core/workflows/party-mode/workflow.md'
|
partyModeWorkflow: '{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md'
|
||||||
|
|
||||||
# Template References
|
# Template References
|
||||||
epicsTemplate: '{workflow_path}/templates/epics-template.md'
|
epicsTemplate: '{workflow_path}/templates/epics-template.md'
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,10 @@ This document provides the complete epic and story breakdown for {{project_name}
|
||||||
|
|
||||||
{{additional_requirements}}
|
{{additional_requirements}}
|
||||||
|
|
||||||
|
### UX Design Requirements
|
||||||
|
|
||||||
|
{{ux_design_requirements}}
|
||||||
|
|
||||||
### FR Coverage Map
|
### FR Coverage Map
|
||||||
|
|
||||||
{{requirements_coverage_map}}
|
{{requirements_coverage_map}}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ description: 'Perform adversarial code review finding specific issues. Use when
|
||||||
- Generate all documents in {document_output_language}
|
- Generate all documents in {document_output_language}
|
||||||
- Your purpose: Validate story file claims against actual implementation
|
- Your purpose: Validate story file claims against actual implementation
|
||||||
- Challenge everything: Are tasks marked [x] actually done? Are ACs really implemented?
|
- Challenge everything: Are tasks marked [x] actually done? Are ACs really implemented?
|
||||||
- Find 3-10 specific issues in every review minimum - no lazy "looks good" reviews - YOU are so much better than the dev agent that wrote this slop
|
- Be thorough and specific — find real issues, not manufactured ones. If the code is genuinely good after fixes, say so
|
||||||
- Read EVERY file in the File List - verify implementation against story requirements
|
- Read EVERY file in the File List - verify implementation against story requirements
|
||||||
- Tasks marked complete but not done = CRITICAL finding
|
- Tasks marked complete but not done = CRITICAL finding
|
||||||
- Acceptance Criteria not implemented = HIGH severity finding
|
- Acceptance Criteria not implemented = HIGH severity finding
|
||||||
|
|
@ -136,17 +136,14 @@ Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
|
||||||
5. **Test Quality**: Are tests real assertions or placeholders?
|
5. **Test Quality**: Are tests real assertions or placeholders?
|
||||||
</action>
|
</action>
|
||||||
|
|
||||||
<check if="total_issues_found lt 3">
|
<check if="total_issues_found == 0">
|
||||||
<critical>NOT LOOKING HARD ENOUGH - Find more problems!</critical>
|
<action>Double-check by re-examining code for:
|
||||||
<action>Re-examine code for:
|
|
||||||
- Edge cases and null handling
|
- Edge cases and null handling
|
||||||
- Architecture violations
|
- Architecture violations
|
||||||
- Documentation gaps
|
|
||||||
- Integration issues
|
- Integration issues
|
||||||
- Dependency problems
|
- Dependency problems
|
||||||
- Git commit message quality (if applicable)
|
|
||||||
</action>
|
</action>
|
||||||
<action>Find at least 3 more specific, actionable issues</action>
|
<action>If still no issues found after thorough re-examination, that is a valid outcome — report a clean review</action>
|
||||||
</check>
|
</check>
|
||||||
</step>
|
</step>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
---
|
---
|
||||||
main_config: '{project-root}/_bmad/bmm/config.yaml'
|
main_config: '{project-root}/_bmad/bmm/config.yaml'
|
||||||
|
|
||||||
# Related workflows
|
|
||||||
advanced_elicitation: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
|
||||||
party_mode_exec: '{project-root}/_bmad/core/workflows/party-mode/workflow.md'
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Quick Dev New Preview Workflow
|
# Quick Dev New Preview Workflow
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
|
||||||
### Related Workflows
|
### Related Workflows
|
||||||
|
|
||||||
- `quick_spec_workflow` = `{project-root}/_bmad/bmm/workflows/bmad-quick-flow/quick-spec/workflow.md`
|
- `quick_spec_workflow` = `{project-root}/_bmad/bmm/workflows/bmad-quick-flow/quick-spec/workflow.md`
|
||||||
- `party_mode_exec` = `{project-root}/_bmad/core/workflows/party-mode/workflow.md`
|
- `party_mode_exec` = `{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md`
|
||||||
- `advanced_elicitation` = `{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md`
|
- `advanced_elicitation` = `{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ main_config: '{project-root}/_bmad/bmm/config.yaml'
|
||||||
|
|
||||||
# Checkpoint handler paths
|
# Checkpoint handler paths
|
||||||
advanced_elicitation: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
advanced_elicitation: '{project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md'
|
||||||
party_mode_exec: '{project-root}/_bmad/core/workflows/party-mode/workflow.md'
|
party_mode_exec: '{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md'
|
||||||
quick_dev_workflow: '{project-root}/_bmad/bmm/workflows/bmad-quick-flow/quick-dev/workflow.md'
|
quick_dev_workflow: '{project-root}/_bmad/bmm/workflows/bmad-quick-flow/quick-dev/workflow.md'
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ This step will generate content and present choices for each rule category:
|
||||||
## PROTOCOL INTEGRATION:
|
## PROTOCOL INTEGRATION:
|
||||||
|
|
||||||
- When 'A' selected: Execute {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
- When 'A' selected: Execute {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md
|
||||||
- When 'P' selected: Execute {project-root}/_bmad/core/workflows/party-mode
|
- When 'P' selected: Execute {project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md
|
||||||
- PROTOCOLS always return to display this step's A/P/C menu after the A or P have completed
|
- 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
|
- User accepts/rejects protocol changes before proceeding
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
module,phase,name,code,sequence,workflow-file,command,required,agent,options,description,output-location,outputs
|
module,phase,name,code,sequence,workflow-file,command,required,agent,options,description,output-location,outputs
|
||||||
core,anytime,Brainstorming,BSP,,_bmad/core/workflows/brainstorming/workflow.md,bmad-brainstorming,false,analyst,,"Generate diverse ideas through interactive techniques. Use early in ideation phase or when stuck generating ideas.",{output_folder}/brainstorming/brainstorming-session-{{date}}.md,,
|
core,anytime,Brainstorming,BSP,,_bmad/core/workflows/brainstorming/workflow.md,bmad-brainstorming,false,analyst,,"Generate diverse ideas through interactive techniques. Use early in ideation phase or when stuck generating ideas.",{output_folder}/brainstorming/brainstorming-session-{{date}}.md,,
|
||||||
core,anytime,Party Mode,PM,,_bmad/core/workflows/party-mode/workflow.md,bmad-party-mode,false,party-mode facilitator,,"Orchestrate multi-agent discussions. Use when you need multiple agent perspectives or want agents to collaborate.",,
|
core,anytime,Party Mode,PM,,skill:bmad-party-mode,bmad-party-mode,false,party-mode facilitator,,"Orchestrate multi-agent discussions. Use when you need multiple agent perspectives or want agents to collaborate.",,
|
||||||
core,anytime,bmad-help,BH,,skill:bmad-help,bmad-help,false,,,"Get unstuck by showing what workflow steps come next or answering BMad Method questions.",,
|
core,anytime,bmad-help,BH,,skill:bmad-help,bmad-help,false,,,"Get unstuck by showing what workflow steps come next or answering BMad Method questions.",,
|
||||||
core,anytime,Index Docs,ID,,skill:bmad-index-docs,bmad-index-docs,false,,,"Create lightweight index for quick LLM scanning. Use when LLM needs to understand available docs without loading everything.",,
|
core,anytime,Index Docs,ID,,skill:bmad-index-docs,bmad-index-docs,false,,,"Create lightweight index for quick LLM scanning. Use when LLM needs to understand available docs without loading everything.",,
|
||||||
core,anytime,Shard Document,SD,,skill:bmad-shard-doc,bmad-shard-doc,false,,,"Split large documents into smaller files by sections. Use when doc becomes too large (>500 lines) to manage effectively.",,
|
core,anytime,Shard Document,SD,,skill:bmad-shard-doc,bmad-shard-doc,false,,,"Split large documents into smaller files by sections. Use when doc becomes too large (>500 lines) to manage effectively.",,
|
||||||
|
|
|
||||||
|
Can't render this file because it has a wrong number of fields in line 2.
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
name: bmad-party-mode
|
||||||
|
description: 'Orchestrates group discussions between all installed BMAD agents, enabling natural multi-agent conversations. Use when user requests party mode.'
|
||||||
|
---
|
||||||
|
|
||||||
|
Follow the instructions in [workflow.md](workflow.md).
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
type: skill
|
||||||
|
|
@ -93,7 +93,6 @@ Final workflow completion steps:
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
stepsCompleted: [1, 2, 3]
|
stepsCompleted: [1, 2, 3]
|
||||||
workflowType: 'party-mode'
|
|
||||||
user_name: '{{user_name}}'
|
user_name: '{{user_name}}'
|
||||||
date: '{{date}}'
|
date: '{{date}}'
|
||||||
agents_loaded: true
|
agents_loaded: true
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
name: party-mode
|
name: bmad-party-mode
|
||||||
description: 'Orchestrates group discussions between all installed BMAD agents, enabling natural multi-agent conversations. Use when user requests party mode.'
|
description: 'Orchestrates group discussions between all installed BMAD agents, enabling natural multi-agent conversations. Use when user requests party mode.'
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -36,7 +36,6 @@ Load config from `{project-root}/_bmad/core/config.yaml` and resolve:
|
||||||
|
|
||||||
### Paths
|
### Paths
|
||||||
|
|
||||||
- `installed_path` = `{project-root}/_bmad/core/workflows/party-mode`
|
|
||||||
- `agent_manifest_path` = `{project-root}/_bmad/_config/agent-manifest.csv`
|
- `agent_manifest_path` = `{project-root}/_bmad/_config/agent-manifest.csv`
|
||||||
- `standalone_mode` = `true` (party mode is an interactive workflow)
|
- `standalone_mode` = `true` (party mode is an interactive workflow)
|
||||||
|
|
||||||
|
|
@ -115,7 +114,6 @@ Load step: `./steps/step-02-discussion-orchestration.md`
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
stepsCompleted: [1]
|
stepsCompleted: [1]
|
||||||
workflowType: 'party-mode'
|
|
||||||
user_name: '{{user_name}}'
|
user_name: '{{user_name}}'
|
||||||
date: '{{date}}'
|
date: '{{date}}'
|
||||||
agents_loaded: true
|
agents_loaded: true
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
canonicalId: bmad-party-mode
|
|
||||||
type: workflow
|
|
||||||
description: "Orchestrates group discussions between all installed BMAD agents"
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
module,phase,name,code,sequence,workflow-file,command,required,agent,options,description,output-location,outputs
|
module,phase,name,code,sequence,workflow-file,command,required,agent,options,description,output-location,outputs
|
||||||
core,anytime,Brainstorming,BSP,,_bmad/core/workflows/brainstorming/workflow.md,bmad-brainstorming,false,analyst,,"Generate ideas",{output_folder}/brainstorming.md,
|
core,anytime,Brainstorming,BSP,,_bmad/core/workflows/brainstorming/workflow.md,bmad-brainstorming,false,analyst,,"Generate ideas",{output_folder}/brainstorming.md,
|
||||||
core,anytime,Party Mode,PM,,_bmad/core/workflows/party-mode/workflow.md,bmad-party-mode,false,facilitator,,"Multi-agent discussion",,
|
core,anytime,Party Mode,PM,,_bmad/core/workflows/bmad-party-mode/workflow.md,bmad-party-mode,false,facilitator,,"Multi-agent discussion",,
|
||||||
|
|
|
||||||
|
|
|
@ -71,7 +71,7 @@ test('core-style.csv: extracts refs from core module-help format', () => {
|
||||||
const refs = extractCsvRefs(fullPath, content);
|
const refs = extractCsvRefs(fullPath, content);
|
||||||
assert(refs.length === 2, `Expected 2 refs, got ${refs.length}`);
|
assert(refs.length === 2, `Expected 2 refs, got ${refs.length}`);
|
||||||
assert(refs[0].raw === '_bmad/core/workflows/brainstorming/workflow.md', `Wrong raw[0]: ${refs[0].raw}`);
|
assert(refs[0].raw === '_bmad/core/workflows/brainstorming/workflow.md', `Wrong raw[0]: ${refs[0].raw}`);
|
||||||
assert(refs[1].raw === '_bmad/core/workflows/party-mode/workflow.md', `Wrong raw[1]: ${refs[1].raw}`);
|
assert(refs[1].raw === '_bmad/core/workflows/bmad-party-mode/workflow.md', `Wrong raw[1]: ${refs[1].raw}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('minimal.csv: extracts refs from minimal 3-column CSV', () => {
|
test('minimal.csv: extracts refs from minimal 3-column CSV', () => {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,492 @@
|
||||||
|
/**
|
||||||
|
* Native fs Wrapper Tests
|
||||||
|
*
|
||||||
|
* Validates that tools/cli/lib/fs.js provides the same API surface
|
||||||
|
* as fs-extra but backed entirely by native node:fs. Exercises every
|
||||||
|
* exported method the CLI codebase relies on.
|
||||||
|
*
|
||||||
|
* Usage: node test/test-fs-wrapper.js
|
||||||
|
* Exit codes: 0 = all tests pass, 1 = test failures
|
||||||
|
*/
|
||||||
|
|
||||||
|
const nativeFs = require('node:fs');
|
||||||
|
const path = require('node:path');
|
||||||
|
const fs = require('../tools/cli/lib/fs');
|
||||||
|
|
||||||
|
// ANSI color codes
|
||||||
|
const colors = {
|
||||||
|
reset: '\u001B[0m',
|
||||||
|
green: '\u001B[32m',
|
||||||
|
red: '\u001B[31m',
|
||||||
|
yellow: '\u001B[33m',
|
||||||
|
cyan: '\u001B[36m',
|
||||||
|
dim: '\u001B[2m',
|
||||||
|
};
|
||||||
|
|
||||||
|
let totalTests = 0;
|
||||||
|
let passedTests = 0;
|
||||||
|
const failures = [];
|
||||||
|
|
||||||
|
function test(name, fn) {
|
||||||
|
totalTests++;
|
||||||
|
try {
|
||||||
|
fn();
|
||||||
|
passedTests++;
|
||||||
|
console.log(` ${colors.green}\u2713${colors.reset} ${name}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(` ${colors.red}\u2717${colors.reset} ${name} ${colors.red}${error.message}${colors.reset}`);
|
||||||
|
failures.push({ name, message: error.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function asyncTest(name, fn) {
|
||||||
|
totalTests++;
|
||||||
|
try {
|
||||||
|
await fn();
|
||||||
|
passedTests++;
|
||||||
|
console.log(` ${colors.green}\u2713${colors.reset} ${name}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(` ${colors.red}\u2717${colors.reset} ${name} ${colors.red}${error.message}${colors.reset}`);
|
||||||
|
failures.push({ name, message: error.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assert(condition, message) {
|
||||||
|
if (!condition) throw new Error(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertEqual(actual, expected, message) {
|
||||||
|
if (actual !== expected) {
|
||||||
|
throw new Error(`${message}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Test fixtures ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const TMP = path.join(__dirname, '.tmp-fs-wrapper-test');
|
||||||
|
|
||||||
|
function setup() {
|
||||||
|
nativeFs.rmSync(TMP, { recursive: true, force: true });
|
||||||
|
nativeFs.mkdirSync(TMP, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function teardown() {
|
||||||
|
nativeFs.rmSync(TMP, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Tests ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async function runTests() {
|
||||||
|
console.log(`${colors.cyan}========================================`);
|
||||||
|
console.log('Native fs Wrapper Tests');
|
||||||
|
console.log(`========================================${colors.reset}\n`);
|
||||||
|
|
||||||
|
setup();
|
||||||
|
|
||||||
|
// ── Re-exported native members ──────────────────────────────────────────
|
||||||
|
|
||||||
|
console.log(`${colors.yellow}Re-exported native fs members${colors.reset}`);
|
||||||
|
|
||||||
|
test('exports fs.constants', () => {
|
||||||
|
assert(fs.constants !== undefined, 'fs.constants is undefined');
|
||||||
|
assert(typeof fs.constants.F_OK === 'number', 'fs.constants.F_OK is not a number');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('exports fs.existsSync', () => {
|
||||||
|
assert(typeof fs.existsSync === 'function', 'fs.existsSync is not a function');
|
||||||
|
assert(fs.existsSync(__dirname), 'existsSync returns false for existing dir');
|
||||||
|
assert(!fs.existsSync(path.join(TMP, 'nonexistent')), 'existsSync returns true for missing path');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('exports fs.readFileSync', () => {
|
||||||
|
const content = fs.readFileSync(__filename, 'utf8');
|
||||||
|
assert(content.includes('Native fs Wrapper Tests'), 'readFileSync did not return expected content');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('exports fs.writeFileSync', () => {
|
||||||
|
const p = path.join(TMP, 'write-sync.txt');
|
||||||
|
fs.writeFileSync(p, 'hello sync');
|
||||||
|
assertEqual(nativeFs.readFileSync(p, 'utf8'), 'hello sync', 'writeFileSync content mismatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('exports fs.mkdirSync', () => {
|
||||||
|
const p = path.join(TMP, 'mkdir-sync');
|
||||||
|
fs.mkdirSync(p);
|
||||||
|
assert(nativeFs.statSync(p).isDirectory(), 'mkdirSync did not create directory');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('exports fs.readdirSync', () => {
|
||||||
|
const entries = fs.readdirSync(TMP);
|
||||||
|
assert(Array.isArray(entries), 'readdirSync did not return array');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('exports fs.statSync', () => {
|
||||||
|
const stat = fs.statSync(__dirname);
|
||||||
|
assert(stat.isDirectory(), 'statSync did not return directory stat');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('exports fs.copyFileSync', () => {
|
||||||
|
const src = path.join(TMP, 'copy-src.txt');
|
||||||
|
const dest = path.join(TMP, 'copy-dest.txt');
|
||||||
|
nativeFs.writeFileSync(src, 'copy me');
|
||||||
|
fs.copyFileSync(src, dest);
|
||||||
|
assertEqual(nativeFs.readFileSync(dest, 'utf8'), 'copy me', 'copyFileSync content mismatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('exports fs.accessSync', () => {
|
||||||
|
// Should not throw for existing file
|
||||||
|
fs.accessSync(__filename);
|
||||||
|
let threw = false;
|
||||||
|
try {
|
||||||
|
fs.accessSync(path.join(TMP, 'nonexistent'));
|
||||||
|
} catch {
|
||||||
|
threw = true;
|
||||||
|
}
|
||||||
|
assert(threw, 'accessSync did not throw for missing file');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('exports fs.createReadStream', () => {
|
||||||
|
assert(typeof fs.createReadStream === 'function', 'createReadStream is not a function');
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// ── Async promise-based methods ─────────────────────────────────────────
|
||||||
|
|
||||||
|
console.log(`${colors.yellow}Async promise-based methods${colors.reset}`);
|
||||||
|
|
||||||
|
await asyncTest('readFile returns promise with content', async () => {
|
||||||
|
const content = await fs.readFile(__filename, 'utf8');
|
||||||
|
assert(content.includes('Native fs Wrapper Tests'), 'readFile did not return expected content');
|
||||||
|
});
|
||||||
|
|
||||||
|
await asyncTest('writeFile writes content asynchronously', async () => {
|
||||||
|
const p = path.join(TMP, 'write-async.txt');
|
||||||
|
await fs.writeFile(p, 'hello async');
|
||||||
|
assertEqual(nativeFs.readFileSync(p, 'utf8'), 'hello async', 'writeFile content mismatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
await asyncTest('readdir returns directory entries', async () => {
|
||||||
|
const dir = path.join(TMP, 'readdir-test');
|
||||||
|
nativeFs.mkdirSync(dir, { recursive: true });
|
||||||
|
nativeFs.writeFileSync(path.join(dir, 'a.txt'), 'a');
|
||||||
|
const entries = await fs.readdir(dir);
|
||||||
|
assert(Array.isArray(entries), 'readdir did not return array');
|
||||||
|
assert(entries.length > 0, 'readdir returned empty array for non-empty dir');
|
||||||
|
});
|
||||||
|
|
||||||
|
await asyncTest('readdir with withFileTypes returns Dirent objects', async () => {
|
||||||
|
const dir = path.join(TMP, 'dirent-test');
|
||||||
|
nativeFs.mkdirSync(dir, { recursive: true });
|
||||||
|
nativeFs.writeFileSync(path.join(dir, 'file.txt'), 'content');
|
||||||
|
nativeFs.mkdirSync(path.join(dir, 'subdir'));
|
||||||
|
|
||||||
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
||||||
|
assert(Array.isArray(entries), 'should return array');
|
||||||
|
|
||||||
|
const fileEntry = entries.find((e) => e.name === 'file.txt');
|
||||||
|
const dirEntry = entries.find((e) => e.name === 'subdir');
|
||||||
|
|
||||||
|
assert(fileEntry && typeof fileEntry.isFile === 'function', 'entry should have isFile method');
|
||||||
|
assert(dirEntry && typeof dirEntry.isDirectory === 'function', 'entry should have isDirectory method');
|
||||||
|
assert(fileEntry.isFile(), 'file entry should return true for isFile()');
|
||||||
|
assert(dirEntry.isDirectory(), 'dir entry should return true for isDirectory()');
|
||||||
|
});
|
||||||
|
|
||||||
|
await asyncTest('stat returns file stats', async () => {
|
||||||
|
const stat = await fs.stat(__dirname);
|
||||||
|
assert(stat.isDirectory(), 'stat did not return directory stat');
|
||||||
|
});
|
||||||
|
|
||||||
|
await asyncTest('access resolves for existing file', async () => {
|
||||||
|
await fs.access(__filename); // should not throw
|
||||||
|
});
|
||||||
|
|
||||||
|
await asyncTest('access rejects for missing file', async () => {
|
||||||
|
let threw = false;
|
||||||
|
try {
|
||||||
|
await fs.access(path.join(TMP, 'nonexistent'));
|
||||||
|
} catch {
|
||||||
|
threw = true;
|
||||||
|
}
|
||||||
|
assert(threw, 'access did not reject for missing file');
|
||||||
|
});
|
||||||
|
|
||||||
|
await asyncTest('rename moves a file', async () => {
|
||||||
|
const src = path.join(TMP, 'rename-src.txt');
|
||||||
|
const dest = path.join(TMP, 'rename-dest.txt');
|
||||||
|
nativeFs.writeFileSync(src, 'rename me');
|
||||||
|
await fs.rename(src, dest);
|
||||||
|
assert(!nativeFs.existsSync(src), 'rename did not remove source');
|
||||||
|
assertEqual(nativeFs.readFileSync(dest, 'utf8'), 'rename me', 'rename content mismatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
await asyncTest('realpath resolves path', async () => {
|
||||||
|
const resolved = await fs.realpath(__dirname);
|
||||||
|
assert(typeof resolved === 'string', 'realpath did not return string');
|
||||||
|
assert(resolved.length > 0, 'realpath returned empty string');
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// ── fs-extra compatible methods ─────────────────────────────────────────
|
||||||
|
|
||||||
|
console.log(`${colors.yellow}fs-extra compatible methods${colors.reset}`);
|
||||||
|
|
||||||
|
await asyncTest('ensureDir creates nested directories', async () => {
|
||||||
|
const p = path.join(TMP, 'ensure', 'deep', 'nested');
|
||||||
|
await fs.ensureDir(p);
|
||||||
|
assert(nativeFs.statSync(p).isDirectory(), 'ensureDir did not create nested dirs');
|
||||||
|
});
|
||||||
|
|
||||||
|
await asyncTest('ensureDir is idempotent on existing directory', async () => {
|
||||||
|
const p = path.join(TMP, 'ensure', 'deep', 'nested');
|
||||||
|
await fs.ensureDir(p); // should not throw
|
||||||
|
assert(nativeFs.statSync(p).isDirectory(), 'ensureDir failed on existing dir');
|
||||||
|
});
|
||||||
|
|
||||||
|
await asyncTest('pathExists returns true for existing path', async () => {
|
||||||
|
assertEqual(await fs.pathExists(__filename), true, 'pathExists returned false for existing file');
|
||||||
|
});
|
||||||
|
|
||||||
|
await asyncTest('pathExists returns false for missing path', async () => {
|
||||||
|
assertEqual(await fs.pathExists(path.join(TMP, 'nonexistent')), false, 'pathExists returned true for missing path');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('pathExistsSync returns true for existing path', () => {
|
||||||
|
assertEqual(fs.pathExistsSync(__filename), true, 'pathExistsSync returned false for existing file');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('pathExistsSync returns false for missing path', () => {
|
||||||
|
assertEqual(fs.pathExistsSync(path.join(TMP, 'nonexistent')), false, 'pathExistsSync returned true for missing path');
|
||||||
|
});
|
||||||
|
|
||||||
|
await asyncTest('copy copies a single file', async () => {
|
||||||
|
const src = path.join(TMP, 'copy-file-src.txt');
|
||||||
|
const dest = path.join(TMP, 'copy-file-dest.txt');
|
||||||
|
nativeFs.writeFileSync(src, 'copy file');
|
||||||
|
await fs.copy(src, dest);
|
||||||
|
assertEqual(nativeFs.readFileSync(dest, 'utf8'), 'copy file', 'copy file content mismatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
await asyncTest('copy creates parent directories for dest', async () => {
|
||||||
|
const src = path.join(TMP, 'copy-mkdir-src.txt');
|
||||||
|
nativeFs.writeFileSync(src, 'copy mkdir');
|
||||||
|
const dest = path.join(TMP, 'copy-deep', 'nested', 'dest.txt');
|
||||||
|
await fs.copy(src, dest);
|
||||||
|
assertEqual(nativeFs.readFileSync(dest, 'utf8'), 'copy mkdir', 'copy with mkdir content mismatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
await asyncTest('copy copies a directory recursively', async () => {
|
||||||
|
const srcDir = path.join(TMP, 'copy-dir-src');
|
||||||
|
nativeFs.mkdirSync(path.join(srcDir, 'sub'), { recursive: true });
|
||||||
|
nativeFs.writeFileSync(path.join(srcDir, 'a.txt'), 'file a');
|
||||||
|
nativeFs.writeFileSync(path.join(srcDir, 'sub', 'b.txt'), 'file b');
|
||||||
|
|
||||||
|
const destDir = path.join(TMP, 'copy-dir-dest');
|
||||||
|
await fs.copy(srcDir, destDir);
|
||||||
|
|
||||||
|
assertEqual(nativeFs.readFileSync(path.join(destDir, 'a.txt'), 'utf8'), 'file a', 'copy dir: top-level file mismatch');
|
||||||
|
assertEqual(nativeFs.readFileSync(path.join(destDir, 'sub', 'b.txt'), 'utf8'), 'file b', 'copy dir: nested file mismatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
await asyncTest('copy respects overwrite: false for files', async () => {
|
||||||
|
const src = path.join(TMP, 'overwrite-src.txt');
|
||||||
|
const dest = path.join(TMP, 'overwrite-dest.txt');
|
||||||
|
nativeFs.writeFileSync(src, 'new content');
|
||||||
|
nativeFs.writeFileSync(dest, 'original content');
|
||||||
|
await fs.copy(src, dest, { overwrite: false });
|
||||||
|
assertEqual(nativeFs.readFileSync(dest, 'utf8'), 'original content', 'copy overwrote file when overwrite: false');
|
||||||
|
});
|
||||||
|
|
||||||
|
await asyncTest('copy respects overwrite: false for directories', async () => {
|
||||||
|
const srcDir = path.join(TMP, 'ow-dir-src');
|
||||||
|
nativeFs.mkdirSync(srcDir, { recursive: true });
|
||||||
|
nativeFs.writeFileSync(path.join(srcDir, 'file.txt'), 'new');
|
||||||
|
|
||||||
|
const destDir = path.join(TMP, 'ow-dir-dest');
|
||||||
|
nativeFs.mkdirSync(destDir, { recursive: true });
|
||||||
|
nativeFs.writeFileSync(path.join(destDir, 'file.txt'), 'original');
|
||||||
|
|
||||||
|
await fs.copy(srcDir, destDir, { overwrite: false });
|
||||||
|
assertEqual(nativeFs.readFileSync(path.join(destDir, 'file.txt'), 'utf8'), 'original', 'copy dir overwrote file when overwrite: false');
|
||||||
|
});
|
||||||
|
|
||||||
|
await asyncTest('copy respects filter option for files', async () => {
|
||||||
|
const srcDir = path.join(TMP, 'filter-src');
|
||||||
|
nativeFs.mkdirSync(srcDir, { recursive: true });
|
||||||
|
nativeFs.writeFileSync(path.join(srcDir, 'keep.txt'), 'keep me');
|
||||||
|
nativeFs.writeFileSync(path.join(srcDir, 'skip.log'), 'skip me');
|
||||||
|
|
||||||
|
const destDir = path.join(TMP, 'filter-dest');
|
||||||
|
await fs.copy(srcDir, destDir, {
|
||||||
|
filter: (src) => !src.endsWith('.log'),
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(nativeFs.existsSync(path.join(destDir, 'keep.txt')), 'filter: kept file is missing');
|
||||||
|
assert(!nativeFs.existsSync(path.join(destDir, 'skip.log')), 'filter: skipped file was copied');
|
||||||
|
});
|
||||||
|
|
||||||
|
await asyncTest('copy respects filter option for directories', async () => {
|
||||||
|
const srcDir = path.join(TMP, 'filter-dir-src');
|
||||||
|
nativeFs.mkdirSync(path.join(srcDir, 'include'), { recursive: true });
|
||||||
|
nativeFs.mkdirSync(path.join(srcDir, 'node_modules'), { recursive: true });
|
||||||
|
nativeFs.writeFileSync(path.join(srcDir, 'include', 'a.txt'), 'included');
|
||||||
|
nativeFs.writeFileSync(path.join(srcDir, 'node_modules', 'b.txt'), 'excluded');
|
||||||
|
|
||||||
|
const destDir = path.join(TMP, 'filter-dir-dest');
|
||||||
|
await fs.copy(srcDir, destDir, {
|
||||||
|
filter: (src) => !src.includes('node_modules'),
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(nativeFs.existsSync(path.join(destDir, 'include', 'a.txt')), 'filter: included dir file is missing');
|
||||||
|
assert(!nativeFs.existsSync(path.join(destDir, 'node_modules')), 'filter: excluded dir was copied');
|
||||||
|
});
|
||||||
|
|
||||||
|
await asyncTest('copy filter skips top-level src when filter returns false', async () => {
|
||||||
|
const src = path.join(TMP, 'filter-skip-src.txt');
|
||||||
|
const dest = path.join(TMP, 'filter-skip-dest.txt');
|
||||||
|
nativeFs.writeFileSync(src, 'should not be copied');
|
||||||
|
await fs.copy(src, dest, {
|
||||||
|
filter: () => false,
|
||||||
|
});
|
||||||
|
assert(!nativeFs.existsSync(dest), 'filter: file was copied despite filter returning false');
|
||||||
|
});
|
||||||
|
|
||||||
|
await asyncTest('remove deletes a file', async () => {
|
||||||
|
const p = path.join(TMP, 'remove-file.txt');
|
||||||
|
nativeFs.writeFileSync(p, 'delete me');
|
||||||
|
await fs.remove(p);
|
||||||
|
assert(!nativeFs.existsSync(p), 'remove did not delete file');
|
||||||
|
});
|
||||||
|
|
||||||
|
await asyncTest('remove deletes a directory recursively', async () => {
|
||||||
|
const dir = path.join(TMP, 'remove-dir');
|
||||||
|
nativeFs.mkdirSync(path.join(dir, 'sub'), { recursive: true });
|
||||||
|
nativeFs.writeFileSync(path.join(dir, 'sub', 'file.txt'), 'nested');
|
||||||
|
await fs.remove(dir);
|
||||||
|
assert(!nativeFs.existsSync(dir), 'remove did not delete directory');
|
||||||
|
});
|
||||||
|
|
||||||
|
await asyncTest('remove does not throw for missing path', async () => {
|
||||||
|
await fs.remove(path.join(TMP, 'nonexistent-remove-target'));
|
||||||
|
// should not throw — force: true
|
||||||
|
});
|
||||||
|
|
||||||
|
await asyncTest('move renames a file', async () => {
|
||||||
|
const src = path.join(TMP, 'move-src.txt');
|
||||||
|
const dest = path.join(TMP, 'move-dest.txt');
|
||||||
|
nativeFs.writeFileSync(src, 'move me');
|
||||||
|
await fs.move(src, dest);
|
||||||
|
assert(!nativeFs.existsSync(src), 'move did not remove source');
|
||||||
|
assertEqual(nativeFs.readFileSync(dest, 'utf8'), 'move me', 'move content mismatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
await asyncTest('move renames a directory', async () => {
|
||||||
|
const srcDir = path.join(TMP, 'move-dir-src');
|
||||||
|
nativeFs.mkdirSync(srcDir, { recursive: true });
|
||||||
|
nativeFs.writeFileSync(path.join(srcDir, 'file.txt'), 'dir move');
|
||||||
|
|
||||||
|
const destDir = path.join(TMP, 'move-dir-dest');
|
||||||
|
await fs.move(srcDir, destDir);
|
||||||
|
assert(!nativeFs.existsSync(srcDir), 'move did not remove source dir');
|
||||||
|
assertEqual(nativeFs.readFileSync(path.join(destDir, 'file.txt'), 'utf8'), 'dir move', 'move dir content mismatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('readJsonSync parses JSON file', () => {
|
||||||
|
const p = path.join(TMP, 'test.json');
|
||||||
|
nativeFs.writeFileSync(p, JSON.stringify({ key: 'value', num: 42 }));
|
||||||
|
const result = fs.readJsonSync(p);
|
||||||
|
assertEqual(result.key, 'value', 'readJsonSync key mismatch');
|
||||||
|
assertEqual(result.num, 42, 'readJsonSync num mismatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('readJsonSync throws on invalid JSON', () => {
|
||||||
|
const p = path.join(TMP, 'bad.json');
|
||||||
|
nativeFs.writeFileSync(p, '{ invalid json }');
|
||||||
|
let threw = false;
|
||||||
|
let errorMessage = '';
|
||||||
|
try {
|
||||||
|
fs.readJsonSync(p);
|
||||||
|
} catch (error) {
|
||||||
|
threw = true;
|
||||||
|
errorMessage = error.message;
|
||||||
|
}
|
||||||
|
assert(threw, 'readJsonSync did not throw on invalid JSON');
|
||||||
|
assert(errorMessage.includes(p), 'readJsonSync error did not include file path');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('readJsonSync strips UTF-8 BOM', () => {
|
||||||
|
const p = path.join(TMP, 'bom.json');
|
||||||
|
nativeFs.writeFileSync(p, '\uFEFF{"bom": true}');
|
||||||
|
const result = fs.readJsonSync(p);
|
||||||
|
assertEqual(result.bom, true, 'readJsonSync failed to parse BOM-prefixed JSON');
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// ── Bulk copy stress test ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
console.log(`${colors.yellow}Bulk copy determinism${colors.reset}`);
|
||||||
|
|
||||||
|
await asyncTest('copy preserves all files in a large directory tree', async () => {
|
||||||
|
// Create a tree with 200+ files to verify no silent loss
|
||||||
|
const srcDir = path.join(TMP, 'bulk-src');
|
||||||
|
const fileCount = 250;
|
||||||
|
|
||||||
|
for (let i = 0; i < fileCount; i++) {
|
||||||
|
const subDir = path.join(srcDir, `dir-${String(Math.floor(i / 10)).padStart(2, '0')}`);
|
||||||
|
nativeFs.mkdirSync(subDir, { recursive: true });
|
||||||
|
nativeFs.writeFileSync(path.join(subDir, `file-${i}.txt`), `content-${i}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const destDir = path.join(TMP, 'bulk-dest');
|
||||||
|
await fs.copy(srcDir, destDir);
|
||||||
|
|
||||||
|
// Count all files in destination
|
||||||
|
let destCount = 0;
|
||||||
|
const countFiles = (dir) => {
|
||||||
|
const entries = nativeFs.readdirSync(dir, { withFileTypes: true });
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
countFiles(path.join(dir, entry.name));
|
||||||
|
} else {
|
||||||
|
destCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
countFiles(destDir);
|
||||||
|
|
||||||
|
assertEqual(destCount, fileCount, `bulk copy lost files: expected ${fileCount}, got ${destCount}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// ── Cleanup ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
teardown();
|
||||||
|
|
||||||
|
// ── Summary ─────────────────────────────────────────────────────────────
|
||||||
|
console.log(`${colors.cyan}========================================`);
|
||||||
|
console.log('Test Results:');
|
||||||
|
console.log(` Total: ${totalTests}`);
|
||||||
|
console.log(` Passed: ${colors.green}${passedTests}${colors.reset}`);
|
||||||
|
console.log(` Failed: ${colors.red}${totalTests - passedTests}${colors.reset}`);
|
||||||
|
console.log(`========================================${colors.reset}\n`);
|
||||||
|
|
||||||
|
if (failures.length === 0) {
|
||||||
|
console.log(`${colors.green}\u2728 All fs wrapper tests passed!${colors.reset}\n`);
|
||||||
|
process.exit(0);
|
||||||
|
} else {
|
||||||
|
console.log(`${colors.red}\u274C Some fs wrapper tests failed${colors.reset}\n`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run tests
|
||||||
|
runTests().catch((error) => {
|
||||||
|
teardown();
|
||||||
|
console.error(`${colors.red}Test runner failed:${colors.reset}`, error.message);
|
||||||
|
console.error(error.stack);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const os = require('node:os');
|
const os = require('node:os');
|
||||||
const fs = require('fs-extra');
|
const fs = require('../tools/cli/lib/fs');
|
||||||
const { loadSkillManifest, getInstallToBmad } = require('../tools/cli/installers/lib/ide/shared/skill-manifest');
|
const { loadSkillManifest, getInstallToBmad } = require('../tools/cli/installers/lib/ide/shared/skill-manifest');
|
||||||
|
|
||||||
// ANSI colors
|
// ANSI colors
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const os = require('node:os');
|
const os = require('node:os');
|
||||||
const fs = require('fs-extra');
|
const fs = require('../tools/cli/lib/fs');
|
||||||
const { YamlXmlBuilder } = require('../tools/cli/lib/yaml-xml-builder');
|
const { YamlXmlBuilder } = require('../tools/cli/lib/yaml-xml-builder');
|
||||||
const { ManifestGenerator } = require('../tools/cli/installers/lib/core/manifest-generator');
|
const { ManifestGenerator } = require('../tools/cli/installers/lib/core/manifest-generator');
|
||||||
const { IdeManager } = require('../tools/cli/installers/lib/ide/manager');
|
const { IdeManager } = require('../tools/cli/installers/lib/ide/manager');
|
||||||
|
|
@ -81,6 +81,60 @@ async function createTestBmadFixture() {
|
||||||
return fixtureDir;
|
return fixtureDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createSkillCollisionFixture() {
|
||||||
|
const fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-skill-collision-'));
|
||||||
|
const fixtureDir = path.join(fixtureRoot, '_bmad');
|
||||||
|
const configDir = path.join(fixtureDir, '_config');
|
||||||
|
await fs.ensureDir(configDir);
|
||||||
|
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(configDir, 'agent-manifest.csv'),
|
||||||
|
[
|
||||||
|
'name,displayName,title,icon,capabilities,role,identity,communicationStyle,principles,module,path,canonicalId',
|
||||||
|
'"bmad-master","BMAD Master","","","","","","","","core","_bmad/core/agents/bmad-master.md","bmad-master"',
|
||||||
|
'',
|
||||||
|
].join('\n'),
|
||||||
|
);
|
||||||
|
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(configDir, 'workflow-manifest.csv'),
|
||||||
|
[
|
||||||
|
'name,description,module,path,canonicalId',
|
||||||
|
'"help","Workflow help","core","_bmad/core/workflows/help/workflow.md","bmad-help"',
|
||||||
|
'',
|
||||||
|
].join('\n'),
|
||||||
|
);
|
||||||
|
|
||||||
|
await fs.writeFile(path.join(configDir, 'task-manifest.csv'), 'name,displayName,description,module,path,standalone,canonicalId\n');
|
||||||
|
await fs.writeFile(path.join(configDir, 'tool-manifest.csv'), 'name,displayName,description,module,path,standalone,canonicalId\n');
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(configDir, 'skill-manifest.csv'),
|
||||||
|
[
|
||||||
|
'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'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const skillDir = path.join(fixtureDir, 'core', 'tasks', 'bmad-help');
|
||||||
|
await fs.ensureDir(skillDir);
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(skillDir, 'SKILL.md'),
|
||||||
|
['---', 'name: bmad-help', 'description: Native help skill', '---', '', 'Use this skill directly.'].join('\n'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const agentDir = path.join(fixtureDir, 'core', 'agents');
|
||||||
|
await fs.ensureDir(agentDir);
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(agentDir, 'bmad-master.md'),
|
||||||
|
['---', 'name: BMAD Master', 'description: Master agent', '---', '', '<agent name="BMAD Master" title="Master">', '</agent>'].join(
|
||||||
|
'\n',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return { root: fixtureRoot, bmadDir: fixtureDir };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test Suite
|
* Test Suite
|
||||||
*/
|
*/
|
||||||
|
|
@ -104,7 +158,9 @@ async function runTests() {
|
||||||
const tempOutput = path.join(__dirname, 'temp-pm-agent.md');
|
const tempOutput = path.join(__dirname, 'temp-pm-agent.md');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await builder.buildAgent(pmAgentPath, null, tempOutput, { includeMetadata: true });
|
const result = await builder.buildAgent(pmAgentPath, null, tempOutput, {
|
||||||
|
includeMetadata: true,
|
||||||
|
});
|
||||||
|
|
||||||
assert(result && result.outputPath === tempOutput, 'Agent compilation returns result object with outputPath');
|
assert(result && result.outputPath === tempOutput, 'Agent compilation returns result object with outputPath');
|
||||||
|
|
||||||
|
|
@ -808,7 +864,9 @@ async function runTests() {
|
||||||
const tempOutput = path.join(__dirname, 'temp-qa-agent.md');
|
const tempOutput = path.join(__dirname, 'temp-qa-agent.md');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await builder.buildAgent(qaAgentPath, null, tempOutput, { includeMetadata: true });
|
const result = await builder.buildAgent(qaAgentPath, null, tempOutput, {
|
||||||
|
includeMetadata: true,
|
||||||
|
});
|
||||||
const compiled = await fs.readFile(tempOutput, 'utf8');
|
const compiled = await fs.readFile(tempOutput, 'utf8');
|
||||||
|
|
||||||
assert(compiled.includes('QA Engineer'), 'QA agent compilation includes agent title');
|
assert(compiled.includes('QA Engineer'), 'QA agent compilation includes agent title');
|
||||||
|
|
@ -1770,6 +1828,50 @@ async function runTests() {
|
||||||
|
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Test 31: Skill-format installs report unique skill directories
|
||||||
|
// ============================================================
|
||||||
|
console.log(`${colors.yellow}Test Suite 31: Skill Count Reporting${colors.reset}\n`);
|
||||||
|
|
||||||
|
let collisionFixtureRoot = null;
|
||||||
|
let collisionProjectDir = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
clearCache();
|
||||||
|
const collisionFixture = await createSkillCollisionFixture();
|
||||||
|
collisionFixtureRoot = collisionFixture.root;
|
||||||
|
collisionProjectDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-antigravity-test-'));
|
||||||
|
|
||||||
|
const ideManager = new IdeManager();
|
||||||
|
await ideManager.ensureInitialized();
|
||||||
|
const result = await ideManager.setup('antigravity', collisionProjectDir, collisionFixture.bmadDir, {
|
||||||
|
silent: true,
|
||||||
|
selectedModules: ['core'],
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(result.success === true, 'Antigravity setup succeeds with overlapping skill names');
|
||||||
|
assert(result.detail === '2 agents', 'Installer detail reports agents separately from skills');
|
||||||
|
assert(result.handlerResult.results.skillDirectories === 2, 'Result exposes unique skill directory count');
|
||||||
|
assert(result.handlerResult.results.agents === 2, 'Result retains generated agent write count');
|
||||||
|
assert(result.handlerResult.results.workflows === 1, 'Result retains generated workflow count');
|
||||||
|
assert(result.handlerResult.results.skills === 1, 'Result retains verbatim skill count');
|
||||||
|
assert(
|
||||||
|
await fs.pathExists(path.join(collisionProjectDir, '.agent', 'skills', 'bmad-agent-bmad-master', 'SKILL.md')),
|
||||||
|
'Agent skill directory is created',
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
await fs.pathExists(path.join(collisionProjectDir, '.agent', 'skills', 'bmad-help', 'SKILL.md')),
|
||||||
|
'Overlapping skill directory is created once',
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
assert(false, 'Skill-format unique count test succeeds', error.message);
|
||||||
|
} finally {
|
||||||
|
if (collisionProjectDir) await fs.remove(collisionProjectDir).catch(() => {});
|
||||||
|
if (collisionFixtureRoot) await fs.remove(collisionFixtureRoot).catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Summary
|
// Summary
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ module.exports = {
|
||||||
const { bmadDir } = await installer.findBmadDir(projectDir);
|
const { bmadDir } = await installer.findBmadDir(projectDir);
|
||||||
|
|
||||||
// Check if bmad directory exists
|
// Check if bmad directory exists
|
||||||
const fs = require('fs-extra');
|
const fs = require('../lib/fs');
|
||||||
if (!(await fs.pathExists(bmadDir))) {
|
if (!(await fs.pathExists(bmadDir))) {
|
||||||
await prompts.log.warn('No BMAD installation found in the current directory.');
|
await prompts.log.warn('No BMAD installation found in the current directory.');
|
||||||
await prompts.log.message(`Expected location: ${bmadDir}`);
|
await prompts.log.message(`Expected location: ${bmadDir}`);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('../lib/fs');
|
||||||
const prompts = require('../lib/prompts');
|
const prompts = require('../lib/prompts');
|
||||||
const { Installer } = require('../installers/lib/core/installer');
|
const { Installer } = require('../installers/lib/core/installer');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,15 @@
|
||||||
# allowing us to keep the source of these projects in separate repos.
|
# allowing us to keep the source of these projects in separate repos.
|
||||||
|
|
||||||
modules:
|
modules:
|
||||||
bmad-builder:
|
# bmad-builder:
|
||||||
url: https://github.com/bmad-code-org/bmad-builder
|
# url: https://github.com/bmad-code-org/bmad-builder
|
||||||
module-definition: src/module.yaml
|
# module-definition: src/module.yaml
|
||||||
code: bmb
|
# code: bmb
|
||||||
name: "BMad Builder"
|
# name: "BMad Builder"
|
||||||
description: "Agent, Workflow and Module Builder"
|
# description: "Agent, Workflow and Module Builder"
|
||||||
defaultSelected: false
|
# defaultSelected: false
|
||||||
type: bmad-org
|
# type: bmad-org
|
||||||
npmPackage: bmad-builder
|
# npmPackage: bmad-builder
|
||||||
|
|
||||||
bmad-creative-intelligence-suite:
|
bmad-creative-intelligence-suite:
|
||||||
url: https://github.com/bmad-code-org/bmad-module-creative-intelligence-suite
|
url: https://github.com/bmad-code-org/bmad-module-creative-intelligence-suite
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('../../../lib/fs');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
const { getProjectRoot, getModulePath } = require('../../../lib/project-root');
|
const { getProjectRoot, getModulePath } = require('../../../lib/project-root');
|
||||||
const { CLIUtils } = require('../../../lib/cli-utils');
|
const { CLIUtils } = require('../../../lib/cli-utils');
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
* and can be checked into source control
|
* and can be checked into source control
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const fs = require('fs-extra');
|
const fs = require('../../../lib/fs');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const crypto = require('node:crypto');
|
const crypto = require('node:crypto');
|
||||||
const prompts = require('../../../lib/prompts');
|
const prompts = require('../../../lib/prompts');
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
const fs = require('fs-extra');
|
const fs = require('../../../lib/fs');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const glob = require('glob');
|
const glob = require('glob');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('../../../lib/fs');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
const { Manifest } = require('./manifest');
|
const { Manifest } = require('./manifest');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('../../../lib/fs');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
const prompts = require('../../../lib/prompts');
|
const prompts = require('../../../lib/prompts');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('../../../lib/fs');
|
||||||
const { Detector } = require('./detector');
|
const { Detector } = require('./detector');
|
||||||
const { Manifest } = require('./manifest');
|
const { Manifest } = require('./manifest');
|
||||||
const { ModuleManager } = require('../modules/manager');
|
const { ModuleManager } = require('../modules/manager');
|
||||||
|
|
@ -87,7 +87,7 @@ class Installer {
|
||||||
if (textExtensions.includes(ext)) {
|
if (textExtensions.includes(ext)) {
|
||||||
try {
|
try {
|
||||||
// Read the file content
|
// Read the file content
|
||||||
let content = await fs.readFile(sourcePath, 'utf8');
|
const content = await fs.readFile(sourcePath, 'utf8');
|
||||||
|
|
||||||
// Write to target with replaced content
|
// Write to target with replaced content
|
||||||
await fs.ensureDir(path.dirname(targetPath));
|
await fs.ensureDir(path.dirname(targetPath));
|
||||||
|
|
@ -260,7 +260,7 @@ class Installer {
|
||||||
|
|
||||||
// Collect configurations for modules (skip if quick update already collected them)
|
// Collect configurations for modules (skip if quick update already collected them)
|
||||||
let moduleConfigs;
|
let moduleConfigs;
|
||||||
let customModulePaths = new Map();
|
const customModulePaths = new Map();
|
||||||
|
|
||||||
if (config._quickUpdate) {
|
if (config._quickUpdate) {
|
||||||
// Quick update already collected all configs, use them directly
|
// Quick update already collected all configs, use them directly
|
||||||
|
|
@ -524,7 +524,9 @@ class Installer {
|
||||||
// Also check cache directory for custom modules (like quick update does)
|
// Also check cache directory for custom modules (like quick update does)
|
||||||
const cacheDir = path.join(bmadDir, '_config', 'custom');
|
const cacheDir = path.join(bmadDir, '_config', 'custom');
|
||||||
if (await fs.pathExists(cacheDir)) {
|
if (await fs.pathExists(cacheDir)) {
|
||||||
const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
|
const cachedModules = await fs.readdir(cacheDir, {
|
||||||
|
withFileTypes: true,
|
||||||
|
});
|
||||||
|
|
||||||
for (const cachedModule of cachedModules) {
|
for (const cachedModule of cachedModules) {
|
||||||
const moduleId = cachedModule.name;
|
const moduleId = cachedModule.name;
|
||||||
|
|
@ -585,7 +587,9 @@ class Installer {
|
||||||
const relativePath = path.relative(bmadDir, modifiedFile.path);
|
const relativePath = path.relative(bmadDir, modifiedFile.path);
|
||||||
const tempBackupPath = path.join(tempModifiedBackupDir, relativePath);
|
const tempBackupPath = path.join(tempModifiedBackupDir, relativePath);
|
||||||
await fs.ensureDir(path.dirname(tempBackupPath));
|
await fs.ensureDir(path.dirname(tempBackupPath));
|
||||||
await fs.copy(modifiedFile.path, tempBackupPath, { overwrite: true });
|
await fs.copy(modifiedFile.path, tempBackupPath, {
|
||||||
|
overwrite: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
spinner.stop(`Backed up ${modifiedFiles.length} modified files`);
|
spinner.stop(`Backed up ${modifiedFiles.length} modified files`);
|
||||||
|
|
||||||
|
|
@ -608,7 +612,9 @@ class Installer {
|
||||||
// Also check cache directory for custom modules (like quick update does)
|
// Also check cache directory for custom modules (like quick update does)
|
||||||
const cacheDir = path.join(bmadDir, '_config', 'custom');
|
const cacheDir = path.join(bmadDir, '_config', 'custom');
|
||||||
if (await fs.pathExists(cacheDir)) {
|
if (await fs.pathExists(cacheDir)) {
|
||||||
const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
|
const cachedModules = await fs.readdir(cacheDir, {
|
||||||
|
withFileTypes: true,
|
||||||
|
});
|
||||||
|
|
||||||
for (const cachedModule of cachedModules) {
|
for (const cachedModule of cachedModules) {
|
||||||
const moduleId = cachedModule.name;
|
const moduleId = cachedModule.name;
|
||||||
|
|
@ -668,7 +674,9 @@ class Installer {
|
||||||
const relativePath = path.relative(bmadDir, modifiedFile.path);
|
const relativePath = path.relative(bmadDir, modifiedFile.path);
|
||||||
const tempBackupPath = path.join(tempModifiedBackupDir, relativePath);
|
const tempBackupPath = path.join(tempModifiedBackupDir, relativePath);
|
||||||
await fs.ensureDir(path.dirname(tempBackupPath));
|
await fs.ensureDir(path.dirname(tempBackupPath));
|
||||||
await fs.copy(modifiedFile.path, tempBackupPath, { overwrite: true });
|
await fs.copy(modifiedFile.path, tempBackupPath, {
|
||||||
|
overwrite: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
spinner.stop(`Backed up ${modifiedFiles.length} modified files`);
|
spinner.stop(`Backed up ${modifiedFiles.length} modified files`);
|
||||||
config._tempModifiedBackupDir = tempModifiedBackupDir;
|
config._tempModifiedBackupDir = tempModifiedBackupDir;
|
||||||
|
|
@ -907,7 +915,11 @@ class Installer {
|
||||||
let taskResolution;
|
let taskResolution;
|
||||||
|
|
||||||
// Collect directory creation results for output after tasks() completes
|
// Collect directory creation results for output after tasks() completes
|
||||||
const dirResults = { createdDirs: [], movedDirs: [], createdWdsFolders: [] };
|
const dirResults = {
|
||||||
|
createdDirs: [],
|
||||||
|
movedDirs: [],
|
||||||
|
createdWdsFolders: [],
|
||||||
|
};
|
||||||
|
|
||||||
// Build task list conditionally
|
// Build task list conditionally
|
||||||
const installTasks = [];
|
const installTasks = [];
|
||||||
|
|
@ -919,7 +931,9 @@ class Installer {
|
||||||
task: async (message) => {
|
task: async (message) => {
|
||||||
await this.installCoreWithDependencies(bmadDir, { core: {} });
|
await this.installCoreWithDependencies(bmadDir, { core: {} });
|
||||||
addResult('Core', 'ok', isQuickUpdate ? 'updated' : 'installed');
|
addResult('Core', 'ok', isQuickUpdate ? 'updated' : 'installed');
|
||||||
await this.generateModuleConfigs(bmadDir, { core: config.coreConfig || {} });
|
await this.generateModuleConfigs(bmadDir, {
|
||||||
|
core: config.coreConfig || {},
|
||||||
|
});
|
||||||
return isQuickUpdate ? 'Core updated' : 'Core installed';
|
return isQuickUpdate ? 'Core updated' : 'Core installed';
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -965,7 +979,11 @@ class Installer {
|
||||||
const cachedModule = finalCustomContent.cachedModules.find((m) => m.id === moduleName);
|
const cachedModule = finalCustomContent.cachedModules.find((m) => m.id === moduleName);
|
||||||
if (cachedModule) {
|
if (cachedModule) {
|
||||||
isCustomModule = true;
|
isCustomModule = true;
|
||||||
customInfo = { id: moduleName, path: cachedModule.cachePath, config: {} };
|
customInfo = {
|
||||||
|
id: moduleName,
|
||||||
|
path: cachedModule.cachePath,
|
||||||
|
config: {},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1015,7 +1033,11 @@ class Installer {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
await this.generateModuleConfigs(bmadDir, {
|
await this.generateModuleConfigs(bmadDir, {
|
||||||
[moduleName]: { ...config.coreConfig, ...customInfo.config, ...collectedModuleConfig },
|
[moduleName]: {
|
||||||
|
...config.coreConfig,
|
||||||
|
...customInfo.config,
|
||||||
|
...collectedModuleConfig,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (!resolution || !resolution.byModule) {
|
if (!resolution || !resolution.byModule) {
|
||||||
|
|
@ -1153,12 +1175,6 @@ class Installer {
|
||||||
preservedModules: modulesForCsvPreserve,
|
preservedModules: modulesForCsvPreserve,
|
||||||
});
|
});
|
||||||
|
|
||||||
addResult(
|
|
||||||
'Manifests',
|
|
||||||
'ok',
|
|
||||||
`${manifestStats.workflows} workflows, ${manifestStats.agents} agents, ${manifestStats.tasks} tasks, ${manifestStats.tools} tools`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Merge help catalogs
|
// Merge help catalogs
|
||||||
message('Generating help catalog...');
|
message('Generating help catalog...');
|
||||||
await this.mergeModuleHelpCatalogs(bmadDir);
|
await this.mergeModuleHelpCatalogs(bmadDir);
|
||||||
|
|
@ -1379,10 +1395,27 @@ class Installer {
|
||||||
*/
|
*/
|
||||||
async renderInstallSummary(results, context = {}) {
|
async renderInstallSummary(results, context = {}) {
|
||||||
const color = await prompts.getColor();
|
const color = await prompts.getColor();
|
||||||
|
const selectedIdes = new Set((context.ides || []).map((ide) => String(ide).toLowerCase()));
|
||||||
|
|
||||||
// Build step lines with status indicators
|
// Build step lines with status indicators
|
||||||
const lines = [];
|
const lines = [];
|
||||||
for (const r of results) {
|
for (const r of results) {
|
||||||
|
let stepLabel = null;
|
||||||
|
|
||||||
|
if (r.status !== 'ok') {
|
||||||
|
stepLabel = r.step;
|
||||||
|
} else if (r.step === 'Core') {
|
||||||
|
stepLabel = 'BMAD';
|
||||||
|
} else if (r.step.startsWith('Module: ')) {
|
||||||
|
stepLabel = r.step;
|
||||||
|
} else if (selectedIdes.has(String(r.step).toLowerCase())) {
|
||||||
|
stepLabel = r.step;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stepLabel) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let icon;
|
let icon;
|
||||||
if (r.status === 'ok') {
|
if (r.status === 'ok') {
|
||||||
icon = color.green('\u2713');
|
icon = color.green('\u2713');
|
||||||
|
|
@ -1392,7 +1425,11 @@ class Installer {
|
||||||
icon = color.red('\u2717');
|
icon = color.red('\u2717');
|
||||||
}
|
}
|
||||||
const detail = r.detail ? color.dim(` (${r.detail})`) : '';
|
const detail = r.detail ? color.dim(` (${r.detail})`) : '';
|
||||||
lines.push(` ${icon} ${r.step}${detail}`);
|
lines.push(` ${icon} ${stepLabel}${detail}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((context.ides || []).length === 0) {
|
||||||
|
lines.push(` ${color.green('\u2713')} No IDE selected ${color.dim('(installed in _bmad only)')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context and warnings
|
// Context and warnings
|
||||||
|
|
@ -1415,8 +1452,10 @@ class Installer {
|
||||||
` Join our Discord: ${color.dim('https://discord.gg/gk8jAdXWmj')}`,
|
` Join our Discord: ${color.dim('https://discord.gg/gk8jAdXWmj')}`,
|
||||||
` Star us on GitHub: ${color.dim('https://github.com/bmad-code-org/BMAD-METHOD/')}`,
|
` Star us on GitHub: ${color.dim('https://github.com/bmad-code-org/BMAD-METHOD/')}`,
|
||||||
` Subscribe on YouTube: ${color.dim('https://www.youtube.com/@BMadCode')}`,
|
` Subscribe on YouTube: ${color.dim('https://www.youtube.com/@BMadCode')}`,
|
||||||
` Invoke the ${color.cyan('bmad-help')} skill in your IDE Agent to get started`,
|
|
||||||
);
|
);
|
||||||
|
if (context.ides && context.ides.length > 0) {
|
||||||
|
lines.push(` Invoke the ${color.cyan('bmad-help')} skill in your IDE Agent to get started`);
|
||||||
|
}
|
||||||
|
|
||||||
await prompts.note(lines.join('\n'), 'BMAD is ready to use!');
|
await prompts.note(lines.join('\n'), 'BMAD is ready to use!');
|
||||||
}
|
}
|
||||||
|
|
@ -1457,7 +1496,9 @@ class Installer {
|
||||||
// Also check cache directory
|
// Also check cache directory
|
||||||
const cacheDir = path.join(bmadDir, '_config', 'custom');
|
const cacheDir = path.join(bmadDir, '_config', 'custom');
|
||||||
if (await fs.pathExists(cacheDir)) {
|
if (await fs.pathExists(cacheDir)) {
|
||||||
const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
|
const cachedModules = await fs.readdir(cacheDir, {
|
||||||
|
withFileTypes: true,
|
||||||
|
});
|
||||||
|
|
||||||
for (const cachedModule of cachedModules) {
|
for (const cachedModule of cachedModules) {
|
||||||
if (cachedModule.isDirectory()) {
|
if (cachedModule.isDirectory()) {
|
||||||
|
|
@ -1532,7 +1573,9 @@ class Installer {
|
||||||
|
|
||||||
for (const module of existingInstall.modules) {
|
for (const module of existingInstall.modules) {
|
||||||
spinner.message(`Updating module: ${module.id}...`);
|
spinner.message(`Updating module: ${module.id}...`);
|
||||||
await this.moduleManager.update(module.id, bmadDir, config.force, { installer: this });
|
await this.moduleManager.update(module.id, bmadDir, config.force, {
|
||||||
|
installer: this,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update manifest
|
// Update manifest
|
||||||
|
|
@ -1591,7 +1634,9 @@ class Installer {
|
||||||
|
|
||||||
// 2. IDE CLEANUP (before _bmad/ deletion so configs are accessible)
|
// 2. IDE CLEANUP (before _bmad/ deletion so configs are accessible)
|
||||||
if (options.removeIdeConfigs !== false) {
|
if (options.removeIdeConfigs !== false) {
|
||||||
await this.uninstallIdeConfigs(projectDir, existingInstall, { silent: options.silent });
|
await this.uninstallIdeConfigs(projectDir, existingInstall, {
|
||||||
|
silent: options.silent,
|
||||||
|
});
|
||||||
removed.ideConfigs = true;
|
removed.ideConfigs = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1830,7 +1875,11 @@ class Installer {
|
||||||
|
|
||||||
// Lookup agent info
|
// Lookup agent info
|
||||||
const cleanAgentName = agentName ? agentName.trim() : '';
|
const cleanAgentName = agentName ? agentName.trim() : '';
|
||||||
const agentData = agentInfo.get(cleanAgentName) || { command: '', displayName: '', title: '' };
|
const agentData = agentInfo.get(cleanAgentName) || {
|
||||||
|
command: '',
|
||||||
|
displayName: '',
|
||||||
|
title: '',
|
||||||
|
};
|
||||||
|
|
||||||
// Build new row with agent info
|
// Build new row with agent info
|
||||||
const newRow = [
|
const newRow = [
|
||||||
|
|
@ -1885,8 +1934,8 @@ class Installer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sequence comparison
|
// Sequence comparison
|
||||||
const seqA = parseInt(colsA[4] || '0', 10);
|
const seqA = Number.parseInt(colsA[4] || '0', 10);
|
||||||
const seqB = parseInt(colsB[4] || '0', 10);
|
const seqB = Number.parseInt(colsB[4] || '0', 10);
|
||||||
return seqA - seqB;
|
return seqA - seqB;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -2428,7 +2477,9 @@ class Installer {
|
||||||
}
|
}
|
||||||
const cacheDir = path.join(bmadDir, '_config', 'custom');
|
const cacheDir = path.join(bmadDir, '_config', 'custom');
|
||||||
if (await fs.pathExists(cacheDir)) {
|
if (await fs.pathExists(cacheDir)) {
|
||||||
const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
|
const cachedModules = await fs.readdir(cacheDir, {
|
||||||
|
withFileTypes: true,
|
||||||
|
});
|
||||||
|
|
||||||
for (const cachedModule of cachedModules) {
|
for (const cachedModule of cachedModules) {
|
||||||
const moduleId = cachedModule.name;
|
const moduleId = cachedModule.name;
|
||||||
|
|
@ -2663,7 +2714,9 @@ class Installer {
|
||||||
const customModuleSources = new Map();
|
const customModuleSources = new Map();
|
||||||
const cacheDir = path.join(bmadDir, '_config', 'custom');
|
const cacheDir = path.join(bmadDir, '_config', 'custom');
|
||||||
if (await fs.pathExists(cacheDir)) {
|
if (await fs.pathExists(cacheDir)) {
|
||||||
const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
|
const cachedModules = await fs.readdir(cacheDir, {
|
||||||
|
withFileTypes: true,
|
||||||
|
});
|
||||||
|
|
||||||
for (const cachedModule of cachedModules) {
|
for (const cachedModule of cachedModules) {
|
||||||
if (cachedModule.isDirectory()) {
|
if (cachedModule.isDirectory()) {
|
||||||
|
|
@ -3135,8 +3188,7 @@ class Installer {
|
||||||
// Remove the module from filesystem and manifest
|
// Remove the module from filesystem and manifest
|
||||||
const modulePath = path.join(bmadDir, missing.id);
|
const modulePath = path.join(bmadDir, missing.id);
|
||||||
if (await fs.pathExists(modulePath)) {
|
if (await fs.pathExists(modulePath)) {
|
||||||
const fsExtra = require('fs-extra');
|
await fs.remove(modulePath);
|
||||||
await fsExtra.remove(modulePath);
|
|
||||||
await prompts.log.warn(`Deleted module directory: ${path.relative(projectRoot, modulePath)}`);
|
await prompts.log.warn(`Deleted module directory: ${path.relative(projectRoot, modulePath)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('../../../lib/fs');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
const crypto = require('node:crypto');
|
const crypto = require('node:crypto');
|
||||||
const csv = require('csv-parse/sync');
|
const csv = require('csv-parse/sync');
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('../../../lib/fs');
|
||||||
const crypto = require('node:crypto');
|
const crypto = require('node:crypto');
|
||||||
const { getProjectRoot } = require('../../../lib/project-root');
|
const { getProjectRoot } = require('../../../lib/project-root');
|
||||||
const prompts = require('../../../lib/prompts');
|
const prompts = require('../../../lib/prompts');
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('../../../lib/fs');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
const prompts = require('../../../lib/prompts');
|
const prompts = require('../../../lib/prompts');
|
||||||
const { FileOps } = require('../../../lib/file-ops');
|
const { FileOps } = require('../../../lib/file-ops');
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('../../../lib/fs');
|
||||||
const { XmlHandler } = require('../../../lib/xml-handler');
|
const { XmlHandler } = require('../../../lib/xml-handler');
|
||||||
const prompts = require('../../../lib/prompts');
|
const prompts = require('../../../lib/prompts');
|
||||||
const { getSourcePath } = require('../../../lib/project-root');
|
const { getSourcePath } = require('../../../lib/project-root');
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
const os = require('node:os');
|
const os = require('node:os');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('../../../lib/fs');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
const { BaseIdeSetup } = require('./_base-ide');
|
const { BaseIdeSetup } = require('./_base-ide');
|
||||||
const prompts = require('../../../lib/prompts');
|
const prompts = require('../../../lib/prompts');
|
||||||
|
|
@ -129,6 +129,7 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
|
||||||
|
|
||||||
const selectedModules = options.selectedModules || [];
|
const selectedModules = options.selectedModules || [];
|
||||||
const results = { agents: 0, workflows: 0, tasks: 0, tools: 0, skills: 0 };
|
const results = { agents: 0, workflows: 0, tasks: 0, tools: 0, skills: 0 };
|
||||||
|
this.skillWriteTracker = config.skill_format ? new Set() : null;
|
||||||
|
|
||||||
// Install standard artifacts (agents, workflows, tasks, tools)
|
// Install standard artifacts (agents, workflows, tasks, tools)
|
||||||
if (!skipStandardArtifacts) {
|
if (!skipStandardArtifacts) {
|
||||||
|
|
@ -159,9 +160,11 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
|
||||||
// Install verbatim skills (type: skill)
|
// Install verbatim skills (type: skill)
|
||||||
if (config.skill_format) {
|
if (config.skill_format) {
|
||||||
results.skills = await this.installVerbatimSkills(projectDir, bmadDir, targetPath, config);
|
results.skills = await this.installVerbatimSkills(projectDir, bmadDir, targetPath, config);
|
||||||
|
results.skillDirectories = this.skillWriteTracker ? this.skillWriteTracker.size : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.printSummary(results, target_dir, options);
|
await this.printSummary(results, target_dir, options);
|
||||||
|
this.skillWriteTracker = null;
|
||||||
return { success: true, results };
|
return { success: true, results };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -495,6 +498,7 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
|
||||||
// Create skill directory
|
// Create skill directory
|
||||||
const skillDir = path.join(targetPath, skillName);
|
const skillDir = path.join(targetPath, skillName);
|
||||||
await this.ensureDir(skillDir);
|
await this.ensureDir(skillDir);
|
||||||
|
this.skillWriteTracker?.add(skillName);
|
||||||
|
|
||||||
// Transform content: rewrite frontmatter for skills format
|
// Transform content: rewrite frontmatter for skills format
|
||||||
const skillContent = this.transformToSkillFormat(content, skillName);
|
const skillContent = this.transformToSkillFormat(content, skillName);
|
||||||
|
|
@ -667,6 +671,7 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
|
||||||
const skillDir = path.join(targetPath, canonicalId);
|
const skillDir = path.join(targetPath, canonicalId);
|
||||||
await fs.remove(skillDir);
|
await fs.remove(skillDir);
|
||||||
await fs.ensureDir(skillDir);
|
await fs.ensureDir(skillDir);
|
||||||
|
this.skillWriteTracker?.add(canonicalId);
|
||||||
|
|
||||||
// Copy all skill files, filtering OS/editor artifacts recursively
|
// Copy all skill files, filtering OS/editor artifacts recursively
|
||||||
const skipPatterns = new Set(['.DS_Store', 'Thumbs.db', 'desktop.ini']);
|
const skipPatterns = new Set(['.DS_Store', 'Thumbs.db', 'desktop.ini']);
|
||||||
|
|
@ -707,11 +712,11 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
|
||||||
async printSummary(results, targetDir, options = {}) {
|
async printSummary(results, targetDir, options = {}) {
|
||||||
if (options.silent) return;
|
if (options.silent) return;
|
||||||
const parts = [];
|
const parts = [];
|
||||||
|
const totalDirs =
|
||||||
|
results.skillDirectories || (results.workflows || 0) + (results.tasks || 0) + (results.tools || 0) + (results.skills || 0);
|
||||||
|
const skillCount = totalDirs - (results.agents || 0);
|
||||||
|
if (skillCount > 0) parts.push(`${skillCount} skills`);
|
||||||
if (results.agents > 0) parts.push(`${results.agents} agents`);
|
if (results.agents > 0) parts.push(`${results.agents} agents`);
|
||||||
if (results.workflows > 0) parts.push(`${results.workflows} workflows`);
|
|
||||||
if (results.tasks > 0) parts.push(`${results.tasks} tasks`);
|
|
||||||
if (results.tools > 0) parts.push(`${results.tools} tools`);
|
|
||||||
if (results.skills > 0) parts.push(`${results.skills} skills`);
|
|
||||||
await prompts.log.success(`${this.name} configured: ${parts.join(', ')} → ${targetDir}`);
|
await prompts.log.success(`${this.name} configured: ${parts.join(', ')} → ${targetDir}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -162,10 +162,10 @@ class IdeManager {
|
||||||
// Config-driven handlers return { success, results: { agents, workflows, tasks, tools } }
|
// Config-driven handlers return { success, results: { agents, workflows, tasks, tools } }
|
||||||
const r = handlerResult.results;
|
const r = handlerResult.results;
|
||||||
const parts = [];
|
const parts = [];
|
||||||
|
const totalDirs = r.skillDirectories || (r.workflows || 0) + (r.tasks || 0) + (r.tools || 0) + (r.skills || 0);
|
||||||
|
const skillCount = totalDirs - (r.agents || 0);
|
||||||
|
if (skillCount > 0) parts.push(`${skillCount} skills`);
|
||||||
if (r.agents > 0) parts.push(`${r.agents} agents`);
|
if (r.agents > 0) parts.push(`${r.agents} agents`);
|
||||||
if (r.workflows > 0) parts.push(`${r.workflows} workflows`);
|
|
||||||
if (r.tasks > 0) parts.push(`${r.tasks} tasks`);
|
|
||||||
if (r.tools > 0) parts.push(`${r.tools} tools`);
|
|
||||||
detail = parts.join(', ');
|
detail = parts.join(', ');
|
||||||
}
|
}
|
||||||
// Propagate handler's success status (default true for backward compat)
|
// Propagate handler's success status (default true for backward compat)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
const fs = require('fs-extra');
|
const fs = require('../../../lib/fs');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('../../../../lib/fs');
|
||||||
const { toColonPath, toDashPath, customAgentColonName, customAgentDashName, BMAD_FOLDER_NAME } = require('./path-utils');
|
const { toColonPath, toDashPath, customAgentColonName, customAgentDashName, BMAD_FOLDER_NAME } = require('./path-utils');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('../../../../lib/fs');
|
||||||
const { loadSkillManifest, getCanonicalId } = require('./skill-manifest');
|
const { loadSkillManifest, getCanonicalId } = require('./skill-manifest');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('../../../../lib/fs');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
const { glob } = require('glob');
|
const { glob } = require('glob');
|
||||||
const { getSourcePath } = require('../../../../lib/project-root');
|
const { getSourcePath } = require('../../../../lib/project-root');
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('../../../../lib/fs');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('../../../../lib/fs');
|
||||||
const csv = require('csv-parse/sync');
|
const csv = require('csv-parse/sync');
|
||||||
const { toColonName, toColonPath, toDashPath, BMAD_FOLDER_NAME } = require('./path-utils');
|
const { toColonName, toColonPath, toDashPath, BMAD_FOLDER_NAME } = require('./path-utils');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('../../../../lib/fs');
|
||||||
const csv = require('csv-parse/sync');
|
const csv = require('csv-parse/sync');
|
||||||
const { BMAD_FOLDER_NAME } = require('./path-utils');
|
const { BMAD_FOLDER_NAME } = require('./path-utils');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
const fs = require('fs-extra');
|
const fs = require('../../lib/fs');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
const prompts = require('../../lib/prompts');
|
const prompts = require('../../lib/prompts');
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
const fs = require('fs-extra');
|
const fs = require('../../../lib/fs');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
const prompts = require('../../../lib/prompts');
|
const prompts = require('../../../lib/prompts');
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('../../../lib/fs');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
const prompts = require('../../../lib/prompts');
|
const prompts = require('../../../lib/prompts');
|
||||||
const { XmlHandler } = require('../../../lib/xml-handler');
|
const { XmlHandler } = require('../../../lib/xml-handler');
|
||||||
|
|
@ -14,7 +14,7 @@ const { BMAD_FOLDER_NAME } = require('../ide/shared/path-utils');
|
||||||
* and agent file management including XML activation block injection.
|
* and agent file management including XML activation block injection.
|
||||||
*
|
*
|
||||||
* @class ModuleManager
|
* @class ModuleManager
|
||||||
* @requires fs-extra
|
* @requires lib/fs
|
||||||
* @requires yaml
|
* @requires yaml
|
||||||
* @requires prompts
|
* @requires prompts
|
||||||
* @requires XmlHandler
|
* @requires XmlHandler
|
||||||
|
|
@ -208,7 +208,9 @@ class ModuleManager {
|
||||||
if (this.bmadDir) {
|
if (this.bmadDir) {
|
||||||
const customCacheDir = path.join(this.bmadDir, '_config', 'custom');
|
const customCacheDir = path.join(this.bmadDir, '_config', 'custom');
|
||||||
if (await fs.pathExists(customCacheDir)) {
|
if (await fs.pathExists(customCacheDir)) {
|
||||||
const cacheEntries = await fs.readdir(customCacheDir, { withFileTypes: true });
|
const cacheEntries = await fs.readdir(customCacheDir, {
|
||||||
|
withFileTypes: true,
|
||||||
|
});
|
||||||
for (const entry of cacheEntries) {
|
for (const entry of cacheEntries) {
|
||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
const cachePath = path.join(customCacheDir, entry.name);
|
const cachePath = path.join(customCacheDir, entry.name);
|
||||||
|
|
@ -387,7 +389,12 @@ class ModuleManager {
|
||||||
const fetchSpinner = await createSpinner();
|
const fetchSpinner = await createSpinner();
|
||||||
fetchSpinner.start(`Fetching ${moduleInfo.name}...`);
|
fetchSpinner.start(`Fetching ${moduleInfo.name}...`);
|
||||||
try {
|
try {
|
||||||
const currentRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
|
const currentRef = execSync('git rev-parse HEAD', {
|
||||||
|
cwd: moduleCacheDir,
|
||||||
|
stdio: 'pipe',
|
||||||
|
})
|
||||||
|
.toString()
|
||||||
|
.trim();
|
||||||
// Fetch and reset to remote - works better with shallow clones than pull
|
// Fetch and reset to remote - works better with shallow clones than pull
|
||||||
execSync('git fetch origin --depth 1', {
|
execSync('git fetch origin --depth 1', {
|
||||||
cwd: moduleCacheDir,
|
cwd: moduleCacheDir,
|
||||||
|
|
@ -399,7 +406,12 @@ class ModuleManager {
|
||||||
stdio: ['ignore', 'pipe', 'pipe'],
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
||||||
});
|
});
|
||||||
const newRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
|
const newRef = execSync('git rev-parse HEAD', {
|
||||||
|
cwd: moduleCacheDir,
|
||||||
|
stdio: 'pipe',
|
||||||
|
})
|
||||||
|
.toString()
|
||||||
|
.trim();
|
||||||
|
|
||||||
fetchSpinner.stop(`Fetched ${moduleInfo.name}`);
|
fetchSpinner.stop(`Fetched ${moduleInfo.name}`);
|
||||||
// Force dependency install if we got new code
|
// Force dependency install if we got new code
|
||||||
|
|
@ -521,7 +533,9 @@ class ModuleManager {
|
||||||
* @param {Object} options.logger - Logger instance for output
|
* @param {Object} options.logger - Logger instance for output
|
||||||
*/
|
*/
|
||||||
async install(moduleName, bmadDir, fileTrackingCallback = null, options = {}) {
|
async install(moduleName, bmadDir, fileTrackingCallback = null, options = {}) {
|
||||||
const sourcePath = await this.findModuleSource(moduleName, { silent: options.silent });
|
const sourcePath = await this.findModuleSource(moduleName, {
|
||||||
|
silent: options.silent,
|
||||||
|
});
|
||||||
const targetPath = path.join(bmadDir, moduleName);
|
const targetPath = path.join(bmadDir, moduleName);
|
||||||
|
|
||||||
// Check if source module exists
|
// Check if source module exists
|
||||||
|
|
@ -619,7 +633,9 @@ class ModuleManager {
|
||||||
if (force) {
|
if (force) {
|
||||||
// Force update - remove and reinstall
|
// Force update - remove and reinstall
|
||||||
await fs.remove(targetPath);
|
await fs.remove(targetPath);
|
||||||
return await this.install(moduleName, bmadDir, null, { installer: options.installer });
|
return await this.install(moduleName, bmadDir, null, {
|
||||||
|
installer: options.installer,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// Selective update - preserve user modifications
|
// Selective update - preserve user modifications
|
||||||
await this.syncModule(sourcePath, targetPath);
|
await this.syncModule(sourcePath, targetPath);
|
||||||
|
|
@ -855,7 +871,7 @@ class ModuleManager {
|
||||||
|
|
||||||
// Check for customizations and build answers object
|
// Check for customizations and build answers object
|
||||||
let customizedFields = [];
|
let customizedFields = [];
|
||||||
let answers = {};
|
const answers = {};
|
||||||
if (await fs.pathExists(customizePath)) {
|
if (await fs.pathExists(customizePath)) {
|
||||||
const customizeContent = await fs.readFile(customizePath, 'utf8');
|
const customizeContent = await fs.readFile(customizePath, 'utf8');
|
||||||
const customizeData = yaml.parse(customizeContent);
|
const customizeData = yaml.parse(customizeContent);
|
||||||
|
|
@ -928,7 +944,9 @@ class ModuleManager {
|
||||||
|
|
||||||
// Copy any non-sidecar files from agent directory (e.g., foo.md)
|
// Copy any non-sidecar files from agent directory (e.g., foo.md)
|
||||||
const agentDir = path.dirname(agentFile);
|
const agentDir = path.dirname(agentFile);
|
||||||
const agentEntries = await fs.readdir(agentDir, { withFileTypes: true });
|
const agentEntries = await fs.readdir(agentDir, {
|
||||||
|
withFileTypes: true,
|
||||||
|
});
|
||||||
|
|
||||||
for (const entry of agentEntries) {
|
for (const entry of agentEntries) {
|
||||||
if (entry.isFile() && !entry.name.endsWith('.agent.yaml') && !entry.name.endsWith('.md')) {
|
if (entry.isFile() && !entry.name.endsWith('.agent.yaml') && !entry.name.endsWith('.md')) {
|
||||||
|
|
@ -1139,7 +1157,11 @@ class ModuleManager {
|
||||||
const moduleConfig = options.moduleConfig || {};
|
const moduleConfig = options.moduleConfig || {};
|
||||||
const existingModuleConfig = options.existingModuleConfig || {};
|
const existingModuleConfig = options.existingModuleConfig || {};
|
||||||
const projectRoot = path.dirname(bmadDir);
|
const projectRoot = path.dirname(bmadDir);
|
||||||
const emptyResult = { createdDirs: [], movedDirs: [], createdWdsFolders: [] };
|
const emptyResult = {
|
||||||
|
createdDirs: [],
|
||||||
|
movedDirs: [],
|
||||||
|
createdWdsFolders: [],
|
||||||
|
};
|
||||||
|
|
||||||
// Special handling for core module - it's in src/core not src/modules
|
// Special handling for core module - it's in src/core not src/modules
|
||||||
let sourcePath;
|
let sourcePath;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
const fs = require('fs-extra');
|
const fs = require('./fs');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const { getSourcePath } = require('./project-root');
|
const { getSourcePath } = require('./project-root');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
const fs = require('fs-extra');
|
const fs = require('./fs');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Analyzes agent YAML files to detect which handlers are needed
|
* Analyzes agent YAML files to detect which handlers are needed
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('./fs');
|
||||||
const { escapeXml } = require('../../lib/xml-utils');
|
const { escapeXml } = require('../../lib/xml-utils');
|
||||||
|
|
||||||
const AgentPartyGenerator = {
|
const AgentPartyGenerator = {
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,7 @@ function buildMenuXml(menuItems) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
xml += ` <item cmd="PM or fuzzy match on party-mode" exec="{project-root}/_bmad/core/workflows/party-mode/workflow.md">[PM] Start Party Mode</item>\n`;
|
xml += ` <item cmd="PM or fuzzy match on party-mode" exec="{project-root}/_bmad/core/workflows/bmad-party-mode/workflow.md">[PM] Start Party Mode</item>\n`;
|
||||||
xml += ` <item cmd="DA or fuzzy match on exit, leave, goodbye or dismiss agent">[DA] Dismiss Agent</item>\n`;
|
xml += ` <item cmd="DA or fuzzy match on exit, leave, goodbye or dismiss agent">[DA] Dismiss Agent</item>\n`;
|
||||||
|
|
||||||
xml += ' </menu>\n';
|
xml += ' </menu>\n';
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
const fs = require('fs-extra');
|
const fs = require('./fs');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const packageJson = require('../../../package.json');
|
const packageJson = require('../../../package.json');
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
const fs = require('fs-extra');
|
const fs = require('./fs');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const crypto = require('node:crypto');
|
const crypto = require('node:crypto');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,175 @@
|
||||||
|
/**
|
||||||
|
* Drop-in replacement for fs-extra that uses only native Node.js fs.
|
||||||
|
*
|
||||||
|
* fs-extra routes every call through graceful-fs, whose EMFILE retry queue
|
||||||
|
* causes non-deterministic file loss on macOS during bulk copy operations.
|
||||||
|
* This module provides the same API surface used by the CLI codebase but
|
||||||
|
* backed entirely by `node:fs` and `node:fs/promises` — no third-party
|
||||||
|
* wrappers, no retry queues, no silent data loss.
|
||||||
|
*
|
||||||
|
* Async methods return native promises (from `node:fs/promises`).
|
||||||
|
* Sync methods delegate directly to `node:fs`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const fsp = require('node:fs/promises');
|
||||||
|
const path = require('node:path');
|
||||||
|
|
||||||
|
// ── Re-export every native fs member ────────────────────────────────────────
|
||||||
|
// Callers that use fs.constants, fs.createReadStream, etc. keep working.
|
||||||
|
module.exports = { ...fs };
|
||||||
|
|
||||||
|
// ── Async methods (return promises, like fs-extra) ──────────────────────────
|
||||||
|
|
||||||
|
module.exports.readFile = fsp.readFile;
|
||||||
|
module.exports.writeFile = fsp.writeFile;
|
||||||
|
module.exports.readdir = fsp.readdir;
|
||||||
|
module.exports.stat = fsp.stat;
|
||||||
|
module.exports.access = fsp.access;
|
||||||
|
module.exports.mkdtemp = fsp.mkdtemp;
|
||||||
|
module.exports.rename = fsp.rename;
|
||||||
|
module.exports.realpath = fsp.realpath;
|
||||||
|
module.exports.rmdir = fsp.rmdir;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively ensure a directory exists.
|
||||||
|
* @param {string} dirPath
|
||||||
|
*/
|
||||||
|
module.exports.ensureDir = async function ensureDir(dirPath) {
|
||||||
|
await fsp.mkdir(dirPath, { recursive: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether a path exists.
|
||||||
|
* @param {string} p
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
module.exports.pathExists = async function pathExists(p) {
|
||||||
|
try {
|
||||||
|
await fsp.access(p);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
if (error && (error.code === 'ENOENT' || error.code === 'ENOTDIR')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronous variant of pathExists.
|
||||||
|
* @param {string} p
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
module.exports.pathExistsSync = function pathExistsSync(p) {
|
||||||
|
return fs.existsSync(p);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively copy a directory tree synchronously.
|
||||||
|
* @param {string} src - Source directory
|
||||||
|
* @param {string} dest - Destination directory
|
||||||
|
* @param {boolean} force - Whether to overwrite existing files
|
||||||
|
* @param {Function} [filter] - Optional filter(srcPath) → boolean; return false to skip
|
||||||
|
*/
|
||||||
|
function copyDirSync(src, dest, force, filter) {
|
||||||
|
if (filter && !filter(src)) return;
|
||||||
|
fs.mkdirSync(dest, { recursive: true });
|
||||||
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
||||||
|
for (const entry of entries) {
|
||||||
|
const srcPath = path.join(src, entry.name);
|
||||||
|
const destPath = path.join(dest, entry.name);
|
||||||
|
if (filter && !filter(srcPath)) continue;
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
copyDirSync(srcPath, destPath, force, filter);
|
||||||
|
} else {
|
||||||
|
if (!force && fs.existsSync(destPath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
fs.copyFileSync(srcPath, destPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy a file or directory.
|
||||||
|
* @param {string} src
|
||||||
|
* @param {string} dest
|
||||||
|
* @param {object} [options]
|
||||||
|
* @param {boolean} [options.overwrite=true]
|
||||||
|
* @param {Function} [options.filter] - Optional filter(srcPath) → boolean; return false to skip
|
||||||
|
*/
|
||||||
|
module.exports.copy = async function copy(src, dest, options = {}) {
|
||||||
|
const overwrite = options.overwrite !== false;
|
||||||
|
const filter = options.filter;
|
||||||
|
|
||||||
|
if (filter && !filter(src)) return;
|
||||||
|
|
||||||
|
const srcStat = await fsp.stat(src);
|
||||||
|
|
||||||
|
if (srcStat.isDirectory()) {
|
||||||
|
copyDirSync(src, dest, overwrite, filter);
|
||||||
|
} else {
|
||||||
|
await fsp.mkdir(path.dirname(dest), { recursive: true });
|
||||||
|
if (!overwrite) {
|
||||||
|
try {
|
||||||
|
await fsp.access(dest);
|
||||||
|
return; // dest exists, skip
|
||||||
|
} catch (error) {
|
||||||
|
if (error && error.code !== 'ENOENT' && error.code !== 'ENOTDIR') {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
// dest doesn't exist, proceed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fs.copyFileSync(src, dest);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively remove a file or directory.
|
||||||
|
* @param {string} p
|
||||||
|
*/
|
||||||
|
module.exports.remove = async function remove(p) {
|
||||||
|
fs.rmSync(p, { recursive: true, force: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move (rename) a file or directory, with cross-device fallback.
|
||||||
|
* @param {string} src
|
||||||
|
* @param {string} dest
|
||||||
|
*/
|
||||||
|
module.exports.move = async function move(src, dest) {
|
||||||
|
try {
|
||||||
|
await fsp.rename(src, dest);
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code === 'EXDEV') {
|
||||||
|
// Cross-device: copy then remove
|
||||||
|
const srcStat = fs.statSync(src);
|
||||||
|
if (srcStat.isDirectory()) {
|
||||||
|
copyDirSync(src, dest, true);
|
||||||
|
} else {
|
||||||
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
||||||
|
fs.copyFileSync(src, dest);
|
||||||
|
}
|
||||||
|
fs.rmSync(src, { recursive: true, force: true });
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read and parse a JSON file synchronously.
|
||||||
|
* @param {string} filePath
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
|
module.exports.readJsonSync = function readJsonSync(filePath) {
|
||||||
|
const raw = fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, '');
|
||||||
|
try {
|
||||||
|
return JSON.parse(raw);
|
||||||
|
} catch (error) {
|
||||||
|
error.message = `Failed to parse JSON in ${filePath}: ${error.message}`;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
const fs = require('fs-extra');
|
const fs = require('./fs');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
const { getProjectRoot } = require('./project-root');
|
const { getProjectRoot } = require('./project-root');
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('./fs');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the BMAD project root directory by looking for package.json
|
* Find the BMAD project root directory by looking for package.json
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue