Skip to content

fix: resolve command references per integration type (dot vs hyphen)#2354

Merged
mnriem merged 4 commits intogithub:mainfrom
mnriem:fix/issue-2347-command-ref-placeholders
Apr 24, 2026
Merged

fix: resolve command references per integration type (dot vs hyphen)#2354
mnriem merged 4 commits intogithub:mainfrom
mnriem:fix/issue-2347-command-ref-placeholders

Conversation

@mnriem
Copy link
Copy Markdown
Collaborator

@mnriem mnriem commented Apr 24, 2026

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:

  • Markdown/TOML/YAML agents (invoke_separator=".") → /speckit.plan, /speckit.git.commit
  • Skills agents (invoke_separator="-") → /speckit-plan, /speckit-git-commit

Changes

Core (src/specify_cli/integrations/base.py):

  • Added resolve_command_refs() static method using regex to replace __SPECKIT_COMMAND_<NAME>__ placeholders
  • Added invoke_separator class attribute ("." on IntegrationBase, "-" on SkillsIntegration)
  • Added invoke_separator parameter to process_template() (step 8)
  • SkillsIntegration.setup() passes its separator to process_template()

Infrastructure (src/specify_cli/__init__.py):

  • _install_shared_infra() now accepts invoke_separator and processes page templates through resolve_command_refs() instead of plain shutil.copy2()
  • All 4 callers (init, integration_install, integration_switch, integration_upgrade) pass the integration's invoke_separator

Templates (8 files):

  • Replaced all /speckit.<cmd> references with __SPECKIT_COMMAND_<CMD>__ in:
    • templates/commands/: specify.md, analyze.md, clarify.md, implement.md, checklist.md
    • templates/: plan-template.md, checklist-template.md, tasks-template.md

Tests (10 files):

  • Unit tests for resolve_command_refs(): dot/hyphen separators, extension commands, multiple placeholders, malformed/partial placeholders, digits
  • Integration tests: verified generated on-disk content for skills and markdown base classes
  • End-to-end CLI tests: specify init --integration claude/speckit-plan in page templates; specify init --integration copilot/speckit.plan
  • Added __SPECKIT_COMMAND_ assertion to all integration test files (base classes + custom: Claude, Copilot, Forge, Generic)

Design notes

  • The placeholder convention __SPECKIT_COMMAND_<NAME>__ supports both core commands (__SPECKIT_COMMAND_PLAN__) and extension commands (__SPECKIT_COMMAND_GIT_COMMIT__) — underscores become the separator
  • workflow.yml command: fields are unchangedbuild_command_invocation() already normalizes at dispatch time
  • vscode-settings.json prompt file references are unchanged — they are Copilot-specific identifiers, not user-facing command invocations

Test results

1701 passed, 18 warnings (pre-existing)

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
Copilot AI review requested due to automatic review settings April 24, 2026 13:36
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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-level invoke_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_install calls _install_shared_infra() before parsing --integration-options and before integration.setup(), but invoke_separator can depend on those options (e.g., copilot --skills). With the current ordering, page templates may be installed with the wrong command separator. Consider parsing integration_options first (or running setup() 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

Comment thread src/specify_cli/__init__.py Outdated
Comment thread src/specify_cli/integrations/base.py
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.
@mnriem mnriem requested review from Copilot and removed request for Copilot April 24, 2026 14:48
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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

Comment thread src/specify_cli/integrations/copilot/__init__.py
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.
@mnriem mnriem requested a review from Copilot April 24, 2026 14:59
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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

@mnriem mnriem merged commit 52c0a5f into github:main Apr 24, 2026
15 checks passed
@mnriem mnriem deleted the fix/issue-2347-command-ref-placeholders branch April 24, 2026 15:04
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.

v0.8.0 templates still reference dot-notation slash commands (/speckit.plan) while skills are hyphen-named (/speckit-plan)

2 participants