feat: generate and update AGENTS.md for OpenCode integration with agent and task details
This commit is contained in:
parent
7b47b8a334
commit
172a39050d
|
|
@ -523,6 +523,158 @@ class IdeSetup extends BaseIdeSetup {
|
||||||
return { configObj, summary };
|
return { configObj, summary };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper: generate AGENTS.md section for OpenCode (acts as system prompt memory)
|
||||||
|
const generateOpenCodeAgentsMd = async () => {
|
||||||
|
try {
|
||||||
|
const filePath = path.join(installDir, 'AGENTS.md');
|
||||||
|
const startMarker = '<!-- BEGIN: BMAD-AGENTS-OPENCODE -->';
|
||||||
|
const endMarker = '<!-- END: BMAD-AGENTS-OPENCODE -->';
|
||||||
|
|
||||||
|
const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
|
||||||
|
const tasks = await this.getAllTaskIds(installDir);
|
||||||
|
|
||||||
|
let section = '';
|
||||||
|
section += `${startMarker}\n`;
|
||||||
|
section += `# BMAD-METHOD Agents and Tasks (OpenCode)\n\n`;
|
||||||
|
section += `OpenCode reads AGENTS.md during initialization and uses it as part of its system prompt for the session. This section is auto-generated by BMAD-METHOD for OpenCode.\n\n`;
|
||||||
|
section += `## How To Use With OpenCode\n\n`;
|
||||||
|
section += `- Run \`opencode\` in this project. OpenCode will read \`AGENTS.md\` and your OpenCode config (opencode.json[c]).\n`;
|
||||||
|
section += `- Reference a role naturally, e.g., "As dev, implement ..." or use commands defined in your BMAD tasks.\n`;
|
||||||
|
section += `- Commit \`.bmad-core\` and \`AGENTS.md\` if you want teammates to share the same configuration.\n`;
|
||||||
|
section += `- Refresh this section after BMAD updates: \`npx bmad-method install -f -i opencode\`.\n\n`;
|
||||||
|
|
||||||
|
section += `### Helpful Commands\n\n`;
|
||||||
|
section += `- List agents: \`npx bmad-method list:agents\`\n`;
|
||||||
|
section += `- Reinstall BMAD core and regenerate this section: \`npx bmad-method install -f -i opencode\`\n`;
|
||||||
|
section += `- Validate configuration: \`npx bmad-method validate\`\n\n`;
|
||||||
|
|
||||||
|
// Brief context note for modes and tools
|
||||||
|
section += `Note\n`;
|
||||||
|
section += `- Orchestrators run as mode: primary; other agents as subagent.\n`;
|
||||||
|
section += `- All agents have tools enabled: write, edit, bash.\n\n`;
|
||||||
|
|
||||||
|
section += `## Agents\n\n`;
|
||||||
|
section += `### Directory\n\n`;
|
||||||
|
section += `| Title | ID | When To Use |\n|---|---|---|\n`;
|
||||||
|
|
||||||
|
// Fallback descriptions for core agents (used if whenToUse is missing)
|
||||||
|
const fallbackDescriptions = {
|
||||||
|
'ux-expert':
|
||||||
|
'Use for UI/UX design, wireframes, prototypes, front-end specs, and user experience optimization',
|
||||||
|
sm: 'Use for story creation, epic management, retrospectives in party-mode, and agile process guidance',
|
||||||
|
qa: 'Ensure quality strategy, test design, risk profiling, and QA gates across features',
|
||||||
|
po: 'Backlog management, story refinement, acceptance criteria, sprint planning, prioritization decisions',
|
||||||
|
pm: 'PRDs, product strategy, feature prioritization, roadmap planning, and stakeholder communication',
|
||||||
|
dev: 'Code implementation, debugging, refactoring, and development best practices',
|
||||||
|
'bmad-orchestrator':
|
||||||
|
'Workflow coordination, multi-agent tasks, role switching guidance, and when unsure which specialist to consult',
|
||||||
|
'bmad-master':
|
||||||
|
'Comprehensive cross-domain execution for tasks that do not require a specific persona',
|
||||||
|
architect:
|
||||||
|
'System design, architecture docs, technology selection, API design, and infrastructure planning',
|
||||||
|
analyst:
|
||||||
|
'Discovery/research, competitive analysis, project briefs, initial discovery, and brownfield documentation',
|
||||||
|
};
|
||||||
|
|
||||||
|
const sanitizeDesc = (s) => {
|
||||||
|
if (!s) return '';
|
||||||
|
let t = String(s).trim();
|
||||||
|
// Drop surrounding single/double/backtick quotes
|
||||||
|
t = t.replaceAll(/^['"`]+|['"`]+$/g, '');
|
||||||
|
// Collapse whitespace
|
||||||
|
t = t.replaceAll(/\s+/g, ' ').trim();
|
||||||
|
return t;
|
||||||
|
};
|
||||||
|
const agentSummaries = [];
|
||||||
|
for (const agentId of agents) {
|
||||||
|
const agentPath = await this.findAgentPath(agentId, installDir);
|
||||||
|
if (!agentPath) continue;
|
||||||
|
let whenToUse = '';
|
||||||
|
try {
|
||||||
|
const raw = await fileManager.readFile(agentPath);
|
||||||
|
const yamlMatch = raw.match(/```ya?ml\r?\n([\s\S]*?)```/);
|
||||||
|
const yamlBlock = yamlMatch ? yamlMatch[1].trim() : null;
|
||||||
|
if (yamlBlock) {
|
||||||
|
try {
|
||||||
|
const data = yaml.load(yamlBlock);
|
||||||
|
if (data && typeof data.whenToUse === 'string') {
|
||||||
|
whenToUse = data.whenToUse;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore YAML parse errors
|
||||||
|
}
|
||||||
|
if (!whenToUse) {
|
||||||
|
// Fallback regex supporting single or double quotes
|
||||||
|
const m1 = yamlBlock.match(/whenToUse:\s*"([^\n"]+)"/i);
|
||||||
|
const m2 = yamlBlock.match(/whenToUse:\s*'([^\n']+)'/i);
|
||||||
|
const m3 = yamlBlock.match(/whenToUse:\s*([^\n\r]+)/i);
|
||||||
|
whenToUse = (m1?.[1] || m2?.[1] || m3?.[1] || '').trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore read/parse errors for agent metadata extraction
|
||||||
|
}
|
||||||
|
const title = await this.getAgentTitle(agentId, installDir);
|
||||||
|
const finalDesc = sanitizeDesc(whenToUse) || fallbackDescriptions[agentId] || '—';
|
||||||
|
agentSummaries.push({ agentId, title, whenToUse: finalDesc, path: agentPath });
|
||||||
|
// Strict 3-column row
|
||||||
|
section += `| ${title} | ${agentId} | ${finalDesc} |\n`;
|
||||||
|
}
|
||||||
|
section += `\n`;
|
||||||
|
|
||||||
|
for (const { agentId, title, whenToUse, path: agentPath } of agentSummaries) {
|
||||||
|
const relativePath = path.relative(installDir, agentPath).replaceAll('\\', '/');
|
||||||
|
section += `### ${title} (id: ${agentId})\n`;
|
||||||
|
section += `Source: [${relativePath}](${relativePath})\n\n`;
|
||||||
|
if (whenToUse) section += `- When to use: ${whenToUse}\n`;
|
||||||
|
section += `- How to activate: Mention "As ${agentId}, ..." to get role-aligned behavior\n`;
|
||||||
|
section += `- Full definition: open the source file above (content not embedded)\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tasks && tasks.length > 0) {
|
||||||
|
section += `## Tasks\n\n`;
|
||||||
|
section += `These are reusable task briefs; use the paths to open them as needed.\n\n`;
|
||||||
|
for (const taskId of tasks) {
|
||||||
|
const taskPath = await this.findTaskPath(taskId, installDir);
|
||||||
|
if (!taskPath) continue;
|
||||||
|
const relativePath = path.relative(installDir, taskPath).replaceAll('\\', '/');
|
||||||
|
section += `### Task: ${taskId}\n`;
|
||||||
|
section += `Source: [${relativePath}](${relativePath})\n`;
|
||||||
|
section += `- How to use: Reference the task in your prompt or execute via your configured commands.\n`;
|
||||||
|
section += `- Full brief: open the source file above (content not embedded)\n\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
section += `${endMarker}\n`;
|
||||||
|
|
||||||
|
let finalContent = '';
|
||||||
|
if (await fileManager.pathExists(filePath)) {
|
||||||
|
const existing = await fileManager.readFile(filePath);
|
||||||
|
if (existing.includes(startMarker) && existing.includes(endMarker)) {
|
||||||
|
const pattern = String.raw`${startMarker}[\s\S]*?${endMarker}`;
|
||||||
|
const replaced = existing.replace(new RegExp(pattern, 'm'), section);
|
||||||
|
finalContent = replaced;
|
||||||
|
} else {
|
||||||
|
finalContent = existing.trimEnd() + `\n\n` + section;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
finalContent += '# Project Agents\n\n';
|
||||||
|
finalContent += 'This file provides guidance and memory for your coding CLI.\n\n';
|
||||||
|
finalContent += section;
|
||||||
|
}
|
||||||
|
|
||||||
|
await fileManager.writeFile(filePath, finalContent);
|
||||||
|
console.log(chalk.green('✓ Created/updated AGENTS.md for OpenCode CLI integration'));
|
||||||
|
console.log(
|
||||||
|
chalk.dim(
|
||||||
|
'OpenCode reads AGENTS.md automatically on init. Run `opencode` in this project to use BMAD agents.',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
console.log(chalk.yellow('⚠︎ Skipped creating AGENTS.md for OpenCode (write failed)'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (hasJson || hasJsonc) {
|
if (hasJson || hasJsonc) {
|
||||||
// Preserve existing top-level fields; only touch instructions
|
// Preserve existing top-level fields; only touch instructions
|
||||||
const targetPath = hasJsonc ? jsoncPath : jsonPath;
|
const targetPath = hasJsonc ? jsoncPath : jsonPath;
|
||||||
|
|
@ -545,6 +697,8 @@ class IdeSetup extends BaseIdeSetup {
|
||||||
` File: ${path.basename(targetPath)} | Agents +${summary.agentsAdded} ~${summary.agentsUpdated} ⨯${summary.agentsSkipped} | Commands +${summary.commandsAdded} ~${summary.commandsUpdated} ⨯${summary.commandsSkipped}`,
|
` File: ${path.basename(targetPath)} | Agents +${summary.agentsAdded} ~${summary.agentsUpdated} ⨯${summary.agentsSkipped} | Commands +${summary.commandsAdded} ~${summary.commandsUpdated} ⨯${summary.commandsSkipped}`,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
// Ensure AGENTS.md is created/updated for OpenCode as well
|
||||||
|
await generateOpenCodeAgentsMd();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(chalk.red('✗ Failed to update existing OpenCode config'), error.message);
|
console.log(chalk.red('✗ Failed to update existing OpenCode config'), error.message);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -571,6 +725,8 @@ class IdeSetup extends BaseIdeSetup {
|
||||||
` File: opencode.jsonc | Agents +${summary.agentsAdded} | Commands +${summary.commandsAdded}`,
|
` File: opencode.jsonc | Agents +${summary.agentsAdded} | Commands +${summary.commandsAdded}`,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
// Also create/update AGENTS.md for OpenCode on new-config path
|
||||||
|
await generateOpenCodeAgentsMd();
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(chalk.red('✗ Failed to create opencode.jsonc'), error.message);
|
console.log(chalk.red('✗ Failed to create opencode.jsonc'), error.message);
|
||||||
|
|
@ -715,7 +871,6 @@ class IdeSetup extends BaseIdeSetup {
|
||||||
if (options.webEnabled) {
|
if (options.webEnabled) {
|
||||||
if (exists) {
|
if (exists) {
|
||||||
let gi = await fileManager.readFile(gitignorePath);
|
let gi = await fileManager.readFile(gitignorePath);
|
||||||
// Remove lines that ignore BMAD dot-folders
|
|
||||||
const updated = gi
|
const updated = gi
|
||||||
.split(/\r?\n/)
|
.split(/\r?\n/)
|
||||||
.filter((l) => !/^\s*\.bmad-core\/?\s*$/.test(l) && !/^\s*\.bmad-\*\/?\s*$/.test(l))
|
.filter((l) => !/^\s*\.bmad-core\/?\s*$/.test(l) && !/^\s*\.bmad-\*\/?\s*$/.test(l))
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue