From 18846b91f4566bec1c7755b7e6652dd39ed15f0b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Sep 2025 22:41:27 +0000 Subject: [PATCH] Implement effective state computation and update apply logic Co-authored-by: AstroSteveo <34114851+AstroSteveo@users.noreply.github.com> --- apply-config.js | 119 ++++++++++++++++---------------------- config-manager.js | 87 +++++++++++++++++++++++++++- test-effective-config.yml | 30 ++++++++++ 3 files changed, 167 insertions(+), 69 deletions(-) create mode 100644 test-effective-config.yml diff --git a/apply-config.js b/apply-config.js index 675acfd..fa27c64 100755 --- a/apply-config.js +++ b/apply-config.js @@ -100,8 +100,28 @@ async function applyConfig(configPath = "awesome-copilot.config.yml") { collections: 0 }; - // Process collections first (they can enable individual items) - const enabledItems = new Set(); + // Import config manager for effective state computation + 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) { for (const [collectionName, enabled] of Object.entries(config.collections)) { if (enabled) { @@ -109,9 +129,6 @@ async function applyConfig(configPath = "awesome-copilot.config.yml") { if (fs.existsSync(collectionPath)) { const collection = parseCollectionYaml(collectionPath); if (collection && collection.items) { - collection.items.forEach(item => { - enabledItems.add(item.path); - }); summary.collections++; console.log(`✓ Enabled collection: ${collectionName} (${collection.items.length} items)`); } @@ -120,70 +137,36 @@ async function applyConfig(configPath = "awesome-copilot.config.yml") { } } - // Process prompts - if (config.prompts) { - for (const [promptName, enabled] of Object.entries(config.prompts)) { - 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); + // Process prompts using effective states + for (const promptName of effectivelyEnabledSets.prompts) { + const sourcePath = path.join(rootDir, "prompts", `${promptName}.prompt.md`); if (fs.existsSync(sourcePath)) { - const fileName = path.basename(itemPath); - let destPath; - - if (fileName.endsWith('.prompt.md')) { - destPath = path.join(outputDir, "prompts", fileName); - } else if (fileName.endsWith('.chatmode.md')) { - destPath = path.join(outputDir, "chatmodes", fileName); - } else if (fileName.endsWith('.instructions.md')) { - destPath = path.join(outputDir, "instructions", fileName); - } - - if (destPath && !fs.existsSync(destPath)) { - copyFile(sourcePath, destPath); - copiedCount++; - } + const destPath = path.join(outputDir, "prompts", `${promptName}.prompt.md`); + copyFile(sourcePath, destPath); + copiedCount++; + summary.prompts++; + } + } + + // Process instructions using effective states + for (const instructionName of effectivelyEnabledSets.instructions) { + 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 using effective states + for (const chatmodeName of effectivelyEnabledSets.chatmodes) { + 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++; } } diff --git a/config-manager.js b/config-manager.js index 628837f..0b535a3 100644 --- a/config-manager.js +++ b/config-manager.js @@ -158,6 +158,90 @@ function getAllAvailableItems(type) { return getAvailableItems(path.join(__dirname, meta.dir), meta.ext); } +/** + * 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 = { DEFAULT_CONFIG_PATH, CONFIG_SECTIONS, @@ -168,5 +252,6 @@ module.exports = { ensureConfigStructure, sortObjectKeys, countEnabledItems, - getAllAvailableItems + getAllAvailableItems, + computeEffectiveItemStates }; diff --git a/test-effective-config.yml b/test-effective-config.yml new file mode 100644 index 0000000..ed0fb6d --- /dev/null +++ b/test-effective-config.yml @@ -0,0 +1,30 @@ +# 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: true + +prompts: + playwright-generate-test: true + # Note: playwright-explore-website is not defined (undefined) + # Note: csharp-nunit is not defined (undefined) + # Note: java-junit is not defined (undefined) + ai-prompt-engineering-safety-review: false + +instructions: + # Note: playwright-typescript is not defined (undefined) + # Note: playwright-python is not defined (undefined) + +chatmodes: + # Note: tdd-red is not defined (undefined) + # Note: tdd-green is not defined (undefined) + # Note: tdd-refactor is not defined (undefined) + # Note: playwright-tester is not defined (undefined)