Skip to content

fix(extensions): list commands the way the active agent registers them - #4964

Merged
mnriem merged 6 commits into
github:mainfrom
tkv00:fix/extension-listing-command-names
Oct 15, 2026
Merged

mnriem merged 6 commits into
github:mainfrom
tkv00:fix/extension-listing-command-names

Conversation

@tkv00

@tkv00 tkv00 commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Description

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 speckit.demo.hello <adapter folder>/skills/speckit-demo-hello/SKILL.md

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.

tkv00 and others added 2 commits October 15, 2026 09:16
`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>

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.

🟡 Changes recommended

Generic skills projects still receive dotted command names that do not match their registered hyphenated skills.

1 open finding
What changed in this PR

Aligns Specify CLI extension command listings with the active integration’s registered names.

Changes:

  • Replaces agent-specific branches with a shared naming helper.
  • Adds listing regression tests and a registry naming check.
  • Documents hyphenated command names.

Validation used isolated source-function checks; project tests were not run.

AI-generated review and suggestions by GitHub Copilot (model: unknown, autonomous) for the requester.

File Description
tests/​specify_cli/​extensions/​test_command_info.py Tests info-listing names.
tests/​specify_cli/​extensions/​test_command_add.py Tests post-install names.
tests/​specify_cli/​extensions/​_helpers.py Shares agent and mode expectations.
tests/​integrations/​test_registry.py Checks separator-to-registration consistency.
src/​specify_cli/​extensions/​command_info.py Uses shared name formatting.
src/​specify_cli/​extensions/​command_add.py Uses shared name formatting.
src/​specify_cli/​extensions/​_commands.py Adds integration-aware naming helper.
extensions/​EXTENSION-USER-GUIDE.md Explains hyphenated listings.

🧠 Review effort: Max


💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/specify_cli/extensions/_commands.py Outdated
@mnriem

mnriem commented Oct 15, 2026

Copy link
Copy Markdown
Collaborator

Please address Copilot feedback

tkv00 and others added 2 commits October 16, 2026 00:16
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>
@tkv00

tkv00 commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor Author

@mnriem Fixed in 7dd86aa and a93af0b.

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.

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.

🔵 Needs a closer look

Unloaded external skills adapters still cause command listings to show dotted names instead of registered skill names.

0 open findings

1 resolved since last review
Previously missed (1)

In code that hasn't changed since last review

Medium severity 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.

🧠 Review effort: Max

@mnriem

mnriem commented Oct 15, 2026

Copy link
Copy Markdown
Collaborator

Please address Copilot feedback, specifically the previously missed. If not applicable, please explain why

tkv00 and others added 2 commits October 16, 2026 01:13
`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>
@tkv00

tkv00 commented Oct 15, 2026

Copy link
Copy Markdown
Contributor Author

@mnriem Fixed the previously missed one in 5d7a45e and 635e520.

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.

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.

🟢 Approval recommended

The display-only change has focused regression coverage and no unresolved blocking issues.

0 open findings

🧠 Review effort: Max

@mnriem
mnriem merged commit 1beafe5 into github:main Oct 15, 2026
16 checks passed
@mnriem

mnriem commented Oct 15, 2026

Copy link
Copy Markdown
Collaborator

Thank you!

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.

3 participants