Compare commits

...

7 Commits

Author SHA1 Message Date
Jonah Schulte 3bdeb80752
Merge 1622e2c17a into 5b5cb1a396 2026-02-15 00:00:25 -05:00
Brian Madison 5b5cb1a396 modify post install notes example 2026-02-14 21:25:58 -06:00
Brian Madison 98c1fa8282 document project remove legacy workflow init and workflow- status function calls 2026-02-14 15:16:51 -06:00
Jonah Schulte 1622e2c17a
Merge branch 'main' into fix/cli-custom-content-config 2026-02-13 17:31:07 -05:00
Davor Racic 98a24fe4a7
Merge branch 'main' into fix/cli-custom-content-config 2026-02-11 18:39:58 +01:00
Jonah Schulte b9b51cd3fe fix: address PR review feedback for CLI --custom-content config
- Use module display name in sources (name: moduleMeta.name || code)
- Add null guard for empty module.yaml (yaml.parse returns null)
- Remove unused paths property to match promptCustomContentSource shape
2026-02-11 10:17:20 -05:00
Jonah Schulte eeeea6f204 fix: add missing sources/selectedFiles to CLI --custom-content config
The non-interactive --custom-content CLI flag builds a customContentConfig
object missing the `sources`, `selected`, and `selectedFiles` properties
that the installer expects. This causes findModuleSource() to fail with
"Source for module X is not available" because customModulePaths is never
populated.

Align both CLI code paths (modify and fresh install flows) with the config
shape produced by the interactive promptCustomContentSource() method.

Fixes #1623
2026-02-10 22:03:36 -05:00
4 changed files with 50 additions and 114 deletions

View File

@ -78,7 +78,6 @@ your-project/
├── _bmad/ # BMad configuration ├── _bmad/ # BMad configuration
├── _bmad-output/ ├── _bmad-output/
│ ├── PRD.md # Your requirements document │ ├── PRD.md # Your requirements document
│ └── bmm-workflow-status.yaml # Progress tracking
└── ... └── ...
``` ```
```` ````
@ -142,12 +141,12 @@ your-project/
### Types ### Types
| Type | Example | | Type | Example |
| ----------------- | ---------------------------- | | ----------------- | ----------------------------- |
| **Index/Landing** | `core-concepts/index.md` | | **Index/Landing** | `core-concepts/index.md` |
| **Concept** | `what-are-agents.md` | | **Concept** | `what-are-agents.md` |
| **Feature** | `quick-flow.md` | | **Feature** | `quick-flow.md` |
| **Philosophy** | `why-solutioning-matters.md` | | **Philosophy** | `why-solutioning-matters.md` |
| **FAQ** | `established-projects-faq.md` | | **FAQ** | `established-projects-faq.md` |
### General Template ### General Template

View File

@ -8,55 +8,7 @@
<critical>This router determines workflow mode and delegates to specialized sub-workflows</critical> <critical>This router determines workflow mode and delegates to specialized sub-workflows</critical>
<step n="1" goal="Validate workflow and get project info"> <step n="1" goal="Check for ability to resume and determine workflow mode">
<invoke-workflow path="{project-root}/_bmad/bmm/workflows/workflow-status">
<param>mode: data</param>
<param>data_request: project_config</param>
</invoke-workflow>
<check if="status_exists == false">
<output>{{suggestion}}</output>
<output>Note: Documentation workflow can run standalone. Continuing without progress tracking.</output>
<action>Set standalone_mode = true</action>
<action>Set status_file_found = false</action>
</check>
<check if="status_exists == true">
<action>Store {{status_file_path}} for later updates</action>
<action>Set status_file_found = true</action>
<!-- Extract brownfield/greenfield from status data -->
<check if="field_type == 'greenfield'">
<output>Note: This is a greenfield project. Documentation workflow is typically for brownfield projects.</output>
<ask>Continue anyway to document planning artifacts? (y/n)</ask>
<check if="n">
<action>Exit workflow</action>
</check>
</check>
<!-- Now validate sequencing -->
<invoke-workflow path="{project-root}/_bmad/bmm/workflows/workflow-status">
<param>mode: validate</param>
<param>calling_workflow: document-project</param>
</invoke-workflow>
<check if="warning != ''">
<output>{{warning}}</output>
<output>Note: This may be auto-invoked by prd for brownfield documentation.</output>
<ask>Continue with documentation? (y/n)</ask>
<check if="n">
<output>{{suggestion}}</output>
<action>Exit workflow</action>
</check>
</check>
</check>
</step>
<step n="2" goal="Check for resumability and determine workflow mode">
<critical>SMART LOADING STRATEGY: Check state file FIRST before loading any CSV files</critical>
<action>Check for existing state file at: {project_knowledge}/project-scan-report.json</action> <action>Check for existing state file at: {project_knowledge}/project-scan-report.json</action>
<check if="project-scan-report.json exists"> <check if="project-scan-report.json exists">
@ -66,21 +18,21 @@
<ask>I found an in-progress workflow state from {{last_updated}}. <ask>I found an in-progress workflow state from {{last_updated}}.
**Current Progress:** **Current Progress:**
- Mode: {{mode}} - Mode: {{mode}}
- Scan Level: {{scan_level}} - Scan Level: {{scan_level}}
- Completed Steps: {{completed_steps_count}}/{{total_steps}} - Completed Steps: {{completed_steps_count}}/{{total_steps}}
- Last Step: {{current_step}} - Last Step: {{current_step}}
- Project Type(s): {{cached_project_types}} - Project Type(s): {{cached_project_types}}
Would you like to: Would you like to:
1. **Resume from where we left off** - Continue from step {{current_step}} 1. **Resume from where we left off** - Continue from step {{current_step}}
2. **Start fresh** - Archive old state and begin new scan 2. **Start fresh** - Archive old state and begin new scan
3. **Cancel** - Exit without changes 3. **Cancel** - Exit without changes
Your choice [1/2/3]: Your choice [1/2/3]:
</ask> </ask>
<check if="user selects 1"> <check if="user selects 1">
@ -175,47 +127,4 @@ Your choice [1/2/3]:
</step> </step>
<step n="4" goal="Update status and complete">
<check if="status_file_found == true">
<invoke-workflow path="{project-root}/_bmad/bmm/workflows/workflow-status">
<param>mode: update</param>
<param>action: complete_workflow</param>
<param>workflow_name: document-project</param>
</invoke-workflow>
<check if="success == true">
<output>Status updated!</output>
</check>
</check>
<output>**✅ Document Project Workflow Complete, {user_name}!**
**Documentation Generated:**
- Mode: {{workflow_mode}}
- Scan Level: {{scan_level}}
- Output: {project_knowledge}/index.md and related files
{{#if status_file_found}}
**Status Updated:**
- Progress tracking updated
**Next Steps:**
- **Next required:** {{next_workflow}} ({{next_agent}} agent)
Check status anytime with: `workflow-status`
{{else}}
**Next Steps:**
Since no workflow is in progress:
- Refer to the BMM workflow guide if unsure what to do next
- Or run `workflow-init` to create a workflow path and get guided next steps
{{/if}}
</output>
</step>
</workflow> </workflow>

View File

@ -16,8 +16,8 @@ Always displayed after the module is configured:
```yaml ```yaml
post-install-notes: | post-install-notes: |
Remember to set the API_KEY environment variable. Thank you for choosing the XYZ Cool Module
See: https://example.com/setup For Support about this Module call 555-1212
``` ```
### Conditional Format ### Conditional Format

View File

@ -305,6 +305,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 selectedModuleMetas = [];
for (const customPath of paths) { for (const customPath of paths) {
const expandedPath = this.expandUserPath(customPath); const expandedPath = this.expandUserPath(customPath);
@ -326,6 +327,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,14 +339,22 @@ class UI {
customPaths.push(expandedPath); customPaths.push(expandedPath);
selectedModuleIds.push(moduleMeta.code); selectedModuleIds.push(moduleMeta.code);
selectedModuleMetas.push(moduleMeta);
} }
if (customPaths.length > 0) { if (customPaths.length > 0) {
const sources = customPaths.map((p, i) => ({
path: p,
id: selectedModuleIds[i],
name: selectedModuleMetas[i].name || selectedModuleIds[i],
}));
customModuleResult = { customModuleResult = {
selectedCustomModules: selectedModuleIds, selectedCustomModules: selectedModuleIds,
customContentConfig: { customContentConfig: {
hasCustomContent: true, hasCustomContent: true,
paths: customPaths, selected: true,
sources: sources,
selectedFiles: customPaths.map((p) => path.join(p, 'module.yaml')),
selectedModuleIds: selectedModuleIds, selectedModuleIds: selectedModuleIds,
}, },
}; };
@ -446,6 +460,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 selectedModuleMetas = [];
for (const customPath of paths) { for (const customPath of paths) {
const expandedPath = this.expandUserPath(customPath); const expandedPath = this.expandUserPath(customPath);
@ -467,6 +482,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 +494,20 @@ class UI {
customPaths.push(expandedPath); customPaths.push(expandedPath);
selectedModuleIds.push(moduleMeta.code); selectedModuleIds.push(moduleMeta.code);
selectedModuleMetas.push(moduleMeta);
} }
if (customPaths.length > 0) { if (customPaths.length > 0) {
const sources = customPaths.map((p, i) => ({
path: p,
id: selectedModuleIds[i],
name: selectedModuleMetas[i].name || selectedModuleIds[i],
}));
customContentConfig = { customContentConfig = {
hasCustomContent: true, hasCustomContent: true,
paths: customPaths, selected: true,
sources: sources,
selectedFiles: customPaths.map((p) => path.join(p, 'module.yaml')),
selectedModuleIds: selectedModuleIds, selectedModuleIds: selectedModuleIds,
}; };
} }