Skip to content

Commit eeaf85f

Browse files
mnriemCopilot
andcommitted
fix(bob): scope persisted ai_skills flag to active agent when resolving command-ref separator (review #3415)
`register_commands` runs once per detected agent, but the persisted `ai_skills` flag describes only the active integration (`opts["ai"]`). When another agent (e.g. Copilot) is active in skills mode while a legacy `.bob/commands` layout is also present, the previous code passed that global `True` to Bob's `invoke_separator_for_mode`, rewriting Bob 1.x command refs to `/speckit-*` instead of `/speckit.*`. Only consult the persisted flag for the agent it describes (`opts["ai"] == agent_name`); otherwise resolve the separator from the agent's own project-aware `effective_invoke_separator(None, project_root)`. Add regression tests covering the mismatched-active-agent case and a control for Bob-active skills mode. Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 63f93544-a77f-4f01-bf04-c88806a97dbf
1 parent 8a162b3 commit eeaf85f

2 files changed

Lines changed: 87 additions & 3 deletions

File tree

src/specify_cli/agents.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -648,9 +648,21 @@ def register_commands(
648648

649649
_integ = get_integration(agent_name)
650650
if _integ is not None:
651-
_sep = _integ.invoke_separator_for_mode(
652-
is_ai_skills_enabled(load_init_options(project_root))
653-
)
651+
_opts = load_init_options(project_root)
652+
# The persisted ``ai_skills`` flag describes only the active
653+
# integration (``opts["ai"]``). ``register_commands_for_all_agents``
654+
# calls this for every detected agent, so trusting that flag for a
655+
# different agent would, e.g., render a legacy ``.bob/commands``
656+
# project's refs as ``/speckit-*`` just because Copilot is active in
657+
# skills mode. Only consult the flag for the agent it describes;
658+
# otherwise resolve this agent's separator from its own project-aware
659+
# detection.
660+
if _opts.get("ai") == agent_name:
661+
_sep = _integ.invoke_separator_for_mode(
662+
is_ai_skills_enabled(_opts)
663+
)
664+
else:
665+
_sep = _integ.effective_invoke_separator(None, project_root)
654666
except Exception:
655667
pass
656668

tests/integrations/test_integration_bob.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,3 +622,75 @@ def test_use_force_keeps_legacy_command_refs_in_shared_templates(self, tmp_path)
622622
# And the persisted separator must reflect the legacy layout.
623623
data = json.loads(integ_json.read_text(encoding="utf-8"))
624624
assert data["integration_settings"]["bob"].get("invoke_separator") == "."
625+
626+
627+
class TestBobCommandRefScopedToActiveAgent:
628+
"""Regression (review #3415, 4716424313).
629+
630+
``CommandRegistrar.register_commands`` runs once per detected agent, but the
631+
persisted ``ai_skills`` flag describes only the *active* integration
632+
(``opts["ai"]``). When another agent (e.g. Copilot) is active in skills
633+
mode while a legacy ``.bob/commands`` layout is also present, Bob's command
634+
references must still render with the ``.`` separator (Bob 1.x
635+
``/speckit.<cmd>``) rather than inheriting Copilot's ``ai_skills=True`` and
636+
rendering ``/speckit-<cmd>``.
637+
"""
638+
639+
def _write_command_ref_ext(self, source_dir):
640+
source_dir.mkdir(parents=True, exist_ok=True)
641+
cmd = source_dir / "run.md"
642+
cmd.write_text(
643+
"---\ndescription: Run\n---\n\nUse __SPECKIT_COMMAND_PLAN__ first.\n",
644+
encoding="utf-8",
645+
)
646+
return [{"name": "speckit.ext.run", "file": "run.md"}]
647+
648+
def test_legacy_bob_ref_not_rewritten_when_other_agent_active_in_skills(
649+
self, tmp_path
650+
):
651+
from specify_cli._init_options import save_init_options
652+
from specify_cli.agents import CommandRegistrar
653+
654+
# Legacy Bob layout on disk; skills layout absent.
655+
(tmp_path / ".bob" / "commands").mkdir(parents=True)
656+
# A different agent (Copilot) is the active integration, in skills mode.
657+
save_init_options(tmp_path, {"ai": "copilot", "ai_skills": True})
658+
659+
source_dir = tmp_path / "ext-src"
660+
commands = self._write_command_ref_ext(source_dir)
661+
662+
registrar = CommandRegistrar()
663+
registered = registrar.register_commands(
664+
"bob", commands, "ext", source_dir, tmp_path,
665+
)
666+
assert "speckit.ext.run" in registered
667+
668+
written = list((tmp_path / ".bob" / "commands").glob("*.md"))
669+
assert written, "expected a rendered Bob command file"
670+
content = written[0].read_text(encoding="utf-8")
671+
assert "__SPECKIT_COMMAND_PLAN__" not in content
672+
assert "/speckit.plan" in content, (
673+
"legacy Bob command refs must use the dot separator even when "
674+
"another agent is active in skills mode"
675+
)
676+
assert "/speckit-plan" not in content
677+
678+
def test_active_bob_skills_still_uses_hyphen(self, tmp_path):
679+
"""Control: when Bob itself is the active skills agent, refs use ``-``."""
680+
from specify_cli._init_options import save_init_options
681+
from specify_cli.agents import CommandRegistrar
682+
683+
(tmp_path / ".bob" / "skills").mkdir(parents=True)
684+
save_init_options(tmp_path, {"ai": "bob", "ai_skills": True})
685+
686+
source_dir = tmp_path / "ext-src"
687+
commands = self._write_command_ref_ext(source_dir)
688+
689+
registrar = CommandRegistrar()
690+
registrar.register_commands("bob", commands, "ext", source_dir, tmp_path)
691+
692+
written = list((tmp_path / ".bob" / "commands").glob("*.md"))
693+
assert written, "expected a rendered Bob command file"
694+
content = written[0].read_text(encoding="utf-8")
695+
assert "/speckit-plan" in content
696+
assert "/speckit.plan" not in content

0 commit comments

Comments
 (0)