diff --git a/src/specify_cli/integrations/copilot/__init__.py b/src/specify_cli/integrations/copilot/__init__.py index 0d32edff72..6e3daeefeb 100644 --- a/src/specify_cli/integrations/copilot/__init__.py +++ b/src/specify_cli/integrations/copilot/__init__.py @@ -283,58 +283,13 @@ def command_filename(self, template_name: str) -> str: return f"speckit.{template_name}.agent.md" def post_process_skill_content(self, content: str) -> str: - """Inject shared hook guidance and Copilot ``mode:`` frontmatter. + """Inject shared hook guidance into Copilot skill content. - Inserts ``mode: speckit.`` before the closing ``---`` so - Copilot can associate the skill with its agent mode. + Delegates to :class:`_CopilotSkillsHelper` for shared post-processing. + The ``mode:`` frontmatter field is intentionally omitted: VS Code + Copilot Agent Skills do not support it (see issue #2799). """ - updated = _CopilotSkillsHelper().post_process_skill_content(content) - lines = updated.splitlines(keepends=True) - - # Extract skill name from frontmatter to derive the mode value - dash_count = 0 - skill_name = "" - for line in lines: - stripped = line.rstrip("\n\r") - if stripped == "---": - dash_count += 1 - if dash_count == 2: - break - continue - if dash_count == 1: - if stripped.startswith("mode:"): - return updated # already present - if stripped.startswith("name:"): - # Parse: name: "speckit-plan" → speckit.plan - val = stripped.split(":", 1)[1].strip().strip('"').strip("'") - # Convert speckit-plan → speckit.plan - if val.startswith("speckit-"): - skill_name = "speckit." + val[len("speckit-"):] - else: - skill_name = val - - if not skill_name: - return updated - - # Inject mode: before the closing --- of frontmatter - out: list[str] = [] - dash_count = 0 - injected = False - for line in lines: - stripped = line.rstrip("\n\r") - if stripped == "---": - dash_count += 1 - if dash_count == 2 and not injected: - if line.endswith("\r\n"): - eol = "\r\n" - elif line.endswith("\n"): - eol = "\n" - else: - eol = "" - out.append(f"mode: {skill_name}{eol}") - injected = True - out.append(line) - return "".join(out) + return _CopilotSkillsHelper().post_process_skill_content(content) def setup( self, diff --git a/tests/integrations/test_integration_copilot.py b/tests/integrations/test_integration_copilot.py index b22e9c530a..650d01ac96 100644 --- a/tests/integrations/test_integration_copilot.py +++ b/tests/integrations/test_integration_copilot.py @@ -426,8 +426,8 @@ def test_skill_frontmatter_structure(self, tmp_path): # -- Copilot-specific post-processing --------------------------------- - def test_post_process_skill_content_injects_mode(self): - """post_process_skill_content() should inject mode: field.""" + def test_post_process_skill_content_does_not_inject_mode(self): + """post_process_skill_content() must NOT inject mode: — VS Code Copilot does not support it.""" copilot = self._make_copilot() content = ( "---\n" @@ -437,10 +437,10 @@ def test_post_process_skill_content_injects_mode(self): "\nBody content\n" ) updated = copilot.post_process_skill_content(content) - assert "mode: speckit.plan" in updated + assert "mode:" not in updated def test_post_process_skill_content_injects_hook_note(self): - """post_process_skill_content() should inject shared hook guidance.""" + """post_process_skill_content() should inject shared hook guidance but not mode:.""" copilot = self._make_copilot() content = ( "---\n" @@ -451,7 +451,7 @@ def test_post_process_skill_content_injects_hook_note(self): ) updated = copilot.post_process_skill_content(content) assert "replace dots" in updated - assert "mode: speckit.specify" in updated + assert "mode:" not in updated def test_post_process_idempotent(self): """post_process_skill_content() must be idempotent.""" @@ -467,8 +467,8 @@ def test_post_process_idempotent(self): second = copilot.post_process_skill_content(first) assert first == second - def test_skills_have_mode_in_frontmatter(self, tmp_path): - """Generated SKILL.md files should have mode: field from post-processing.""" + def test_skills_do_not_have_mode_in_frontmatter(self, tmp_path): + """Generated SKILL.md files must NOT contain mode: — VS Code Copilot does not support it.""" copilot = self._make_copilot() created, _ = self._setup_skills(copilot, tmp_path) skill_files = [f for f in created if f.name == "SKILL.md"] @@ -477,11 +477,7 @@ def test_skills_have_mode_in_frontmatter(self, tmp_path): content = f.read_text(encoding="utf-8") parts = content.split("---", 2) fm = yaml.safe_load(parts[1]) - assert "mode" in fm, f"{f} frontmatter missing 'mode'" - # mode should be speckit. - skill_dir_name = f.parent.name - stem = skill_dir_name.removeprefix("speckit-") - assert fm["mode"] == f"speckit.{stem}" + assert "mode" not in fm, f"{f} frontmatter must not contain unsupported 'mode' field" def test_skills_hook_sections_explain_dotted_command_conversion(self, tmp_path): """Generated skills with hook sections should include shared hook guidance.""" diff --git a/tests/integrations/test_integration_subcommand.py b/tests/integrations/test_integration_subcommand.py index e96640d787..d7a26c11f8 100644 --- a/tests/integrations/test_integration_subcommand.py +++ b/tests/integrations/test_integration_subcommand.py @@ -898,11 +898,10 @@ def test_switch_migrates_copilot_skills_extension_commands(self, tmp_path): assert copilot_git_feature.exists(), "Git extension skill should exist for Copilot skills mode" assert not copilot_agent_file.exists(), "Copilot skills mode should not create extension .agent.md files" - # Verify Copilot-specific frontmatter: mode field should map from - # skill name (speckit-git-feature) back to dot notation (speckit.git-feature) + # Verify Copilot skill frontmatter does NOT contain mode: — VS Code Copilot does not support it skill_content = copilot_git_feature.read_text(encoding="utf-8") - assert "mode: speckit.git-feature" in skill_content, ( - "Copilot skill frontmatter should contain mode mapped from skill name" + assert "mode:" not in skill_content, ( + "Copilot skill frontmatter must not contain unsupported 'mode' field" ) registry = json.loads(