Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,14 @@ class KilocodeIntegration(MarkdownIntegration):
key = "kilocode"
config = {
"name": "Kilo Code",
"folder": ".kilocode/",
"commands_subdir": "workflows",
"folder": ".kilo/",
"commands_subdir": "commands",
"install_url": None,
"requires_cli": False,
}
registrar_config = {
"dir": ".kilocode/workflows",
"dir": ".kilo/commands",
"legacy_dir": ".kilocode/workflows",
"format": "markdown",
"args": "$ARGUMENTS",
"extension": ".md",
Expand Down Expand Up @@ -201,8 +202,8 @@ Only add custom setup logic when the agent needs non-standard behavior. Integrat
specify init my-project --integration <key>

# Verify files were created in the commands directory configured by
# config["folder"] + config["commands_subdir"] (for example, .kilocode/workflows/)
ls -R my-project/.kilocode/workflows/
# config["folder"] + config["commands_subdir"] (for example, .kilo/commands/)
ls -R my-project/.kilo/commands/

# Uninstall cleanly
cd my-project && specify integration uninstall <key>
Expand Down
4 changes: 2 additions & 2 deletions docs/reference/integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ The Specify CLI supports a wide range of AI coding agents. When you run `specify
| [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` | Skills-based integration by default; installs skills as `speckit-<command>/SKILL.md` under `.bob/skills/` and invokes them as `/speckit-<command>`. Pass `--integration-options="--legacy-commands"` to scaffold the deprecated Bob 1.x layout (`.bob/commands/*.md`) instead; that flag will be removed in a future release. Existing legacy installs can migrate with `specify integration upgrade bob --integration-options="--skills"`, which converts them to the skills layout and removes the old command files. If preset overrides are installed, the migration is rejected with an actionable error (preset artifacts cannot yet be reconciled across a layout change) — remove the preset(s), migrate, then reinstall them. |
| [Junie](https://junie.jetbrains.com/) | `junie` | |
| [Kilo Code](https://github.com/Kilo-Org/kilocode) | `kilocode` | |
| [Kilo Code](https://github.com/Kilo-Org/kilocode) | `kilocode` | Installs commands into `.kilo/commands`; legacy `.kilocode/workflows` installs remain supported as a registration fallback |
| [Kimi Code](https://code.kimi.com/) | `kimi` | Skills-based integration; installs into `.kimi-code/skills/`. `--migrate-legacy` moves old `.kimi/skills/` installs to the new paths |
| [Kiro CLI](https://kiro.dev/docs/cli/) | `kiro-cli` | Kiro CLI does not substitute `$ARGUMENTS` in file-based prompts, so Spec Kit ships a prose fallback at render time (see [Manage prompts](https://kiro.dev/docs/cli/chat/manage-prompts/) and issue [#1926](https://github.com/github/spec-kit/issues/1926)). Alias: `--integration kiro` |
| [Lingma](https://lingma.aliyun.com/) | `lingma` | Skills-based integration; skills are installed automatically |
Expand Down Expand Up @@ -275,7 +275,7 @@ The currently declared multi-install safe integrations are:
| `gemini` | `.gemini/commands`, `GEMINI.md` |
| `grok` | `.grok/skills` |
| `junie` | `.junie/commands`, `.junie/AGENTS.md` |
| `kilocode` | `.kilocode/workflows`, `.kilocode/rules/specify-rules.md` |
| `kilocode` | `.kilo/commands`, `.kilocode/rules/specify-rules.md` |
| `qodercli` | `.qoder/commands`, `QODER.md` |
| `qwen` | `.qwen/commands`, `QWEN.md` |
| `shai` | `.shai/commands`, `SHAI.md` |
Expand Down
6 changes: 3 additions & 3 deletions docs/upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ Some IDE-based agents (like Kilo Code, Cline) may show **duplicate slash command

```bash
# Navigate to the agent's commands folder
cd .kilocode/workflows/
cd .kilo/commands/

# List files and identify duplicates
ls -la
Expand Down Expand Up @@ -248,8 +248,8 @@ specify extension update
This happens with IDE-based agents (Kilo Code, Cline, etc.).

```bash
# Find the agent folder (example: .kilocode/workflows/)
cd .kilocode/workflows/
# Find the agent folder (example: .kilo/commands/)
cd .kilo/commands/

# List all files
ls -la
Expand Down
7 changes: 4 additions & 3 deletions src/specify_cli/integrations/kilocode/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ class KilocodeIntegration(MarkdownIntegration):
key = "kilocode"
config = {
"name": "Kilo Code",
"folder": ".kilocode/",
"commands_subdir": "workflows",
"folder": ".kilo/",
"commands_subdir": "commands",
"install_url": None,
"requires_cli": False,
}
registrar_config = {
"dir": ".kilocode/workflows",
"dir": ".kilo/commands",
"legacy_dir": ".kilocode/workflows",
Comment on lines +16 to +17
"format": "markdown",
"args": "$ARGUMENTS",
"extension": ".md",
Expand Down
7 changes: 6 additions & 1 deletion tests/integrations/test_integration_forge.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,12 @@ def test_registrar_does_not_affect_other_agents(self, tmp_path):

# Kilo Code uses standard markdown format without name injection.
# The format_name callback should not be invoked for non-Forge agents.
kilocode_cmd = tmp_path / ".kilocode" / "workflows" / "speckit.my-extension.example.md"
kilocode_cmd = (
tmp_path
/ ".kilo"
/ "commands"
/ "speckit.my-extension.example.md"
)
assert kilocode_cmd.exists()

content = kilocode_cmd.read_text(encoding="utf-8")
Expand Down
72 changes: 69 additions & 3 deletions tests/integrations/test_integration_kilocode.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,76 @@
"""Tests for KilocodeIntegration."""

from specify_cli.agents import CommandRegistrar
from specify_cli.integrations import get_integration

from .test_integration_base_markdown import MarkdownIntegrationTests


class TestKilocodeIntegration(MarkdownIntegrationTests):
KEY = "kilocode"
FOLDER = ".kilocode/"
COMMANDS_SUBDIR = "workflows"
REGISTRAR_DIR = ".kilocode/workflows"
FOLDER = ".kilo/"
COMMANDS_SUBDIR = "commands"
REGISTRAR_DIR = ".kilo/commands"

def test_registrar_config_has_legacy_dir(self):
integration = get_integration(self.KEY)
assert integration.registrar_config["legacy_dir"] == ".kilocode/workflows"

def test_legacy_dir_extension_registration(self, tmp_path):
"""Extension commands still register into legacy Kilo projects."""
legacy_dir = tmp_path / ".kilocode" / "workflows"
legacy_dir.mkdir(parents=True)
(legacy_dir / "speckit.specify.md").write_text(
"# existing", encoding="utf-8"
)

src_dir = tmp_path / "_ext_src"
src_dir.mkdir()
(src_dir / "myext.md").write_text(
"---\ndescription: test\n---\n# ext command",
encoding="utf-8",
)

registrar = CommandRegistrar()
commands = [{"name": "speckit.myext", "file": "myext.md"}]
results = registrar.register_commands(
self.KEY,
commands,
"test-ext",
src_dir,
tmp_path,
)

assert results == ["speckit.myext"]
assert (legacy_dir / "speckit.myext.md").exists()
assert not (tmp_path / ".kilo" / "commands").exists()

def test_legacy_dir_extension_unregister(self, tmp_path):
"""Unregister removes commands from legacy Kilo projects."""
legacy_dir = tmp_path / ".kilocode" / "workflows"
legacy_dir.mkdir(parents=True)
cmd_file = legacy_dir / "speckit.myext.md"
cmd_file.write_text("# ext command", encoding="utf-8")

registrar = CommandRegistrar()
registrar.unregister_commands({"kilocode": ["speckit.myext"]}, tmp_path)

assert not cmd_file.exists()

def test_unregister_cleans_legacy_when_both_dirs_exist(self, tmp_path):
"""Unregister removes stale legacy files after Kilo path migration."""
canonical_dir = tmp_path / ".kilo" / "commands"
canonical_dir.mkdir(parents=True)
legacy_dir = tmp_path / ".kilocode" / "workflows"
legacy_dir.mkdir(parents=True)

canonical_cmd = canonical_dir / "speckit.myext.md"
canonical_cmd.write_text("# ext command", encoding="utf-8")
legacy_cmd = legacy_dir / "speckit.myext.md"
legacy_cmd.write_text("# stale ext command", encoding="utf-8")

registrar = CommandRegistrar()
registrar.unregister_commands({"kilocode": ["speckit.myext"]}, tmp_path)

assert not canonical_cmd.exists()
assert not legacy_cmd.exists()