Skip to content

fix: resolve __SPECKIT_COMMAND_*__ refs in preset skill rendering (#2717)#2718

Merged
mnriem merged 2 commits into
github:mainfrom
huy1010:fix/2717-skill-command-refs
May 27, 2026
Merged

fix: resolve __SPECKIT_COMMAND_*__ refs in preset skill rendering (#2717)#2718
mnriem merged 2 commits into
github:mainfrom
huy1010:fix/2717-skill-command-refs

Conversation

@huy1010
Copy link
Copy Markdown
Contributor

@huy1010 huy1010 commented May 27, 2026

Description

Presets that override core commands under the agent skill layer (e.g. Claude with --ai-skills) were leaking raw __SPECKIT_COMMAND_*__ placeholders into the generated SKILL.md instead of rendering them as slash-command invocations like /speckit-plan.

Root cause: the preset skill-rendering paths in src/specify_cli/presets.py ran only resolve_skill_placeholders(), whereas the command layer (CommandRegistrar.register_commands()) additionally calls IntegrationBase.resolve_command_refs(). The skill paths never resolved the command-reference tokens — {ARGS}$ARGUMENTS resolved correctly, so only the command tokens leaked, matching the report.

Fixes #2717. This is a concrete instance of the path divergence tracked in #1976.

Change: added a small shared helper PresetManager._resolve_skill_command_refs() that maps the agent's invoke separator to resolve_command_refs(), and called it right after resolve_skill_placeholders() in every preset skill-rendering path:

  • _register_skills() — preset install (the path reported in the issue)
  • _reconcile_skills() — override-restoration block
  • _unregister_skills() — both restore paths (core + extension templates)

The issue's proposed patch covered only _register_skills(); I extended the same one-line fix to the sibling restore/reconcile paths because they share the identical omission and would otherwise still leak tokens on preset removal / reconciliation. extensions.py is intentionally left out of scope (separate concern).

Testing

  • Tested locally with uv run specify --help
  • Ran existing tests with uv sync && uv run pytest2988 passed, 35 skipped
  • Tested with a sample project (Claude --ai-skills: preset add --dev + preset remove)

⚠️ Note for reviewers: uv run specify served a stale cached wheel (project version 0.8.15.dev0 unchanged → uv build-cache hit), so it kept showing the pre-fix behavior even after uv pip install -e .. I verified working-tree behavior by invoking .venv/bin/specify directly. Worth flagging since the documented uv run specify flow can silently exercise old code when the dev version string is unchanged.

Manual test results

Agent: Claude Code (CLI) | OS/Shell: macOS / zsh

Repro: scaffold a Claude --ai-skills project, install a local preset overriding speckit.specify whose body references __SPECKIT_COMMAND_PLAN__ / __SPECKIT_COMMAND_TASKS__, then remove it (restoring from a core template that also carries the tokens). Inspected .claude/skills/speckit-specify/SKILL.md.

Command tested Notes
specify init --integration claude Skills installed to .claude/skills; speckit-specify present
specify preset add --dev <preset> SKILL.md renders /speckit-plan, /speckit-tasks; 0 raw __SPECKIT_COMMAND_*__ (was 2 before the fix)
specify preset remove <preset> Restored SKILL.md renders /speckit-plan; 0 raw tokens (was 1 before the fix)

Test selection reasoning

Changed file Affects Test Why
src/specify_cli/presets.py specify preset add / remove, preset skill overrides T1–T3 Rule: src/specify_cli/*.py → CLI preset commands; preset command overrides mirror into the skill layer

Required tests (prerequisites first):

  • T1: specify init --integration claude — scaffold the skills layer (prerequisite for T2/T3)
  • T2: specify preset add --dev — install path (_register_skills)
  • T3: specify preset remove — restore path (_unregister_skills); requires T2

New automated regression tests added in tests/test_presets.py: test_register_skills_resolves_command_refs (install) and test_restore_skill_resolves_command_refs (restore). Both fail on main and pass with the fix.

AI Disclosure

  • I did use AI assistance (describe below)

This PR was developed with Claude Code (Anthropic): issue analysis, the patch, and the regression tests were AI-generated. The full pytest suite and the manual CLI install/remove cycle reported above were run as part of development, and the change was reviewed (including a follow-up pass to remove a lint-suppression # noqa in favor of a top-level import and to make the helper docstring describe behavior) before submission.

…thub#2717)

The preset skill layer mirrors command templates into SKILL.md files but
only ran resolve_skill_placeholders(), leaving command cross-references as
raw __SPECKIT_COMMAND_<NAME>__ placeholders instead of rendering them as
/speckit-<cmd> the way CommandRegistrar.register_commands() does. As a
result, presets that override core commands under the agent skill layer
(e.g. Claude --ai-skills) leaked the raw tokens into SKILL.md.

Add a shared PresetManager._resolve_skill_command_refs() helper that maps
the agent's invoke separator to IntegrationBase.resolve_command_refs(), and
call it right after resolve_skill_placeholders() in every preset
skill-rendering path: _register_skills() (install), the _reconcile_skills()
override-restoration block, and both _unregister_skills() restore paths.
This mirrors register_commands() and addresses the path divergence flagged
in github#1976.

Add regression tests covering the install and restore paths.

AI assistance: authored with Claude Code (Anthropic) — analysis, patch, and
tests. Verified via the existing pytest suite plus a manual CLI install and
remove cycle on a Claude --ai-skills project.
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 preset skill rendering so __SPECKIT_COMMAND_*__ placeholders are resolved to agent-specific slash-command invocations when preset command overrides are mirrored/restored into skill files.

Changes:

  • Adds a shared preset helper to resolve command-reference tokens using the selected agent’s invoke separator.
  • Applies the helper across preset skill install, reconcile, and removal/restore paths.
  • Adds regression tests for install and core-template restore behavior.
Show a summary per file
File Description
src/specify_cli/presets.py Resolves command-reference placeholders after skill placeholder rendering in preset skill paths.
tests/test_presets.py Adds regression tests for command-ref resolution during preset skill install and core restore.

Copilot's findings

Tip

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

  • Files reviewed: 2/2 changed files
  • Comments generated: 2

Comment thread src/specify_cli/presets.py
Comment thread src/specify_cli/presets.py
github#2718 review)

Copilot review flagged that the install and core-template restore paths
gained regression tests, but the reconcile project-override branch and the
extension-backed restore branch were uncovered. Add focused tests for both:

- test_reconcile_override_skill_resolves_command_refs: a project override
  wins after preset removal; _reconcile_skills must render command refs.
- test_extension_restore_resolves_command_refs: a skill restored from an
  extension command body must also render command refs.

Both fail on main and pass with the fix in 8dd93c0.
@huy1010
Copy link
Copy Markdown
Contributor Author

huy1010 commented May 27, 2026

Hi @mnriem, I consolidated the test following the agent's comments. Hopefully, all is good to go

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

  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new

@mnriem mnriem merged commit 66884db into github:main May 27, 2026
11 checks passed
@mnriem
Copy link
Copy Markdown
Collaborator

mnriem commented May 27, 2026

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.

[Bug]: Preset _register_skills() leaks raw __SPECKIT_COMMAND_*__ placeholders into SKILL.md

3 participants