Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
88c9c13
fix(integrations): dispatch kiro-cli through chat --no-interactive
kartsan03 Sep 30, 2026
86afed7
fix(integrations): install kiro-cli prompts with hyphenated names
kartsan03 Sep 30, 2026
cb898ce
test(integrations): restore the kiro prompt as bytes in the upgrade test
kartsan03 Sep 30, 2026
270f740
fix(integrations): remove dotted kiro extension prompts on upgrade
kartsan03 Sep 30, 2026
569a1bd
fix(integrations): only treat real renames as a Kiro filename migration
kartsan03 Sep 30, 2026
268a908
fix(integrations): keep disabled preset prompts and SKILL.md paths ou…
kartsan03 Sep 30, 2026
cc91560
fix(integrations): retire dotted Kiro prompts only after their replac…
kartsan03 Sep 30, 2026
f53b21f
fix(integrations): refuse the Kiro prompt rename while presets are in…
kartsan03 Sep 30, 2026
ec15335
fix(integrations): stop treating the Kiro IDE launcher as Kiro CLI
kartsan03 Oct 1, 2026
ed42004
test(workflows): pin the zip entry time in catalog release archives
kartsan03 Oct 1, 2026
a5334ea
Merge branch 'main' into fix/kiro-cli-dispatch
kartsan03 Oct 2, 2026
234bbd2
fix(integrations): retire dotted Kiro extension prompts on every regi…
kartsan03 Oct 2, 2026
3bcee7e
fix(integrations): keep Kiro prompts tracked when a command source is…
kartsan03 Oct 3, 2026
62732e8
fix(integrations): keep Kiro prompts that hyphenate to one file
kartsan03 Oct 5, 2026
7034726
Merge branch 'main' into fix/kiro-cli-dispatch
kartsan03 Oct 6, 2026
8570f0a
fix(integrations): skip Kiro prompts that share a file instead of rew…
kartsan03 Oct 6, 2026
fdb8d75
fix(extensions): count unreadable owners at install, remove skipped Q…
kartsan03 Oct 6, 2026
a80126f
fix(extensions): preserve shared migrated commands during removal
mnriem Oct 7, 2026
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: 3 additions & 8 deletions .devcontainer/post-create.sh
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,12 @@ run_command "echo \"$KIRO_INSTALLER_SHA256 $KIRO_INSTALLER_PATH\" | sha256sum -

run_command "bash \"$KIRO_INSTALLER_PATH\""

kiro_binary=""
if command -v kiro-cli >/dev/null 2>&1; then
kiro_binary="kiro-cli"
elif command -v kiro >/dev/null 2>&1; then
kiro_binary="kiro"
else
echo -e "\033[0;31m[ERROR] Kiro CLI installation did not create 'kiro-cli' or 'kiro' in PATH.\033[0m" >&2
if ! command -v kiro-cli >/dev/null 2>&1; then
echo -e "\033[0;31m[ERROR] Kiro CLI installation did not create 'kiro-cli' in PATH.\033[0m" >&2
exit 1
fi

run_command "$kiro_binary --help > /dev/null"
run_command "kiro-cli --help > /dev/null"
echo "✅ Done"

echo -e "\n🤖 Installing Kimi Code CLI..."
Expand Down
7 changes: 7 additions & 0 deletions docs/reference/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ specify extension add <name>

Installs an extension from the catalog, a URL, or a local directory. Extension commands are registered with the active AI coding agent integration. For `generic`, invocations use the configured `--commands-dir`: flat command files by default, or `speckit-<name>/SKILL.md` with `--skills`. The core `speckit.taskstoissues` command remains available alongside the GitHub extension's namespaced replacement during migration.

Installation is refused when a command or alias would write the same file as a core command or another installed extension's command once dots become hyphens, as Cline, Forge, Junie, Kiro CLI and skills-based integrations name their files (for example `speckit.foo.bar-baz` and `speckit.foo-bar.baz`). An extension installed before this check that shares such a file is not registered for Kiro CLI or Qoder CLI, and keeps its existing files, until it is updated or removed, or the extension it shares the file with is removed.

If a generic integration refresh cannot produce every extension invocation (for example, because a command or skill is user-modified or its source is missing), it warns and restores that extension's prior registered artifacts. Other extensions can still refresh.

An unqualified catalog install still selects the advertised current version.
Expand All @@ -56,6 +58,11 @@ specify extension remove <name>

Removes an installed extension. Configuration files are backed up by default; use `--keep-config` to leave them in place or `--force` to skip the confirmation.

For older Kiro CLI or Qoder CLI extensions whose names collide after hyphenation,
removal deletes the extension's legacy commands but preserves a shared migrated
prompt or skill unless its source marker identifies the removed extension as
the owner. Installed core commands are also preserved.

## List Installed Extensions

```bash
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ The Specify CLI supports a wide range of AI coding agents. When you run `specify
| [Junie](https://junie.jetbrains.com/) | `junie` | |
| [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` |
| [Kiro CLI](https://kiro.dev/docs/cli/) | `kiro-cli` | Installs prompts as `.kiro/prompts/speckit-<command>.md` and invokes them as `/speckit-<command>`, because Kiro CLI does not run slash commands whose names contain dots. Headless workflow dispatch runs `kiro-cli chat --no-interactive --trust-all-tools`, which auto-approves every tool call: without it, Kiro denies all file writes in non-interactive mode and still exits 0. Spec Kit looks for the `kiro-cli` executable only, because a bare `kiro` launches Kiro IDE by default. 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)). `specify integration upgrade kiro-cli` renames existing prompts, and refuses before changing files while presets have commands registered for Kiro CLI, or while an extension alias such as `speckit-plan` (accepted by Spec Kit 1.0.7 and earlier) already has its own prompt where a core prompt goes. An extension installed before Spec Kit rejected names that hyphenate to an existing prompt is not registered for Kiro CLI and keeps its existing prompts: two extensions with `speckit.foo.bar-baz` and `speckit.foo-bar.baz` stay that way until one is removed, and one with an alias such as `plan` until it is updated or removed. |
| [Lingma](https://lingma.aliyun.com/) | `lingma` | Skills-based integration; skills are installed automatically |
| [MiniMax Code](https://github.com/MiniMax-AI/minimax-code) | `mcode` | Skills-based integration; installs skills into `.minimax/skills` and invokes them as `/speckit-<command>`. Headless workflow dispatch uses `--permission full` so tool actions are not blocked by headless permission gating; set `SPECKIT_INTEGRATION_MCODE_EXTRA_ARGS="--permission smart"` to use MiniMax Code's stricter policy. |
| [Mistral Vibe](https://github.com/mistralai/mistral-vibe) | `vibe` | |
Expand Down
6 changes: 1 addition & 5 deletions src/specify_cli/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,7 @@ def check_tool(tool: str, tracker=None) -> bool:
return True

# Per-integration executable resolution.
if tool == "kiro-cli":
# Kiro currently supports both executable names. Prefer kiro-cli and
# accept kiro as a compatibility fallback.
found = shutil.which("kiro-cli") is not None or shutil.which("kiro") is not None
elif tool == "rovodev":
if tool == "rovodev":
found = shutil.which("acli") is not None
elif tool == "docker-agent":
found = docker_agent_command() is not None
Expand Down
26 changes: 23 additions & 3 deletions src/specify_cli/agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -1352,7 +1352,10 @@ def register_commands_for_non_skill_agents(
return results

def unregister_commands(
self, registered_commands: Dict[str, List[str]], project_root: Path
self,
registered_commands: Dict[str, List[str]],
project_root: Path,
preserved_output_names: Dict[str, set[str]] | None = None,
) -> None:
"""Remove previously registered command files from agent directories.

Expand All @@ -1361,9 +1364,15 @@ def unregister_commands(
commands left behind after an ``integration upgrade`` are
cleaned up as well.

``preserved_output_names`` maps an agent to formatted stems that
must stay. Extension removal protects installed core commands and
shared migrated files not owned by the extension being removed.
Protection applies to both formatted and raw command names.

Args:
registered_commands: Dict mapping agent names to command name lists
project_root: Path to project root
preserved_output_names: Agent -> formatted stems to leave in place
"""
self._ensure_configs()
for agent_name, cmd_names in registered_commands.items():
Expand All @@ -1384,13 +1393,24 @@ def unregister_commands(
if legacy_dir.exists() and legacy_dir != commands_dir:
dirs_to_clean.append(legacy_dir)

preserved = {
os.path.normcase(name)
for name in (preserved_output_names or {}).get(agent_name, ())
if isinstance(name, str)
}
for cmd_name in cmd_names:
output_name = self._compute_output_name(
agent_name, cmd_name, agent_config
)

names_to_clean = [output_name]
if output_name != cmd_name and self._is_safe_command_name(cmd_name):
names_to_clean = []
if os.path.normcase(output_name) not in preserved:
names_to_clean.append(output_name)
if (
output_name != cmd_name
and os.path.normcase(cmd_name) not in preserved
and self._is_safe_command_name(cmd_name)
):
names_to_clean.append(cmd_name)

for target_dir in dirs_to_clean:
Expand Down
Loading
Loading