Implement GitHub Copilot repository instructions support (#32)
This commit is contained in:
commit
18ddbe7eee
1
.github/copilot-instructions.md
vendored
1
.github/copilot-instructions.md
vendored
@ -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.
|
* [ ] The file name is lower case, with words separated by hyphens.
|
||||||
* [ ] Encourage the use of `tools`, but it's not required.
|
* [ ] 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.
|
* [ ] Strongly encourage the use of `model` to specify the model that the chat mode is optimised for.
|
||||||
|
|
||||||
|
|||||||
58
CONFIG.md
58
CONFIG.md
@ -183,6 +183,64 @@ The `node awesome-copilot.js init` command automatically configures VS Code to d
|
|||||||
|
|
||||||
No manual VS Code configuration needed!
|
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
|
## Examples
|
||||||
|
|
||||||
### Frontend React Project
|
### Frontend React Project
|
||||||
|
|||||||
25
README.md
25
README.md
@ -85,8 +85,33 @@ Use our configuration system to manage all customizations in one place:
|
|||||||
node /path/to/awesome-copilot/awesome-copilot.js apply
|
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.
|
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
|
#### ⚖️ 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:
|
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:
|
||||||
|
|||||||
@ -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("2. Use prompts with /awesome-copilot command in GitHub Copilot Chat");
|
||||||
console.log("3. Instructions will automatically apply to your coding");
|
console.log("3. Instructions will automatically apply to your coding");
|
||||||
console.log("4. Import chat modes in VS Code settings");
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -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: {
|
help: {
|
||||||
description: "Show help information",
|
description: "Show help information",
|
||||||
usage: "awesome-copilot help",
|
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 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 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 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("");
|
||||||
console.log("Workflow:");
|
console.log("Workflow:");
|
||||||
console.log(" 1. Run 'awesome-copilot init' to create a configuration file");
|
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(" 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(" 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) {
|
function showError(message) {
|
||||||
|
|||||||
344
repository-instructions.js
Normal file
344
repository-instructions.js
Normal 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);
|
||||||
|
}
|
||||||
28
test-all.js
28
test-all.js
@ -11,7 +11,7 @@ const { runTests: runApplyTests } = require('./test-apply-effective');
|
|||||||
|
|
||||||
async function runAllTests() {
|
async function runAllTests() {
|
||||||
console.log('🧪 Running Awesome Copilot Comprehensive Test Suite\n');
|
console.log('🧪 Running Awesome Copilot Comprehensive Test Suite\n');
|
||||||
console.log('=' * 60);
|
console.log('='.repeat(60));
|
||||||
|
|
||||||
const results = {
|
const results = {
|
||||||
unit: false,
|
unit: false,
|
||||||
@ -22,7 +22,7 @@ async function runAllTests() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('\n📊 Unit Tests (Effective State Computation)');
|
console.log('\n📊 Unit Tests (Effective State Computation)');
|
||||||
console.log('-' * 45);
|
console.log('-'.repeat(45));
|
||||||
results.unit = await runUnitTests();
|
results.unit = await runUnitTests();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Unit tests failed with error:', error.message);
|
console.error('Unit tests failed with error:', error.message);
|
||||||
@ -30,7 +30,7 @@ async function runAllTests() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('\n🔄 Integration Tests (Toggle+Apply Idempotency)');
|
console.log('\n🔄 Integration Tests (Toggle+Apply Idempotency)');
|
||||||
console.log('-' * 48);
|
console.log('-'.repeat(48));
|
||||||
results.integration = await runIntegrationTests();
|
results.integration = await runIntegrationTests();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Integration tests failed with error:', error.message);
|
console.error('Integration tests failed with error:', error.message);
|
||||||
@ -38,7 +38,7 @@ async function runAllTests() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('\n⌨️ CLI Tests (List and Toggle Commands)');
|
console.log('\n⌨️ CLI Tests (List and Toggle Commands)');
|
||||||
console.log('-' * 40);
|
console.log('-'.repeat(40));
|
||||||
results.cli = await runCliTests();
|
results.cli = await runCliTests();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('CLI tests failed with error:', error.message);
|
console.error('CLI tests failed with error:', error.message);
|
||||||
@ -46,22 +46,32 @@ async function runAllTests() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('\n🎯 Apply Tests (Effective States in Apply)');
|
console.log('\n🎯 Apply Tests (Effective States in Apply)');
|
||||||
console.log('-' * 42);
|
console.log('-'.repeat(42));
|
||||||
results.apply = await runApplyTests();
|
results.apply = await runApplyTests();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Apply tests failed with error:', error.message);
|
console.error('Apply tests failed with error:', error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('\n🤖 Repository Instructions Tests');
|
||||||
|
console.log('-'.repeat(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
|
// Summary
|
||||||
console.log('\n' + '=' * 60);
|
console.log('\n' + '='.repeat(60));
|
||||||
console.log('📋 Test Suite Summary');
|
console.log('📋 Test Suite Summary');
|
||||||
console.log('=' * 60);
|
console.log('='.repeat(60));
|
||||||
|
|
||||||
const testTypes = [
|
const testTypes = [
|
||||||
{ name: 'Unit Tests', result: results.unit, emoji: '📊' },
|
{ name: 'Unit Tests', result: results.unit, emoji: '📊' },
|
||||||
{ name: 'Integration Tests', result: results.integration, emoji: '🔄' },
|
{ name: 'Integration Tests', result: results.integration, emoji: '🔄' },
|
||||||
{ name: 'CLI Tests', result: results.cli, 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 => {
|
testTypes.forEach(test => {
|
||||||
@ -72,7 +82,7 @@ async function runAllTests() {
|
|||||||
const passedCount = Object.values(results).filter(Boolean).length;
|
const passedCount = Object.values(results).filter(Boolean).length;
|
||||||
const totalCount = Object.keys(results).length;
|
const totalCount = Object.keys(results).length;
|
||||||
|
|
||||||
console.log('\n' + '-' * 60);
|
console.log('\n' + '-'.repeat(60));
|
||||||
console.log(`Overall Result: ${passedCount}/${totalCount} test suites passed`);
|
console.log(`Overall Result: ${passedCount}/${totalCount} test suites passed`);
|
||||||
|
|
||||||
if (passedCount === totalCount) {
|
if (passedCount === totalCount) {
|
||||||
|
|||||||
34
test-integration.yml
Normal file
34
test-integration.yml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# Awesome Copilot Configuration File
|
||||||
|
# Generated on 2025-09-23T23:16:09.548Z
|
||||||
|
#
|
||||||
|
# This file uses effective state precedence:
|
||||||
|
# 1. Explicit item settings (true/false) override everything
|
||||||
|
# 2. Items not listed inherit from enabled collections
|
||||||
|
# 3. Otherwise items are disabled
|
||||||
|
#
|
||||||
|
# To use:
|
||||||
|
# - Enable collections for curated sets of related items
|
||||||
|
# - Explicitly set individual items to true/false to override collections
|
||||||
|
# - Items not mentioned will follow collection settings
|
||||||
|
#
|
||||||
|
# After configuring, run: awesome-copilot apply
|
||||||
|
#
|
||||||
|
|
||||||
|
version: "1.0"
|
||||||
|
project:
|
||||||
|
name: "My Project"
|
||||||
|
description: "A project using awesome-copilot customizations"
|
||||||
|
output_directory: ".github"
|
||||||
|
prompts:
|
||||||
|
instructions:
|
||||||
|
chatmodes:
|
||||||
|
collections:
|
||||||
|
azure-cloud-development: false
|
||||||
|
csharp-dotnet-development: false
|
||||||
|
database-data-management: false
|
||||||
|
devops-oncall: false
|
||||||
|
frontend-web-dev: false
|
||||||
|
project-planning: false
|
||||||
|
security-best-practices: false
|
||||||
|
technical-spike: false
|
||||||
|
testing-automation: false
|
||||||
198
test-repository-instructions.js
Normal file
198
test-repository-instructions.js
Normal 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 };
|
||||||
Loading…
x
Reference in New Issue
Block a user