fix: escape YAML descriptions and preserve user copilot-instructions
- Escape single quotes in YAML frontmatter descriptions across all prompt generators (createWorkflowPromptContent, createTechWriterPromptContent, createAgentActivatorPromptContent) to match createAgentContent behavior - Make copilot-instructions.md non-destructive using BMAD markers (<!-- BMAD:START --> / <!-- BMAD:END -->) to preserve user content - On cleanup, only remove content between markers; skip files without markers - Back up existing unmarked files before overwriting Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7d0de22ddc
commit
3ba4bbcb73
|
|
@ -255,7 +255,7 @@ You must fully embody this agent's persona and follow all activation instruction
|
||||||
* @returns {string} Prompt file content
|
* @returns {string} Prompt file content
|
||||||
*/
|
*/
|
||||||
createWorkflowPromptContent(entry, workflowFile) {
|
createWorkflowPromptContent(entry, workflowFile) {
|
||||||
const description = this.createPromptDescription(entry.name);
|
const description = this.escapeYamlSingleQuote(this.createPromptDescription(entry.name));
|
||||||
const configLine = '1. Load {project-root}/_bmad/bmm/config.yaml and store ALL fields as session variables';
|
const configLine = '1. Load {project-root}/_bmad/bmm/config.yaml and store ALL fields as session variables';
|
||||||
|
|
||||||
let body;
|
let body;
|
||||||
|
|
@ -348,8 +348,10 @@ ${body}
|
||||||
const cmd = techWriterCommands[entry.name];
|
const cmd = techWriterCommands[entry.name];
|
||||||
if (!cmd) return null;
|
if (!cmd) return null;
|
||||||
|
|
||||||
|
const safeDescription = this.escapeYamlSingleQuote(cmd.description);
|
||||||
|
|
||||||
const content = `---
|
const content = `---
|
||||||
description: '${cmd.description}'
|
description: '${safeDescription}'
|
||||||
agent: 'agent'
|
agent: 'agent'
|
||||||
tools: ['read', 'edit', 'search', 'execute']
|
tools: ['read', 'edit', 'search', 'execute']
|
||||||
---
|
---
|
||||||
|
|
@ -376,11 +378,12 @@ tools: ['read', 'edit', 'search', 'execute']
|
||||||
description = this.formatTitle(artifact.name);
|
description = this.formatTitle(artifact.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const safeDescription = this.escapeYamlSingleQuote(description);
|
||||||
const agentPath = artifact.agentPath || artifact.relativePath;
|
const agentPath = artifact.agentPath || artifact.relativePath;
|
||||||
const agentFilePath = `{project-root}/_bmad/${agentPath}`;
|
const agentFilePath = `{project-root}/_bmad/${agentPath}`;
|
||||||
|
|
||||||
return `---
|
return `---
|
||||||
description: '${description}'
|
description: '${safeDescription}'
|
||||||
agent: 'agent'
|
agent: 'agent'
|
||||||
tools: ['read', 'edit', 'search', 'execute']
|
tools: ['read', 'edit', 'search', 'execute']
|
||||||
---
|
---
|
||||||
|
|
@ -428,7 +431,7 @@ tools: ['read', 'edit', 'search', 'execute']
|
||||||
}
|
}
|
||||||
|
|
||||||
const bmad = '_bmad';
|
const bmad = '_bmad';
|
||||||
const content = `# BMAD Method — Project Instructions
|
const bmadSection = `# BMAD Method — Project Instructions
|
||||||
|
|
||||||
## Project Configuration
|
## Project Configuration
|
||||||
|
|
||||||
|
|
@ -471,13 +474,39 @@ tools: ['read', 'edit', 'search', 'execute']
|
||||||
${agentsTable}
|
${agentsTable}
|
||||||
## Slash Commands
|
## Slash Commands
|
||||||
|
|
||||||
Type \`/bmad-\` in Copilot Chat to see all available BMAD workflows and agent activators. Agents are also available in the agents dropdown.
|
Type \`/bmad-\` in Copilot Chat to see all available BMAD workflows and agent activators. Agents are also available in the agents dropdown.`;
|
||||||
`;
|
|
||||||
|
|
||||||
const instructionsPath = path.join(projectDir, this.configDir, 'copilot-instructions.md');
|
const instructionsPath = path.join(projectDir, this.configDir, 'copilot-instructions.md');
|
||||||
await this.writeFile(instructionsPath, content);
|
const markerStart = '<!-- BMAD:START -->';
|
||||||
|
const markerEnd = '<!-- BMAD:END -->';
|
||||||
|
const markedContent = `${markerStart}\n${bmadSection}\n${markerEnd}`;
|
||||||
|
|
||||||
|
if (await fs.pathExists(instructionsPath)) {
|
||||||
|
const existing = await fs.readFile(instructionsPath, 'utf8');
|
||||||
|
const startIdx = existing.indexOf(markerStart);
|
||||||
|
const endIdx = existing.indexOf(markerEnd);
|
||||||
|
|
||||||
|
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
||||||
|
// Replace only the BMAD section between markers
|
||||||
|
const before = existing.slice(0, startIdx);
|
||||||
|
const after = existing.slice(endIdx + markerEnd.length);
|
||||||
|
const merged = `${before}${markedContent}${after}`;
|
||||||
|
await this.writeFile(instructionsPath, merged);
|
||||||
|
console.log(chalk.green(' ✓ Updated BMAD section in copilot-instructions.md'));
|
||||||
|
} else {
|
||||||
|
// Existing file without markers — back it up before overwriting
|
||||||
|
const backupPath = `${instructionsPath}.bak`;
|
||||||
|
await fs.copy(instructionsPath, backupPath);
|
||||||
|
console.log(chalk.yellow(` ⚠ Backed up existing copilot-instructions.md → copilot-instructions.md.bak`));
|
||||||
|
await this.writeFile(instructionsPath, `${markedContent}\n`);
|
||||||
|
console.log(chalk.green(' ✓ Generated copilot-instructions.md (with BMAD markers)'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No existing file — create fresh with markers
|
||||||
|
await this.writeFile(instructionsPath, `${markedContent}\n`);
|
||||||
console.log(chalk.green(' ✓ Generated copilot-instructions.md'));
|
console.log(chalk.green(' ✓ Generated copilot-instructions.md'));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load module config.yaml for template variables
|
* Load module config.yaml for template variables
|
||||||
|
|
@ -502,6 +531,16 @@ Type \`/bmad-\` in Copilot Chat to see all available BMAD workflows and agent ac
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escape a string for use inside YAML single-quoted values.
|
||||||
|
* In YAML, the only escape inside single quotes is '' for a literal '.
|
||||||
|
* @param {string} value - Raw string
|
||||||
|
* @returns {string} Escaped string safe for YAML single-quoted context
|
||||||
|
*/
|
||||||
|
escapeYamlSingleQuote(value) {
|
||||||
|
return value.replaceAll("'", "''");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format name as title
|
* Format name as title
|
||||||
*/
|
*/
|
||||||
|
|
@ -552,12 +591,34 @@ Type \`/bmad-\` in Copilot Chat to see all available BMAD workflows and agent ac
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up copilot-instructions.md
|
// Clean up BMAD section from copilot-instructions.md (preserve user content)
|
||||||
const instructionsPath = path.join(projectDir, this.configDir, 'copilot-instructions.md');
|
const instructionsPath = path.join(projectDir, this.configDir, 'copilot-instructions.md');
|
||||||
if (await fs.pathExists(instructionsPath)) {
|
if (await fs.pathExists(instructionsPath)) {
|
||||||
|
const existing = await fs.readFile(instructionsPath, 'utf8');
|
||||||
|
const markerStart = '<!-- BMAD:START -->';
|
||||||
|
const markerEnd = '<!-- BMAD:END -->';
|
||||||
|
const startIdx = existing.indexOf(markerStart);
|
||||||
|
const endIdx = existing.indexOf(markerEnd);
|
||||||
|
|
||||||
|
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
||||||
|
// Remove only the BMAD section between markers (inclusive)
|
||||||
|
const before = existing.slice(0, startIdx);
|
||||||
|
const after = existing.slice(endIdx + markerEnd.length);
|
||||||
|
const remaining = (before + after).trim();
|
||||||
|
|
||||||
|
if (remaining.length > 0) {
|
||||||
|
await fs.writeFile(instructionsPath, `${remaining}\n`);
|
||||||
|
console.log(chalk.dim(' Cleaned up BMAD section from copilot-instructions.md (user content preserved)'));
|
||||||
|
} else {
|
||||||
await fs.remove(instructionsPath);
|
await fs.remove(instructionsPath);
|
||||||
console.log(chalk.dim(' Cleaned up copilot-instructions.md'));
|
console.log(chalk.dim(' Cleaned up copilot-instructions.md'));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// No markers — file is either entirely BMAD-generated or entirely user content.
|
||||||
|
// Leave it alone during cleanup to avoid destroying user content.
|
||||||
|
console.log(chalk.dim(' Skipped copilot-instructions.md (no BMAD markers found, not modified)'));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue