awesome-copilot/repository-instructions.js
copilot-swe-agent[bot] 920c966ed9 Implement GitHub Copilot repository instructions support
Co-authored-by: AstroSteveo <34114851+AstroSteveo@users.noreply.github.com>
2025-09-23 23:01:05 +00:00

344 lines
9.8 KiB
JavaScript

#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const { parseConfigYamlContent } = require("./apply-config");
const { computeEffectiveItemStates } = require("./config-manager");
/**
* Generate or update .github/copilot-instructions.md based on enabled instructions
*/
async function generateRepositoryInstructions(configPath = "awesome-copilot.config.yml", options = {}) {
const {
outputFile = ".github/copilot-instructions.md",
template = "repository",
includeHeader = true,
rootDir = __dirname
} = options;
console.log("🤖 Generating GitHub Copilot repository instructions...");
// Load configuration
if (!fs.existsSync(configPath)) {
throw new Error(`Configuration file not found: ${configPath}`);
}
const rawContent = fs.readFileSync(configPath, "utf8");
const config = parseConfigYamlContent(rawContent) || {};
// Compute effective states to determine which instructions are enabled
const effectiveStates = computeEffectiveItemStates(config);
// Get enabled instructions
const enabledInstructions = [];
for (const [instructionName, state] of Object.entries(effectiveStates.instructions)) {
if (state.enabled) {
const instructionPath = path.join(rootDir, "instructions", `${instructionName}.instructions.md`);
if (fs.existsSync(instructionPath)) {
enabledInstructions.push({
name: instructionName,
path: instructionPath,
reason: state.reason
});
}
}
}
console.log(`📋 Found ${enabledInstructions.length} enabled instructions`);
// Generate the repository instructions content
const content = await generateInstructionsContent(enabledInstructions, template, includeHeader);
// Ensure output directory exists
const outputDir = path.dirname(outputFile);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
console.log(`📁 Created directory: ${outputDir}`);
}
// Write the file
fs.writeFileSync(outputFile, content);
console.log(`✅ Generated repository instructions: ${outputFile}`);
return {
file: outputFile,
instructionsCount: enabledInstructions.length,
instructions: enabledInstructions.map(i => i.name)
};
}
/**
* Generate the content for the repository instructions file
*/
async function generateInstructionsContent(enabledInstructions, template, includeHeader) {
let content = "";
if (includeHeader) {
content += generateHeader();
}
if (enabledInstructions.length === 0) {
content += generateEmptyInstructions();
return content;
}
// Add instructions based on template
if (template === "repository") {
content += generateRepositoryTemplate(enabledInstructions);
} else if (template === "consolidated") {
content += await generateConsolidatedTemplate(enabledInstructions);
} else {
content += await generateBasicTemplate(enabledInstructions);
}
return content;
}
/**
* Generate header for the repository instructions
*/
function generateHeader() {
return `# GitHub Copilot Repository Instructions
This file contains custom instructions for GitHub Copilot when working in this repository.
These instructions are automatically generated from the enabled instruction files in the awesome-copilot configuration.
## How This Works
GitHub Copilot will automatically use these instructions when:
- You're working in this repository
- Copilot is generating code, explanations, or suggestions
- You're using Copilot Chat within this repository context
## Instructions Source
These instructions are compiled from the following sources:
- Enabled instruction files from the awesome-copilot configuration
- Repository-specific coding standards and best practices
- Technology-specific guidelines relevant to this project
---
`;
}
/**
* Generate content when no instructions are enabled
*/
function generateEmptyInstructions() {
return `## No Instructions Enabled
Currently, no instruction files are enabled in the awesome-copilot configuration.
To add instructions:
1. Edit your \`awesome-copilot.config.yml\` file
2. Enable the desired instruction files
3. Run \`awesome-copilot generate-repo-instructions\` to update this file
Available instructions can be found in the \`instructions/\` directory.
`;
}
/**
* Generate repository-style template with references to instruction files
*/
function generateRepositoryTemplate(enabledInstructions) {
let content = `## Active Instructions
The following ${enabledInstructions.length} instruction set${enabledInstructions.length !== 1 ? 's are' : ' is'} currently active for this repository:
`;
// Group instructions by reason
const groupedByReason = {};
enabledInstructions.forEach(instruction => {
const reason = instruction.reason || 'explicit';
if (!groupedByReason[reason]) {
groupedByReason[reason] = [];
}
groupedByReason[reason].push(instruction);
});
// Add instructions grouped by reason
for (const [reason, instructions] of Object.entries(groupedByReason)) {
content += `### ${reason === 'explicit' ? 'Explicitly Enabled' :
reason === 'collection' ? 'Enabled via Collections' :
reason.charAt(0).toUpperCase() + reason.slice(1)}\n\n`;
instructions.forEach(instruction => {
const title = extractTitleFromInstruction(instruction.path) || instruction.name;
content += `- **${title}** (\`${instruction.name}\`)\n`;
});
content += '\n';
}
content += `## Instruction Details
For detailed information about each instruction set, refer to the individual instruction files in the \`instructions/\` directory.
`;
return content;
}
/**
* Generate consolidated template with full instruction content
*/
async function generateConsolidatedTemplate(enabledInstructions) {
let content = `## Consolidated Instructions
The following instructions combine all enabled instruction sets:
`;
for (const instruction of enabledInstructions) {
const title = extractTitleFromInstruction(instruction.path) || instruction.name;
const instructionContent = fs.readFileSync(instruction.path, 'utf8');
// Extract the main content (skip frontmatter)
const cleanContent = extractInstructionContent(instructionContent);
content += `### ${title}
${cleanContent}
---
`;
}
return content;
}
/**
* Generate basic template with simple list
*/
async function generateBasicTemplate(enabledInstructions) {
let content = `## Enabled Instructions
`;
enabledInstructions.forEach(instruction => {
const title = extractTitleFromInstruction(instruction.path) || instruction.name;
content += `- ${title}\n`;
});
content += `\nFor detailed instruction content, see the individual files in the \`instructions/\` directory.
`;
return content;
}
/**
* Extract title from instruction file
*/
function extractTitleFromInstruction(filePath) {
try {
const content = fs.readFileSync(filePath, 'utf8');
const lines = content.split('\n');
// Look for title in frontmatter first
let inFrontmatter = false;
for (const line of lines) {
if (line.trim() === '---') {
inFrontmatter = !inFrontmatter;
continue;
}
if (inFrontmatter && line.includes('title:')) {
const title = line.substring(line.indexOf('title:') + 6).trim();
return title.replace(/^['"]|['"]$/g, '');
}
}
// Look for first heading
for (const line of lines) {
if (line.startsWith('# ')) {
return line.substring(2).trim();
}
}
return null;
} catch (error) {
return null;
}
}
/**
* Extract main content from instruction file (skip frontmatter)
*/
function extractInstructionContent(content) {
const lines = content.split('\n');
let inFrontmatter = false;
let frontmatterEnded = false;
const contentLines = [];
for (const line of lines) {
if (line.trim() === '---') {
if (!inFrontmatter) {
inFrontmatter = true;
continue;
} else if (inFrontmatter && !frontmatterEnded) {
frontmatterEnded = true;
continue;
}
}
if (!inFrontmatter || frontmatterEnded) {
contentLines.push(line);
}
}
return contentLines.join('\n').trim();
}
/**
* CLI handler for generate-repo-instructions command
*/
async function handleGenerateRepoInstructions(args) {
const configFile = args.find(arg => !arg.startsWith('--')) || "awesome-copilot.config.yml";
// Parse flags
const template = args.includes('--consolidated') ? 'consolidated' :
args.includes('--basic') ? 'basic' : 'repository';
const outputFile = args.find(arg => arg.startsWith('--output='))?.split('=')[1] ||
".github/copilot-instructions.md";
const noHeader = args.includes('--no-header');
try {
const result = await generateRepositoryInstructions(configFile, {
outputFile,
template,
includeHeader: !noHeader
});
console.log(`\n📋 Repository instructions generated successfully!`);
console.log(`📁 File: ${result.file}`);
console.log(`📊 Instructions: ${result.instructionsCount}`);
if (result.instructions.length > 0) {
console.log(`📝 Included: ${result.instructions.join(', ')}`);
}
console.log(`\n💡 Next steps:`);
console.log(` • Commit the generated file to enable repository-wide Copilot instructions`);
console.log(` • GitHub Copilot will automatically use these instructions in this repository`);
console.log(` • Update instructions by modifying your config and re-running this command`);
} catch (error) {
console.error(`❌ Error generating repository instructions: ${error.message}`);
process.exit(1);
}
}
module.exports = {
generateRepositoryInstructions,
handleGenerateRepoInstructions
};
// CLI usage
if (require.main === module) {
const args = process.argv.slice(2);
handleGenerateRepoInstructions(args);
}