feat: generate workflow prompts from path files
This commit is contained in:
parent
28933486d4
commit
25793c33d7
|
|
@ -128,7 +128,10 @@ class GitHubCopilotSetup extends BaseIdeSetup {
|
|||
// Generate workflow prompts from config (shared logic)
|
||||
// Each prompt includes nextSteps guidance for the agent to suggest next workflows
|
||||
const promptGen = new WorkflowPromptGenerator();
|
||||
const promptRecommendations = await promptGen.generatePromptFiles(promptsDir, options.selectedModules || []);
|
||||
const promptRecommendations = await promptGen.generatePromptFiles(promptsDir, options.selectedModules || [], {
|
||||
projectDir,
|
||||
bmadDir,
|
||||
});
|
||||
const promptCount = Object.keys(promptRecommendations).length;
|
||||
|
||||
// Configure VS Code settings using pre-collected config if available
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
const path = require('node:path');
|
||||
const fs = require('fs-extra');
|
||||
const yaml = require('yaml');
|
||||
const { workflowPromptsConfig } = require('./workflow-prompts-config');
|
||||
|
||||
/**
|
||||
|
|
@ -40,11 +41,17 @@ class WorkflowPromptGenerator {
|
|||
* @param {Array<string>} selectedModules - Modules to include
|
||||
* @returns {Object} Map of prompt names to true for VS Code settings
|
||||
*/
|
||||
async generatePromptFiles(promptsDir, selectedModules = []) {
|
||||
async generatePromptFiles(promptsDir, selectedModules = [], options = {}) {
|
||||
const prompts = this.getWorkflowPrompts(selectedModules);
|
||||
const pathPrompts = await this.getWorkflowPathPrompts({
|
||||
bmadDir: options.bmadDir,
|
||||
projectDir: options.projectDir,
|
||||
selectedModules,
|
||||
});
|
||||
const mergedPrompts = this.mergePrompts(prompts, pathPrompts);
|
||||
const recommendations = {};
|
||||
|
||||
for (const prompt of prompts) {
|
||||
for (const prompt of mergedPrompts) {
|
||||
const frontmatter = ['---', `agent: ${prompt.agent}`, `description: "${prompt.description}"`];
|
||||
|
||||
if (prompt.model) {
|
||||
|
|
@ -60,6 +67,231 @@ class WorkflowPromptGenerator {
|
|||
|
||||
return recommendations;
|
||||
}
|
||||
|
||||
mergePrompts(staticPrompts, dynamicPrompts) {
|
||||
const merged = [];
|
||||
const seen = new Set();
|
||||
|
||||
for (const prompt of [...staticPrompts, ...dynamicPrompts]) {
|
||||
if (seen.has(prompt.name)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(prompt.name);
|
||||
merged.push(prompt);
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
async getWorkflowPathPrompts({ bmadDir, projectDir, selectedModules = [] }) {
|
||||
if (!bmadDir) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const prompts = [];
|
||||
const promptKeys = new Map();
|
||||
|
||||
for (const moduleName of selectedModules) {
|
||||
const pathFilesDir = await this.resolvePathFilesDir({ moduleName, bmadDir, projectDir });
|
||||
if (!pathFilesDir) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let pathFiles = [];
|
||||
try {
|
||||
pathFiles = await fs.readdir(pathFilesDir);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
const yamlFiles = pathFiles.filter((file) => file.endsWith('.yaml') || file.endsWith('.yml'));
|
||||
|
||||
for (const file of yamlFiles) {
|
||||
const filePath = path.join(pathFilesDir, file);
|
||||
let pathData;
|
||||
|
||||
try {
|
||||
pathData = yaml.parse(await fs.readFile(filePath, 'utf8'));
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
const phases = Array.isArray(pathData?.phases) ? pathData.phases : [];
|
||||
let lastAgent = null;
|
||||
|
||||
for (const phase of phases) {
|
||||
const phaseName = phase?.name || '';
|
||||
const workflows = Array.isArray(phase?.workflows) ? phase.workflows : [];
|
||||
|
||||
for (const workflow of workflows) {
|
||||
const agentKey = workflow?.agent || '';
|
||||
const command = workflow?.command || '';
|
||||
const workflowId = workflow?.id || '';
|
||||
|
||||
if (!command && !workflowId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const promptName = this.buildPromptName({ moduleName, workflowId, command });
|
||||
const prompt = this.buildPromptText({ moduleName, workflowId, command });
|
||||
const agent = this.buildAgentName({ moduleName, agentKey });
|
||||
const requiresNewChat = !!lastAgent && !!agentKey && agentKey !== lastAgent;
|
||||
const description = this.buildPromptDescription({
|
||||
workflow,
|
||||
promptName,
|
||||
requiresNewChat,
|
||||
});
|
||||
const model = this.resolveModel({ phaseName, workflow, agentKey });
|
||||
|
||||
lastAgent = agentKey || lastAgent;
|
||||
|
||||
const promptKey = `${moduleName}:${promptName}`;
|
||||
const existing = promptKeys.get(promptKey);
|
||||
|
||||
if (!existing) {
|
||||
const promptEntry = {
|
||||
name: promptName,
|
||||
agent,
|
||||
description,
|
||||
model,
|
||||
prompt,
|
||||
};
|
||||
|
||||
promptKeys.set(promptKey, promptEntry);
|
||||
prompts.push(promptEntry);
|
||||
} else if (requiresNewChat && !existing.description.includes('Ctrl+Shift+Enter')) {
|
||||
existing.description = this.appendNewChatHint(existing.description);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return prompts;
|
||||
}
|
||||
|
||||
async resolvePathFilesDir({ moduleName, bmadDir, projectDir }) {
|
||||
const workflowStatusPath = path.join(bmadDir, moduleName, 'workflows', 'workflow-status', 'workflow.yaml');
|
||||
|
||||
if (!(await fs.pathExists(workflowStatusPath))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let workflowStatus;
|
||||
try {
|
||||
workflowStatus = yaml.parse(await fs.readFile(workflowStatusPath, 'utf8'));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
const pathFilesRaw = workflowStatus?.path_files;
|
||||
if (!pathFilesRaw || typeof pathFilesRaw !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const workflowDir = path.dirname(workflowStatusPath).replaceAll('\\', '/');
|
||||
let resolved = pathFilesRaw.replace('{installed_path}', workflowDir);
|
||||
|
||||
if (projectDir) {
|
||||
const normalizedProjectDir = projectDir.replaceAll('\\', '/');
|
||||
resolved = resolved.replace('{project-root}', normalizedProjectDir);
|
||||
|
||||
const relativeBmadDir = path.relative(projectDir, bmadDir).replaceAll('\\', '/');
|
||||
if (relativeBmadDir && relativeBmadDir !== '_bmad') {
|
||||
resolved = resolved.replace('/_bmad/', `/${relativeBmadDir}/`);
|
||||
}
|
||||
}
|
||||
|
||||
resolved = resolved.replaceAll('\\', '/');
|
||||
|
||||
return path.resolve(resolved);
|
||||
}
|
||||
|
||||
buildPromptName({ moduleName, workflowId, command }) {
|
||||
let baseName = workflowId || command || 'workflow';
|
||||
|
||||
if (command && command.startsWith('/')) {
|
||||
baseName = command.split(':').pop() || baseName;
|
||||
} else if (command) {
|
||||
baseName = command;
|
||||
}
|
||||
|
||||
baseName = baseName.replace(/^\/+/, '');
|
||||
|
||||
return `${moduleName}-${baseName}`;
|
||||
}
|
||||
|
||||
buildPromptText({ moduleName, workflowId, command }) {
|
||||
if (command) {
|
||||
if (command.startsWith('/')) {
|
||||
return command;
|
||||
}
|
||||
|
||||
return `/bmad:${moduleName}:workflows:${command}`;
|
||||
}
|
||||
|
||||
if (workflowId) {
|
||||
return `/bmad:${moduleName}:workflows:${workflowId}`;
|
||||
}
|
||||
|
||||
return '*workflow-status';
|
||||
}
|
||||
|
||||
buildAgentName({ moduleName, agentKey }) {
|
||||
if (!agentKey) {
|
||||
return `bmd-custom-${moduleName}-pm`;
|
||||
}
|
||||
|
||||
return `bmd-custom-${moduleName}-${agentKey}`;
|
||||
}
|
||||
|
||||
buildPromptDescription({ workflow, promptName, requiresNewChat }) {
|
||||
const title = this.toTitle(promptName.replace(/^[^-]+-/, ''));
|
||||
const detail = workflow?.note || workflow?.output || workflow?.description || '';
|
||||
const baseDescription = detail ? `${title} — ${detail}` : title;
|
||||
|
||||
return requiresNewChat ? this.appendNewChatHint(baseDescription) : baseDescription;
|
||||
}
|
||||
|
||||
appendNewChatHint(description) {
|
||||
return `${description} (open a new chat with Ctrl+Shift+Enter)`;
|
||||
}
|
||||
|
||||
resolveModel({ phaseName, workflow, agentKey }) {
|
||||
const phase = (phaseName || '').toLowerCase();
|
||||
const id = (workflow?.id || '').toLowerCase();
|
||||
const command = (workflow?.command || '').toLowerCase();
|
||||
const agent = (agentKey || '').toLowerCase();
|
||||
|
||||
if (id.includes('code-review') || command.includes('code-review') || agent.endsWith('dev') || agent.includes('game-dev')) {
|
||||
return 'gpt-5.2-codex';
|
||||
}
|
||||
|
||||
if (id.includes('dev-story') || command.includes('dev-story') || id.includes('implement') || command.includes('implement')) {
|
||||
return 'gpt-5.2-codex';
|
||||
}
|
||||
|
||||
if (id.includes('sprint-planning') || command.includes('sprint-planning')) {
|
||||
return 'claude-opus-4.5';
|
||||
}
|
||||
|
||||
if (
|
||||
phase.includes('analysis') ||
|
||||
phase.includes('planning') ||
|
||||
phase.includes('solution') ||
|
||||
phase.includes('design') ||
|
||||
phase.includes('technical') ||
|
||||
phase.includes('pre-production')
|
||||
) {
|
||||
return 'claude-opus-4.5';
|
||||
}
|
||||
|
||||
return 'gpt-5.2';
|
||||
}
|
||||
|
||||
toTitle(value) {
|
||||
return value.replaceAll(/[-_]/g, ' ').replaceAll(/\b\w/g, (match) => match.toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { WorkflowPromptGenerator };
|
||||
|
|
|
|||
|
|
@ -11,11 +11,8 @@
|
|||
*/
|
||||
|
||||
const workflowPromptsConfig = {
|
||||
// BMad Method Module (bmm) - Standard development workflow
|
||||
// BMad Method Module (bmm) - Static prompts not covered by path files
|
||||
bmm: [
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// Phase 1 - Analysis (Optional)
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
{
|
||||
name: 'workflow-init',
|
||||
agent: 'bmd-custom-bmm-analyst',
|
||||
|
|
@ -24,14 +21,6 @@ const workflowPromptsConfig = {
|
|||
model: 'claude-opus-4.5',
|
||||
prompt: '*workflow-init',
|
||||
},
|
||||
{
|
||||
name: 'brainstorm',
|
||||
agent: 'bmd-custom-bmm-analyst',
|
||||
shortcut: 'BP',
|
||||
description: '[BP] Brainstorm project ideas and concepts',
|
||||
model: 'claude-opus-4.5',
|
||||
prompt: '*brainstorm-project',
|
||||
},
|
||||
{
|
||||
name: 'workflow-status',
|
||||
agent: 'bmd-custom-bmm-pm',
|
||||
|
|
@ -40,67 +29,6 @@ const workflowPromptsConfig = {
|
|||
model: 'claude-opus-4.5',
|
||||
prompt: '*workflow-status',
|
||||
},
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// Phase 2 - Planning (Required)
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
{
|
||||
name: 'prd',
|
||||
agent: 'bmd-custom-bmm-pm',
|
||||
shortcut: 'PD',
|
||||
description: '[PD] Create Product Requirements Document (PRD)',
|
||||
model: 'claude-opus-4.5',
|
||||
prompt: '*prd',
|
||||
},
|
||||
{
|
||||
name: 'ux-design',
|
||||
agent: 'bmd-custom-bmm-ux-designer',
|
||||
shortcut: 'UD',
|
||||
description: '[UD] Create UX Design specification (open a new chat with Ctrl+Shift+Enter)',
|
||||
model: 'claude-opus-4.5',
|
||||
prompt: '*ux-design',
|
||||
},
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// Phase 3 - Solutioning
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
{
|
||||
name: 'create-architecture',
|
||||
agent: 'bmd-custom-bmm-architect',
|
||||
shortcut: 'CA',
|
||||
description: '[CA] Create system architecture document (open a new chat with Ctrl+Shift+Enter)',
|
||||
model: 'claude-opus-4.5',
|
||||
prompt: '*create-architecture',
|
||||
},
|
||||
{
|
||||
name: 'epics-stories',
|
||||
agent: 'bmd-custom-bmm-pm',
|
||||
shortcut: 'ES',
|
||||
description: '[ES] Create Epics and User Stories from PRD (open a new chat with Ctrl+Shift+Enter)',
|
||||
model: 'claude-opus-4.5',
|
||||
prompt: '*epics-stories',
|
||||
},
|
||||
{
|
||||
name: 'implementation-readiness',
|
||||
agent: 'bmd-custom-bmm-architect',
|
||||
shortcut: 'IR',
|
||||
description: '[IR] Check implementation readiness across all docs (open a new chat with Ctrl+Shift+Enter)',
|
||||
model: 'claude-opus-4.5',
|
||||
prompt: '*implementation-readiness',
|
||||
},
|
||||
{
|
||||
name: 'sprint-planning',
|
||||
agent: 'bmd-custom-bmm-sm',
|
||||
shortcut: 'SP',
|
||||
description: '[SP] Initialize sprint planning from epics (open a new chat with Ctrl+Shift+Enter)',
|
||||
model: 'claude-opus-4.5',
|
||||
prompt: '*sprint-planning',
|
||||
},
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// Phase 4 - Implementation: The "Keep Going" Cycle
|
||||
// SM → create-story → DEV → dev-story → code-review → (create-story | retrospective)
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
{
|
||||
name: 'create-story',
|
||||
agent: 'bmd-custom-bmm-sm',
|
||||
|
|
@ -143,9 +71,8 @@ const workflowPromptsConfig = {
|
|||
},
|
||||
],
|
||||
|
||||
// BMad Game Development Module (bmgd)
|
||||
// BMad Game Development Module (bmgd) - Static prompts not covered by path files
|
||||
bmgd: [
|
||||
// Implementation cycle
|
||||
{
|
||||
name: 'game-implement',
|
||||
agent: 'bmd-custom-bmgd-game-dev',
|
||||
|
|
@ -162,31 +89,6 @@ const workflowPromptsConfig = {
|
|||
model: 'gpt-5.2',
|
||||
prompt: '*game-qa',
|
||||
},
|
||||
// Planning & Design
|
||||
{
|
||||
name: 'game-design',
|
||||
agent: 'bmd-custom-bmgd-game-designer',
|
||||
shortcut: 'GD',
|
||||
description: '[GD] Design game mechanics and systems (open a new chat with Ctrl+Shift+Enter)',
|
||||
model: 'claude-opus-4.5',
|
||||
prompt: '*game-design',
|
||||
},
|
||||
{
|
||||
name: 'game-architecture',
|
||||
agent: 'bmd-custom-bmgd-game-architect',
|
||||
shortcut: 'GA',
|
||||
description: '[GA] Create game technical architecture (open a new chat with Ctrl+Shift+Enter)',
|
||||
model: 'claude-opus-4.5',
|
||||
prompt: '*game-architecture',
|
||||
},
|
||||
{
|
||||
name: 'game-sprint',
|
||||
agent: 'bmd-custom-bmgd-game-scrum-master',
|
||||
shortcut: 'GS',
|
||||
description: '[GS] Plan game development sprint (open a new chat with Ctrl+Shift+Enter)',
|
||||
model: 'claude-opus-4.5',
|
||||
prompt: '*game-sprint',
|
||||
},
|
||||
],
|
||||
|
||||
// Core agents (always available)
|
||||
|
|
|
|||
Loading…
Reference in New Issue