diff --git a/awesome-copilot.js b/awesome-copilot.js index 15580e7..4784e95 100755 --- a/awesome-copilot.js +++ b/awesome-copilot.js @@ -149,8 +149,16 @@ function handleListCommand(rawArgs) { const availableItems = getAllAvailableItems(section); // Count effectively enabled items - const effectivelyEnabled = Object.values(effectiveStates[section] || {}) - .filter(state => state.enabled).length; + let effectivelyEnabled; + if (section === "collections") { + // For collections, count explicitly enabled ones + effectivelyEnabled = Object.values(sanitizedConfig[section] || {}) + .filter(value => value === true).length; + } else { + // For other sections, count effectively enabled items + effectivelyEnabled = Object.values(effectiveStates[section] || {}) + .filter(state => state.enabled).length; + } const { totalCharacters } = calculateSectionFootprint(section, sanitizedConfig[section]); const headingParts = [ @@ -170,20 +178,40 @@ function handleListCommand(rawArgs) { // Show items with effective state and reason if (section === "collections") { - // Collections show simple enabled/disabled + // Collections show simple enabled/disabled with count of effectively enabled items availableItems.forEach(itemName => { const isEnabled = Boolean(sanitizedConfig[section]?.[itemName]); - console.log(` [${isEnabled ? "✓" : " "}] ${itemName}`); + + // Count how many items this collection would enable + let enabledCount = 0; + if (isEnabled) { + // Simulate what would happen if only this collection was enabled + const testConfig = { collections: { [itemName]: true } }; + const testEffectiveStates = computeEffectiveItemStates(testConfig); + + for (const sectionName of ["prompts", "instructions", "chatmodes"]) { + enabledCount += Object.values(testEffectiveStates[sectionName] || {}) + .filter(state => state.enabled && state.reason === "collection").length; + } + } + + const countText = isEnabled ? ` (${enabledCount} items effectively enabled)` : ""; + console.log(` [${isEnabled ? "✓" : " "}] ${itemName}${countText}`); }); } else { - // Other sections show effective state with reason + // Other sections show effective state with detailed reason availableItems.forEach(itemName => { const effectiveState = effectiveStates[section]?.[itemName]; if (effectiveState) { const symbol = effectiveState.enabled ? "✓" : " "; - const reasonText = effectiveState.reason === "explicit" - ? ` (${effectiveState.reason})` - : effectiveState.enabled ? ` (${effectiveState.reason})` : ""; + let reasonText = ""; + + if (effectiveState.reason === "explicit") { + reasonText = ` (explicit:${effectiveState.enabled})`; + } else if (effectiveState.reason === "collection" && effectiveState.collections && effectiveState.collections.length > 0) { + reasonText = ` (via:[${effectiveState.collections.join(',')}])`; + } + console.log(` [${symbol}] ${itemName}${reasonText}`); } else { console.log(` [ ] ${itemName}`); diff --git a/config-manager.js b/config-manager.js index d074fd1..fbc2cb7 100644 --- a/config-manager.js +++ b/config-manager.js @@ -214,14 +214,21 @@ function computeEffectiveItemStates(config) { chatmodes: {} }; - // Build membership maps: Map> per section for O(1) lookups + // Build detailed membership maps: Map> per section + const collectionMemberships = { + prompts: new Map(), + instructions: new Map(), + chatmodes: new Map() + }; + + // Build simple enabled sets for O(1) lookups const collectionEnabledItems = { prompts: new Set(), instructions: new Set(), chatmodes: new Set() }; - // Identify enabled collections per section + // Identify enabled collections per section and track memberships if (config.collections) { for (const [collectionName, enabled] of Object.entries(config.collections)) { if (enabled === true) { @@ -233,12 +240,24 @@ function computeEffectiveItemStates(config) { // Extract item name from path - remove directory and all extensions const itemName = path.basename(item.path).replace(/\.(prompt|instructions|chatmode)\.md$/, ''); + let sectionName; if (item.kind === "prompt") { - collectionEnabledItems.prompts.add(itemName); + sectionName = "prompts"; } else if (item.kind === "instruction") { - collectionEnabledItems.instructions.add(itemName); + sectionName = "instructions"; } else if (item.kind === "chat-mode") { - collectionEnabledItems.chatmodes.add(itemName); + sectionName = "chatmodes"; + } + + if (sectionName) { + // Track which collections enable this item + if (!collectionMemberships[sectionName].has(itemName)) { + collectionMemberships[sectionName].set(itemName, new Set()); + } + collectionMemberships[sectionName].get(itemName).add(collectionName); + + // Add to enabled set for O(1) lookups + collectionEnabledItems[sectionName].add(itemName); } }); } @@ -258,6 +277,7 @@ function computeEffectiveItemStates(config) { for (const itemName of availableItems) { const explicitValue = sectionConfig[itemName]; const isEnabledByCollection = collectionEnabled.has(itemName); + const enablingCollections = collectionMemberships[section].get(itemName) || new Set(); // Precedence rules with strict undefined handling: // 1. If explicitly set to true or false, use that value (highest priority) @@ -269,6 +289,7 @@ function computeEffectiveItemStates(config) { let enabled = false; let reason = "disabled"; + let collections = []; if (explicitValue === true) { enabled = true; @@ -281,9 +302,10 @@ function computeEffectiveItemStates(config) { // undefined values can be enabled by collections (not treated as disabled) enabled = true; reason = "collection"; + collections = Array.from(enablingCollections).sort(); } - effectiveStates[section][itemName] = { enabled, reason }; + effectiveStates[section][itemName] = { enabled, reason, collections }; } } diff --git a/test-cli.js b/test-cli.js index 41cb2dc..a3cfd90 100644 --- a/test-cli.js +++ b/test-cli.js @@ -92,7 +92,7 @@ async function runTests() { 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('(via:[testing-automation])'), 'Should show collection reason'); assert(result.stdout.includes('[✓]'), 'Should show enabled items'); }); @@ -105,7 +105,7 @@ async function runTests() { 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 (explicit)'), + assert(listResult.stdout.includes('playwright-generate-test (explicit:false)'), 'Explicitly disabled item should show explicit reason'); }); diff --git a/test-effective-config.yml b/test-effective-config.yml deleted file mode 100644 index 3187a8d..0000000 --- a/test-effective-config.yml +++ /dev/null @@ -1,17 +0,0 @@ -# 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: ".github" -collections: - testing-automation: false -prompts: - ai-prompt-engineering-safety-review: false - playwright-generate-test: true -instructions: -chatmodes: diff --git a/test-new-config.yml b/test-new-config.yml deleted file mode 100644 index 4c92107..0000000 --- a/test-new-config.yml +++ /dev/null @@ -1,35 +0,0 @@ -# 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: ".github" -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 diff --git a/test-new-features.js b/test-new-features.js index badbe82..766d511 100755 --- a/test-new-features.js +++ b/test-new-features.js @@ -119,14 +119,14 @@ async function runTests() { // Check it's explicitly disabled const listResult1 = await runCommand(`node awesome-copilot.js list prompts --config ${TEST_CONFIG}`); - assert(listResult1.stdout.includes('create-readme (explicit)'), 'Should show explicit disabled'); + assert(listResult1.stdout.includes('create-readme (explicit:false)'), 'Should show explicit disabled'); // Force enable all with --all flag await runCommand(`node awesome-copilot.js toggle prompts all on --all --config ${TEST_CONFIG}`); // Check it's now explicitly enabled const listResult2 = await runCommand(`node awesome-copilot.js list prompts --config ${TEST_CONFIG}`); - assert(listResult2.stdout.includes('create-readme (explicit)') && listResult2.stdout.includes('[✓]'), 'Should be explicitly enabled'); + assert(listResult2.stdout.includes('create-readme (explicit:true)') && listResult2.stdout.includes('[✓]'), 'Should be explicitly enabled'); }); // Test 4: File cleanup on disable @@ -180,7 +180,7 @@ async function runTests() { // Double-check with list command const listResult = await runCommand(`node awesome-copilot.js list prompts --config ${TEST_CONFIG}`); - assert(listResult.stdout.includes('playwright-generate-test (explicit)'), "'playwright-generate-test (explicit)' should be present in the list output"); + assert(listResult.stdout.includes('playwright-generate-test (explicit:false)'), "'playwright-generate-test (explicit:false)' should be present in the list output"); assert(!listResult.stdout.includes('[✓] playwright-generate-test'), "'[✓] playwright-generate-test' should NOT be present in the list output (should remain explicitly disabled)"); });