Compare commits

..

1 Commits

Author SHA1 Message Date
Mario Semper 10afcd5486
Merge 4d48b0dbe1 into be7e07cc1a 2025-12-11 16:32:28 -06:00
17 changed files with 63 additions and 120 deletions

View File

@ -330,7 +330,7 @@ Review was saved to story file, but sprint-status.yaml may be out of sync.
<action>All action items are included in the standalone review report</action> <action>All action items are included in the standalone review report</action>
<ask if="action items exist">Would you like me to create tracking items for these action items? (backlog/tasks)</ask> <ask if="action items exist">Would you like me to create tracking items for these action items? (backlog/tasks)</ask>
<action if="user confirms"> <action if="user confirms">
If {{backlog_file}} does not exist, copy {installed_path}/backlog-template.md to {{backlog_file}} location. If {{backlog_file}} does not exist, copy {installed_path}/backlog_template.md to {{backlog_file}} location.
Append a row per action item with Date={{date}}, Story="Ad-Hoc Review", Epic="N/A", Type, Severity, Owner (or "TBD"), Status="Open", Notes with file refs and context. Append a row per action item with Date={{date}}, Story="Ad-Hoc Review", Epic="N/A", Type, Severity, Owner (or "TBD"), Status="Open", Notes with file refs and context.
</action> </action>
</check> </check>
@ -342,7 +342,7 @@ Review was saved to story file, but sprint-status.yaml may be out of sync.
Append under the story's "Tasks / Subtasks" a new subsection titled "Review Follow-ups (AI)", adding each item as an unchecked checkbox in imperative form, prefixed with "[AI-Review]" and severity. Example: "- [ ] [AI-Review][High] Add input validation on server route /api/x (AC #2)". Append under the story's "Tasks / Subtasks" a new subsection titled "Review Follow-ups (AI)", adding each item as an unchecked checkbox in imperative form, prefixed with "[AI-Review]" and severity. Example: "- [ ] [AI-Review][High] Add input validation on server route /api/x (AC #2)".
</action> </action>
<action> <action>
If {{backlog_file}} does not exist, copy {installed_path}/backlog-template.md to {{backlog_file}} location. If {{backlog_file}} does not exist, copy {installed_path}/backlog_template.md to {{backlog_file}} location.
Append a row per action item with Date={{date}}, Story={{epic_num}}.{{story_num}}, Epic={{epic_num}}, Type, Severity, Owner (or "TBD"), Status="Open", Notes with short context and file refs. Append a row per action item with Date={{date}}, Story={{epic_num}}.{{story_num}}, Epic={{epic_num}}, Type, Severity, Owner (or "TBD"), Status="Open", Notes with short context and file refs.
</action> </action>
<action> <action>

View File

@ -24,7 +24,7 @@ agent:
critical_actions: critical_actions:
- "READ the entire story file BEFORE any implementation - tasks/subtasks sequence is your authoritative implementation guide" - "READ the entire story file BEFORE any implementation - tasks/subtasks sequence is your authoritative implementation guide"
- "Load project-context.md if available for coding standards only - never let it override story requirements" - "Load project_context.md if available for coding standards only - never let it override story requirements"
- "Execute tasks/subtasks IN ORDER as written in story file - no skipping, no reordering, no doing what you want" - "Execute tasks/subtasks IN ORDER as written in story file - no skipping, no reordering, no doing what you want"
- "For each task/subtask: follow red-green-refactor cycle - write failing test first, then implementation" - "For each task/subtask: follow red-green-refactor cycle - write failing test first, then implementation"
- "Mark task/subtask [x] ONLY when both implementation AND tests are complete and passing" - "Mark task/subtask [x] ONLY when both implementation AND tests are complete and passing"

View File

@ -1,6 +1,6 @@
--- ---
name: create-prd name: create-prd
description: Creates a comprehensive PRD through collaborative step-by-step discovery between two product managers working as peers. description: Creates a comprehensive PRDs through collaborative step-by-step discovery between two product managers working as peers.
main_config: '{project-root}/.bmad/bmm/config.yaml' main_config: '{project-root}/.bmad/bmm/config.yaml'
web_bundle: true web_bundle: true
--- ---

View File

@ -94,7 +94,7 @@ Discover and load context documents using smart discovery:
**Project Context Rules (Critical for AI Agents):** **Project Context Rules (Critical for AI Agents):**
1. Check for project context file: `**/project-context.md` 1. Check for project context file: `**/project_context.md`
2. If exists: Load COMPLETE file contents - this contains critical rules for AI agents 2. If exists: Load COMPLETE file contents - this contains critical rules for AI agents
3. Add to frontmatter `hasProjectContext: true` and track file path 3. Add to frontmatter `hasProjectContext: true` and track file path
4. Report to user: "Found existing project context with {number_of_rules} agent rules" 4. Report to user: "Found existing project context with {number_of_rules} agent rules"

View File

@ -280,7 +280,7 @@ Your architecture will ensure consistent, high-quality implementation across all
**💡 Optional Enhancement: Project Context File** **💡 Optional Enhancement: Project Context File**
Would you like to create a `project-context.md` file? This is a concise, optimized guide for AI agents that captures: Would you like to create a `project_context.md` file? This is a concise, optimized guide for AI agents that captures:
- Critical language and framework rules they might miss - Critical language and framework rules they might miss
- Specific patterns and conventions for your project - Specific patterns and conventions for your project
@ -310,7 +310,7 @@ This will help ensure consistent implementation by capturing:
- Testing and quality standards - Testing and quality standards
- Anti-patterns to avoid - Anti-patterns to avoid
The workflow will collaborate with you to create an optimized `project-context.md` file that AI agents will read before implementing any code." The workflow will collaborate with you to create an optimized `project_context.md` file that AI agents will read before implementing any code."
**Execute the Generate Project Context workflow:** **Execute the Generate Project Context workflow:**

View File

@ -217,7 +217,7 @@
**Issues Fixed:** {{fixed_count}} **Issues Fixed:** {{fixed_count}}
**Action Items Created:** {{action_count}} **Action Items Created:** {{action_count}}
{{#if new_status == "done"}}Code review complete!{{else}}Address the action items and continue development.{{/if}} {{#if new_status == "done"}}Story is ready for next work!{{else}}Address the action items and continue development.{{/if}}
</output> </output>
</step> </step>

View File

@ -35,7 +35,7 @@ validation-rules:
- [ ] **Acceptance Criteria Satisfaction:** Implementation satisfies EVERY Acceptance Criterion in the story - [ ] **Acceptance Criteria Satisfaction:** Implementation satisfies EVERY Acceptance Criterion in the story
- [ ] **No Ambiguous Implementation:** Clear, unambiguous implementation that meets story requirements - [ ] **No Ambiguous Implementation:** Clear, unambiguous implementation that meets story requirements
- [ ] **Edge Cases Handled:** Error conditions and edge cases appropriately addressed - [ ] **Edge Cases Handled:** Error conditions and edge cases appropriately addressed
- [ ] **Dependencies Within Scope:** Only uses dependencies specified in story or project-context.md - [ ] **Dependencies Within Scope:** Only uses dependencies specified in story or project_context.md
## 🧪 Testing & Quality Assurance ## 🧪 Testing & Quality Assurance

View File

@ -33,7 +33,7 @@ Discover the project's technology stack, existing patterns, and critical impleme
First, check if project context already exists: First, check if project context already exists:
- Look for file at `{output_folder}/project-context.md` - Look for file at `{output_folder}/project_context.md`
- If exists: Read complete file to understand existing rules - If exists: Read complete file to understand existing rules
- Present to user: "Found existing project context with {number_of_sections} sections. Would you like to update this or create a new one?" - Present to user: "Found existing project context with {number_of_sections} sections. Would you like to update this or create a new one?"
@ -122,7 +122,7 @@ Based on discovery, create or update the context document:
#### A. Fresh Document Setup (if no existing context) #### A. Fresh Document Setup (if no existing context)
Copy template from `{installed_path}/project-context-template.md` to `{output_folder}/project-context.md` Copy template from `{installed_path}/project-context-template.md` to `{output_folder}/project_context.md`
Initialize frontmatter with: Initialize frontmatter with:
```yaml ```yaml

View File

@ -288,7 +288,7 @@ After each category, show the generated rules and present choices:
## APPEND TO PROJECT CONTEXT: ## APPEND TO PROJECT CONTEXT:
When user selects 'C' for a category, append the content directly to `{output_folder}/project-context.md` using the structure from step 8. When user selects 'C' for a category, append the content directly to `{output_folder}/project_context.md` using the structure from step 8.
## SUCCESS METRICS: ## SUCCESS METRICS:

View File

@ -134,7 +134,7 @@ Based on user skill level, present the completion:
**Expert Mode:** **Expert Mode:**
"Project context complete. Optimized for LLM consumption with {{rule_count}} critical rules across {{section_count}} sections. "Project context complete. Optimized for LLM consumption with {{rule_count}} critical rules across {{section_count}} sections.
File saved to: `{output_folder}/project-context.md` File saved to: `{output_folder}/project_context.md`
Ready for AI agent integration." Ready for AI agent integration."
@ -227,7 +227,7 @@ Present final completion to user:
"✅ **Project Context Generation Complete!** "✅ **Project Context Generation Complete!**
Your optimized project context file is ready at: Your optimized project context file is ready at:
`{output_folder}/project-context.md` `{output_folder}/project_context.md`
**📊 Context Summary:** **📊 Context Summary:**

View File

@ -1,11 +1,11 @@
--- ---
name: generate-project-context name: generate-project-context
description: Creates a concise project-context.md file with critical rules and patterns that AI agents must follow when implementing code. Optimized for LLM context efficiency. description: Creates a concise project_context.md file with critical rules and patterns that AI agents must follow when implementing code. Optimized for LLM context efficiency.
--- ---
# Generate Project Context Workflow # Generate Project Context Workflow
**Goal:** Create a concise, optimized `project-context.md` file containing critical rules, patterns, and guidelines that AI agents must follow when implementing code. This file focuses on unobvious details that LLMs need to be reminded of. **Goal:** Create a concise, optimized `project_context.md` file containing critical rules, patterns, and guidelines that AI agents must follow when implementing code. This file focuses on unobvious details that LLMs need to be reminded of.
**Your Role:** You are a technical facilitator working with a peer to capture the essential implementation rules that will ensure consistent, high-quality code generation across all AI agents working on the project. **Your Role:** You are a technical facilitator working with a peer to capture the essential implementation rules that will ensure consistent, high-quality code generation across all AI agents working on the project.
@ -37,7 +37,7 @@ Load config from `{project-root}/.bmad/bmm/config.yaml` and resolve:
- `installed_path` = `{project-root}/.bmad/bmm/workflows/generate-project-context` - `installed_path` = `{project-root}/.bmad/bmm/workflows/generate-project-context`
- `template_path` = `{installed_path}/project-context-template.md` - `template_path` = `{installed_path}/project-context-template.md`
- `output_file` = `{output_folder}/project-context.md` - `output_file` = `{output_folder}/project_context.md`
--- ---

View File

@ -248,21 +248,14 @@ class ConfigCollector {
const configKeys = Object.keys(moduleConfig).filter((key) => key !== 'prompt'); const configKeys = Object.keys(moduleConfig).filter((key) => key !== 'prompt');
const existingKeys = this.existingConfig && this.existingConfig[moduleName] ? Object.keys(this.existingConfig[moduleName]) : []; const existingKeys = this.existingConfig && this.existingConfig[moduleName] ? Object.keys(this.existingConfig[moduleName]) : [];
// Find new interactive fields (with prompt)
const newKeys = configKeys.filter((key) => { const newKeys = configKeys.filter((key) => {
const item = moduleConfig[key]; const item = moduleConfig[key];
// Check if it's a config item and doesn't exist in existing config // Check if it's a config item and doesn't exist in existing config
return item && typeof item === 'object' && item.prompt && !existingKeys.includes(key); return item && typeof item === 'object' && item.prompt && !existingKeys.includes(key);
}); });
// Find new static fields (without prompt, just result) // If in silent mode and no new keys, use existing config and skip prompts
const newStaticKeys = configKeys.filter((key) => { if (silentMode && newKeys.length === 0) {
const item = moduleConfig[key];
return item && typeof item === 'object' && !item.prompt && item.result && !existingKeys.includes(key);
});
// If in silent mode and no new keys (neither interactive nor static), use existing config and skip prompts
if (silentMode && newKeys.length === 0 && newStaticKeys.length === 0) {
if (this.existingConfig && this.existingConfig[moduleName]) { if (this.existingConfig && this.existingConfig[moduleName]) {
if (!this.collectedConfig[moduleName]) { if (!this.collectedConfig[moduleName]) {
this.collectedConfig[moduleName] = {}; this.collectedConfig[moduleName] = {};
@ -301,12 +294,9 @@ class ConfigCollector {
return false; // No new fields return false; // No new fields
} }
// If we have new fields (interactive or static), process them // If we have new fields, build questions first
if (newKeys.length > 0 || newStaticKeys.length > 0) { if (newKeys.length > 0) {
const questions = []; const questions = [];
const staticAnswers = {};
// Build questions for interactive fields
for (const key of newKeys) { for (const key of newKeys) {
const item = moduleConfig[key]; const item = moduleConfig[key];
const question = await this.buildQuestion(moduleName, key, item, moduleConfig); const question = await this.buildQuestion(moduleName, key, item, moduleConfig);
@ -315,50 +305,39 @@ class ConfigCollector {
} }
} }
// Prepare static answers (no prompt, just result)
for (const key of newStaticKeys) {
staticAnswers[`${moduleName}_${key}`] = undefined;
}
// Collect all answers (static + prompted)
let allAnswers = { ...staticAnswers };
if (questions.length > 0) { if (questions.length > 0) {
// Only show header if we actually have questions // Only show header if we actually have questions
CLIUtils.displayModuleConfigHeader(moduleName, moduleConfig.header, moduleConfig.subheader); CLIUtils.displayModuleConfigHeader(moduleName, moduleConfig.header, moduleConfig.subheader);
console.log(); // Line break before questions console.log(); // Line break before questions
const promptedAnswers = await inquirer.prompt(questions); const answers = await inquirer.prompt(questions);
// Merge prompted answers with static answers // Store answers for cross-referencing
Object.assign(allAnswers, promptedAnswers); Object.assign(this.allAnswers, answers);
} else if (newStaticKeys.length > 0) {
// Only static fields, no questions - show no config message // Process answers and build result values
for (const key of Object.keys(answers)) {
const originalKey = key.replace(`${moduleName}_`, '');
const item = moduleConfig[originalKey];
const value = answers[key];
let result;
if (Array.isArray(value)) {
result = value;
} else if (item.result) {
result = this.processResultTemplate(item.result, value);
} else {
result = value;
}
if (!this.collectedConfig[moduleName]) {
this.collectedConfig[moduleName] = {};
}
this.collectedConfig[moduleName][originalKey] = result;
}
} else {
// New keys exist but no questions generated - show no config message
CLIUtils.displayModuleNoConfig(moduleName, moduleConfig.header, moduleConfig.subheader); CLIUtils.displayModuleNoConfig(moduleName, moduleConfig.header, moduleConfig.subheader);
} }
// Store all answers for cross-referencing
Object.assign(this.allAnswers, allAnswers);
// Process all answers (both static and prompted)
for (const key of Object.keys(allAnswers)) {
const originalKey = key.replace(`${moduleName}_`, '');
const item = moduleConfig[originalKey];
const value = allAnswers[key];
let result;
if (Array.isArray(value)) {
result = value;
} else if (item.result) {
result = this.processResultTemplate(item.result, value);
} else {
result = value;
}
if (!this.collectedConfig[moduleName]) {
this.collectedConfig[moduleName] = {};
}
this.collectedConfig[moduleName][originalKey] = result;
}
} }
// Copy over existing values for fields that weren't prompted // Copy over existing values for fields that weren't prompted
@ -374,7 +353,7 @@ class ConfigCollector {
} }
} }
return newKeys.length > 0 || newStaticKeys.length > 0; // Return true if we had any new fields (interactive or static) return newKeys.length > 0; // Return true if we prompted for new fields
} }
/** /**
@ -522,52 +501,30 @@ class ConfigCollector {
// Process each config item // Process each config item
const questions = []; const questions = [];
const staticAnswers = {};
const configKeys = Object.keys(moduleConfig).filter((key) => key !== 'prompt'); const configKeys = Object.keys(moduleConfig).filter((key) => key !== 'prompt');
for (const key of configKeys) { for (const key of configKeys) {
const item = moduleConfig[key]; const item = moduleConfig[key];
// Skip if not a config object // Skip if not a config object
if (!item || typeof item !== 'object') { if (!item || typeof item !== 'object' || !item.prompt) {
continue; continue;
} }
// Handle static values (no prompt, just result) const question = await this.buildQuestion(moduleName, key, item, moduleConfig);
if (!item.prompt && item.result) { if (question) {
// Add to static answers with a marker value questions.push(question);
staticAnswers[`${moduleName}_${key}`] = undefined;
continue;
}
// Handle interactive values (with prompt)
if (item.prompt) {
const question = await this.buildQuestion(moduleName, key, item, moduleConfig);
if (question) {
questions.push(question);
}
} }
} }
// Collect all answers (static + prompted)
let allAnswers = { ...staticAnswers };
// Display appropriate header based on whether there are questions // Display appropriate header based on whether there are questions
if (questions.length > 0) { if (questions.length > 0) {
CLIUtils.displayModuleConfigHeader(moduleName, moduleConfig.header, moduleConfig.subheader); CLIUtils.displayModuleConfigHeader(moduleName, moduleConfig.header, moduleConfig.subheader);
console.log(); // Line break before questions console.log(); // Line break before questions
const promptedAnswers = await inquirer.prompt(questions); const answers = await inquirer.prompt(questions);
// Merge prompted answers with static answers // Store answers for cross-referencing
Object.assign(allAnswers, promptedAnswers); Object.assign(this.allAnswers, answers);
}
// Store all answers for cross-referencing
Object.assign(this.allAnswers, allAnswers);
// Process all answers (both static and prompted)
if (Object.keys(allAnswers).length > 0) {
const answers = allAnswers;
// Process answers and build result values // Process answers and build result values
for (const key of Object.keys(answers)) { for (const key of Object.keys(answers)) {

View File

@ -581,11 +581,6 @@ class ManifestGenerator {
*/ */
async writeWorkflowManifest(cfgDir) { async writeWorkflowManifest(cfgDir) {
const csvPath = path.join(cfgDir, 'workflow-manifest.csv'); const csvPath = path.join(cfgDir, 'workflow-manifest.csv');
const escapeCsv = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`;
const parseCsvLine = (line) => {
const columns = line.match(/(".*?"|[^",\s]+)(?=\s*,|\s*$)/g) || [];
return columns.map((c) => c.replaceAll(/^"|"$/g, ''));
};
// Read existing manifest to preserve entries // Read existing manifest to preserve entries
const existingEntries = new Map(); const existingEntries = new Map();
@ -597,21 +592,18 @@ class ManifestGenerator {
for (let i = 1; i < lines.length; i++) { for (let i = 1; i < lines.length; i++) {
const line = lines[i]; const line = lines[i];
if (line) { if (line) {
const parts = parseCsvLine(line); // Parse CSV (simple parsing assuming no commas in quoted fields)
const parts = line.split('","');
if (parts.length >= 4) { if (parts.length >= 4) {
const [name, description, module, workflowPath] = parts; const name = parts[0].replace(/^"/, '');
existingEntries.set(`${module}:${name}`, { const module = parts[2];
name, existingEntries.set(`${module}:${name}`, line);
description,
module,
path: workflowPath,
});
} }
} }
} }
} }
// Create CSV header - standalone column removed, everything is canonicalized to 4 columns // Create CSV header - removed standalone column as ALL workflows now generate commands
let csv = 'name,description,module,path\n'; let csv = 'name,description,module,path\n';
// Combine existing and new workflows // Combine existing and new workflows
@ -625,18 +617,12 @@ class ManifestGenerator {
// Add/update new workflows // Add/update new workflows
for (const workflow of this.workflows) { for (const workflow of this.workflows) {
const key = `${workflow.module}:${workflow.name}`; const key = `${workflow.module}:${workflow.name}`;
allWorkflows.set(key, { allWorkflows.set(key, `"${workflow.name}","${workflow.description}","${workflow.module}","${workflow.path}"`);
name: workflow.name,
description: workflow.description,
module: workflow.module,
path: workflow.path,
});
} }
// Write all workflows // Write all workflows
for (const [, value] of allWorkflows) { for (const [, value] of allWorkflows) {
const row = [escapeCsv(value.name), escapeCsv(value.description), escapeCsv(value.module), escapeCsv(value.path)].join(','); csv += value + '\n';
csv += row + '\n';
} }
await fs.writeFile(csvPath, csv); await fs.writeFile(csvPath, csv);