You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(extensions): list commands the way the active agent registers them - #4964
specify extension add ("Provided commands") and specify extension info ("Commands:") list an extension's commands. Both special-cased Cline and Forge by agent key, and every other agent got the manifest's dotted name. For integrations that register hyphenated names, the listings show commands the agent does not have:
Project
Listed on main
Registered
--integration codex
speckit.demo.hello
.agents/skills/speckit-demo-hello/SKILL.md
--integration claude
speckit.demo.hello
.claude/skills/speckit-demo-hello/SKILL.md
--integration junie
speckit.demo.hello
.junie/commands/speckit-demo-hello.md
--integration copilot (skills, the default)
speckit.demo.hello
.github/skills/speckit-demo-hello/SKILL.md
--integration generic with --skills
speckit.demo.hello
<commands-dir>/speckit-demo-hello/SKILL.md
Catalog-installed skills adapter, extension info in a new process
The per-agent branches grew one agent at a time (#2508 for Cline, #3669 and #3744 for Forge). Of the 41 registered integrations (other than generic), 25 register hyphenated names, and the listings handled 2.
This change replaces both branches with one helper in extensions/_commands.py. In a skills project (the persisted ai_skills flag), the helper hyphenates with ExtensionManager._skill_name_for_command, the function extension skill registration uses. In command mode it asks the active integration for its separator with invoke_separator_for_mode(False) and hyphenates only for -. The skills check comes first and needs no integration lookup. Generic does not override that hook and returns . for both of its layouts, and extension info does not load catalog-installed adapters, so a lookup there finds nothing. This follows #4910, which derived the init next-steps names from the integration for the same reason.
What stays the same:
Cline and Forge print the same names. Manifest command names must match ^speckit\.([a-z0-9-]+)\.([a-z0-9-]+)$, and for such names their formatters and the shared helper return the same string.
Agents that register dotted names, such as Gemini, Copilot with --commands and generic without --skills, keep the dotted name.
Without a readable ai value, or in command mode for an agent that is not loaded, the listing falls back to the manifest name, as before.
The helper relies on a - command-mode separator meaning hyphenated registration. A new registry test checks this for every registered integration other than generic (all 41 hold today). Generic has no static registrar config to check, so the listing tests cover both of its layouts instead.
Related: #4954 reports a similar mismatch in hook "To execute" lines for Codex. That code path is separate and is not changed here.
Testing
New test_extension_install_listing_matches_registered_command_names and test_extension_info_listing_matches_registered_command_names share LISTED_COMMAND_NAME_CASES (10 agent and mode cases). On main, 10 fail (claude, codex, junie, copilot-skills and generic-skills, for each command) and 10 pass. With the fix, all 20 pass.
New test_extension_info_lists_registered_names_without_loading_adapter in tests/specify_cli/integrations/test_installed_adapters.py installs the sample adapter in its skills and command layouts, adds an extension, unloads the adapter and runs extension info. The skills case fails on the previous head. With the fix both cases pass, and the adapter stays unloaded.
New test_command_mode_separator_matches_registered_names in tests/integrations/test_registry.py (41 cases). It also passes on main; it guards the assumption the helper relies on.
tests/specify_cli/extensions, tests/integrations, tests/test_extensions.py and tests/test_installed_list_json.py show no new failures compared with main.
Full suite on the current head: 12150 passed, 368 skipped and 6 failed. The same 6 tests fail on main in the same local environment, all because fetching the remote catalogs fails SSL certificate verification there. The branch adds no new failures.
CLI: I created projects with specify init --integration <codex|claude|junie|copilot|gemini|cline|forge>, with copilot --integration-options="--commands", and with generic with and without --skills. For a sample extension, extension add --dev and extension info now list speckit-demo-hello for codex, claude, junie, copilot and generic --skills, still list it for cline and forge, and still list speckit.demo.hello for gemini, copilot --commands and generic without --skills. In each project the listed name matches the file the agent registered.
uvx ruff@0.15.0 check src tests passes.
Tests ran with the project virtualenv's python -m pytest after uv sync --extra test.
Tested locally with uv run specify --help
Ran existing tests with uv sync && uv run pytest
Tested with a sample project (if applicable)
AI Disclosure
I did not use AI assistance for this contribution
I did use AI assistance (fill in the disclosure below)
AI disclosure: Prepared with Claude Code (model: Claude Opus 5.5) on behalf of @tkv00, running autonomously under step-by-step direction. The contributor chose the topic and the fix approach and approved each step. Claude Code found the issue, wrote the code, tests, documentation line, commit messages and this description, and ran the tests. AI generated the code, tests and text. No human reviewed the diff line by line before submission.
`extension add` and `extension info` list an extension's commands, but
only Cline and Forge get the names they register. Claude, Codex, Junie
and Copilot in skills mode register `speckit-<ext>-<cmd>`, yet both
listings print the manifest's dotted `speckit.<ext>.<cmd>`.
Add one case table shared by both listing tests: the four agents above,
Cline and Forge (already correct), and Copilot in commands mode and
Gemini, which register dotted names and must keep them. On main the
first four cases fail for both commands.
Assisted-by: Claude Code (model: Claude Opus 5.5, autonomous)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both listings special-cased Cline and Forge by name, so every other
agent that registers hyphenated names (25 of 41 integrations register
them; the listings handled 2) was shown names it does not have.
Ask the active integration for its command separator in the project's
mode (`invoke_separator_for_mode` with the persisted `ai_skills` flag)
and hyphenate with the helper extension skill registration already
uses. This is the approach github#4910 took for the init next-steps banner.
Agents that register dotted names, and unknown agents, keep the
manifest name.
A registry test checks that every integration's command-mode separator
matches the names CommandRegistrar writes, since the listings now rely
on that. The extension user guide notes that listed names follow the
agent.
Assisted-by: Claude Code (model: Claude Opus 5.5, autonomous)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Generic registers extension commands as speckit-<ext>-<cmd>/SKILL.md
skills with --skills, and as dotted speckit.<ext>.<cmd>.md files
without it. Add both layouts to the listing case table. A shared
helper records the init options and, for generic, sets up the
integration with --commands-dir, which generic needs before it can
register extension commands.
On the previous head the --skills case fails for both listings.
Assisted-by: Claude Code (model: Claude Opus 5.5, autonomous)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The listings asked invoke_separator_for_mode() for the project's mode.
Generic does not override that hook, so it returns "." for both of its
layouts, and a generic --skills project listed speckit.<ext>.<cmd>
while its skills are registered as speckit-<ext>-<cmd>.
Hyphenate whenever the persisted ai_skills flag is set, since extension
skill registration always uses that name, and consult the separator
only in command mode. The hook itself is unchanged because preset skill
rendering also reads it. The extension user guide now mentions
projects initialized with --skills.
Assisted-by: Claude Code (model: Claude Opus 5.5, autonomous)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Generic doesn't override invoke_separator_for_mode(), so generic --skills projects were still listed with dotted names. Now the listing always hyphenates in skills mode and only looks at the separator in command mode. I added both generic layouts to the listing tests (the --skills case fails without the fix) and updated the PR description to match.
extension info lists manifest name instead of registered skill name
src/specify_cli/extensions/_commands.py:150
For a project using an installed external skills adapter, this return makes extension info list the dotted manifest name instead of the registered skill name. In a fresh process, extension info does not load project-installed adapters, so get_integration() returns None. Registration still uses _skill_name_for_command() and writes speckit-test-ext-hello/SKILL.md. The listing therefore depends on whether the adapter has already been loaded; extension add loads it during registration and event refresh.
Check for a non-empty string ai, then honor is_ai_skills_enabled(opts) before looking up the integration. Only command mode needs the separator lookup. This preserves the no-import inspection boundary described in design/integration.md, “Storage, loading, and lifecycle.” Add a listing regression case with an unloaded adapter key and ai_skills: true.
AI-generated review and suggested fix by GitHub Copilot (model: unknown, autonomous) for the requester.
`extension info` does not load installed adapters, so in a fresh process
get_integration() returns None for a catalog-installed adapter. Install
the sample adapter in its skills and command layouts, add an extension,
unload the adapter, and check that `extension info` lists the names
`extension add` registered and leaves the adapter unloaded.
On the previous head the skills case lists speckit.<ext>.<cmd>.
Assisted-by: Claude Code (model: Claude Opus 5.5, autonomous)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The listing helper looked up the integration before checking skills
mode. An installed adapter that `extension info` had not loaded fell
back to the manifest's dotted name, while its skills are registered as
speckit-<ext>-<cmd>.
Check the persisted ai_skills flag first and hyphenate without a lookup,
as extension skill registration does. Only command mode consults the
integration, and there an agent that is not loaded keeps the manifest
name.
Assisted-by: Claude Code (model: Claude Opus 5.5, autonomous)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
extension info doesn't load installed adapters, so the listing now checks skills mode before it looks up the integration and hyphenates without the lookup. Command mode still asks the integration, and an adapter that isn't loaded keeps the manifest name there. The new test installs the sample adapter in both layouts and runs extension info with the adapter unloaded; the skills case fails on the previous head. I updated the description too.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
specify extension add("Provided commands") andspecify extension info("Commands:") list an extension's commands. Both special-cased Cline and Forge by agent key, and every other agent got the manifest's dotted name. For integrations that register hyphenated names, the listings show commands the agent does not have:main--integration codexspeckit.demo.hello.agents/skills/speckit-demo-hello/SKILL.md--integration claudespeckit.demo.hello.claude/skills/speckit-demo-hello/SKILL.md--integration juniespeckit.demo.hello.junie/commands/speckit-demo-hello.md--integration copilot(skills, the default)speckit.demo.hello.github/skills/speckit-demo-hello/SKILL.md--integration genericwith--skillsspeckit.demo.hello<commands-dir>/speckit-demo-hello/SKILL.mdextension infoin a new processspeckit.demo.hello<adapter folder>/skills/speckit-demo-hello/SKILL.mdThe per-agent branches grew one agent at a time (#2508 for Cline, #3669 and #3744 for Forge). Of the 41 registered integrations (other than
generic), 25 register hyphenated names, and the listings handled 2.This change replaces both branches with one helper in
extensions/_commands.py. In a skills project (the persistedai_skillsflag), the helper hyphenates withExtensionManager._skill_name_for_command, the function extension skill registration uses. In command mode it asks the active integration for its separator withinvoke_separator_for_mode(False)and hyphenates only for-. The skills check comes first and needs no integration lookup. Generic does not override that hook and returns.for both of its layouts, andextension infodoes not load catalog-installed adapters, so a lookup there finds nothing. This follows #4910, which derived theinitnext-steps names from the integration for the same reason.What stays the same:
^speckit\.([a-z0-9-]+)\.([a-z0-9-]+)$, and for such names their formatters and the shared helper return the same string.--commandsand generic without--skills, keep the dotted name.aivalue, or in command mode for an agent that is not loaded, the listing falls back to the manifest name, as before.The helper relies on a
-command-mode separator meaning hyphenated registration. A new registry test checks this for every registered integration other thangeneric(all 41 hold today). Generic has no static registrar config to check, so the listing tests cover both of its layouts instead.Related: #4954 reports a similar mismatch in hook "To execute" lines for Codex. That code path is separate and is not changed here.
Testing
test_extension_install_listing_matches_registered_command_namesandtest_extension_info_listing_matches_registered_command_namesshareLISTED_COMMAND_NAME_CASES(10 agent and mode cases). Onmain, 10 fail (claude, codex, junie, copilot-skills and generic-skills, for each command) and 10 pass. With the fix, all 20 pass.test_extension_info_lists_registered_names_without_loading_adapterintests/specify_cli/integrations/test_installed_adapters.pyinstalls the sample adapter in its skills and command layouts, adds an extension, unloads the adapter and runsextension info. The skills case fails on the previous head. With the fix both cases pass, and the adapter stays unloaded.test_command_mode_separator_matches_registered_namesintests/integrations/test_registry.py(41 cases). It also passes onmain; it guards the assumption the helper relies on.tests/specify_cli/extensions,tests/integrations,tests/test_extensions.pyandtests/test_installed_list_json.pyshow no new failures compared withmain.mainin the same local environment, all because fetching the remote catalogs fails SSL certificate verification there. The branch adds no new failures.specify init --integration <codex|claude|junie|copilot|gemini|cline|forge>, withcopilot --integration-options="--commands", and withgenericwith and without--skills. For a sample extension,extension add --devandextension infonow listspeckit-demo-hellofor codex, claude, junie, copilot and generic--skills, still list it for cline and forge, and still listspeckit.demo.hellofor gemini, copilot--commandsand generic without--skills. In each project the listed name matches the file the agent registered.uvx ruff@0.15.0 check src testspasses.Tests ran with the project virtualenv's
python -m pytestafteruv sync --extra test.uv run specify --helpuv sync && uv run pytestAI Disclosure
AI disclosure: Prepared with Claude Code (model: Claude Opus 5.5) on behalf of @tkv00, running autonomously under step-by-step direction. The contributor chose the topic and the fix approach and approved each step. Claude Code found the issue, wrote the code, tests, documentation line, commit messages and this description, and ran the tests. AI generated the code, tests and text. No human reviewed the diff line by line before submission.