Compare commits

...

6 Commits

Author SHA1 Message Date
Tolga Karatas 8edc498fe4
Merge 8bb2615561 into d4b4bdfa12 2026-02-16 01:38:20 +00:00
Brian Madison d4b4bdfa12 docs: improve project-context documentation and fix folder structure
- Add explanation/how-to docs for project-context.md feature
- Update workflow-map Context Management section with comprehensive guidance
- Fix folder structure examples to show proper nested subdirectories
  (planning-artifacts/ and implementation-artifacts/ under _bmad-output/)
- Update established-projects.md with project-context creation step
- Standardize terminology: use "existing projects" instead of "brownfield"
2026-02-15 17:32:05 -06:00
Brian Madison d6cc8b060a refactor: rename skill folders to bmad-os- prefix
Standardizes all skill folder names with bmad-os- prefix:
- changelog-social → bmad-os-changelog-social
- draft-changelog → bmad-os-draft-changelog
- gh-triage → bmad-os-gh-triage
- release-module → bmad-os-release-module
2026-02-15 17:31:10 -06:00
Brian Madison ccaa88bb2d feat: add Diataxis style fixer skill
Adds bmad-os-diataxis-style-fix skill that automatically fixes
documentation to comply with the Diataxis framework and BMad
Method style guide rules.

- Includes Diataxis framework primer (4 document types)
- References main docs/_STYLE_GUIDE.md as single source of truth
- Detects doc type by folder location
- Applies fixes without committing (user reviews first)
2026-02-15 17:29:12 -06:00
Curtis Ide 382ab8ed45
Fix: --custom-content flag and workflow config.yaml copying (#1651)
* fix custom install bug

* fix manager.js

* From PR #1624: added empty module.yaml handling (skip + warn) and removed paths from the config to match promptCustomContentSource()

* fix: custom-content quick-update ENOENT, pass --custom-content through, add PR#1624 improvements to allow update installs to work using non-interactive mode
2026-02-15 15:44:28 -06:00
Davor Racic 1937552da3
fix: improve module config UX messaging and spacing (#1656)
- Move "Module configuration complete" to appear after all customization
  prompts finish, not just after defaults are applied
- Change spinner stop message to "Module defaults applied" for clarity
  when customization follows; keep "Module configuration complete" for
  express mode where no customization prompts follow
- Remove extra blank line before post-install notes
- Wrap spinner loop in try/finally for error safety

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 15:39:53 -06:00
26 changed files with 791 additions and 102 deletions

View File

@ -0,0 +1,7 @@
---
name: bmad-os-diataxis-style-fix
description: Fixes documentation to comply with Diataxis framework and BMad Method style guide rules
disable-model-invocation: true
---
Read `prompts/instructions.md` and execute.

View File

@ -0,0 +1,229 @@
# Diataxis Style Fixer
Automatically fixes documentation to comply with the Diataxis framework and BMad Method style guide.
## CRITICAL RULES
- **NEVER commit or push changes** — let the user review first
- **NEVER make destructive edits** — preserve all content, only fix formatting
- **Use Edit tool** — make targeted fixes, not full file rewrites
- **Show summary** — after fixing, list all changes made
## Input
Documentation file path or directory to fix. Defaults to `docs/` if not specified.
## Step 1: Understand Diataxis Framework
**Diataxis** is a documentation framework that categorizes content into four types based on two axes:
| | **Learning** (oriented toward future) | **Doing** (oriented toward present) |
| -------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| **Practical** | **Tutorials** — lessons that guide learners through achieving a specific goal | **How-to guides** — step-by-step instructions for solving a specific problem |
| **Conceptual** | **Explanation** — content that clarifies and describes underlying concepts | **Reference** — technical descriptions, organized for lookup |
**Key principles:**
- Each document type serves a distinct user need
- Don't mix types — a tutorial shouldn't explain concepts deeply
- Focus on the user's goal, not exhaustive coverage
- Structure follows purpose (tutorials are linear, reference is scannable)
## Step 2: Read the Style Guide
Read the project's style guide at `docs/_STYLE_GUIDE.md` to understand all project-specific conventions.
## Step 3: Detect Document Type
Based on file location, determine the document type:
| Location | Diataxis Type |
| -------------------- | -------------------- |
| `/docs/tutorials/` | Tutorial |
| `/docs/how-to/` | How-to guide |
| `/docs/explanation/` | Explanation |
| `/docs/reference/` | Reference |
| `/docs/glossary/` | Reference (glossary) |
## Step 4: Find and Fix Issues
For each markdown file, scan for issues and fix them:
### Universal Fixes (All Doc Types)
**Horizontal Rules (`---`)**
- Remove any `---` outside of YAML frontmatter
- Replace with `##` section headers or admonitions as appropriate
**`####` Headers**
- Replace with bold text: `#### Header``**Header**`
- Or convert to admonition if it's a warning/notice
**"Related" or "Next:" Sections**
- Remove entire section including links
- The sidebar handles navigation
**Deeply Nested Lists**
- Break into sections with `##` headers
- Flatten to max 3 levels
**Code Blocks for Dialogue/Examples**
- Convert to admonitions:
```
:::note[Example]
[content]
:::
```
**Bold Paragraph Callouts**
- Convert to admonitions with appropriate type
**Too Many Admonitions**
- Limit to 1-2 per section (tutorials allow 3-4 per major section)
- Consolidate related admonitions
- Remove less critical ones if over limit
**Table Cells / List Items > 2 Sentences**
- Break into multiple rows/cells
- Or shorten to 1-2 sentences
**Header Budget Exceeded**
- Merge related sections
- Convert some `##` to `###` subsections
- Goal: 8-12 `##` per doc; 2-3 `###` per section
### Type-Specific Fixes
**Tutorials** (`/docs/tutorials/`)
- Ensure hook describes outcome in 1-2 sentences
- Add "What You'll Learn" bullet section if missing
- Add `:::note[Prerequisites]` if missing
- Add `:::tip[Quick Path]` TL;DR at top if missing
- Use tables for phases, commands, agents
- Add "What You've Accomplished" section if missing
- Add Quick Reference table if missing
- Add Common Questions section if missing
- Add Getting Help section if missing
- Add `:::tip[Key Takeaways]` at end if missing
**How-To** (`/docs/how-to/`)
- Ensure hook starts with "Use the `X` workflow to..."
- Add "When to Use This" with 3-5 bullets if missing
- Add `:::note[Prerequisites]` if missing
- Ensure steps are numbered `###` with action verbs
- Add "What You Get" describing outputs if missing
**Explanation** (`/docs/explanation/`)
- Ensure hook states what document explains
- Organize content into scannable `##` sections
- Add comparison tables for 3+ options
- Link to how-to guides for procedural questions
- Limit to 2-3 admonitions per document
**Reference** (`/docs/reference/`)
- Ensure hook states what document references
- Ensure structure matches reference type
- Use consistent item structure throughout
- Use tables for structured/comparative data
- Link to explanation docs for conceptual depth
- Limit to 1-2 admonitions per document
**Glossary** (`/docs/glossary/` or glossary files)
- Ensure categories as `##` headers
- Ensure terms in tables (not individual headers)
- Definitions 1-2 sentences max
- Bold term names in cells
## Step 5: Apply Fixes
For each file with issues:
1. Read the file
2. Use Edit tool for each fix
3. Track what was changed
## Step 6: Summary
After processing all files, output a summary:
```markdown
# Style Fixes Applied
**Files processed:** N
**Files modified:** N
## Changes Made
### `path/to/file.md`
- Removed horizontal rule at line 45
- Converted `####` headers to bold text
- Added `:::tip[Quick Path]` admonition
- Consolidated 3 admonitions into 2
### `path/to/other.md`
- Removed "Related:" section
- Fixed table cell length (broke into 2 rows)
## Review Required
Please review the changes. When satisfied, commit and push as needed.
```
## Common Patterns
**Converting `####` to bold:**
```markdown
#### Important Note
Some text here.
```
```markdown
**Important Note**
Some text here.
```
**Removing horizontal rule:**
```markdown
Some content above.
---
Some content below.
```
```markdown
Some content above.
## [Descriptive Section Header]
Some content below.
```
**Converting code block to admonition:**
```markdown
```
User: What should I do?
Agent: Run the workflow.
```
```
```markdown
:::note[Example]
**User:** What should I do?
**Agent:** Run the workflow.
:::
```
**Converting bold paragraph to admonition:**
```markdown
**IMPORTANT:** This is critical that you read this before proceeding.
```
```markdown
:::caution[Important]
This is critical that you read this before proceeding.
:::
```

View File

@ -77,7 +77,10 @@ Show in "What You've Accomplished" sections:
your-project/ your-project/
├── _bmad/ # BMad configuration ├── _bmad/ # BMad configuration
├── _bmad-output/ ├── _bmad-output/
│ ├── PRD.md # Your requirements document │ ├── planning-artifacts/
│ │ └── PRD.md # Your requirements document
│ ├── implementation-artifacts/
│ └── project-context.md # Implementation rules (optional)
└── ... └── ...
``` ```
```` ````

View File

@ -0,0 +1,157 @@
---
title: "Project Context"
description: How project-context.md guides AI agents with your project's rules and preferences
sidebar:
order: 7
---
The `project-context.md` file is your project's implementation guide for AI agents. Similar to a "constitution" in other development systems, it captures the rules, patterns, and preferences that ensure consistent code generation across all workflows.
## What It Does
AI agents make implementation decisions constantly — which patterns to follow, how to structure code, what conventions to use. Without clear guidance, they may:
- Follow generic best practices that don't match your codebase
- Make inconsistent decisions across different stories
- Miss project-specific requirements or constraints
The `project-context.md` file solves this by documenting what agents need to know in a concise, LLM-optimized format.
## How It Works
Every implementation workflow automatically loads `project-context.md` if it exists. The architect workflow also loads it to respect your technical preferences when designing the architecture.
**Loaded by these workflows:**
- `create-architecture` — respects technical preferences during solutioning
- `create-story` — informs story creation with project patterns
- `dev-story` — guides implementation decisions
- `code-review` — validates against project standards
- `quick-dev` — applies patterns when implementing tech-specs
- `sprint-planning`, `retrospective`, `correct-course` — provides project-wide context
## When to Create It
The `project-context.md` file is useful at any stage of a project:
| Scenario | When to Create | Purpose |
|----------|----------------|---------|
| **New project, before architecture** | Manually, before `create-architecture` | Document your technical preferences so the architect respects them |
| **New project, after architecture** | Via `generate-project-context` or manually | Capture architecture decisions for implementation agents |
| **Existing project** | Via `generate-project-context` | Discover existing patterns so agents follow established conventions |
| **Quick Flow project** | Before or during `quick-dev` | Ensure quick implementation respects your patterns |
:::tip[Recommended]
For new projects, create it manually before architecture if you have strong technical preferences. Otherwise, generate it after architecture to capture those decisions.
:::
## What Goes In It
The file has two main sections:
### Technology Stack & Versions
Documents the frameworks, languages, and tools your project uses with specific versions:
```markdown
## Technology Stack & Versions
- Node.js 20.x, TypeScript 5.3, React 18.2
- State: Zustand (not Redux)
- Testing: Vitest, Playwright, MSW
- Styling: Tailwind CSS with custom design tokens
```
### Critical Implementation Rules
Documents patterns and conventions that agents might otherwise miss:
```markdown
## Critical Implementation Rules
**TypeScript Configuration:**
- Strict mode enabled — no `any` types without explicit approval
- Use `interface` for public APIs, `type` for unions/intersections
**Code Organization:**
- Components in `/src/components/` with co-located `.test.tsx`
- Utilities in `/src/lib/` for reusable pure functions
- API calls use the `apiClient` singleton — never fetch directly
**Testing Patterns:**
- Unit tests focus on business logic, not implementation details
- Integration tests use MSW to mock API responses
- E2E tests cover critical user journeys only
**Framework-Specific:**
- All async operations use the `handleError` wrapper for consistent error handling
- Feature flags accessed via `featureFlag()` from `@/lib/flags`
- New routes follow the file-based routing pattern in `/src/app/`
```
Focus on what's **unobvious** — things agents might not infer from reading code snippets. Don't document standard practices that apply universally.
## Creating the File
You have three options:
### Manual Creation
Create the file at `_bmad-output/project-context.md` and add your rules:
```bash
# In your project root
mkdir -p _bmad-output
touch _bmad-output/project-context.md
```
Edit it with your technology stack and implementation rules. The architect and implementation workflows will automatically find and load it.
### Generate After Architecture
Run the `generate-project-context` workflow after completing your architecture:
```bash
/bmad-bmm-generate-project-context
```
This scans your architecture document and project files to generate a context file capturing the decisions made.
### Generate for Existing Projects
For existing projects, run `generate-project-context` to discover existing patterns:
```bash
/bmad-bmm-generate-project-context
```
The workflow analyzes your codebase to identify conventions, then generates a context file you can review and refine.
## Why It Matters
Without `project-context.md`, agents make assumptions that may not match your project:
| Without Context | With Context |
|----------------|--------------|
| Uses generic patterns | Follows your established conventions |
| Inconsistent style across stories | Consistent implementation |
| May miss project-specific constraints | Respects all technical requirements |
| Each agent decides independently | All agents align with same rules |
This is especially important for:
- **Quick Flow** — skips PRD and architecture, so context file fills the gap
- **Team projects** — ensures all agents follow the same standards
- **Existing projects** — prevents breaking established patterns
## Editing and Updating
The `project-context.md` file is a living document. Update it when:
- Architecture decisions change
- New conventions are established
- Patterns evolve during implementation
- You identify gaps from agent behavior
You can edit it manually at any time, or re-run `generate-project-context` to update it after significant changes.
:::note[File Location]
The default location is `_bmad-output/project-context.md`. Workflows search for it there, and also check `**/project-context.md` anywhere in your project.
:::

View File

@ -5,7 +5,7 @@ sidebar:
order: 6 order: 6
--- ---
Use BMad Method effectively when working on existing projects and legacy codebases, sometimes also referred to as brownfield projects. Use BMad Method effectively when working on existing projects and legacy codebases.
This guide covers the essential workflow for onboarding to existing projects with BMad Method. This guide covers the essential workflow for onboarding to existing projects with BMad Method.
@ -23,7 +23,30 @@ If you have completed all PRD epics and stories through the BMad process, clean
- `_bmad-output/planning-artifacts/` - `_bmad-output/planning-artifacts/`
- `_bmad-output/implementation-artifacts/` - `_bmad-output/implementation-artifacts/`
## Step 2: Maintain Quality Project Documentation ## Step 2: Create Project Context
:::tip[Recommended for Existing Projects]
Generate `project-context.md` to capture your existing codebase patterns and conventions. This ensures AI agents follow your established practices when implementing changes.
:::
Run the generate project context workflow:
```bash
/bmad-bmm-generate-project-context
```
This scans your codebase to identify:
- Technology stack and versions
- Code organization patterns
- Naming conventions
- Testing approaches
- Framework-specific patterns
You can review and refine the generated file, or create it manually at `_bmad-output/project-context.md` if you prefer.
[Learn more about project context](../explanation/project-context.md)
## Step 3: Maintain Quality Project Documentation
Your `docs/` folder should contain succinct, well-organized documentation that accurately represents your project: Your `docs/` folder should contain succinct, well-organized documentation that accurately represents your project:

View File

@ -0,0 +1,136 @@
---
title: "Manage Project Context"
description: Create and maintain project-context.md to guide AI agents
sidebar:
order: 7
---
Use the `project-context.md` file to ensure AI agents follow your project's technical preferences and implementation rules throughout all workflows.
:::note[Prerequisites]
- BMad Method installed
- Understanding of your project's technology stack and conventions
:::
## When to Use This
- You have strong technical preferences before starting architecture
- You've completed architecture and want to capture decisions for implementation
- You're working on an existing codebase with established patterns
- You notice agents making inconsistent decisions across stories
## Step 1: Choose Your Approach
**Manual creation** — Best when you know exactly what rules you want to document
**Generate after architecture** — Best for capturing decisions made during solutioning
**Generate for existing projects** — Best for discovering patterns in existing codebases
## Step 2: Create the File
### Option A: Manual Creation
Create the file at `_bmad-output/project-context.md`:
```bash
mkdir -p _bmad-output
touch _bmad-output/project-context.md
```
Add your technology stack and implementation rules:
```markdown
---
project_name: 'MyProject'
user_name: 'YourName'
date: '2026-02-15'
sections_completed: ['technology_stack', 'critical_rules']
---
# Project Context for AI Agents
## Technology Stack & Versions
- Node.js 20.x, TypeScript 5.3, React 18.2
- State: Zustand
- Testing: Vitest, Playwright
- Styling: Tailwind CSS
## Critical Implementation Rules
**TypeScript:**
- Strict mode enabled, no `any` types
- Use `interface` for public APIs, `type` for unions
**Code Organization:**
- Components in `/src/components/` with co-located tests
- API calls use `apiClient` singleton — never fetch directly
**Testing:**
- Unit tests focus on business logic
- Integration tests use MSW for API mocking
```
### Option B: Generate After Architecture
Run the workflow in a fresh chat:
```bash
/bmad-bmm-generate-project-context
```
The workflow scans your architecture document and project files to generate a context file capturing the decisions made.
### Option C: Generate for Existing Projects
For existing projects, run:
```bash
/bmad-bmm-generate-project-context
```
The workflow analyzes your codebase to identify conventions, then generates a context file you can review and refine.
## Step 3: Verify Content
Review the generated file and ensure it captures:
- Correct technology versions
- Your actual conventions (not generic best practices)
- Rules that prevent common mistakes
- Framework-specific patterns
Edit manually to add anything missing or remove inaccuracies.
## What You Get
A `project-context.md` file that:
- Ensures all agents follow the same conventions
- Prevents inconsistent decisions across stories
- Captures architecture decisions for implementation
- Serves as a reference for your project's patterns and rules
## Tips
:::tip[Focus on the Unobvious]
Document patterns agents might miss such as "Use JSDoc style comments on every public class, function and variable", not universal practices like "use meaningful variable names" which LLMs know at this point.
:::
:::tip[Keep It Lean]
This file is loaded by every implementation workflow. Long files waste context. Do not include content that only applies to narrow scope or specific stories or features.
:::
:::tip[Update as Needed]
Edit manually when patterns change, or re-generate after significant architecture changes.
:::
:::tip[Works for All Project Types]
Just as useful for Quick Flow as for full BMad Method projects.
:::
## Next Steps
- [**Project Context Explanation**](../explanation/project-context.md) — Learn more about how it works
- [**Workflow Map**](../reference/workflow-map.md) — See which workflows load project context

View File

@ -23,11 +23,11 @@ Document sharding splits large markdown files into smaller, organized files base
```text ```text
Before Sharding: Before Sharding:
docs/ _bmad-output/planning-artifacts/
└── PRD.md (large 50k token file) └── PRD.md (large 50k token file)
After Sharding: After Sharding:
docs/ _bmad-output/planning-artifacts/
└── prd/ └── prd/
├── index.md # Table of contents with descriptions ├── index.md # Table of contents with descriptions
├── overview.md # Section 1 ├── overview.md # Section 1

View File

@ -77,12 +77,44 @@ Skip phases 1-3 for small, well-understood work.
Each document becomes context for the next phase. The PRD tells the architect what constraints matter. The architecture tells the dev agent which patterns to follow. Story files give focused, complete context for implementation. Without this structure, agents make inconsistent decisions. Each document becomes context for the next phase. The PRD tells the architect what constraints matter. The architecture tells the dev agent which patterns to follow. Story files give focused, complete context for implementation. Without this structure, agents make inconsistent decisions.
For established projects, `document-project` creates or updates `project-context.md` - what exists in the codebase and the rules all implementation workflows must observe. Run it just before Phase 4, and again when something significant changes - structure, architecture, or those rules. You can also edit `project-context.md` by hand. ### Project Context
All implementation workflows load `project-context.md` if it exists. Additional context per workflow: :::tip[Recommended]
Create `project-context.md` to ensure AI agents follow your project's rules and preferences. This file works like a constitution for your project — it guides implementation decisions across all workflows.
:::
**When to create it:**
| Scenario | Approach |
|----------|----------|
| Before architecture (manual) | Document technical preferences you want the architect to respect |
| After architecture | Generate it to capture decisions made during solutioning |
| Existing projects | Run `generate-project-context` to discover established patterns |
| Quick Flow | Create before `quick-dev` to ensure consistent implementation |
**How to create it:**
- **Manually** — Create `_bmad-output/project-context.md` with your technology stack and implementation rules
- **Generate it** — Run `/bmad-bmm-generate-project-context` to auto-generate from your architecture or codebase
**What workflows load it:**
| Workflow | Purpose |
|----------|---------|
| `create-architecture` | Respects technical preferences when designing |
| `create-story` | Informs story creation with project patterns |
| `dev-story` | Guides implementation decisions |
| `code-review` | Validates against project standards |
| `quick-dev` | Applies patterns when implementing |
[**Learn more about project-context.md**](../explanation/project-context.md)
### Additional Context by Workflow
Beyond `project-context.md`, each workflow loads specific documents:
| Workflow | Also Loads | | Workflow | Also Loads |
| -------------- | ---------------------------- | |----------|------------|
| `create-story` | epics, PRD, architecture, UX | | `create-story` | epics, PRD, architecture, UX |
| `dev-story` | story file | | `dev-story` | story file |
| `code-review` | architecture, story file | | `code-review` | architecture, story file |

View File

@ -79,6 +79,12 @@ Always start a fresh chat for each workflow. This prevents context limitations f
Work through phases 1-3. **Use fresh chats for each workflow.** Work through phases 1-3. **Use fresh chats for each workflow.**
:::tip[Project Context (Optional)]
Before starting, consider creating `project-context.md` to document your technical preferences and implementation rules. This ensures all AI agents follow your conventions throughout the project.
Create it manually at `_bmad-output/project-context.md` or generate it after architecture using `/bmad-bmm-generate-project-context`. [Learn more](../explanation/project-context.md).
:::
### Phase 1: Analysis (Optional) ### Phase 1: Analysis (Optional)
All workflows in this phase are optional: All workflows in this phase are optional:
@ -157,10 +163,13 @@ Your project now has:
your-project/ your-project/
├── _bmad/ # BMad configuration ├── _bmad/ # BMad configuration
├── _bmad-output/ ├── _bmad-output/
│ ├── PRD.md # Your requirements document │ ├── planning-artifacts/
│ ├── architecture.md # Technical decisions │ │ ├── PRD.md # Your requirements document
│ ├── epics/ # Epic and story files │ │ ├── architecture.md # Technical decisions
│ └── sprint-status.yaml # Sprint tracking │ │ └── epics/ # Epic and story files
│ ├── implementation-artifacts/
│ │ └── sprint-status.yaml # Sprint tracking
│ └── project-context.md # Implementation rules (optional)
└── ... └── ...
``` ```
@ -171,6 +180,7 @@ your-project/
| `help` | `/bmad-help` | Any | Get guidance on what to do next | | `help` | `/bmad-help` | Any | Get guidance on what to do next |
| `prd` | `/bmad-bmm-create-prd` | PM | Create Product Requirements Document | | `prd` | `/bmad-bmm-create-prd` | PM | Create Product Requirements Document |
| `create-architecture` | `/bmad-bmm-create-architecture` | Architect | Create architecture document | | `create-architecture` | `/bmad-bmm-create-architecture` | Architect | Create architecture document |
| `generate-project-context` | `/bmad-bmm-generate-project-context` | Analyst | Create project context file |
| `create-epics-and-stories` | `/bmad-bmm-create-epics-and-stories` | PM | Break down PRD into epics | | `create-epics-and-stories` | `/bmad-bmm-create-epics-and-stories` | PM | Break down PRD into epics |
| `check-implementation-readiness` | `/bmad-bmm-check-implementation-readiness` | Architect | Validate planning cohesion | | `check-implementation-readiness` | `/bmad-bmm-check-implementation-readiness` | Architect | Validate planning cohesion |
| `sprint-planning` | `/bmad-bmm-sprint-planning` | SM | Initialize sprint tracking | | `sprint-planning` | `/bmad-bmm-sprint-planning` | SM | Initialize sprint tracking |

View File

@ -302,6 +302,7 @@ class ConfigCollector {
const configSpinner = await prompts.spinner(); const configSpinner = await prompts.spinner();
configSpinner.start('Configuring modules...'); configSpinner.start('Configuring modules...');
try {
for (const moduleName of defaultModules) { for (const moduleName of defaultModules) {
const displayName = displayNameMap.get(moduleName) || moduleName.toUpperCase(); const displayName = displayNameMap.get(moduleName) || moduleName.toUpperCase();
configSpinner.message(`Configuring ${displayName}...`); configSpinner.message(`Configuring ${displayName}...`);
@ -312,13 +313,19 @@ class ConfigCollector {
this._silentConfig = false; this._silentConfig = false;
} }
} }
configSpinner.stop('Module configuration complete'); } finally {
configSpinner.stop(customizeModules.length > 0 ? 'Module defaults applied' : 'Module configuration complete');
}
} }
// Run customized modules individually (may show interactive prompts) // Run customized modules individually (may show interactive prompts)
for (const moduleName of customizeModules) { for (const moduleName of customizeModules) {
await this.collectModuleConfig(moduleName, projectDir); await this.collectModuleConfig(moduleName, projectDir);
} }
if (customizeModules.length > 0) {
await prompts.log.step('Module configuration complete');
}
} }
// Add metadata // Add metadata
@ -1239,7 +1246,6 @@ class ConfigCollector {
hasOutput = true; hasOutput = true;
const message = valueMessages[selectedValue]; const message = valueMessages[selectedValue];
await prompts.log.message('');
for (const line of message.trim().split('\n')) { for (const line of message.trim().split('\n')) {
const trimmedLine = line.trim(); const trimmedLine = line.trim();
if (trimmedLine.endsWith(':') && !trimmedLine.startsWith(' ')) { if (trimmedLine.endsWith(':') && !trimmedLine.startsWith(' ')) {

View File

@ -527,8 +527,13 @@ class Installer {
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()) {
const moduleId = cachedModule.name; const moduleId = cachedModule.name;
const cachedPath = path.join(cacheDir, moduleId);
// Skip if path doesn't exist (broken symlink, deleted dir) - avoids lstat ENOENT
if (!(await fs.pathExists(cachedPath)) || !cachedModule.isDirectory()) {
continue;
}
// Skip if we already have this module from manifest // Skip if we already have this module from manifest
if (customModulePaths.has(moduleId)) { if (customModulePaths.has(moduleId)) {
@ -542,15 +547,12 @@ class Installer {
continue; continue;
} }
const cachedPath = path.join(cacheDir, moduleId);
// Check if this is actually a custom module (has module.yaml) // Check if this is actually a custom module (has module.yaml)
const moduleYamlPath = path.join(cachedPath, 'module.yaml'); const moduleYamlPath = path.join(cachedPath, 'module.yaml');
if (await fs.pathExists(moduleYamlPath)) { if (await fs.pathExists(moduleYamlPath)) {
customModulePaths.set(moduleId, cachedPath); customModulePaths.set(moduleId, cachedPath);
} }
} }
}
// Update module manager with the new custom module paths from cache // Update module manager with the new custom module paths from cache
this.moduleManager.setCustomModulePaths(customModulePaths); this.moduleManager.setCustomModulePaths(customModulePaths);
@ -609,8 +611,13 @@ class Installer {
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()) {
const moduleId = cachedModule.name; const moduleId = cachedModule.name;
const cachedPath = path.join(cacheDir, moduleId);
// Skip if path doesn't exist (broken symlink, deleted dir) - avoids lstat ENOENT
if (!(await fs.pathExists(cachedPath)) || !cachedModule.isDirectory()) {
continue;
}
// Skip if we already have this module from manifest // Skip if we already have this module from manifest
if (customModulePaths.has(moduleId)) { if (customModulePaths.has(moduleId)) {
@ -624,15 +631,12 @@ class Installer {
continue; continue;
} }
const cachedPath = path.join(cacheDir, moduleId);
// Check if this is actually a custom module (has module.yaml) // Check if this is actually a custom module (has module.yaml)
const moduleYamlPath = path.join(cachedPath, 'module.yaml'); const moduleYamlPath = path.join(cachedPath, 'module.yaml');
if (await fs.pathExists(moduleYamlPath)) { if (await fs.pathExists(moduleYamlPath)) {
customModulePaths.set(moduleId, cachedPath); customModulePaths.set(moduleId, cachedPath);
} }
} }
}
// Update module manager with the new custom module paths from cache // Update module manager with the new custom module paths from cache
this.moduleManager.setCustomModulePaths(customModulePaths); this.moduleManager.setCustomModulePaths(customModulePaths);
@ -949,12 +953,11 @@ class Installer {
if (!isCustomModule && config._customModuleSources && config._customModuleSources.has(moduleName)) { if (!isCustomModule && config._customModuleSources && config._customModuleSources.has(moduleName)) {
customInfo = config._customModuleSources.get(moduleName); customInfo = config._customModuleSources.get(moduleName);
isCustomModule = true; isCustomModule = true;
if ( if (customInfo.sourcePath && !customInfo.path) {
customInfo.sourcePath && customInfo.path = path.isAbsolute(customInfo.sourcePath)
(customInfo.sourcePath.startsWith('_config') || customInfo.sourcePath.includes('_config/custom')) && ? customInfo.sourcePath
!customInfo.path : path.join(bmadDir, customInfo.sourcePath);
) }
customInfo.path = customInfo.sourcePath;
} }
// Finally check regular custom content // Finally check regular custom content
@ -2373,15 +2376,35 @@ class Installer {
const configuredIdes = existingInstall.ides || []; const configuredIdes = existingInstall.ides || [];
const projectRoot = path.dirname(bmadDir); const projectRoot = path.dirname(bmadDir);
// Get custom module sources from cache // Get custom module sources: first from --custom-content (re-cache from source), then from cache
const customModuleSources = new Map(); const customModuleSources = new Map();
if (config.customContent?.sources?.length > 0) {
for (const source of config.customContent.sources) {
if (source.id && source.path && (await fs.pathExists(source.path))) {
customModuleSources.set(source.id, {
id: source.id,
name: source.name || source.id,
sourcePath: source.path,
cached: false, // From CLI, will be re-cached
});
}
}
}
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()) {
const moduleId = cachedModule.name; const moduleId = cachedModule.name;
const cachedPath = path.join(cacheDir, moduleId);
// Skip if path doesn't exist (broken symlink, deleted dir) - avoids lstat ENOENT
if (!(await fs.pathExists(cachedPath))) {
continue;
}
if (!cachedModule.isDirectory()) {
continue;
}
// Skip if we already have this module from manifest // Skip if we already have this module from manifest
if (customModuleSources.has(moduleId)) { if (customModuleSources.has(moduleId)) {
@ -2395,8 +2418,6 @@ class Installer {
continue; continue;
} }
const cachedPath = path.join(cacheDir, moduleId);
// Check if this is actually a custom module (has module.yaml) // Check if this is actually a custom module (has module.yaml)
const moduleYamlPath = path.join(cachedPath, 'module.yaml'); const moduleYamlPath = path.join(cachedPath, 'module.yaml');
if (await fs.pathExists(moduleYamlPath)) { if (await fs.pathExists(moduleYamlPath)) {
@ -2410,7 +2431,6 @@ class Installer {
} }
} }
} }
}
// Load saved IDE configurations // Load saved IDE configurations
const savedIdeConfigs = await this.ideConfigManager.loadAllIdeConfigs(bmadDir); const savedIdeConfigs = await this.ideConfigManager.loadAllIdeConfigs(bmadDir);
@ -2544,6 +2564,7 @@ class Installer {
_savedIdeConfigs: savedIdeConfigs, // Pass saved IDE configs to installer _savedIdeConfigs: savedIdeConfigs, // Pass saved IDE configs to installer
_customModuleSources: customModuleSources, // Pass custom module sources for updates _customModuleSources: customModuleSources, // Pass custom module sources for updates
_existingModules: installedModules, // Pass all installed modules for manifest generation _existingModules: installedModules, // Pass all installed modules for manifest generation
customContent: config.customContent, // Pass through for re-caching from source
}; };
// Call the standard install method // Call the standard install method

View File

@ -734,8 +734,10 @@ class ModuleManager {
continue; continue;
} }
// Skip config.yaml templates - we'll generate clean ones with actual values // Skip module root config.yaml only - generated by config collector with actual values
if (file === 'config.yaml' || file.endsWith('/config.yaml')) { // Workflow-level config.yaml (e.g. workflows/orchestrate-story/config.yaml) must be copied
// for custom modules that use workflow-specific configuration
if (file === 'config.yaml') {
continue; continue;
} }

View File

@ -245,11 +245,48 @@ class UI {
// Handle quick update separately // Handle quick update separately
if (actionType === 'quick-update') { if (actionType === 'quick-update') {
// Quick update doesn't install custom content - just updates existing modules // Pass --custom-content through so installer can re-cache if cache is missing
let customContentForQuickUpdate = { hasCustomContent: false };
if (options.customContent) {
const paths = options.customContent
.split(',')
.map((p) => p.trim())
.filter(Boolean);
if (paths.length > 0) {
const customPaths = [];
const selectedModuleIds = [];
const sources = [];
for (const customPath of paths) {
const expandedPath = this.expandUserPath(customPath);
const validation = this.validateCustomContentPathSync(expandedPath);
if (validation) continue;
let moduleMeta;
try {
const moduleYamlPath = path.join(expandedPath, 'module.yaml');
moduleMeta = require('yaml').parse(await fs.readFile(moduleYamlPath, 'utf-8'));
} catch {
continue;
}
if (!moduleMeta?.code) continue;
customPaths.push(expandedPath);
selectedModuleIds.push(moduleMeta.code);
sources.push({ path: expandedPath, id: moduleMeta.code, name: moduleMeta.name || moduleMeta.code });
}
if (customPaths.length > 0) {
customContentForQuickUpdate = {
hasCustomContent: true,
selected: true,
sources,
selectedFiles: customPaths.map((p) => path.join(p, 'module.yaml')),
selectedModuleIds,
};
}
}
}
return { return {
actionType: 'quick-update', actionType: 'quick-update',
directory: confirmedDirectory, directory: confirmedDirectory,
customContent: { hasCustomContent: false }, customContent: customContentForQuickUpdate,
skipPrompts: options.yes || false, skipPrompts: options.yes || false,
}; };
} }
@ -305,6 +342,7 @@ class UI {
// Build custom content config similar to promptCustomContentSource // Build custom content config similar to promptCustomContentSource
const customPaths = []; const customPaths = [];
const selectedModuleIds = []; const selectedModuleIds = [];
const sources = [];
for (const customPath of paths) { for (const customPath of paths) {
const expandedPath = this.expandUserPath(customPath); const expandedPath = this.expandUserPath(customPath);
@ -326,6 +364,11 @@ class UI {
continue; continue;
} }
if (!moduleMeta) {
await prompts.log.warn(`Skipping custom content path: ${customPath} - module.yaml is empty`);
continue;
}
if (!moduleMeta.code) { if (!moduleMeta.code) {
await prompts.log.warn(`Skipping custom content path: ${customPath} - module.yaml missing 'code' field`); await prompts.log.warn(`Skipping custom content path: ${customPath} - module.yaml missing 'code' field`);
continue; continue;
@ -333,6 +376,11 @@ class UI {
customPaths.push(expandedPath); customPaths.push(expandedPath);
selectedModuleIds.push(moduleMeta.code); selectedModuleIds.push(moduleMeta.code);
sources.push({
path: expandedPath,
id: moduleMeta.code,
name: moduleMeta.name || moduleMeta.code,
});
} }
if (customPaths.length > 0) { if (customPaths.length > 0) {
@ -340,7 +388,9 @@ class UI {
selectedCustomModules: selectedModuleIds, selectedCustomModules: selectedModuleIds,
customContentConfig: { customContentConfig: {
hasCustomContent: true, hasCustomContent: true,
paths: customPaths, selected: true,
sources,
selectedFiles: customPaths.map((p) => path.join(p, 'module.yaml')),
selectedModuleIds: selectedModuleIds, selectedModuleIds: selectedModuleIds,
}, },
}; };
@ -446,6 +496,7 @@ class UI {
// Build custom content config similar to promptCustomContentSource // Build custom content config similar to promptCustomContentSource
const customPaths = []; const customPaths = [];
const selectedModuleIds = []; const selectedModuleIds = [];
const sources = [];
for (const customPath of paths) { for (const customPath of paths) {
const expandedPath = this.expandUserPath(customPath); const expandedPath = this.expandUserPath(customPath);
@ -467,6 +518,11 @@ class UI {
continue; continue;
} }
if (!moduleMeta) {
await prompts.log.warn(`Skipping custom content path: ${customPath} - module.yaml is empty`);
continue;
}
if (!moduleMeta.code) { if (!moduleMeta.code) {
await prompts.log.warn(`Skipping custom content path: ${customPath} - module.yaml missing 'code' field`); await prompts.log.warn(`Skipping custom content path: ${customPath} - module.yaml missing 'code' field`);
continue; continue;
@ -474,12 +530,19 @@ class UI {
customPaths.push(expandedPath); customPaths.push(expandedPath);
selectedModuleIds.push(moduleMeta.code); selectedModuleIds.push(moduleMeta.code);
sources.push({
path: expandedPath,
id: moduleMeta.code,
name: moduleMeta.name || moduleMeta.code,
});
} }
if (customPaths.length > 0) { if (customPaths.length > 0) {
customContentConfig = { customContentConfig = {
hasCustomContent: true, hasCustomContent: true,
paths: customPaths, selected: true,
sources,
selectedFiles: customPaths.map((p) => path.join(p, 'module.yaml')),
selectedModuleIds: selectedModuleIds, selectedModuleIds: selectedModuleIds,
}; };
} }