fix: resolve command references per integration type (dot vs hyphen)#2354
Merged
mnriem merged 4 commits intogithub:mainfrom Apr 24, 2026
Merged
Conversation
Replace hardcoded /speckit.<cmd> references in templates with __SPECKIT_COMMAND_<NAME>__ placeholders that are resolved at setup time based on the integration type: - Markdown/TOML/YAML agents: separator='.' → /speckit.plan - Skills agents: separator='-' → /speckit-plan Changes: - Add resolve_command_refs() static method to IntegrationBase - Add invoke_separator class attribute (. for base, - for skills) - Wire into process_template() as step 8 - Update _install_shared_infra() to process page templates - Replace /speckit.* in 5 command templates and 3 page templates - Add unit tests for resolve_command_refs (positive + negative) - Add integration tests verifying on-disk content for all agents - Add end-to-end CLI tests for Claude (skills) and Copilot (markdown) Fixes github#2347
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes incorrect hardcoded /speckit.<cmd> references in generated templates for skills-based integrations by introducing __SPECKIT_COMMAND_<NAME>__ placeholders and resolving them to dot- or hyphen-style invocations during setup.
Changes:
- Added placeholder resolution (
__SPECKIT_COMMAND_<NAME>__) in template processing, parameterized by an integration-levelinvoke_separator(.vs-). - Updated core/page templates and command templates to use the new placeholders instead of hardcoded
/speckit.<cmd>strings. - Expanded test coverage to assert placeholders are fully resolved and that skills integrations don’t leak dot-notation invocations.
Show a summary per file
| File | Description |
|---|---|
src/specify_cli/integrations/base.py |
Adds invoke_separator, resolve_command_refs(), and integrates placeholder resolution into process_template(); sets SkillsIntegration.invoke_separator = "-". |
src/specify_cli/__init__.py |
Extends _install_shared_infra() to resolve placeholders in page templates and threads invoke_separator through init/install/switch/upgrade call sites. |
templates/plan-template.md |
Replaces hardcoded /speckit.plan and /speckit.tasks references with __SPECKIT_COMMAND_*__ placeholders. |
templates/tasks-template.md |
Replaces /speckit.tasks reference with a placeholder. |
templates/checklist-template.md |
Replaces /speckit.checklist references with placeholders. |
templates/commands/specify.md |
Swaps embedded /speckit.* references to placeholders for integration-specific resolution. |
templates/commands/clarify.md |
Replaces /speckit.* references with placeholders. |
templates/commands/implement.md |
Replaces /speckit.tasks reference with a placeholder. |
templates/commands/analyze.md |
Replaces /speckit.* references with placeholders. |
templates/commands/checklist.md |
Replaces /speckit.checklist reference with a placeholder. |
tests/integrations/test_base.py |
Adds unit tests for placeholder resolution and separator behavior. |
tests/integrations/test_cli.py |
Adds tests ensuring _install_shared_infra() and full init resolve page-template command refs correctly for dot vs hyphen integrations. |
tests/integrations/test_integration_base_markdown.py |
Asserts no unresolved __SPECKIT_COMMAND_ placeholders remain after setup. |
tests/integrations/test_integration_base_toml.py |
Asserts no unresolved __SPECKIT_COMMAND_ placeholders remain after setup. |
tests/integrations/test_integration_base_yaml.py |
Asserts no unresolved __SPECKIT_COMMAND_ placeholders remain after setup. |
tests/integrations/test_integration_base_skills.py |
Adds assertions for placeholder resolution and forbids dot-notation /speckit. in skills outputs. |
tests/integrations/test_integration_claude.py |
Ensures Claude skills output has no unresolved placeholders and no /speckit. dot-notation. |
tests/integrations/test_integration_copilot.py |
Asserts no unresolved __SPECKIT_COMMAND_ placeholders remain after setup. |
tests/integrations/test_integration_forge.py |
Asserts no unresolved __SPECKIT_COMMAND_ placeholders remain after setup. |
tests/integrations/test_integration_generic.py |
Asserts no unresolved __SPECKIT_COMMAND_ placeholders remain after setup. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comments suppressed due to low confidence (1)
src/specify_cli/init.py:2089
integration_installcalls_install_shared_infra()before parsing--integration-optionsand beforeintegration.setup(), butinvoke_separatorcan depend on those options (e.g.,copilot --skills). With the current ordering, page templates may be installed with the wrong command separator. Consider parsingintegration_optionsfirst (or runningsetup()first) to determine the effective separator, then pass that into_install_shared_infra().
selected_script = _resolve_script_type(project_root, script)
# Ensure shared infrastructure is present (safe to run unconditionally;
# _install_shared_infra merges missing files without overwriting).
_install_shared_infra(project_root, selected_script, invoke_separator=integration.invoke_separator)
if os.name != "nt":
ensure_executable_scripts(project_root)
- Files reviewed: 20/20 changed files
- Comments generated: 2
Address PR review feedback: instead of bleeding _skills_mode knowledge into the CLI layer, add effective_invoke_separator() method to IntegrationBase that accepts parsed_options. CopilotIntegration overrides it to return "-" when skills mode is requested. The CLI layer simply asks the integration for its separator — no hasattr or _skills_mode coupling. Also adds tests for the new method on both base and Copilot, plus an end-to-end test for 'specify init --integration copilot --integration-options --skills' verifying page templates get hyphen refs.
…mands
Previously rsplit('.', 1)[-1] on 'speckit.git.commit' yielded
just 'commit', producing /speckit.commit instead of
/speckit.git.commit (or /speckit-git-commit for skills).
Fix: strip only the 'speckit.' prefix when present, then join
remaining segments with the appropriate separator.
Updated in IntegrationBase, SkillsIntegration, and
CopilotIntegration. Added tests for extension commands in
build_command_invocation across all three.
Contributor
There was a problem hiding this comment.
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 21/21 changed files
- Comments generated: 1
dispatch_command() had the same rsplit('.', 1)[-1] bug as
build_command_invocation() — speckit.git.commit would dispatch
as /speckit-commit instead of /speckit-git-commit in skills
mode, or --agent speckit.commit instead of speckit.git.commit
in default mode.
Contributor
There was a problem hiding this comment.
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 21/21 changed files
- Comments generated: 0 new
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Fixes #2347 — templates and command files contained hardcoded
/speckit.<cmd>references that were incorrect for skills-based integrations (which use/speckit-<cmd>).Problem
After
specify init, page templates (plan-template.md,checklist-template.md,tasks-template.md) and command template bodies contained hardcoded/speckit.plan,/speckit.tasks, etc. These references are correct for markdown/TOML/YAML agents but wrong for skills agents (Claude, Codex, etc.) which use hyphen-separated names (/speckit-plan,/speckit-tasks).Solution
Introduced
__SPECKIT_COMMAND_<NAME>__placeholders that are resolved at setup time based on the integration type:invoke_separator=".") →/speckit.plan,/speckit.git.commitinvoke_separator="-") →/speckit-plan,/speckit-git-commitChanges
Core (
src/specify_cli/integrations/base.py):resolve_command_refs()static method using regex to replace__SPECKIT_COMMAND_<NAME>__placeholdersinvoke_separatorclass attribute ("."onIntegrationBase,"-"onSkillsIntegration)invoke_separatorparameter toprocess_template()(step 8)SkillsIntegration.setup()passes its separator toprocess_template()Infrastructure (
src/specify_cli/__init__.py):_install_shared_infra()now acceptsinvoke_separatorand processes page templates throughresolve_command_refs()instead of plainshutil.copy2()init,integration_install,integration_switch,integration_upgrade) pass the integration'sinvoke_separatorTemplates (8 files):
/speckit.<cmd>references with__SPECKIT_COMMAND_<CMD>__in:templates/commands/: specify.md, analyze.md, clarify.md, implement.md, checklist.mdtemplates/: plan-template.md, checklist-template.md, tasks-template.mdTests (10 files):
resolve_command_refs(): dot/hyphen separators, extension commands, multiple placeholders, malformed/partial placeholders, digitsspecify init --integration claude→/speckit-planin page templates;specify init --integration copilot→/speckit.plan__SPECKIT_COMMAND_assertion to all integration test files (base classes + custom: Claude, Copilot, Forge, Generic)Design notes
__SPECKIT_COMMAND_<NAME>__supports both core commands (__SPECKIT_COMMAND_PLAN__) and extension commands (__SPECKIT_COMMAND_GIT_COMMIT__) — underscores become the separatorworkflow.ymlcommand:fields are unchanged —build_command_invocation()already normalizes at dispatch timevscode-settings.jsonprompt file references are unchanged — they are Copilot-specific identifiers, not user-facing command invocationsTest results