Skip to content

Commit 735fe0c

Browse files
jawwad-aliclaude
andauthored
fix(extensions): render hyphenated hook invocations for Forge projects (#3641)
Forge is a hyphenated slash-command agent: it registers its commands as `/speckit-<name>` (see `format_forge_command_name` and `ForgeIntegration.build_command_invocation`), exactly like Cline. `HookExecutor._render_hook_invocation` special-cases dollar-skills agents, kimi, cline, and slash-skills agents, but had no Forge branch. Forge matches none of those, so it fell through to `return f"/{command_id}"` and rendered the DOTTED form — `/speckit.plan`, `/speckit.git.commit` — which Forge does not recognize as a registered command. Add a Forge branch mirroring the adjacent Cline branch, using `format_forge_command_name` (idempotent, same contract as the Cline formatter). Non-Forge agents are unaffected. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 0add713 commit 735fe0c

2 files changed

Lines changed: 41 additions & 0 deletions

File tree

src/specify_cli/extensions/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3608,6 +3608,7 @@ def _render_hook_invocation(self, command: Any) -> str:
36083608
dollar_skill_mode = is_dollar_skills_agent(selected_ai, ai_skills_enabled)
36093609
kimi_skill_mode = selected_ai == "kimi"
36103610
cline_mode = selected_ai == "cline"
3611+
forge_mode = selected_ai == "forge"
36113612

36123613
skill_name = self._skill_name_from_command(command_id)
36133614
if dollar_skill_mode and skill_name:
@@ -3618,6 +3619,10 @@ def _render_hook_invocation(self, command: Any) -> str:
36183619
from ..integrations.cline import format_cline_command_name
36193620

36203621
return f"/{format_cline_command_name(command_id)}"
3622+
if forge_mode:
3623+
from ..integrations.forge import format_forge_command_name
3624+
3625+
return f"/{format_forge_command_name(command_id)}"
36213626

36223627
use_slash = is_slash_skills_agent(selected_ai, ai_skills_enabled)
36233628

tests/test_extensions.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8238,6 +8238,42 @@ def test_cline_hooks_render_extension_command(self, project_dir):
82388238
assert execution["command"] == "my-extension.do-something"
82398239
assert execution["invocation"] == "/speckit-my-extension-do-something"
82408240

8241+
def test_forge_hooks_render_hyphenated_invocation(self, project_dir):
8242+
"""Forge projects should render /speckit-* invocations (like Cline)."""
8243+
init_options = project_dir / ".specify" / "init-options.json"
8244+
init_options.parent.mkdir(parents=True, exist_ok=True)
8245+
init_options.write_text(json.dumps({"ai": "forge"}))
8246+
8247+
hook_executor = HookExecutor(project_dir)
8248+
execution = hook_executor.execute_hook(
8249+
{
8250+
"extension": "test-ext",
8251+
"command": "speckit.tasks",
8252+
"optional": False,
8253+
}
8254+
)
8255+
8256+
assert execution["command"] == "speckit.tasks"
8257+
assert execution["invocation"] == "/speckit-tasks"
8258+
8259+
def test_forge_hooks_render_extension_command(self, project_dir):
8260+
"""Forge projects should render /speckit-my-ext-cmd for extension hooks."""
8261+
init_options = project_dir / ".specify" / "init-options.json"
8262+
init_options.parent.mkdir(parents=True, exist_ok=True)
8263+
init_options.write_text(json.dumps({"ai": "forge"}))
8264+
8265+
hook_executor = HookExecutor(project_dir)
8266+
execution = hook_executor.execute_hook(
8267+
{
8268+
"extension": "test-ext",
8269+
"command": "my-extension.do-something",
8270+
"optional": False,
8271+
}
8272+
)
8273+
8274+
assert execution["command"] == "my-extension.do-something"
8275+
assert execution["invocation"] == "/speckit-my-extension-do-something"
8276+
82418277
def test_non_skill_command_keeps_slash_invocation(self, project_dir):
82428278
"""Custom hook commands should keep slash invocation style."""
82438279
init_options = project_dir / ".specify" / "init-options.json"

0 commit comments

Comments
 (0)