Skip to content

fix: render extension skills for target agent, not just active agent …#3599

Open
Nimraakram22 wants to merge 2 commits into
github:mainfrom
Nimraakram22:fix/2948-agent-aware-skill-rendering
Open

fix: render extension skills for target agent, not just active agent …#3599
Nimraakram22 wants to merge 2 commits into
github:mainfrom
Nimraakram22:fix/2948-agent-aware-skill-rendering

Conversation

@Nimraakram22

Copy link
Copy Markdown
Contributor

Fixes #2948.

Problem

Extension skill rendering was scoped to the active agent (init-options.json's ai field) even when registering extensions for a different, non-active agent — e.g. after specify integration install copilot --integration-options "--skills" while Claude remained active, or specify integration upgrade copilot. The target agent received command files instead of SKILL.md files.

Root cause

ExtensionManager._get_skills_dir() and _register_extension_skills() always resolved the skills directory/settings from the active agent in init-options.json, ignoring the agent_name argument passed to register_enabled_extensions_for_agent().

Fix

  • Added is_agent_skills_enabled() to resolve per-agent skills-mode from .specify/integration.json's integration_settings[agent].parsed_options.skills, falling back to the legacy global ai_skills flag for the active agent.
  • Threaded agent_name through _get_skills_dir() and _register_extension_skills().
  • register_enabled_extensions_for_agent() now computes skills_mode_active per the target agent, and always attempts skill registration for that agent (matching prior best-effort error handling).

Testing

  • Manually verified: specify integration upgrade copilot --force with a non-active, skills-mode Copilot integration now renders .github/skills/speckit-git-*/SKILL.md files instead of command files, with no regression to the active agent's own skills.
  • pytest tests/ -k extension: 616/616 pass.
  • Full suite: 44 pre-existing failures unrelated to this change (Windows symlink-privilege and PowerShell path-resolution issues — confirmed present on main without this change).

…ithub#2948)

Extension skill rendering was previously scoped only to the active
agent (init-options.json's 'ai' field), even when
register_enabled_extensions_for_agent was called for a different,
non-active agent (e.g. after 'integration install <agent>
--skills' or 'integration upgrade <agent>' for a secondary
integration). This meant a skills-mode agent that wasn't the
active one received command files instead of skills.

Changes:
- Add is_agent_skills_enabled() in _init_options.py: resolves
  per-agent skills-mode from .specify/integration.json's
  integration_settings[agent].parsed_options.skills, falling back
  to the legacy global init-options ai_skills flag for the active
  agent.
- ExtensionManager._get_skills_dir() now accepts an optional
  agent_name and resolves the skills directory for that specific
  agent instead of always using the active agent.
- ExtensionManager._register_extension_skills() now accepts and
  threads through agent_name.
- register_enabled_extensions_for_agent() now computes
  skills_mode_active per the target agent_name (not the active
  agent), and always attempts skill registration for that agent
  (matching the prior best-effort error handling), while still
  skipping duplicate command-file registration when the target is
  in skills mode.

Verified manually: 'specify integration upgrade copilot --force'
with a non-active, skills-mode Copilot integration now renders
.github/skills/speckit-git-*/SKILL.md files instead of command
files, with no change to the active agent's own skills.

Test suite: 616/616 extension-related tests pass. Full suite has
44 pre-existing failures unrelated to this change (Windows
symlink-privilege and PowerShell path-resolution issues,
confirmed present on main via git stash).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Makes extension skill rendering aware of the target integration rather than only the active agent.

Changes:

  • Resolves per-agent skills mode from integration state.
  • Threads target agent through skill-directory resolution and rendering.
  • Updates extension re-registration behavior and documentation.
Show a summary per file
File Description
src/specify_cli/_init_options.py Adds per-agent skills-mode resolution.
src/specify_cli/extensions/__init__.py Targets extension skill generation by agent.
src/specify_cli/integrations/_helpers.py Updates registration behavior documentation.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (2)

src/specify_cli/extensions/init.py:980

  • The target-agent branch bypasses the repository's safe directory creation. resolve_agent_skills_dir only constructs a path, and _ensure_usable follows symlinked parents with mkdir; _register_extension_skills then writes SKILL.md beneath it. A project with (for example) a symlinked .github/skills can therefore make registration write outside the project. Route this path through _ensure_safe_shared_directory as resolve_active_skills_dir does (see shared_infra.py:163-196) before writing.
                skills_dir = resolve_agent_skills_dir(self.project_root, agent_name)

src/specify_cli/extensions/init.py:1856

  • registered_skills is still a single global list of names, so merging a second agent's identically named skills loses which directories own them. For example, after Claude and non-active Copilot both render speckit-git-feature, uninstalling either agent removes its copy and clears the list in unregister_agent_artifacts; the other copy remains but is no longer tracked, so later extension removal leaves it orphaned. Track generated skills per agent (with legacy-list compatibility) before enabling multi-agent skill rendering.
                        existing_skills = self._valid_name_list(
                            metadata.get("registered_skills", [])
                        )
                        merged_skills = list(
                            dict.fromkeys(existing_skills + registered_skills)
  • Files reviewed: 3/3 changed files
  • Comments generated: 2
  • Review effort level: Medium

Comment thread src/specify_cli/extensions/__init__.py
Comment thread src/specify_cli/_init_options.py

@mnriem mnriem left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address Copilot feedback and address test & lint errors

…thub#2948)

Addresses PR review feedback: install_from_directory() (the
'extension add' / dev-install path) still called
_register_extension_skills() with no agent_name, so it remained
scoped to only the active agent, unlike
register_enabled_extensions_for_agent() which was already fixed
to target a specific agent.

Changes:
- Add ExtensionManager._register_extension_skills_for_installed_agents(),
  which renders skills for the active agent (legacy single-agent
  projects) plus every agent listed in
  .specify/integration.json's installed_integrations, for whichever
  of those are individually in skills mode. Failures for one agent
  are warned and do not block the others.
- install_from_directory() now calls this helper instead of a
  single active-agent-only call.
- _get_skills_dir()'s agent_name branch now mirrors
  resolve_active_skills_dir()'s safety checks
  (_ensure_safe_shared_directory symlink/containment/is-a-directory
  validation, plus the Kimi native-skills-dir-must-already-exist
  fallback) instead of resolving the naive per-agent path
  unchecked — fixes a regression this exposed in the Hermes
  marker-file detection test and restores exact parity with the
  active-agent path.

Tests:
- Added TestNonActiveAgentSkillRegistration with 3 new tests
  covering register_enabled_extensions_for_agent for a non-active
  skills-mode agent, isolation from the active agent's own skills,
  and extension add rendering skills for all installed
  skills-mode agents.
- pytest tests/ -k extension: 619/619 pass (616 previous + 3 new).
- Manually verified: 'specify extension add git' with Claude
  active and Copilot installed in --skills mode now renders
  SKILL.md files for both agents ('5 agent skill(s)
  auto-registered'), with no change to Claude's own skills.
- git diff --check: clean, no whitespace errors.
@Nimraakram22

Copy link
Copy Markdown
Contributor Author

@mnriem Thanks for the review! Pushed a follow-up commit addressing both points:

  • Copilot's extension add gap: install_from_directory() now uses a new _register_extension_skills_for_installed_agents() helper that renders skills for the active agent and every agent in .specify/integration.json's installed integrations that has skills mode enabled — not just the active one. Verified manually: specify extension add git with Claude active + Copilot installed in --skills mode now reports "5 agent skill(s) auto-registered" and creates SKILL.md files under both .claude/skills/ and .github/skills/.
  • Test coverage: added TestNonActiveAgentSkillRegistration (3 tests) covering the non-active-agent upgrade path, isolation from the active agent's own skills, and the extension add multi-agent path.

Also had to fix a regression this exposed: my agent-specific _get_skills_dir() branch was resolving the naive per-agent directory without the same symlink/containment/is-a-directory safety checks that resolve_active_skills_dir() applies for the active agent this broke the Hermes marker-file detection test, now fixed to mirror that logic exactly.

pytest tests/ -k extension: 619/619 pass. git diff --check: clean.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Comments suppressed due to low confidence (1)

src/specify_cli/extensions/init.py:1569

  • Adding this second skill pass does not stop the preceding register_commands_for_all_agents pass from registering command files for the same skills-mode agent. For example, if .github/agents already exists (often because it contains user agents), a persisted Copilot --skills install receives both .agent.md files and SKILL.md, violating the acceptance criterion. Filter per-agent skills-mode integrations out of command registration rather than merely adding skills afterward.
        # Auto-register extension commands as agent skills for every
        # skills-mode agent detected (active agent plus any other
        # installed integrations in skills mode), not just the active
        # agent (#2948).
        registered_skills = self._register_extension_skills_for_installed_agents(
            manifest, dest_dir, link_outputs=link_commands
  • Files reviewed: 4/4 changed files
  • Comments generated: 2
  • Review effort level: Medium

Comment on lines +405 to +410
Extension *skill* rendering is scoped to ``agent_key`` itself (using
per-agent settings in ``.specify/integration.json`` when available, with
a fallback to the legacy global init-options for the active agent), so a
skills-mode agent registered while it is not the active agent (e.g.
Copilot ``--skills`` registered while non-active) still receives skill
files here instead of only command files (#2948).
Comment on lines +1273 to +1276
for skill_name in agent_skills:
if skill_name not in seen:
seen.add(skill_name)
combined.append(skill_name)
@mnriem

mnriem commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Please address Copilot feedback and fix test & lint errors

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extension commands aren't rendered as skills for non-active skills-mode agents on add/install/upgrade

4 participants