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) <noreply@anthropic.com>
This commit is contained in:
TÂCHES 2026-03-23 10:19:28 -06:00 committed by GitHub
parent e0c203c3e4
commit e7e22d5eca
2 changed files with 56 additions and 11 deletions

View file

@ -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();

View file

@ -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, /<skill_activation>/);
// 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);
}
});