Fix extension skill command reference rendering - #4116
Conversation
There was a problem hiding this comment.
Pull request overview
Normalizes literal slash-dot command references in generated extension skills.
Changes:
- Adds integration-specific command rendering.
- Preserves native, URL, bare, and selected file-like references.
- Adds tests and author guidance.
Show a summary per file
| File | Description |
|---|---|
src/specify_cli/extensions/__init__.py |
Normalizes literal command references. |
tests/test_extension_skills.py |
Tests invocation styles and exclusions. |
extensions/EXTENSION-USER-GUIDE.md |
Documents portable command references. |
Review details
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Suppressed comments (1)
src/specify_cli/extensions/init.py:1628
- The right boundary both accepts a following
/and rejects every following., so/speckit.foo.bar/scripts/run.shis incorrectly rewritten even though it is path-like, while the ordinary sentenceRun /speckit.foo.bar.is not rewritten at all. Reject/as a path continuation, but treat.as a continuation only when another identifier segment follows.
r"(?![A-Za-z0-9_.-])"
- Files reviewed: 3/3 changed files
- Comments generated: 1
- Review effort level: Balanced
| if command_name.rsplit(".", 1)[-1] in { | ||
| "json", | ||
| "md", | ||
| "toml", | ||
| "txt", | ||
| "yaml", | ||
| "yml", | ||
| }: |
mnriem
left a comment
There was a problem hiding this comment.
Please address Copilot feedback
|
Updated to address the review feedback: command reference rewriting now uses the manifest-declared command names and aliases instead of suffix-based exclusions, with coverage for |
| selected_ai, frontmatter, body, self.project_root, extension_id=manifest.id | ||
| ) | ||
| body = _resolve_command_ref_tokens(body) | ||
| body = _normalize_literal_slash_command_refs(body) |
|
Please address Copilot feedback |
There was a problem hiding this comment.
Review details
Suppressed comments (3)
Previously missed (1) — in code that hasn't changed since the last review.
src/specify_cli/extensions/init.py:1615
- This fallback rendering path has the same incomplete whitelist: only the current manifest's names are eligible. A generated extension skill that references a core command such as
/speckit.tasks(seeextensions/template/commands/example.md:204) or another installed extension remains dotted and is still unusable for Codex/Kimi; the regex also excludes the single-suffix core form. Include core/installed commands and matchspeckit.<name>, or normalize all isolated slash-dot invocations.
This issue also appears on line 1630 of the same file.
known_command_names = {
cmd["name"]
for cmd in manifest.commands
if isinstance(cmd.get("name"), str)
}
src/specify_cli/extensions/init.py:1635
- This regex still rewrites known commands embedded in URL query/fragment values. For example,
https://example.test/redirect?next=/speckit.foo.barpasses the lookbehind at=and becomes an invalid URL, contradicting the stated URL-preservation scope. Detect and skip complete URL tokens (including query and fragment content) before replacing command references.
return re.sub(
(
r"(?<![\w$:/-])"
r"/(?P<command>speckit\.[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)+)"
r"(?!/)"
),
src/specify_cli/agents.py:718
- The boundary check does not actually preserve all URLs as promised: in
https://example.test/redirect?next=/speckit.foo.bar, the slash is preceded by=, so a known command is rewritten and the URL is corrupted (for example tonext=$speckit-foo-bar). Exclude matches within complete URL tokens rather than relying only on the immediately preceding character.
return re.sub(
(
r"(?<![\w$:/-])"
r"/(?P<command>speckit\.[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)+)"
r"(?!/)"
),
- Files reviewed: 4/4 changed files
- Comments generated: 1
- Review effort level: Balanced
| known_command_names = { | ||
| command["name"] | ||
| for command in commands | ||
| if isinstance(command.get("name"), str) | ||
| } |
|
Please address Copilot feedback |
Description
Normalizes literal slash-dot command references when extension commands are rendered into generated
SKILL.mdfiles for skills-based integrations.This keeps the existing
__SPECKIT_COMMAND_*__token path intact, and adds coverage for literal references such as/speckit.foo.barso generated extension skills use the active integration's invocation style ($speckit-foo-bar,/speckit-foo-bar, or/skill:speckit-foo-bar).Scope is limited to generated extension skills. Bare prose, native skill references, URLs, and file-like references are preserved.
Fixes #3451.
Testing
.venv/bin/python -m pytest tests/test_extension_skills.py -q.venv/bin/python -m pytest tests/test_agent_config_consistency.py -qUV_CACHE_DIR=/tmp/spec-kit-uv-cache uvx ruff@0.15.0 check src testsgit diff --check -- src/specify_cli/extensions/__init__.py tests/test_extension_skills.py extensions/EXTENSION-USER-GUIDE.md