Implement GitHub Copilot repository instructions support

Co-authored-by: AstroSteveo <34114851+AstroSteveo@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2025-09-23 23:01:05 +00:00
parent 36bc6de2a9
commit 920c966ed9
8 changed files with 654 additions and 1 deletions

View File

@ -39,3 +39,4 @@ The following instructions are only to be applied when performing a code review.
* [ ] The file name is lower case, with words separated by hyphens.
* [ ] Encourage the use of `tools`, but it's not required.
* [ ] Strongly encourage the use of `model` to specify the model that the chat mode is optimised for.

View File

@ -183,6 +183,64 @@ The `node awesome-copilot.js init` command automatically configures VS Code to d
No manual VS Code configuration needed!
## GitHub Copilot Repository Instructions
Awesome Copilot can generate a `.github/copilot-instructions.md` file that provides repository-level instructions to GitHub Copilot. This feature supports GitHub's native repository instructions capability, which automatically applies instructions to all Copilot interactions within your repository.
### Generating Repository Instructions
```bash
# Generate basic repository instructions file
node /path/to/awesome-copilot/awesome-copilot.js generate-repo-instructions
# Generate with full instruction content included
node /path/to/awesome-copilot/awesome-copilot.js generate-repo-instructions --consolidated
# Custom output location
node /path/to/awesome-copilot/awesome-copilot.js generate-repo-instructions --output=.copilot/instructions.md
# Generate without header
node /path/to/awesome-copilot/awesome-copilot.js generate-repo-instructions --no-header
```
### Template Options
- **`repository` (default)**: Lists enabled instructions with references to individual files
- **`consolidated`**: Includes full content from all enabled instruction files
- **`basic`**: Simple list without detailed formatting
### Benefits of Repository Instructions
- **Automatic Application**: Instructions apply to all team members without manual configuration
- **Version Control**: Instructions are tracked with your code and evolve with your project
- **IDE Agnostic**: Works across VS Code, Visual Studio, GitHub Copilot CLI, and web interfaces
- **Consistency**: Ensures all team members get the same Copilot behavior
- **No Setup Required**: New team members automatically get proper instructions
### Integration with Configuration
The repository instructions are generated based on your enabled instructions in the configuration file:
```yaml
instructions:
csharp: true # Included in repository instructions
python: true # Included in repository instructions
java: false # Excluded from repository instructions
collections:
testing-automation: true # All instructions in this collection included
```
### Workflow Integration
Repository instructions work seamlessly with the existing workflow:
1. **Configure**: Enable instructions in your config file
2. **Apply**: Run `awesome-copilot apply` to copy files to your project
3. **Generate**: Run `generate-repo-instructions` to create the repository instructions file
4. **Commit**: Add the generated file to version control
5. **Collaborate**: Team members automatically get consistent Copilot behavior
## Examples
### Frontend React Project

View File

@ -85,8 +85,33 @@ Use our configuration system to manage all customizations in one place:
node /path/to/awesome-copilot/awesome-copilot.js apply
```
6. **Generate repository instructions** (optional, for GitHub Copilot repository-level instructions):
```bash
# Generate .github/copilot-instructions.md from enabled instructions
node /path/to/awesome-copilot/awesome-copilot.js generate-repo-instructions
# Or generate with full instruction content included
node /path/to/awesome-copilot/awesome-copilot.js generate-repo-instructions --consolidated
```
See [CONFIG.md](CONFIG.md) for detailed configuration documentation.
#### 🤖 GitHub Copilot Repository Instructions
Awesome Copilot can generate a `.github/copilot-instructions.md` file that provides repository-level instructions to GitHub Copilot. This file is automatically recognized by GitHub Copilot and applies to all code generation within your repository.
**Benefits:**
- Instructions automatically apply to all Copilot interactions in your repository
- No need to manually configure VS Code settings for each team member
- Instructions are version-controlled with your code
- Works across all supported IDEs and GitHub Copilot interfaces
**Templates:**
- `--basic`: Simple list of enabled instructions (default)
- `--consolidated`: Includes full content of all enabled instruction files
- Custom templates can be created by modifying the `repository-instructions.js` file
#### ⚖️ Configuration Precedence Rules
Awesome Copilot uses an **effective state system** that respects explicit overrides while allowing collections to provide convenient defaults. The effective state computation follows these precise rules:

View File

@ -196,6 +196,11 @@ async function applyConfig(configPath = "awesome-copilot.config.yml") {
console.log("2. Use prompts with /awesome-copilot command in GitHub Copilot Chat");
console.log("3. Instructions will automatically apply to your coding");
console.log("4. Import chat modes in VS Code settings");
// Suggest generating repository instructions if instructions are enabled
if (summary.instructions > 0) {
console.log("5. Consider running 'awesome-copilot generate-repo-instructions' to create .github/copilot-instructions.md");
}
}
/**

View File

@ -68,6 +68,15 @@ const commands = {
}
},
"generate-repo-instructions": {
description: "Generate .github/copilot-instructions.md from enabled instructions",
usage: "awesome-copilot generate-repo-instructions [config-file] [--consolidated|--basic] [--output=<file>] [--no-header]",
action: async (args) => {
const { handleGenerateRepoInstructions } = require("./repository-instructions");
await handleGenerateRepoInstructions(args);
}
},
help: {
description: "Show help information",
usage: "awesome-copilot help",
@ -101,11 +110,14 @@ function showHelp() {
console.log(" awesome-copilot toggle instructions all off --config team.yml # Disable all instructions");
console.log(" awesome-copilot toggle prompts all on --all # Force enable ALL prompts (override explicit settings)");
console.log(" awesome-copilot toggle collections testing-automation on --apply # Enable collection and apply");
console.log(" awesome-copilot generate-repo-instructions # Generate .github/copilot-instructions.md");
console.log(" awesome-copilot generate-repo-instructions --consolidated # Include full instruction content");
console.log("");
console.log("Workflow:");
console.log(" 1. Run 'awesome-copilot init' to create a configuration file");
console.log(" 2. Use 'awesome-copilot list' and 'awesome-copilot toggle' to manage enabled items");
console.log(" 3. Run 'awesome-copilot apply' to copy files to your project");
console.log(" 4. Run 'awesome-copilot generate-repo-instructions' for GitHub Copilot repository instructions");
}
function showError(message) {

344
repository-instructions.js Normal file
View File

@ -0,0 +1,344 @@
#!/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);
}

View File

@ -52,6 +52,15 @@ async function runAllTests() {
console.error('Apply tests failed with error:', error.message);
}
try {
console.log('\n🤖 Repository Instructions Tests');
console.log('-' * 33);
const { runTests: runRepoInstructionsTests } = require('./test-repository-instructions');
results.repoInstructions = await runRepoInstructionsTests();
} catch (error) {
console.error('Repository instructions tests failed with error:', error.message);
}
// Summary
console.log('\n' + '=' * 60);
console.log('📋 Test Suite Summary');
@ -61,7 +70,8 @@ async function runAllTests() {
{ name: 'Unit Tests', result: results.unit, emoji: '📊' },
{ name: 'Integration Tests', result: results.integration, emoji: '🔄' },
{ name: 'CLI Tests', result: results.cli, emoji: '⌨️' },
{ name: 'Apply Tests', result: results.apply, emoji: '🎯' }
{ name: 'Apply Tests', result: results.apply, emoji: '🎯' },
{ name: 'Repo Instructions', result: results.repoInstructions, emoji: '🤖' }
];
testTypes.forEach(test => {

View File

@ -0,0 +1,198 @@
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const { generateRepositoryInstructions } = require("./repository-instructions");
console.log("🧪 Testing Repository Instructions Generation");
console.log("=".repeat(50));
/**
* Test repository instructions generation
*/
async function testRepositoryInstructions() {
const testConfigPath = "test-repo-instructions.yml";
const testOutputPath = ".github/test-copilot-instructions.md";
// Create a test configuration with some enabled instructions
const testConfig = `# Test configuration for repository instructions
instructions:
javascript: true
python: false
csharp: true
collections:
frontend-web-dev: true
`;
try {
// Write test config
fs.writeFileSync(testConfigPath, testConfig);
console.log("✅ Created test configuration");
// Test basic generation
console.log("\n📋 Testing basic repository instructions generation...");
const result = await generateRepositoryInstructions(testConfigPath, {
outputFile: testOutputPath,
template: "repository"
});
console.log(`✅ Generated file: ${result.file}`);
console.log(`📊 Instructions count: ${result.instructionsCount}`);
console.log(`📝 Instructions: ${result.instructions.join(', ')}`);
// Verify the file was created
if (fs.existsSync(testOutputPath)) {
const content = fs.readFileSync(testOutputPath, 'utf8');
console.log(`📄 File size: ${content.length} characters`);
// Check for expected content
const hasHeader = content.includes("GitHub Copilot Repository Instructions");
const hasInstructions = content.includes("Active Instructions");
console.log(`✅ Has header: ${hasHeader}`);
console.log(`✅ Has instructions section: ${hasInstructions}`);
if (hasHeader && hasInstructions) {
console.log("✅ Content validation passed");
} else {
console.log("❌ Content validation failed");
return false;
}
} else {
console.log("❌ Output file was not created");
return false;
}
// Test consolidated template
console.log("\n📋 Testing consolidated template...");
const consolidatedPath = ".github/test-consolidated-instructions.md";
await generateRepositoryInstructions(testConfigPath, {
outputFile: consolidatedPath,
template: "consolidated"
});
if (fs.existsSync(consolidatedPath)) {
const consolidatedContent = fs.readFileSync(consolidatedPath, 'utf8');
console.log(`✅ Consolidated file created (${consolidatedContent.length} characters)`);
fs.unlinkSync(consolidatedPath);
}
// Test empty config
console.log("\n📋 Testing empty configuration...");
const emptyConfig = `# Empty configuration
instructions: {}
`;
const emptyConfigPath = "test-empty-repo-instructions.yml";
const emptyOutputPath = ".github/test-empty-instructions.md";
fs.writeFileSync(emptyConfigPath, emptyConfig);
await generateRepositoryInstructions(emptyConfigPath, {
outputFile: emptyOutputPath,
template: "repository"
});
if (fs.existsSync(emptyOutputPath)) {
const emptyContent = fs.readFileSync(emptyOutputPath, 'utf8');
const hasEmptyMessage = emptyContent.includes("No Instructions Enabled");
console.log(`✅ Empty config handled correctly: ${hasEmptyMessage}`);
fs.unlinkSync(emptyOutputPath);
}
// Cleanup
fs.unlinkSync(testConfigPath);
fs.unlinkSync(emptyConfigPath);
fs.unlinkSync(testOutputPath);
console.log("\n🎉 All repository instructions tests passed!");
return true;
} catch (error) {
console.error(`❌ Test failed: ${error.message}`);
// Cleanup on error
[testConfigPath, testOutputPath, "test-empty-repo-instructions.yml", ".github/test-empty-instructions.md", ".github/test-consolidated-instructions.md"].forEach(file => {
if (fs.existsSync(file)) {
fs.unlinkSync(file);
}
});
return false;
}
}
/**
* Test CLI integration
*/
async function testCLIIntegration() {
console.log("\n🖥 Testing CLI Integration");
console.log("-".repeat(30));
try {
// Test the awesome-copilot command includes the new command
const { main } = require("./awesome-copilot");
// Check if the command is registered
const originalArgv = process.argv;
process.argv = ["node", "awesome-copilot.js", "help"];
// Capture help output
const originalLog = console.log;
let helpOutput = "";
console.log = (...args) => {
helpOutput += args.join(" ") + "\n";
};
try {
main();
} catch (error) {
// Expected for help command
}
console.log = originalLog;
process.argv = originalArgv;
// Check if our command is in the help output
const hasRepoInstructionsCommand = helpOutput.includes("generate-repo-instructions");
console.log(`✅ Command registered in CLI: ${hasRepoInstructionsCommand}`);
return hasRepoInstructionsCommand;
} catch (error) {
console.error(`❌ CLI integration test failed: ${error.message}`);
return false;
}
}
/**
* Main test runner
*/
async function runTests() {
const test1 = await testRepositoryInstructions();
const test2 = await testCLIIntegration();
console.log("\n" + "=".repeat(50));
console.log("📊 Test Results Summary");
console.log("=".repeat(50));
console.log(`Repository Instructions Generation: ${test1 ? "✅ PASS" : "❌ FAIL"}`);
console.log(`CLI Integration: ${test2 ? "✅ PASS" : "❌ FAIL"}`);
const allPassed = test1 && test2;
console.log(`\nOverall Result: ${allPassed ? "✅ ALL TESTS PASSED" : "❌ SOME TESTS FAILED"}`);
if (allPassed) {
console.log("\n🎉 Repository instructions feature is working correctly!");
}
return allPassed;
}
// Run tests
if (require.main === module) {
runTests().then(success => {
process.exit(success ? 0 : 1);
});
}
module.exports = { runTests };