From e7e22d5ecad699a5437f9b10d2952209bca28067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=82CHES?= Date: Mon, 23 Mar 2026 10:19:28 -0600 Subject: [PATCH] fix(gsd): remove over-broad skill activation heuristic (#2239) (#2244) Remove the blanket loop that auto-activated every visible skill whose name/description substring-matched tokens from extraContext and taskPlanContent. This caused 32+ irrelevant skills (xcode-build, ableton-lom, etc.) to load every auto-mode turn. Skill activation now uses only explicit preference sources: always_use_skills, skill_rules, prefer_skills, and skills_used from task plan frontmatter. Closes #2239 Co-authored-by: Claude Opus 4.6 (1M context) --- src/resources/extensions/gsd/auto-prompts.ts | 8 --- .../gsd/tests/skill-activation.test.ts | 59 ++++++++++++++++++- 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/resources/extensions/gsd/auto-prompts.ts b/src/resources/extensions/gsd/auto-prompts.ts index 48bddc015..62b633893 100644 --- a/src/resources/extensions/gsd/auto-prompts.ts +++ b/src/resources/extensions/gsd/auto-prompts.ts @@ -420,8 +420,6 @@ export function buildSkillActivationBlock(params: { params.sliceTitle, params.taskId, params.taskTitle, - ...(params.extraContext ?? []), - params.taskPlanContent ?? undefined, ); const visibleSkills = (typeof getLoadedSkills === 'function' ? getLoadedSkills() : []).filter(skill => !skill.disableModelInvocation); @@ -452,12 +450,6 @@ export function buildSkillActivationBlock(params: { } } - for (const skill of visibleSkills) { - if (skillMatchesContext(skill, contextTokens)) { - matched.add(normalizeSkillReference(skill.name)); - } - } - const ordered = [...matched] .filter(name => installedNames.has(name) && !avoided.has(name)) .sort(); diff --git a/src/resources/extensions/gsd/tests/skill-activation.test.ts b/src/resources/extensions/gsd/tests/skill-activation.test.ts index e2c6c7be0..673e8911c 100644 --- a/src/resources/extensions/gsd/tests/skill-activation.test.ts +++ b/src/resources/extensions/gsd/tests/skill-activation.test.ts @@ -39,7 +39,7 @@ function buildBlock( }); } -test("buildSkillActivationBlock matches installed skills from task context", () => { +test("buildSkillActivationBlock does not auto-activate skills via broad context heuristic", () => { const base = makeTempBase(); try { writeSkill(base, "react", "Use for React components, hooks, JSX, and frontend UI work."); @@ -52,7 +52,29 @@ test("buildSkillActivationBlock matches installed skills from task context", () taskTitle: "Implement React settings panel", }); - assert.match(result, //); + // Skills should not be activated just because their name appears in task context. + // Activation requires explicit preference sources (always_use, skill_rules, prefer_skills, skills_used). + assert.equal(result, ""); + } finally { + cleanup(base); + } +}); + +test("buildSkillActivationBlock activates skills via prefer_skills when context matches", () => { + const base = makeTempBase(); + try { + writeSkill(base, "react", "Use for React components, hooks, JSX, and frontend UI work."); + writeSkill(base, "swiftui", "Use for SwiftUI views, iOS layout, and Apple platform UI work."); + loadOnlyTestSkills(base); + + const result = buildBlock(base, { + sliceTitle: "Build React dashboard", + taskId: "T01", + taskTitle: "Implement React settings panel", + }, { + prefer_skills: ["react"], + }); + assert.match(result, /Call Skill\('react'\)/); assert.doesNotMatch(result, /swiftui/); } finally { @@ -105,7 +127,7 @@ test("buildSkillActivationBlock includes skill_rules matches and task-plan skill } }); -test("buildSkillActivationBlock honors avoid_skills", () => { +test("buildSkillActivationBlock honors avoid_skills against always_use_skills", () => { const base = makeTempBase(); try { writeSkill(base, "react", "Use for React components and frontend UI work."); @@ -114,6 +136,7 @@ test("buildSkillActivationBlock honors avoid_skills", () => { const result = buildBlock(base, { taskTitle: "Implement React settings panel", }, { + always_use_skills: ["react"], avoid_skills: ["react"], }); @@ -138,3 +161,33 @@ test("buildSkillActivationBlock falls back cleanly when nothing matches", () => cleanup(base); } }); + +test("buildSkillActivationBlock does not activate skills from extraContext or taskPlanContent body", () => { + const base = makeTempBase(); + try { + writeSkill(base, "xcode-build", "Use for Xcode build workflows and iOS compilation."); + writeSkill(base, "ableton-lom", "Use for Ableton Live Object Model scripting."); + writeSkill(base, "frontend-design", "Use for frontend design systems and UI components."); + loadOnlyTestSkills(base); + + const taskPlan = [ + "---", + "skills_used: []", + "---", + "# T01: Build the API endpoint", + "Use xcode-build patterns and frontend-design tokens.", + ].join("\n"); + + const result = buildBlock(base, { + taskTitle: "Build REST API", + extraContext: ["Build workflow for iOS and Ableton integration testing"], + taskPlanContent: taskPlan, + }); + + // None of these skills should activate — extraContext and taskPlanContent body + // must not be used for heuristic matching. + assert.equal(result, ""); + } finally { + cleanup(base); + } +});