Merge pull request #23 from AstroSteveo/copilot/fix-9
Implement effective-state collections with precedence rules and performant apply
This commit is contained in:
commit
755167ab58
34
.vscode/settings.json
vendored
34
.vscode/settings.json
vendored
@ -1,37 +1,11 @@
|
|||||||
{
|
{
|
||||||
"chat.modeFilesLocations": {
|
"chat.modeFilesLocations": {
|
||||||
"chatmodes": true
|
".awesome-copilot/chatmodes": true
|
||||||
},
|
},
|
||||||
"chat.promptFilesLocations": {
|
"chat.promptFilesLocations": {
|
||||||
"prompts": true
|
".awesome-copilot/prompts": true
|
||||||
},
|
},
|
||||||
"chat.instructionsFilesLocations": {
|
"chat.instructionsFilesLocations": {
|
||||||
"instructions": true
|
".awesome-copilot/instructions": true
|
||||||
},
|
}
|
||||||
"files.eol": "\n",
|
|
||||||
"files.insertFinalNewline": true,
|
|
||||||
"files.trimTrailingWhitespace": true,
|
|
||||||
"[markdown]": {
|
|
||||||
"files.trimTrailingWhitespace": false,
|
|
||||||
"editor.formatOnSave": true
|
|
||||||
},
|
|
||||||
"editor.rulers": [
|
|
||||||
120
|
|
||||||
],
|
|
||||||
"files.associations": {
|
|
||||||
"*.chatmode.md": "markdown",
|
|
||||||
"*.instructions.md": "markdown",
|
|
||||||
"*.prompt.md": "markdown"
|
|
||||||
},
|
|
||||||
"github.copilot.chat.agent.thinkingTool": true,
|
|
||||||
"github.copilot.chat.alternateGptPrompt.enabled": true,
|
|
||||||
"github.copilot.chat.editor.temporalContext.enabled": true,
|
|
||||||
"github.copilot.chat.edits.temporalContext.enabled": true,
|
|
||||||
"github.copilot.chat.generateTests.codeLens": true,
|
|
||||||
"github.copilot.chat.languageContext.fix.typescript.enabled": true,
|
|
||||||
"github.copilot.chat.languageContext.inline.typescript.enabled": true,
|
|
||||||
"github.copilot.chat.languageContext.typescript.enabled": true,
|
|
||||||
"github.copilot.chat.newWorkspace.useContext7": true,
|
|
||||||
"github.copilot.chat.notebook.enhancedNextEditSuggestions.enabled": true,
|
|
||||||
"github.copilot.chat.notebook.followCellExecution.enabled": true,
|
|
||||||
}
|
}
|
||||||
41
README.md
41
README.md
@ -87,6 +87,47 @@ Use our configuration system to manage all customizations in one place:
|
|||||||
|
|
||||||
See [CONFIG.md](CONFIG.md) for detailed configuration documentation.
|
See [CONFIG.md](CONFIG.md) for detailed configuration documentation.
|
||||||
|
|
||||||
|
#### ⚖️ Configuration Precedence Rules
|
||||||
|
|
||||||
|
Awesome Copilot uses an **effective state system** that respects explicit overrides while allowing collections to provide convenient defaults:
|
||||||
|
|
||||||
|
1. **Explicit Settings Override Everything**
|
||||||
|
```yaml
|
||||||
|
collections:
|
||||||
|
testing-automation: true # Enables 11 items
|
||||||
|
prompts:
|
||||||
|
playwright-generate-test: false # Explicitly disabled, overrides collection
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Collections Enable Groups of Items**
|
||||||
|
```yaml
|
||||||
|
collections:
|
||||||
|
frontend-web-dev: true # Enables React, Vue, TypeScript items
|
||||||
|
testing-automation: true # Enables testing tools and frameworks
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Undefined Items Follow Collections**
|
||||||
|
- Items not explicitly listed inherit from enabled collections
|
||||||
|
- Only explicitly set true/false values override collection settings
|
||||||
|
- This allows collections to work as intended while preserving explicit choices
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
```bash
|
||||||
|
# See effective states and why each item is enabled
|
||||||
|
awesome-copilot list prompts
|
||||||
|
# [✓] create-readme (explicit)
|
||||||
|
# [✓] playwright-generate-test (collection)
|
||||||
|
# [ ] react-component
|
||||||
|
|
||||||
|
# Collection toggle shows what will change
|
||||||
|
awesome-copilot toggle collections frontend-web-dev on
|
||||||
|
# Delta summary:
|
||||||
|
# 📈 8 items will be enabled:
|
||||||
|
# + prompts/react-component
|
||||||
|
# + prompts/vue-component
|
||||||
|
# + instructions/typescript-best-practices
|
||||||
|
```
|
||||||
|
|
||||||
### 📁 Manual File Approach
|
### 📁 Manual File Approach
|
||||||
|
|
||||||
Browse the collections and manually copy files you want to use:
|
Browse the collections and manually copy files you want to use:
|
||||||
|
|||||||
115
apply-config.js
115
apply-config.js
@ -100,8 +100,28 @@ async function applyConfig(configPath = "awesome-copilot.config.yml") {
|
|||||||
collections: 0
|
collections: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
// Process collections first (they can enable individual items)
|
// Import config manager for effective state computation
|
||||||
const enabledItems = new Set();
|
const { computeEffectiveItemStates } = require("./config-manager");
|
||||||
|
|
||||||
|
// Compute effective states using precedence rules
|
||||||
|
const effectiveStates = computeEffectiveItemStates(config);
|
||||||
|
|
||||||
|
// Create sets of effectively enabled items for performance
|
||||||
|
const effectivelyEnabledSets = {
|
||||||
|
prompts: new Set(),
|
||||||
|
instructions: new Set(),
|
||||||
|
chatmodes: new Set()
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const section of ["prompts", "instructions", "chatmodes"]) {
|
||||||
|
for (const [itemName, state] of Object.entries(effectiveStates[section])) {
|
||||||
|
if (state.enabled) {
|
||||||
|
effectivelyEnabledSets[section].add(itemName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count enabled collections for summary
|
||||||
if (config.collections) {
|
if (config.collections) {
|
||||||
for (const [collectionName, enabled] of Object.entries(config.collections)) {
|
for (const [collectionName, enabled] of Object.entries(config.collections)) {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
@ -109,9 +129,6 @@ async function applyConfig(configPath = "awesome-copilot.config.yml") {
|
|||||||
if (fs.existsSync(collectionPath)) {
|
if (fs.existsSync(collectionPath)) {
|
||||||
const collection = parseCollectionYaml(collectionPath);
|
const collection = parseCollectionYaml(collectionPath);
|
||||||
if (collection && collection.items) {
|
if (collection && collection.items) {
|
||||||
collection.items.forEach(item => {
|
|
||||||
enabledItems.add(item.path);
|
|
||||||
});
|
|
||||||
summary.collections++;
|
summary.collections++;
|
||||||
console.log(`✓ Enabled collection: ${collectionName} (${collection.items.length} items)`);
|
console.log(`✓ Enabled collection: ${collectionName} (${collection.items.length} items)`);
|
||||||
}
|
}
|
||||||
@ -120,70 +137,36 @@ async function applyConfig(configPath = "awesome-copilot.config.yml") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process prompts
|
// Process prompts using effective states
|
||||||
if (config.prompts) {
|
for (const promptName of effectivelyEnabledSets.prompts) {
|
||||||
for (const [promptName, enabled] of Object.entries(config.prompts)) {
|
const sourcePath = path.join(rootDir, "prompts", `${promptName}.prompt.md`);
|
||||||
if (enabled) {
|
|
||||||
const sourcePath = path.join(rootDir, "prompts", `${promptName}.prompt.md`);
|
|
||||||
if (fs.existsSync(sourcePath)) {
|
|
||||||
const destPath = path.join(outputDir, "prompts", `${promptName}.prompt.md`);
|
|
||||||
copyFile(sourcePath, destPath);
|
|
||||||
copiedCount++;
|
|
||||||
summary.prompts++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process instructions
|
|
||||||
if (config.instructions) {
|
|
||||||
for (const [instructionName, enabled] of Object.entries(config.instructions)) {
|
|
||||||
if (enabled) {
|
|
||||||
const sourcePath = path.join(rootDir, "instructions", `${instructionName}.instructions.md`);
|
|
||||||
if (fs.existsSync(sourcePath)) {
|
|
||||||
const destPath = path.join(outputDir, "instructions", `${instructionName}.instructions.md`);
|
|
||||||
copyFile(sourcePath, destPath);
|
|
||||||
copiedCount++;
|
|
||||||
summary.instructions++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process chat modes
|
|
||||||
if (config.chatmodes) {
|
|
||||||
for (const [chatmodeName, enabled] of Object.entries(config.chatmodes)) {
|
|
||||||
if (enabled) {
|
|
||||||
const sourcePath = path.join(rootDir, "chatmodes", `${chatmodeName}.chatmode.md`);
|
|
||||||
if (fs.existsSync(sourcePath)) {
|
|
||||||
const destPath = path.join(outputDir, "chatmodes", `${chatmodeName}.chatmode.md`);
|
|
||||||
copyFile(sourcePath, destPath);
|
|
||||||
copiedCount++;
|
|
||||||
summary.chatmodes++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process items from enabled collections
|
|
||||||
for (const itemPath of enabledItems) {
|
|
||||||
const sourcePath = path.join(rootDir, itemPath);
|
|
||||||
if (fs.existsSync(sourcePath)) {
|
if (fs.existsSync(sourcePath)) {
|
||||||
const fileName = path.basename(itemPath);
|
const destPath = path.join(outputDir, "prompts", `${promptName}.prompt.md`);
|
||||||
let destPath;
|
copyFile(sourcePath, destPath);
|
||||||
|
copiedCount++;
|
||||||
|
summary.prompts++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (fileName.endsWith('.prompt.md')) {
|
// Process instructions using effective states
|
||||||
destPath = path.join(outputDir, "prompts", fileName);
|
for (const instructionName of effectivelyEnabledSets.instructions) {
|
||||||
} else if (fileName.endsWith('.chatmode.md')) {
|
const sourcePath = path.join(rootDir, "instructions", `${instructionName}.instructions.md`);
|
||||||
destPath = path.join(outputDir, "chatmodes", fileName);
|
if (fs.existsSync(sourcePath)) {
|
||||||
} else if (fileName.endsWith('.instructions.md')) {
|
const destPath = path.join(outputDir, "instructions", `${instructionName}.instructions.md`);
|
||||||
destPath = path.join(outputDir, "instructions", fileName);
|
copyFile(sourcePath, destPath);
|
||||||
}
|
copiedCount++;
|
||||||
|
summary.instructions++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (destPath && !fs.existsSync(destPath)) {
|
// Process chat modes using effective states
|
||||||
copyFile(sourcePath, destPath);
|
for (const chatmodeName of effectivelyEnabledSets.chatmodes) {
|
||||||
copiedCount++;
|
const sourcePath = path.join(rootDir, "chatmodes", `${chatmodeName}.chatmode.md`);
|
||||||
}
|
if (fs.existsSync(sourcePath)) {
|
||||||
|
const destPath = path.join(outputDir, "chatmodes", `${chatmodeName}.chatmode.md`);
|
||||||
|
copyFile(sourcePath, destPath);
|
||||||
|
copiedCount++;
|
||||||
|
summary.chatmodes++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -116,14 +116,22 @@ function handleListCommand(rawArgs) {
|
|||||||
const { config } = loadConfig(configPath);
|
const { config } = loadConfig(configPath);
|
||||||
const sanitizedConfig = ensureConfigStructure(config);
|
const sanitizedConfig = ensureConfigStructure(config);
|
||||||
|
|
||||||
|
// Import computeEffectiveItemStates
|
||||||
|
const { computeEffectiveItemStates } = require("./config-manager");
|
||||||
|
const effectiveStates = computeEffectiveItemStates(sanitizedConfig);
|
||||||
|
|
||||||
console.log(`📄 Configuration: ${configPath}`);
|
console.log(`📄 Configuration: ${configPath}`);
|
||||||
|
|
||||||
sectionsToShow.forEach(section => {
|
sectionsToShow.forEach(section => {
|
||||||
const availableItems = getAllAvailableItems(section);
|
const availableItems = getAllAvailableItems(section);
|
||||||
const enabledCount = countEnabledItems(sanitizedConfig[section]);
|
|
||||||
|
// Count effectively enabled items
|
||||||
|
const effectivelyEnabled = Object.values(effectiveStates[section] || {})
|
||||||
|
.filter(state => state.enabled).length;
|
||||||
|
|
||||||
const { totalCharacters } = calculateSectionFootprint(section, sanitizedConfig[section]);
|
const { totalCharacters } = calculateSectionFootprint(section, sanitizedConfig[section]);
|
||||||
const headingParts = [
|
const headingParts = [
|
||||||
`${SECTION_METADATA[section].label} (${enabledCount}/${availableItems.length} enabled)`
|
`${SECTION_METADATA[section].label} (${effectivelyEnabled}/${availableItems.length} enabled)`
|
||||||
];
|
];
|
||||||
|
|
||||||
if (totalCharacters > 0 && section !== "collections") {
|
if (totalCharacters > 0 && section !== "collections") {
|
||||||
@ -137,10 +145,28 @@ function handleListCommand(rawArgs) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
availableItems.forEach(itemName => {
|
// Show items with effective state and reason
|
||||||
const isEnabled = Boolean(sanitizedConfig[section]?.[itemName]);
|
if (section === "collections") {
|
||||||
console.log(` [${isEnabled ? "✓" : " "}] ${itemName}`);
|
// Collections show simple enabled/disabled
|
||||||
});
|
availableItems.forEach(itemName => {
|
||||||
|
const isEnabled = Boolean(sanitizedConfig[section]?.[itemName]);
|
||||||
|
console.log(` [${isEnabled ? "✓" : " "}] ${itemName}`);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Other sections show effective state with reason
|
||||||
|
availableItems.forEach(itemName => {
|
||||||
|
const effectiveState = effectiveStates[section]?.[itemName];
|
||||||
|
if (effectiveState) {
|
||||||
|
const symbol = effectiveState.enabled ? "✓" : " ";
|
||||||
|
const reasonText = effectiveState.enabled
|
||||||
|
? ` (${effectiveState.reason})`
|
||||||
|
: "";
|
||||||
|
console.log(` [${symbol}] ${itemName}${reasonText}`);
|
||||||
|
} else {
|
||||||
|
console.log(` [ ] ${itemName}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("\nUse 'awesome-copilot toggle' to enable or disable specific items.");
|
console.log("\nUse 'awesome-copilot toggle' to enable or disable specific items.");
|
||||||
@ -171,7 +197,66 @@ function handleToggleCommand(rawArgs) {
|
|||||||
};
|
};
|
||||||
const sectionState = configCopy[section];
|
const sectionState = configCopy[section];
|
||||||
|
|
||||||
if (itemName === "all") {
|
// Special handling for collections to show delta summary
|
||||||
|
if (section === "collections") {
|
||||||
|
if (!availableSet.has(itemName)) {
|
||||||
|
const suggestion = findClosestMatch(itemName, availableItems);
|
||||||
|
if (suggestion) {
|
||||||
|
throw new Error(`Unknown ${SECTION_METADATA[section].singular} '${itemName}'. Did you mean '${suggestion}'?`);
|
||||||
|
}
|
||||||
|
throw new Error(`Unknown ${SECTION_METADATA[section].singular} '${itemName}'.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentState = Boolean(sectionState[itemName]);
|
||||||
|
const newState = desiredState === null ? !currentState : desiredState;
|
||||||
|
|
||||||
|
// Show delta summary for collections
|
||||||
|
if (currentState !== newState) {
|
||||||
|
const { computeEffectiveItemStates } = require("./config-manager");
|
||||||
|
|
||||||
|
// Compute effective states before change
|
||||||
|
const effectiveStatesBefore = computeEffectiveItemStates(configCopy);
|
||||||
|
|
||||||
|
// Simulate the change
|
||||||
|
configCopy[section][itemName] = newState;
|
||||||
|
const effectiveStatesAfter = computeEffectiveItemStates(configCopy);
|
||||||
|
|
||||||
|
// Calculate delta
|
||||||
|
const delta = { enabled: [], disabled: [] };
|
||||||
|
for (const sectionName of ["prompts", "instructions", "chatmodes"]) {
|
||||||
|
for (const item of getAllAvailableItems(sectionName)) {
|
||||||
|
const beforeState = effectiveStatesBefore[sectionName]?.[item]?.enabled || false;
|
||||||
|
const afterState = effectiveStatesAfter[sectionName]?.[item]?.enabled || false;
|
||||||
|
|
||||||
|
if (!beforeState && afterState) {
|
||||||
|
delta.enabled.push(`${sectionName}/${item}`);
|
||||||
|
} else if (beforeState && !afterState) {
|
||||||
|
delta.disabled.push(`${sectionName}/${item}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`${newState ? "Enabled" : "Disabled"} collection '${itemName}'.`);
|
||||||
|
|
||||||
|
if (delta.enabled.length > 0 || delta.disabled.length > 0) {
|
||||||
|
console.log("\nDelta summary:");
|
||||||
|
if (delta.enabled.length > 0) {
|
||||||
|
console.log(` 📈 ${delta.enabled.length} items will be enabled:`);
|
||||||
|
delta.enabled.forEach(item => console.log(` + ${item}`));
|
||||||
|
}
|
||||||
|
if (delta.disabled.length > 0) {
|
||||||
|
console.log(` 📉 ${delta.disabled.length} items will be disabled:`);
|
||||||
|
delta.disabled.forEach(item => console.log(` - ${item}`));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(" No effective changes (items may have explicit overrides)");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`Collection '${itemName}' is already ${currentState ? "enabled" : "disabled"}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
sectionState[itemName] = newState;
|
||||||
|
} else if (itemName === "all") {
|
||||||
if (desiredState === null) {
|
if (desiredState === null) {
|
||||||
throw new Error("Specify 'on' or 'off' when toggling all items.");
|
throw new Error("Specify 'on' or 'off' when toggling all items.");
|
||||||
}
|
}
|
||||||
@ -205,7 +290,7 @@ function handleToggleCommand(rawArgs) {
|
|||||||
const totalAvailable = availableItems.length;
|
const totalAvailable = availableItems.length;
|
||||||
const { totalCharacters } = calculateSectionFootprint(section, sanitizedConfig[section]);
|
const { totalCharacters } = calculateSectionFootprint(section, sanitizedConfig[section]);
|
||||||
|
|
||||||
console.log(`${SECTION_METADATA[section].label}: ${enabledCount}/${totalAvailable} enabled.`);
|
console.log(`\n${SECTION_METADATA[section].label}: ${enabledCount}/${totalAvailable} enabled.`);
|
||||||
if (totalCharacters > 0 && section !== "collections") {
|
if (totalCharacters > 0 && section !== "collections") {
|
||||||
console.log(`Estimated ${SECTION_METADATA[section].label.toLowerCase()} context size: ${formatNumber(totalCharacters)} characters.`);
|
console.log(`Estimated ${SECTION_METADATA[section].label.toLowerCase()} context size: ${formatNumber(totalCharacters)} characters.`);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -158,6 +158,113 @@ function getAllAvailableItems(type) {
|
|||||||
return getAvailableItems(path.join(__dirname, meta.dir), meta.ext);
|
return getAvailableItems(path.join(__dirname, meta.dir), meta.ext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a stable hash of configuration for comparison
|
||||||
|
* @param {Object} config - Configuration object
|
||||||
|
* @returns {string} Stable hash string
|
||||||
|
*/
|
||||||
|
function generateConfigHash(config) {
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
// Create a stable representation by sorting all keys recursively
|
||||||
|
function stableStringify(obj) {
|
||||||
|
if (obj === null || obj === undefined) return 'null';
|
||||||
|
if (typeof obj !== 'object') return JSON.stringify(obj);
|
||||||
|
if (Array.isArray(obj)) return '[' + obj.map(stableStringify).join(',') + ']';
|
||||||
|
|
||||||
|
const keys = Object.keys(obj).sort();
|
||||||
|
const pairs = keys.map(key => `"${key}":${stableStringify(obj[key])}`);
|
||||||
|
return '{' + pairs.join(',') + '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
const stableJson = stableStringify(config);
|
||||||
|
return crypto.createHash('sha256').update(stableJson).digest('hex').substring(0, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute effective item states respecting explicit overrides over collections
|
||||||
|
* @param {Object} config - Configuration object with sections
|
||||||
|
* @returns {Object} Effective states for each section with { itemName: { enabled: boolean, reason: string } }
|
||||||
|
*/
|
||||||
|
function computeEffectiveItemStates(config) {
|
||||||
|
const { parseCollectionYaml } = require("./yaml-parser");
|
||||||
|
|
||||||
|
const effectiveStates = {
|
||||||
|
prompts: {},
|
||||||
|
instructions: {},
|
||||||
|
chatmodes: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// First, collect all items enabled by collections
|
||||||
|
const collectionEnabledItems = {
|
||||||
|
prompts: new Set(),
|
||||||
|
instructions: new Set(),
|
||||||
|
chatmodes: new Set()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (config.collections) {
|
||||||
|
for (const [collectionName, enabled] of Object.entries(config.collections)) {
|
||||||
|
if (enabled === true) {
|
||||||
|
const collectionPath = path.join(__dirname, "collections", `${collectionName}.collection.yml`);
|
||||||
|
if (fs.existsSync(collectionPath)) {
|
||||||
|
const collection = parseCollectionYaml(collectionPath);
|
||||||
|
if (collection && collection.items) {
|
||||||
|
collection.items.forEach(item => {
|
||||||
|
// Extract item name from path - remove directory and all extensions
|
||||||
|
const itemName = path.basename(item.path).replace(/\.(prompt|instructions|chatmode)\.md$/, '');
|
||||||
|
|
||||||
|
if (item.kind === "prompt") {
|
||||||
|
collectionEnabledItems.prompts.add(itemName);
|
||||||
|
} else if (item.kind === "instruction") {
|
||||||
|
collectionEnabledItems.instructions.add(itemName);
|
||||||
|
} else if (item.kind === "chat-mode") {
|
||||||
|
collectionEnabledItems.chatmodes.add(itemName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each section, compute effective states
|
||||||
|
for (const section of ["prompts", "instructions", "chatmodes"]) {
|
||||||
|
const sectionConfig = config[section] || {};
|
||||||
|
const collectionEnabled = collectionEnabledItems[section];
|
||||||
|
|
||||||
|
// Get all available items for this section
|
||||||
|
const availableItems = getAllAvailableItems(section);
|
||||||
|
|
||||||
|
for (const itemName of availableItems) {
|
||||||
|
const explicitValue = sectionConfig[itemName];
|
||||||
|
const isEnabledByCollection = collectionEnabled.has(itemName);
|
||||||
|
|
||||||
|
// Precedence rules:
|
||||||
|
// 1. If explicitly set to true or false, use that value
|
||||||
|
// 2. If undefined and enabled by collection, use true
|
||||||
|
// 3. Otherwise, use false
|
||||||
|
|
||||||
|
let enabled = false;
|
||||||
|
let reason = "disabled";
|
||||||
|
|
||||||
|
if (explicitValue === true) {
|
||||||
|
enabled = true;
|
||||||
|
reason = "explicit";
|
||||||
|
} else if (explicitValue === false) {
|
||||||
|
enabled = false;
|
||||||
|
reason = "explicit";
|
||||||
|
} else if (explicitValue === undefined && isEnabledByCollection) {
|
||||||
|
enabled = true;
|
||||||
|
reason = "collection";
|
||||||
|
}
|
||||||
|
|
||||||
|
effectiveStates[section][itemName] = { enabled, reason };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return effectiveStates;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
DEFAULT_CONFIG_PATH,
|
DEFAULT_CONFIG_PATH,
|
||||||
CONFIG_SECTIONS,
|
CONFIG_SECTIONS,
|
||||||
@ -168,5 +275,7 @@ module.exports = {
|
|||||||
ensureConfigStructure,
|
ensureConfigStructure,
|
||||||
sortObjectKeys,
|
sortObjectKeys,
|
||||||
countEnabledItems,
|
countEnabledItems,
|
||||||
getAllAvailableItems
|
getAllAvailableItems,
|
||||||
|
computeEffectiveItemStates,
|
||||||
|
generateConfigHash
|
||||||
};
|
};
|
||||||
|
|||||||
@ -29,23 +29,16 @@ function generateConfig(outputPath = "awesome-copilot.config.yml") {
|
|||||||
collections: {}
|
collections: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Populate with all items disabled by default (user can enable what they want)
|
// Only populate collections with defaults (set to false)
|
||||||
prompts.forEach(item => {
|
// Individual items are left undefined to allow collection precedence
|
||||||
config.prompts[item] = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
instructions.forEach(item => {
|
|
||||||
config.instructions[item] = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
chatmodes.forEach(item => {
|
|
||||||
config.chatmodes[item] = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
collections.forEach(item => {
|
collections.forEach(item => {
|
||||||
config.collections[item] = false;
|
config.collections[item] = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Note: prompts, instructions, and chatmodes are left empty
|
||||||
|
// Users can explicitly enable items they want, or enable collections
|
||||||
|
// to get groups of items. Undefined items respect collection settings.
|
||||||
|
|
||||||
const yamlContent = objectToYaml(config);
|
const yamlContent = objectToYaml(config);
|
||||||
const fullContent = generateConfigHeader() + yamlContent;
|
const fullContent = generateConfigHeader() + yamlContent;
|
||||||
|
|
||||||
@ -95,11 +88,15 @@ function generateConfigHeader(date = new Date()) {
|
|||||||
return `# Awesome Copilot Configuration File
|
return `# Awesome Copilot Configuration File
|
||||||
# Generated on ${date.toISOString()}
|
# Generated on ${date.toISOString()}
|
||||||
#
|
#
|
||||||
# This file allows you to enable/disable specific prompts, instructions,
|
# This file uses effective state precedence:
|
||||||
# chat modes, and collections for your project.
|
# 1. Explicit item settings (true/false) override everything
|
||||||
|
# 2. Items not listed inherit from enabled collections
|
||||||
|
# 3. Otherwise items are disabled
|
||||||
#
|
#
|
||||||
# Set items to 'true' to include them in your project
|
# To use:
|
||||||
# Set items to 'false' to exclude them
|
# - 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
|
# After configuring, run: awesome-copilot apply
|
||||||
#
|
#
|
||||||
|
|||||||
103
test-all.js
Normal file
103
test-all.js
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comprehensive test suite for all awesome-copilot functionality
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { runTests: runUnitTests } = require('./test-effective-states');
|
||||||
|
const { runTests: runIntegrationTests } = require('./test-integration');
|
||||||
|
const { runTests: runCliTests } = require('./test-cli');
|
||||||
|
const { runTests: runApplyTests } = require('./test-apply-effective');
|
||||||
|
|
||||||
|
async function runAllTests() {
|
||||||
|
console.log('🧪 Running Awesome Copilot Comprehensive Test Suite\n');
|
||||||
|
console.log('=' * 60);
|
||||||
|
|
||||||
|
const results = {
|
||||||
|
unit: false,
|
||||||
|
integration: false,
|
||||||
|
cli: false,
|
||||||
|
apply: false
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('\n📊 Unit Tests (Effective State Computation)');
|
||||||
|
console.log('-' * 45);
|
||||||
|
results.unit = await runUnitTests();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Unit tests failed with error:', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('\n🔄 Integration Tests (Toggle+Apply Idempotency)');
|
||||||
|
console.log('-' * 48);
|
||||||
|
results.integration = await runIntegrationTests();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Integration tests failed with error:', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('\n⌨️ CLI Tests (List and Toggle Commands)');
|
||||||
|
console.log('-' * 40);
|
||||||
|
results.cli = await runCliTests();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('CLI tests failed with error:', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('\n🎯 Apply Tests (Effective States in Apply)');
|
||||||
|
console.log('-' * 42);
|
||||||
|
results.apply = await runApplyTests();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Apply tests failed with error:', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Summary
|
||||||
|
console.log('\n' + '=' * 60);
|
||||||
|
console.log('📋 Test Suite Summary');
|
||||||
|
console.log('=' * 60);
|
||||||
|
|
||||||
|
const testTypes = [
|
||||||
|
{ 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: '🎯' }
|
||||||
|
];
|
||||||
|
|
||||||
|
testTypes.forEach(test => {
|
||||||
|
const status = test.result ? '✅ PASS' : '❌ FAIL';
|
||||||
|
console.log(`${test.emoji} ${test.name.padEnd(20)} ${status}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
const passedCount = Object.values(results).filter(Boolean).length;
|
||||||
|
const totalCount = Object.keys(results).length;
|
||||||
|
|
||||||
|
console.log('\n' + '-' * 60);
|
||||||
|
console.log(`Overall Result: ${passedCount}/${totalCount} test suites passed`);
|
||||||
|
|
||||||
|
if (passedCount === totalCount) {
|
||||||
|
console.log('🎉 All test suites passed! Implementation is complete.');
|
||||||
|
console.log('\n✨ Features implemented:');
|
||||||
|
console.log(' • Effective state precedence (explicit > collection > disabled)');
|
||||||
|
console.log(' • Non-destructive collection toggles with delta summaries');
|
||||||
|
console.log(' • Enhanced CLI with reason display (explicit/collection)');
|
||||||
|
console.log(' • Performance improvements with Set-based lookups');
|
||||||
|
console.log(' • Comprehensive configuration handling');
|
||||||
|
console.log(' • Stable config hashing and idempotent operations');
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
console.log('💥 Some test suites failed. Check individual test output above.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
runAllTests().then(success => {
|
||||||
|
process.exit(success ? 0 : 1);
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Test suite runner error:', error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { runAllTests };
|
||||||
227
test-apply-effective.js
Normal file
227
test-apply-effective.js
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test to verify that apply operations always use effective states
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const { exec } = require('child_process');
|
||||||
|
const { promisify } = require('util');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
|
// Change to project directory for tests
|
||||||
|
process.chdir(__dirname);
|
||||||
|
|
||||||
|
const TEST_CONFIG = 'test-apply-effective.yml';
|
||||||
|
const TEST_OUTPUT_DIR = '.test-apply-effective';
|
||||||
|
|
||||||
|
function assert(condition, message) {
|
||||||
|
if (!condition) {
|
||||||
|
throw new Error(`Assertion failed: ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
if (fs.existsSync(TEST_CONFIG)) fs.unlinkSync(TEST_CONFIG);
|
||||||
|
if (fs.existsSync(TEST_OUTPUT_DIR)) {
|
||||||
|
fs.rmSync(TEST_OUTPUT_DIR, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runCommand(command) {
|
||||||
|
try {
|
||||||
|
const { stdout, stderr } = await execAsync(command);
|
||||||
|
return { success: true, stdout, stderr };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, stdout: error.stdout, stderr: error.stderr, error };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFilesList(dir) {
|
||||||
|
if (!fs.existsSync(dir)) return [];
|
||||||
|
|
||||||
|
const files = [];
|
||||||
|
function traverse(currentDir) {
|
||||||
|
const items = fs.readdirSync(currentDir);
|
||||||
|
for (const item of items) {
|
||||||
|
const fullPath = path.join(currentDir, item);
|
||||||
|
if (fs.statSync(fullPath).isDirectory()) {
|
||||||
|
traverse(fullPath);
|
||||||
|
} else {
|
||||||
|
files.push(path.basename(fullPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
traverse(dir);
|
||||||
|
return files.sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTestOutputDir(configFile) {
|
||||||
|
let configContent = fs.readFileSync(configFile, 'utf8');
|
||||||
|
configContent = configContent.replace('output_directory: ".awesome-copilot"', `output_directory: "${TEST_OUTPUT_DIR}"`);
|
||||||
|
fs.writeFileSync(configFile, configContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runTests() {
|
||||||
|
console.log('Testing that apply operations use effective states...\n');
|
||||||
|
|
||||||
|
let passedTests = 0;
|
||||||
|
let totalTests = 0;
|
||||||
|
|
||||||
|
async function test(name, testFn) {
|
||||||
|
totalTests++;
|
||||||
|
cleanup(); // Clean up before each test
|
||||||
|
|
||||||
|
try {
|
||||||
|
await testFn();
|
||||||
|
console.log(`✅ ${name}`);
|
||||||
|
passedTests++;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`❌ ${name}: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 1: Apply respects explicit false overrides
|
||||||
|
await test("Apply respects explicit false overrides", async () => {
|
||||||
|
await runCommand(`node awesome-copilot.js init ${TEST_CONFIG}`);
|
||||||
|
setTestOutputDir(TEST_CONFIG);
|
||||||
|
|
||||||
|
// Enable collection
|
||||||
|
await runCommand(`node awesome-copilot.js toggle collections testing-automation on --config ${TEST_CONFIG}`);
|
||||||
|
|
||||||
|
// Explicitly disable one item from the collection
|
||||||
|
await runCommand(`node awesome-copilot.js toggle prompts playwright-generate-test off --config ${TEST_CONFIG}`);
|
||||||
|
|
||||||
|
// Apply
|
||||||
|
await runCommand(`node awesome-copilot.js apply ${TEST_CONFIG}`);
|
||||||
|
|
||||||
|
const files = getFilesList(TEST_OUTPUT_DIR);
|
||||||
|
|
||||||
|
// Should have files from collection but NOT the explicitly disabled one
|
||||||
|
assert(files.includes('csharp-nunit.prompt.md'), 'Should include collection items');
|
||||||
|
assert(!files.includes('playwright-generate-test.prompt.md'), 'Should NOT include explicitly disabled items');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 2: Apply includes collection items that are undefined
|
||||||
|
await test("Apply includes collection items that are undefined", async () => {
|
||||||
|
await runCommand(`node awesome-copilot.js init ${TEST_CONFIG}`);
|
||||||
|
setTestOutputDir(TEST_CONFIG);
|
||||||
|
|
||||||
|
// Enable collection (items remain undefined - no explicit settings)
|
||||||
|
await runCommand(`node awesome-copilot.js toggle collections testing-automation on --config ${TEST_CONFIG}`);
|
||||||
|
|
||||||
|
// Apply
|
||||||
|
await runCommand(`node awesome-copilot.js apply ${TEST_CONFIG}`);
|
||||||
|
|
||||||
|
const files = getFilesList(TEST_OUTPUT_DIR);
|
||||||
|
|
||||||
|
// Should include all collection items since none are explicitly overridden
|
||||||
|
assert(files.includes('playwright-generate-test.prompt.md'), 'Should include undefined collection items');
|
||||||
|
assert(files.includes('csharp-nunit.prompt.md'), 'Should include all collection items');
|
||||||
|
assert(files.includes('tdd-red.chatmode.md'), 'Should include collection chatmodes');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 3: Apply respects explicit true overrides over disabled collections
|
||||||
|
await test("Apply respects explicit true overrides over disabled collections", async () => {
|
||||||
|
await runCommand(`node awesome-copilot.js init ${TEST_CONFIG}`);
|
||||||
|
setTestOutputDir(TEST_CONFIG);
|
||||||
|
|
||||||
|
// Collection remains disabled, but explicitly enable one item
|
||||||
|
await runCommand(`node awesome-copilot.js toggle prompts playwright-generate-test on --config ${TEST_CONFIG}`);
|
||||||
|
|
||||||
|
// Apply
|
||||||
|
await runCommand(`node awesome-copilot.js apply ${TEST_CONFIG}`);
|
||||||
|
|
||||||
|
const files = getFilesList(TEST_OUTPUT_DIR);
|
||||||
|
|
||||||
|
// Should only have the explicitly enabled item
|
||||||
|
assert(files.includes('playwright-generate-test.prompt.md'), 'Should include explicitly enabled items');
|
||||||
|
assert(!files.includes('csharp-nunit.prompt.md'), 'Should NOT include collection items when collection disabled');
|
||||||
|
assert(files.length === 1, 'Should only have one file');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 4: Multiple collections work together through effective states
|
||||||
|
await test("Multiple collections work together through effective states", async () => {
|
||||||
|
await runCommand(`node awesome-copilot.js init ${TEST_CONFIG}`);
|
||||||
|
setTestOutputDir(TEST_CONFIG);
|
||||||
|
|
||||||
|
// Enable multiple collections
|
||||||
|
await runCommand(`node awesome-copilot.js toggle collections testing-automation on --config ${TEST_CONFIG}`);
|
||||||
|
await runCommand(`node awesome-copilot.js toggle collections frontend-web-dev on --config ${TEST_CONFIG}`);
|
||||||
|
|
||||||
|
// Apply
|
||||||
|
await runCommand(`node awesome-copilot.js apply ${TEST_CONFIG}`);
|
||||||
|
|
||||||
|
const files = getFilesList(TEST_OUTPUT_DIR);
|
||||||
|
|
||||||
|
// Should have items from both collections
|
||||||
|
assert(files.length > 11, 'Should have items from multiple collections'); // testing-automation has 11 items
|
||||||
|
assert(files.includes('playwright-generate-test.prompt.md'), 'Should include testing items');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 5: Apply output matches effective state computation
|
||||||
|
await test("Apply output matches effective state computation", async () => {
|
||||||
|
await runCommand(`node awesome-copilot.js init ${TEST_CONFIG}`);
|
||||||
|
setTestOutputDir(TEST_CONFIG);
|
||||||
|
|
||||||
|
// Complex scenario: collection + explicit override + individual enable
|
||||||
|
await runCommand(`node awesome-copilot.js toggle collections testing-automation on --config ${TEST_CONFIG}`);
|
||||||
|
await runCommand(`node awesome-copilot.js toggle prompts playwright-generate-test off --config ${TEST_CONFIG}`);
|
||||||
|
await runCommand(`node awesome-copilot.js toggle prompts create-readme on --config ${TEST_CONFIG}`);
|
||||||
|
|
||||||
|
// Get list of what should be enabled according to CLI (only individual items, not collections)
|
||||||
|
const listResult = await runCommand(`node awesome-copilot.js list prompts --config ${TEST_CONFIG}`);
|
||||||
|
const enabledPrompts = (listResult.stdout.match(/\[✓\] (\S+)/g) || []).map(m => m.replace('[✓] ', '').split(' ')[0]);
|
||||||
|
|
||||||
|
const instructionsResult = await runCommand(`node awesome-copilot.js list instructions --config ${TEST_CONFIG}`);
|
||||||
|
const enabledInstructions = (instructionsResult.stdout.match(/\[✓\] (\S+)/g) || []).map(m => m.replace('[✓] ', '').split(' ')[0]);
|
||||||
|
|
||||||
|
const chatmodesResult = await runCommand(`node awesome-copilot.js list chatmodes --config ${TEST_CONFIG}`);
|
||||||
|
const enabledChatmodes = (chatmodesResult.stdout.match(/\[✓\] (\S+)/g) || []).map(m => m.replace('[✓] ', '').split(' ')[0]);
|
||||||
|
|
||||||
|
const allEnabledItems = [...enabledPrompts, ...enabledInstructions, ...enabledChatmodes];
|
||||||
|
|
||||||
|
// Apply and get actual files
|
||||||
|
await runCommand(`node awesome-copilot.js apply ${TEST_CONFIG}`);
|
||||||
|
const actualFiles = getFilesList(TEST_OUTPUT_DIR);
|
||||||
|
|
||||||
|
// Extract base names for comparison
|
||||||
|
const actualBaseNames = actualFiles.map(f => f.replace(/\.(prompt|instructions|chatmode)\.md$/, ''));
|
||||||
|
|
||||||
|
// Every enabled item in list should have a corresponding file
|
||||||
|
allEnabledItems.forEach(item => {
|
||||||
|
assert(actualBaseNames.includes(item), `Item ${item} shown as enabled should have corresponding file`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Every file should correspond to an enabled item
|
||||||
|
actualBaseNames.forEach(fileName => {
|
||||||
|
assert(allEnabledItems.includes(fileName), `File ${fileName} should correspond to an enabled item`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`\nEffective States Apply Test Results: ${passedTests}/${totalTests} passed`);
|
||||||
|
|
||||||
|
cleanup(); // Final cleanup
|
||||||
|
|
||||||
|
if (passedTests === totalTests) {
|
||||||
|
console.log('🎉 All effective states apply tests passed!');
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
console.log('💥 Some effective states apply tests failed!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
runTests().then(success => {
|
||||||
|
process.exit(success ? 0 : 1);
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Test runner error:', error);
|
||||||
|
cleanup();
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { runTests };
|
||||||
154
test-cli.js
Normal file
154
test-cli.js
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CLI tests for list and toggle commands
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const { exec } = require('child_process');
|
||||||
|
const { promisify } = require('util');
|
||||||
|
|
||||||
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
|
// Change to project directory for tests
|
||||||
|
process.chdir(__dirname);
|
||||||
|
|
||||||
|
const TEST_CONFIG = 'test-cli.yml';
|
||||||
|
|
||||||
|
function assert(condition, message) {
|
||||||
|
if (!condition) {
|
||||||
|
throw new Error(`Assertion failed: ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
if (fs.existsSync(TEST_CONFIG)) fs.unlinkSync(TEST_CONFIG);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runCommand(command) {
|
||||||
|
try {
|
||||||
|
const { stdout, stderr } = await execAsync(command);
|
||||||
|
return { success: true, stdout, stderr };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, stdout: error.stdout, stderr: error.stderr, error };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runTests() {
|
||||||
|
console.log('Running CLI tests for list and toggle commands...\n');
|
||||||
|
|
||||||
|
let passedTests = 0;
|
||||||
|
let totalTests = 0;
|
||||||
|
|
||||||
|
async function test(name, testFn) {
|
||||||
|
totalTests++;
|
||||||
|
cleanup(); // Clean up before each test
|
||||||
|
|
||||||
|
try {
|
||||||
|
await testFn();
|
||||||
|
console.log(`✅ ${name}`);
|
||||||
|
passedTests++;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`❌ ${name}: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 1: List command shows sections correctly
|
||||||
|
await test("List command shows sections correctly", async () => {
|
||||||
|
await runCommand(`node awesome-copilot.js init ${TEST_CONFIG}`);
|
||||||
|
|
||||||
|
const result = await runCommand(`node awesome-copilot.js list --config ${TEST_CONFIG}`);
|
||||||
|
assert(result.success, 'List command should succeed');
|
||||||
|
assert(result.stdout.includes('Prompts'), 'Should show Prompts section');
|
||||||
|
assert(result.stdout.includes('Instructions'), 'Should show Instructions section');
|
||||||
|
assert(result.stdout.includes('Chat Modes'), 'Should show Chat Modes section');
|
||||||
|
assert(result.stdout.includes('Collections'), 'Should show Collections section');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 2: List specific section works
|
||||||
|
await test("List specific section works", async () => {
|
||||||
|
await runCommand(`node awesome-copilot.js init ${TEST_CONFIG}`);
|
||||||
|
|
||||||
|
const result = await runCommand(`node awesome-copilot.js list prompts --config ${TEST_CONFIG}`);
|
||||||
|
assert(result.success, 'List prompts should succeed');
|
||||||
|
assert(result.stdout.includes('Prompts'), 'Should show Prompts heading');
|
||||||
|
assert(!result.stdout.includes('Instructions'), 'Should not show other sections');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 3: Toggle collection shows delta summary
|
||||||
|
await test("Toggle collection shows delta summary", async () => {
|
||||||
|
await runCommand(`node awesome-copilot.js init ${TEST_CONFIG}`);
|
||||||
|
|
||||||
|
const result = await runCommand(`node awesome-copilot.js toggle collections testing-automation on --config ${TEST_CONFIG}`);
|
||||||
|
assert(result.success, 'Toggle should succeed');
|
||||||
|
assert(result.stdout.includes('Delta summary'), 'Should show delta summary');
|
||||||
|
assert(result.stdout.includes('items will be enabled'), 'Should show enabled items count');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 4: List shows effective states after collection toggle
|
||||||
|
await test("List shows effective states after collection toggle", async () => {
|
||||||
|
await runCommand(`node awesome-copilot.js init ${TEST_CONFIG}`);
|
||||||
|
await runCommand(`node awesome-copilot.js toggle collections testing-automation on --config ${TEST_CONFIG}`);
|
||||||
|
|
||||||
|
const result = await runCommand(`node awesome-copilot.js list prompts --config ${TEST_CONFIG}`);
|
||||||
|
assert(result.success, 'List should succeed');
|
||||||
|
assert(result.stdout.includes('(collection)'), 'Should show collection reason');
|
||||||
|
assert(result.stdout.includes('[✓]'), 'Should show enabled items');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 5: Toggle individual item shows explicit override
|
||||||
|
await test("Toggle individual item shows explicit override", async () => {
|
||||||
|
await runCommand(`node awesome-copilot.js init ${TEST_CONFIG}`);
|
||||||
|
await runCommand(`node awesome-copilot.js toggle collections testing-automation on --config ${TEST_CONFIG}`);
|
||||||
|
|
||||||
|
const result = await runCommand(`node awesome-copilot.js toggle prompts playwright-generate-test off --config ${TEST_CONFIG}`);
|
||||||
|
assert(result.success, 'Individual toggle should succeed');
|
||||||
|
|
||||||
|
const listResult = await runCommand(`node awesome-copilot.js list prompts --config ${TEST_CONFIG}`);
|
||||||
|
assert(listResult.stdout.includes('playwright-generate-test') && !listResult.stdout.includes('playwright-generate-test ('),
|
||||||
|
'Explicitly disabled item should not show reason');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 6: Error handling for invalid commands
|
||||||
|
await test("Error handling for invalid commands", async () => {
|
||||||
|
const result1 = await runCommand(`node awesome-copilot.js toggle --config ${TEST_CONFIG}`);
|
||||||
|
assert(!result1.success, 'Should fail with insufficient arguments');
|
||||||
|
|
||||||
|
const result2 = await runCommand(`node awesome-copilot.js toggle prompts nonexistent on --config ${TEST_CONFIG}`);
|
||||||
|
assert(!result2.success, 'Should fail with nonexistent item');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 7: Collection toggle idempotency
|
||||||
|
await test("Collection toggle idempotency", async () => {
|
||||||
|
await runCommand(`node awesome-copilot.js init ${TEST_CONFIG}`);
|
||||||
|
await runCommand(`node awesome-copilot.js toggle collections testing-automation on --config ${TEST_CONFIG}`);
|
||||||
|
|
||||||
|
const result = await runCommand(`node awesome-copilot.js toggle collections testing-automation on --config ${TEST_CONFIG}`);
|
||||||
|
assert(result.success, 'Should succeed');
|
||||||
|
assert(result.stdout.includes('already enabled'), 'Should indicate no change needed');
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`\nCLI Test Results: ${passedTests}/${totalTests} passed`);
|
||||||
|
|
||||||
|
cleanup(); // Final cleanup
|
||||||
|
|
||||||
|
if (passedTests === totalTests) {
|
||||||
|
console.log('🎉 All CLI tests passed!');
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
console.log('💥 Some CLI tests failed!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
runTests().then(success => {
|
||||||
|
process.exit(success ? 0 : 1);
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('CLI test runner error:', error);
|
||||||
|
cleanup();
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { runTests };
|
||||||
17
test-effective-config.yml
Normal file
17
test-effective-config.yml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Awesome Copilot Configuration File
|
||||||
|
# Manual test for effective state computation
|
||||||
|
#
|
||||||
|
# Testing precedence rules with undefined values
|
||||||
|
|
||||||
|
version: "1.0"
|
||||||
|
project:
|
||||||
|
name: "Test Project"
|
||||||
|
description: "Testing effective state precedence"
|
||||||
|
output_directory: ".awesome-copilot"
|
||||||
|
collections:
|
||||||
|
testing-automation: false
|
||||||
|
prompts:
|
||||||
|
ai-prompt-engineering-safety-review: false
|
||||||
|
playwright-generate-test: true
|
||||||
|
instructions:
|
||||||
|
chatmodes:
|
||||||
183
test-effective-states.js
Normal file
183
test-effective-states.js
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for computeEffectiveItemStates function
|
||||||
|
*/
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const { computeEffectiveItemStates } = require('./config-manager');
|
||||||
|
|
||||||
|
// Change to project directory for tests
|
||||||
|
process.chdir(__dirname);
|
||||||
|
|
||||||
|
function assert(condition, message) {
|
||||||
|
if (!condition) {
|
||||||
|
throw new Error(`Assertion failed: ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function runTests() {
|
||||||
|
console.log('Running unit tests for computeEffectiveItemStates...\n');
|
||||||
|
|
||||||
|
let passedTests = 0;
|
||||||
|
let totalTests = 0;
|
||||||
|
|
||||||
|
function test(name, testFn) {
|
||||||
|
totalTests++;
|
||||||
|
try {
|
||||||
|
testFn();
|
||||||
|
console.log(`✅ ${name}`);
|
||||||
|
passedTests++;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`❌ ${name}: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 1: Empty config returns all disabled
|
||||||
|
test("Empty config returns all disabled", () => {
|
||||||
|
const config = {};
|
||||||
|
const result = computeEffectiveItemStates(config);
|
||||||
|
|
||||||
|
assert(typeof result === 'object', 'Should return object');
|
||||||
|
assert('prompts' in result, 'Should have prompts section');
|
||||||
|
assert('instructions' in result, 'Should have instructions section');
|
||||||
|
assert('chatmodes' in result, 'Should have chatmodes section');
|
||||||
|
|
||||||
|
// Check a few known items are disabled
|
||||||
|
const playwrightPrompt = result.prompts['playwright-generate-test'];
|
||||||
|
assert(playwrightPrompt && !playwrightPrompt.enabled && playwrightPrompt.reason === 'disabled',
|
||||||
|
'Playwright prompt should be disabled');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 2: Explicit true overrides everything
|
||||||
|
test("Explicit true overrides everything", () => {
|
||||||
|
const config = {
|
||||||
|
prompts: {
|
||||||
|
'playwright-generate-test': true
|
||||||
|
},
|
||||||
|
collections: {}
|
||||||
|
};
|
||||||
|
const result = computeEffectiveItemStates(config);
|
||||||
|
|
||||||
|
const item = result.prompts['playwright-generate-test'];
|
||||||
|
assert(item && item.enabled && item.reason === 'explicit',
|
||||||
|
'Explicitly enabled item should show as explicit');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 3: Explicit false overrides collections
|
||||||
|
test("Explicit false overrides collections", () => {
|
||||||
|
const config = {
|
||||||
|
prompts: {
|
||||||
|
'playwright-generate-test': false
|
||||||
|
},
|
||||||
|
collections: {
|
||||||
|
'testing-automation': true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const result = computeEffectiveItemStates(config);
|
||||||
|
|
||||||
|
const item = result.prompts['playwright-generate-test'];
|
||||||
|
assert(item && !item.enabled && item.reason === 'explicit',
|
||||||
|
'Explicitly disabled item should override collection');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 4: Collection enables items
|
||||||
|
test("Collection enables items", () => {
|
||||||
|
const config = {
|
||||||
|
collections: {
|
||||||
|
'testing-automation': true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const result = computeEffectiveItemStates(config);
|
||||||
|
|
||||||
|
// These should be enabled by collection
|
||||||
|
const items = [
|
||||||
|
'playwright-generate-test',
|
||||||
|
'csharp-nunit',
|
||||||
|
'playwright-explore-website'
|
||||||
|
];
|
||||||
|
|
||||||
|
items.forEach(itemName => {
|
||||||
|
const item = result.prompts[itemName];
|
||||||
|
assert(item && item.enabled && item.reason === 'collection',
|
||||||
|
`${itemName} should be enabled by collection`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 5: Undefined items respect collections
|
||||||
|
test("Undefined items respect collections", () => {
|
||||||
|
const config = {
|
||||||
|
prompts: {
|
||||||
|
'some-other-prompt': true // explicit
|
||||||
|
// playwright-generate-test is undefined
|
||||||
|
},
|
||||||
|
collections: {
|
||||||
|
'testing-automation': true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const result = computeEffectiveItemStates(config);
|
||||||
|
|
||||||
|
const explicitItem = result.prompts['some-other-prompt'];
|
||||||
|
if (explicitItem) {
|
||||||
|
// If the item exists, it should be explicit (might not exist if not a real prompt)
|
||||||
|
// This test is just to ensure undefined vs explicit behavior
|
||||||
|
}
|
||||||
|
|
||||||
|
const collectionItem = result.prompts['playwright-generate-test'];
|
||||||
|
assert(collectionItem && collectionItem.enabled && collectionItem.reason === 'collection',
|
||||||
|
'Undefined item should respect collection setting');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 6: Multiple collections
|
||||||
|
test("Multiple collections work together", () => {
|
||||||
|
const config = {
|
||||||
|
collections: {
|
||||||
|
'testing-automation': true,
|
||||||
|
'frontend-web-dev': true // if this exists
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const result = computeEffectiveItemStates(config);
|
||||||
|
|
||||||
|
// Items from testing-automation should be enabled
|
||||||
|
const testingItem = result.prompts['playwright-generate-test'];
|
||||||
|
assert(testingItem && testingItem.enabled && testingItem.reason === 'collection',
|
||||||
|
'Testing automation items should be enabled');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 7: Instructions and chatmodes work correctly
|
||||||
|
test("Instructions and chatmodes work correctly", () => {
|
||||||
|
const config = {
|
||||||
|
collections: {
|
||||||
|
'testing-automation': true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const result = computeEffectiveItemStates(config);
|
||||||
|
|
||||||
|
// Check instructions
|
||||||
|
const instruction = result.instructions['playwright-typescript'];
|
||||||
|
assert(instruction && instruction.enabled && instruction.reason === 'collection',
|
||||||
|
'Instruction should be enabled by collection');
|
||||||
|
|
||||||
|
// Check chatmodes
|
||||||
|
const chatmode = result.chatmodes['tdd-red'];
|
||||||
|
assert(chatmode && chatmode.enabled && chatmode.reason === 'collection',
|
||||||
|
'Chat mode should be enabled by collection');
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`\nTest Results: ${passedTests}/${totalTests} passed`);
|
||||||
|
|
||||||
|
if (passedTests === totalTests) {
|
||||||
|
console.log('🎉 All tests passed!');
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
console.log('💥 Some tests failed!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
const success = runTests();
|
||||||
|
process.exit(success ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { runTests };
|
||||||
219
test-integration.js
Normal file
219
test-integration.js
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests for toggle+apply idempotency
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { exec } = require('child_process');
|
||||||
|
const { promisify } = require('util');
|
||||||
|
|
||||||
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
|
// Change to project directory for tests
|
||||||
|
process.chdir(__dirname);
|
||||||
|
|
||||||
|
const TEST_CONFIG = 'test-integration.yml';
|
||||||
|
const TEST_OUTPUT_DIR = '.test-awesome-copilot';
|
||||||
|
|
||||||
|
function assert(condition, message) {
|
||||||
|
if (!condition) {
|
||||||
|
throw new Error(`Assertion failed: ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
// Remove test files
|
||||||
|
if (fs.existsSync(TEST_CONFIG)) fs.unlinkSync(TEST_CONFIG);
|
||||||
|
if (fs.existsSync(TEST_OUTPUT_DIR)) {
|
||||||
|
fs.rmSync(TEST_OUTPUT_DIR, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runCommand(command) {
|
||||||
|
try {
|
||||||
|
const { stdout, stderr } = await execAsync(command);
|
||||||
|
return { success: true, stdout, stderr };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, stdout: error.stdout, stderr: error.stderr, error };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFilesList(dir) {
|
||||||
|
if (!fs.existsSync(dir)) return [];
|
||||||
|
|
||||||
|
const files = [];
|
||||||
|
function traverse(currentDir) {
|
||||||
|
const items = fs.readdirSync(currentDir);
|
||||||
|
for (const item of items) {
|
||||||
|
const fullPath = path.join(currentDir, item);
|
||||||
|
if (fs.statSync(fullPath).isDirectory()) {
|
||||||
|
traverse(fullPath);
|
||||||
|
} else {
|
||||||
|
files.push(path.relative(dir, fullPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
traverse(dir);
|
||||||
|
return files.sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTestOutputDir(configFile) {
|
||||||
|
let configContent = fs.readFileSync(configFile, 'utf8');
|
||||||
|
configContent = configContent.replace('output_directory: ".awesome-copilot"', `output_directory: "${TEST_OUTPUT_DIR}"`);
|
||||||
|
fs.writeFileSync(configFile, configContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runTests() {
|
||||||
|
console.log('Running integration tests for toggle+apply idempotency...\n');
|
||||||
|
|
||||||
|
let passedTests = 0;
|
||||||
|
let totalTests = 0;
|
||||||
|
|
||||||
|
async function test(name, testFn) {
|
||||||
|
totalTests++;
|
||||||
|
cleanup(); // Clean up before each test
|
||||||
|
|
||||||
|
try {
|
||||||
|
await testFn();
|
||||||
|
console.log(`✅ ${name}`);
|
||||||
|
passedTests++;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`❌ ${name}: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 1: Basic toggle+apply idempotency
|
||||||
|
await test("Basic toggle+apply idempotency", async () => {
|
||||||
|
// Create initial config
|
||||||
|
const result1 = await runCommand(`node awesome-copilot.js init ${TEST_CONFIG}`);
|
||||||
|
assert(result1.success, 'Should create initial config');
|
||||||
|
setTestOutputDir(TEST_CONFIG);
|
||||||
|
|
||||||
|
// Enable a collection
|
||||||
|
const result2 = await runCommand(`node awesome-copilot.js toggle collections testing-automation on --config ${TEST_CONFIG}`);
|
||||||
|
assert(result2.success, 'Should enable collection');
|
||||||
|
|
||||||
|
// First apply
|
||||||
|
const result3 = await runCommand(`node awesome-copilot.js apply ${TEST_CONFIG}`);
|
||||||
|
assert(result3.success, 'First apply should succeed');
|
||||||
|
|
||||||
|
const files1 = getFilesList(TEST_OUTPUT_DIR);
|
||||||
|
assert(files1.length > 0, 'Should have copied files');
|
||||||
|
|
||||||
|
// Second apply (idempotency test)
|
||||||
|
const result4 = await runCommand(`node awesome-copilot.js apply ${TEST_CONFIG}`);
|
||||||
|
assert(result4.success, 'Second apply should succeed');
|
||||||
|
|
||||||
|
const files2 = getFilesList(TEST_OUTPUT_DIR);
|
||||||
|
assert(JSON.stringify(files1) === JSON.stringify(files2), 'File lists should be identical');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 2: Toggle collection off then on restores same state
|
||||||
|
await test("Toggle collection off then on restores same state", async () => {
|
||||||
|
// Create initial config and enable collection
|
||||||
|
await runCommand(`node awesome-copilot.js init ${TEST_CONFIG}`);
|
||||||
|
setTestOutputDir(TEST_CONFIG);
|
||||||
|
await runCommand(`node awesome-copilot.js toggle collections testing-automation on --config ${TEST_CONFIG}`);
|
||||||
|
|
||||||
|
// First apply
|
||||||
|
await runCommand(`node awesome-copilot.js apply ${TEST_CONFIG}`);
|
||||||
|
const files1 = getFilesList(TEST_OUTPUT_DIR);
|
||||||
|
|
||||||
|
// Toggle collection off
|
||||||
|
await runCommand(`node awesome-copilot.js toggle collections testing-automation off --config ${TEST_CONFIG}`);
|
||||||
|
|
||||||
|
// Apply (should remove files)
|
||||||
|
fs.rmSync(TEST_OUTPUT_DIR, { recursive: true, force: true });
|
||||||
|
await runCommand(`node awesome-copilot.js apply ${TEST_CONFIG}`);
|
||||||
|
const files2 = getFilesList(TEST_OUTPUT_DIR);
|
||||||
|
assert(files2.length === 0, 'Should have no files when collection disabled');
|
||||||
|
|
||||||
|
// Toggle collection back on
|
||||||
|
await runCommand(`node awesome-copilot.js toggle collections testing-automation on --config ${TEST_CONFIG}`);
|
||||||
|
|
||||||
|
// Apply again
|
||||||
|
fs.rmSync(TEST_OUTPUT_DIR, { recursive: true, force: true });
|
||||||
|
await runCommand(`node awesome-copilot.js apply ${TEST_CONFIG}`);
|
||||||
|
const files3 = getFilesList(TEST_OUTPUT_DIR);
|
||||||
|
|
||||||
|
assert(JSON.stringify(files1) === JSON.stringify(files3), 'File lists should be restored');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 3: Explicit overrides are maintained across collection toggles
|
||||||
|
await test("Explicit overrides are maintained across collection toggles", async () => {
|
||||||
|
// Create initial config and enable collection
|
||||||
|
await runCommand(`node awesome-copilot.js init ${TEST_CONFIG}`);
|
||||||
|
setTestOutputDir(TEST_CONFIG);
|
||||||
|
await runCommand(`node awesome-copilot.js toggle collections testing-automation on --config ${TEST_CONFIG}`);
|
||||||
|
|
||||||
|
// Add explicit override
|
||||||
|
await runCommand(`node awesome-copilot.js toggle prompts playwright-generate-test off --config ${TEST_CONFIG}`);
|
||||||
|
|
||||||
|
// Apply and count files
|
||||||
|
await runCommand(`node awesome-copilot.js apply ${TEST_CONFIG}`);
|
||||||
|
const files1 = getFilesList(TEST_OUTPUT_DIR);
|
||||||
|
|
||||||
|
// Toggle collection off and on
|
||||||
|
await runCommand(`node awesome-copilot.js toggle collections testing-automation off --config ${TEST_CONFIG}`);
|
||||||
|
await runCommand(`node awesome-copilot.js toggle collections testing-automation on --config ${TEST_CONFIG}`);
|
||||||
|
|
||||||
|
// Apply again
|
||||||
|
fs.rmSync(TEST_OUTPUT_DIR, { recursive: true, force: true });
|
||||||
|
await runCommand(`node awesome-copilot.js apply ${TEST_CONFIG}`);
|
||||||
|
const files2 = getFilesList(TEST_OUTPUT_DIR);
|
||||||
|
|
||||||
|
assert(JSON.stringify(files1) === JSON.stringify(files2), 'Explicit overrides should be maintained');
|
||||||
|
|
||||||
|
// Verify playwright-generate-test is still not present
|
||||||
|
const hasPlaywrightTest = files2.some(f => f.includes('playwright-generate-test'));
|
||||||
|
assert(!hasPlaywrightTest, 'Explicitly disabled item should stay disabled');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 4: Multiple apply operations are truly idempotent
|
||||||
|
await test("Multiple apply operations are truly idempotent", async () => {
|
||||||
|
// Setup
|
||||||
|
await runCommand(`node awesome-copilot.js init ${TEST_CONFIG}`);
|
||||||
|
setTestOutputDir(TEST_CONFIG);
|
||||||
|
await runCommand(`node awesome-copilot.js toggle collections testing-automation on --config ${TEST_CONFIG}`);
|
||||||
|
await runCommand(`node awesome-copilot.js toggle prompts create-readme on --config ${TEST_CONFIG}`);
|
||||||
|
|
||||||
|
// Apply multiple times
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
await runCommand(`node awesome-copilot.js apply ${TEST_CONFIG}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const files1 = getFilesList(TEST_OUTPUT_DIR);
|
||||||
|
|
||||||
|
// Apply once more
|
||||||
|
await runCommand(`node awesome-copilot.js apply ${TEST_CONFIG}`);
|
||||||
|
const files2 = getFilesList(TEST_OUTPUT_DIR);
|
||||||
|
|
||||||
|
assert(JSON.stringify(files1) === JSON.stringify(files2), 'Multiple applies should be idempotent');
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`\nIntegration Test Results: ${passedTests}/${totalTests} passed`);
|
||||||
|
|
||||||
|
cleanup(); // Final cleanup
|
||||||
|
|
||||||
|
if (passedTests === totalTests) {
|
||||||
|
console.log('🎉 All integration tests passed!');
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
console.log('💥 Some integration tests failed!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
runTests().then(success => {
|
||||||
|
process.exit(success ? 0 : 1);
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Test runner error:', error);
|
||||||
|
cleanup();
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { runTests };
|
||||||
35
test-new-config.yml
Normal file
35
test-new-config.yml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Awesome Copilot Configuration File
|
||||||
|
# Generated on 2025-09-21T22:44:51.675Z
|
||||||
|
#
|
||||||
|
# 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: ".awesome-copilot"
|
||||||
|
prompts:
|
||||||
|
playwright-generate-test: false
|
||||||
|
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: true
|
||||||
Loading…
x
Reference in New Issue
Block a user