From 2d9e44c6a633b92491b1923182cad6a4e23015f9 Mon Sep 17 00:00:00 2001 From: Viktorplus <36795799+viktorplus@users.noreply.github.com> Date: Sun, 7 Jun 2026 12:05:13 +0200 Subject: [PATCH] fix: align kimi install roots with docs --- bin/install.js | 6 +- docs/ARCHITECTURE.md | 14 +-- docs/how-to/install-on-your-runtime.md | 17 ++- docs/installer-migrations.md | 4 +- src/runtime-homes.cts | 45 +++++++- .../bug-kimi-path-layout-local-guard.test.cjs | 109 +++++++++++++++--- tests/helpers/install-shared.cjs | 2 +- tests/multi-runtime-select.test.cjs | 4 +- 8 files changed, 160 insertions(+), 41 deletions(-) diff --git a/bin/install.js b/bin/install.js index 98504a91e..17bede13c 100755 --- a/bin/install.js +++ b/bin/install.js @@ -397,7 +397,7 @@ function getConfigDirFromHome(runtime, isGlobal) { if (runtime === 'hermes') return "'.hermes'"; if (runtime === 'codebuddy') return "'.codebuddy'"; if (runtime === 'cline') return "'.cline'"; - if (runtime === 'kimi') return "'.agents'"; + if (runtime === 'kimi') return "'.config', 'agents'"; return "'.claude'"; } @@ -688,7 +688,7 @@ if (hasUninstall) { // Show help if requested if (hasHelp) { - console.log(` ${yellow}Usage:${reset} npx ${pkg.name} [options]\n\n ${yellow}Options:${reset}\n ${cyan}-g, --global${reset} Install globally (to config directory)\n ${cyan}-l, --local${reset} Install locally (to current directory)\n ${cyan}--claude${reset} Install for Claude Code only\n ${cyan}--opencode${reset} Install for OpenCode only\n ${cyan}--gemini${reset} Install for Gemini only\n ${cyan}--kilo${reset} Install for Kilo only\n ${cyan}--codex${reset} Install for Codex only\n ${cyan}--kimi${reset} Install for Kimi CLI only\n ${cyan}--copilot${reset} Install for Copilot only\n ${cyan}--antigravity${reset} Install for Antigravity only\n ${cyan}--cursor${reset} Install for Cursor only\n ${cyan}--windsurf${reset} Install for Windsurf only\n ${cyan}--augment${reset} Install for Augment only\n ${cyan}--trae${reset} Install for Trae only\n ${cyan}--qwen${reset} Install for Qwen Code only\n ${cyan}--hermes${reset} Install for Hermes Agent only\n ${cyan}--cline${reset} Install for Cline only\n ${cyan}--codebuddy${reset} Install for CodeBuddy only\n ${cyan}--all${reset} Install for all runtimes\n ${cyan}-u, --uninstall${reset} Uninstall GSD (remove all GSD files)\n ${cyan}-c, --config-dir ${reset} Specify custom config directory\n ${cyan}-h, --help${reset} Show this help message\n ${cyan}--force-statusline${reset} Replace existing statusline config\n ${cyan}--portable-hooks${reset} Emit \$HOME-relative hook paths in settings.json\n (for WSL/Docker bind-mount setups; also GSD_PORTABLE_HOOKS=1)\n ${cyan}--profile=${reset} Install a named skill profile. Profiles:\n core — 7 main-loop skills incl. phase (~130 desc tokens)\n standard — ~13 skills incl. phase, review, config (~700)\n full — all 66 skills (default)\n Composable: --profile=core,audit installs union of closures.\n Profile is persisted and respected by \`gsd update\`.\n ${cyan}--minimal${reset} Alias for --profile=core (back-compat).\n Cuts cold-start overhead from ~12k tokens to ~700.\n Alias: --core-only.\n\n ${yellow}Examples:${reset}\n ${dim}# Interactive install (prompts for runtime and location)${reset}\n npx ${pkg.name}\n\n ${dim}# Install for Claude Code globally${reset}\n npx ${pkg.name} --claude --global\n\n ${dim}# Install for Gemini globally${reset}\n npx ${pkg.name} --gemini --global\n\n ${dim}# Install for Kilo globally${reset}\n npx ${pkg.name} --kilo --global\n\n ${dim}# Install for Codex globally${reset}\n npx ${pkg.name} --codex --global\n\n ${dim}# Install for Kimi CLI globally${reset}\n npx ${pkg.name} --kimi --global\n\n ${dim}# Install for Kimi CLI under ~/.kimi-code${reset}\n npx ${pkg.name} --kimi --global --config-dir ~/.kimi-code\n\n ${dim}# Install for Copilot globally${reset}\n npx ${pkg.name} --copilot --global\n\n ${dim}# Install for Copilot locally${reset}\n npx ${pkg.name} --copilot --local\n\n ${dim}# Install for Antigravity globally${reset}\n npx ${pkg.name} --antigravity --global\n\n ${dim}# Install for Antigravity locally${reset}\n npx ${pkg.name} --antigravity --local\n\n ${dim}# Install for Cursor globally${reset}\n npx ${pkg.name} --cursor --global\n\n ${dim}# Install for Cursor locally${reset}\n npx ${pkg.name} --cursor --local\n\n ${dim}# Install for Windsurf globally${reset}\n npx ${pkg.name} --windsurf --global\n\n ${dim}# Install for Windsurf locally${reset}\n npx ${pkg.name} --windsurf --local\n\n ${dim}# Install for Augment globally${reset}\n npx ${pkg.name} --augment --global\n\n ${dim}# Install for Augment locally${reset}\n npx ${pkg.name} --augment --local\n\n ${dim}# Install for Trae globally${reset}\n npx ${pkg.name} --trae --global\n\n ${dim}# Install for Trae locally${reset}\n npx ${pkg.name} --trae --local\n\n ${dim}# Install for Hermes Agent globally${reset}\n npx ${pkg.name} --hermes --global\n\n ${dim}# Install for Hermes Agent locally${reset}\n npx ${pkg.name} --hermes --local\n\n ${dim}# Install for Cline locally${reset}\n npx ${pkg.name} --cline --local\n\n ${dim}# Install for CodeBuddy globally${reset}\n npx ${pkg.name} --codebuddy --global\n\n ${dim}# Install for CodeBuddy locally${reset}\n npx ${pkg.name} --codebuddy --local\n\n ${dim}# Install for all runtimes globally${reset}\n npx ${pkg.name} --all --global\n\n ${dim}# Install to custom config directory${reset}\n npx ${pkg.name} --kilo --global --config-dir ~/.kilo-work\n\n ${dim}# Install to current project only${reset}\n npx ${pkg.name} --claude --local\n\n ${dim}# Uninstall GSD from Cursor globally${reset}\n npx ${pkg.name} --cursor --global --uninstall\n\n ${yellow}Notes:${reset}\n The --config-dir option is useful when you have multiple configurations.\n It takes priority over CLAUDE_CONFIG_DIR / OPENCODE_CONFIG_DIR / GEMINI_CONFIG_DIR / KILO_CONFIG_DIR / CODEX_HOME / KIMI_CONFIG_DIR / COPILOT_CONFIG_DIR / ANTIGRAVITY_CONFIG_DIR / CURSOR_CONFIG_DIR / WINDSURF_CONFIG_DIR / AUGMENT_CONFIG_DIR / TRAE_CONFIG_DIR / QWEN_CONFIG_DIR / HERMES_HOME / CLINE_CONFIG_DIR / CODEBUDDY_CONFIG_DIR environment variables.\n Kimi CLI defaults to ${cyan}~/.agents${reset}; use ${cyan}--config-dir ~/.kimi-code${reset} or ${cyan}KIMI_CONFIG_DIR=~/.kimi-code${reset} for brand-specific Kimi installs.\n`); + console.log(` ${yellow}Usage:${reset} npx ${pkg.name} [options]\n\n ${yellow}Options:${reset}\n ${cyan}-g, --global${reset} Install globally (to config directory)\n ${cyan}-l, --local${reset} Install locally (to current directory)\n ${cyan}--claude${reset} Install for Claude Code only\n ${cyan}--opencode${reset} Install for OpenCode only\n ${cyan}--gemini${reset} Install for Gemini only\n ${cyan}--kilo${reset} Install for Kilo only\n ${cyan}--codex${reset} Install for Codex only\n ${cyan}--kimi${reset} Install for Kimi CLI only\n ${cyan}--copilot${reset} Install for Copilot only\n ${cyan}--antigravity${reset} Install for Antigravity only\n ${cyan}--cursor${reset} Install for Cursor only\n ${cyan}--windsurf${reset} Install for Windsurf only\n ${cyan}--augment${reset} Install for Augment only\n ${cyan}--trae${reset} Install for Trae only\n ${cyan}--qwen${reset} Install for Qwen Code only\n ${cyan}--hermes${reset} Install for Hermes Agent only\n ${cyan}--cline${reset} Install for Cline only\n ${cyan}--codebuddy${reset} Install for CodeBuddy only\n ${cyan}--all${reset} Install for all runtimes\n ${cyan}-u, --uninstall${reset} Uninstall GSD (remove all GSD files)\n ${cyan}-c, --config-dir ${reset} Specify custom config directory\n ${cyan}-h, --help${reset} Show this help message\n ${cyan}--force-statusline${reset} Replace existing statusline config\n ${cyan}--portable-hooks${reset} Emit \$HOME-relative hook paths in settings.json\n (for WSL/Docker bind-mount setups; also GSD_PORTABLE_HOOKS=1)\n ${cyan}--profile=${reset} Install a named skill profile. Profiles:\n core — 7 main-loop skills incl. phase (~130 desc tokens)\n standard — ~13 skills incl. phase, review, config (~700)\n full — all 66 skills (default)\n Composable: --profile=core,audit installs union of closures.\n Profile is persisted and respected by \`gsd update\`.\n ${cyan}--minimal${reset} Alias for --profile=core (back-compat).\n Cuts cold-start overhead from ~12k tokens to ~700.\n Alias: --core-only.\n\n ${yellow}Examples:${reset}\n ${dim}# Interactive install (prompts for runtime and location)${reset}\n npx ${pkg.name}\n\n ${dim}# Install for Claude Code globally${reset}\n npx ${pkg.name} --claude --global\n\n ${dim}# Install for Gemini globally${reset}\n npx ${pkg.name} --gemini --global\n\n ${dim}# Install for Kilo globally${reset}\n npx ${pkg.name} --kilo --global\n\n ${dim}# Install for Codex globally${reset}\n npx ${pkg.name} --codex --global\n\n ${dim}# Install for Kimi CLI globally${reset}\n npx ${pkg.name} --kimi --global\n\n ${dim}# Install for Kimi CLI under ~/.kimi-code${reset}\n npx ${pkg.name} --kimi --global --config-dir ~/.kimi-code\n\n ${dim}# Install for Copilot globally${reset}\n npx ${pkg.name} --copilot --global\n\n ${dim}# Install for Copilot locally${reset}\n npx ${pkg.name} --copilot --local\n\n ${dim}# Install for Antigravity globally${reset}\n npx ${pkg.name} --antigravity --global\n\n ${dim}# Install for Antigravity locally${reset}\n npx ${pkg.name} --antigravity --local\n\n ${dim}# Install for Cursor globally${reset}\n npx ${pkg.name} --cursor --global\n\n ${dim}# Install for Cursor locally${reset}\n npx ${pkg.name} --cursor --local\n\n ${dim}# Install for Windsurf globally${reset}\n npx ${pkg.name} --windsurf --global\n\n ${dim}# Install for Windsurf locally${reset}\n npx ${pkg.name} --windsurf --local\n\n ${dim}# Install for Augment globally${reset}\n npx ${pkg.name} --augment --global\n\n ${dim}# Install for Augment locally${reset}\n npx ${pkg.name} --augment --local\n\n ${dim}# Install for Trae globally${reset}\n npx ${pkg.name} --trae --global\n\n ${dim}# Install for Trae locally${reset}\n npx ${pkg.name} --trae --local\n\n ${dim}# Install for Hermes Agent globally${reset}\n npx ${pkg.name} --hermes --global\n\n ${dim}# Install for Hermes Agent locally${reset}\n npx ${pkg.name} --hermes --local\n\n ${dim}# Install for Cline locally${reset}\n npx ${pkg.name} --cline --local\n\n ${dim}# Install for CodeBuddy globally${reset}\n npx ${pkg.name} --codebuddy --global\n\n ${dim}# Install for CodeBuddy locally${reset}\n npx ${pkg.name} --codebuddy --local\n\n ${dim}# Install for all runtimes globally${reset}\n npx ${pkg.name} --all --global\n\n ${dim}# Install to custom config directory${reset}\n npx ${pkg.name} --kilo --global --config-dir ~/.kilo-work\n\n ${dim}# Install to current project only${reset}\n npx ${pkg.name} --claude --local\n\n ${dim}# Uninstall GSD from Cursor globally${reset}\n npx ${pkg.name} --cursor --global --uninstall\n\n ${yellow}Notes:${reset}\n The --config-dir option is useful when you have multiple configurations.\n It takes priority over CLAUDE_CONFIG_DIR / OPENCODE_CONFIG_DIR / GEMINI_CONFIG_DIR / KILO_CONFIG_DIR / CODEX_HOME / KIMI_CONFIG_DIR / COPILOT_CONFIG_DIR / ANTIGRAVITY_CONFIG_DIR / CURSOR_CONFIG_DIR / WINDSURF_CONFIG_DIR / AUGMENT_CONFIG_DIR / TRAE_CONFIG_DIR / QWEN_CONFIG_DIR / HERMES_HOME / CLINE_CONFIG_DIR / CODEBUDDY_CONFIG_DIR environment variables.\n Kimi CLI defaults to the first existing generic skills root: ${cyan}~/.config/agents/skills${reset}, then ${cyan}~/.agents/skills${reset}; if neither exists, GSD creates ${cyan}~/.config/agents${reset}.\n Use ${cyan}--config-dir ~/.kimi-code${reset} or ${cyan}KIMI_CONFIG_DIR=~/.kimi-code${reset} for brand-specific Kimi installs.\n`); process.exit(0); } @@ -10923,7 +10923,7 @@ function buildRuntimePromptText() { ${cyan}8${reset}) Cursor ${dim}(~/.cursor)${reset} ${cyan}9${reset}) Gemini ${dim}(~/.gemini)${reset} ${cyan}10${reset}) Hermes Agent ${dim}(~/.hermes)${reset} - ${cyan}11${reset}) Kimi ${dim}(~/.agents)${reset} + ${cyan}11${reset}) Kimi ${dim}(~/.config/agents, then ~/.agents if existing)${reset} ${cyan}12${reset}) Kilo ${dim}(~/.config/kilo)${reset} ${cyan}13${reset}) OpenCode ${dim}(~/.config/opencode)${reset} ${cyan}14${reset}) Qwen Code ${dim}(~/.qwen)${reset} diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index f16a02572..61e8d187e 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -563,7 +563,7 @@ Equivalent paths for other runtimes: - **OpenCode:** `~/.config/opencode/` global or `./.opencode/` local - **Kilo:** `~/.config/kilo/` global or `./.kilo/` local - **Gemini CLI:** `~/.gemini/` global or `./.gemini/` local -- **Kimi CLI:** `~/.agents/` global; local install is deferred and guarded +- **Kimi CLI:** first-existing generic global root (`~/.config/agents/` recommended, then `~/.agents/` if its `skills/` directory already exists); local install is deferred and guarded - **Codex:** `~/.codex/` global or `./.codex/` local - **Copilot:** `~/.copilot/` global or `./.github/` local - **Antigravity:** auto-detected global root (`~/.gemini/antigravity/`, `~/.gemini/antigravity-ide/`, or `~/.gemini/antigravity-cli/`) or `./.agent/` local @@ -802,7 +802,7 @@ The migration-specific ownership and source snapshots live in | OpenCode | `~/.config/opencode` | `./.opencode` | `command/gsd-*.md` | `agents/gsd-*.md` | `opencode.json` or `opencode.jsonc`; no GSD hooks | | Kilo | `~/.config/kilo` | `./.kilo` | `command/gsd-*.md` | `agents/gsd-*.md` | `kilo.json` or `kilo.jsonc`; no GSD hooks | | Gemini CLI | `~/.gemini` | `./.gemini` | `commands/gsd/*.toml` | `agents/gsd-*.md` | `settings.json` feature flag, hooks, and statusline | -| Kimi CLI | `~/.agents` | Deferred and guarded | `skills/gsd-*/SKILL.md` invoked as `/skill:gsd-*` | `agents/gsd.yaml`, `agents/gsd.md`, and `agents/subagents/gsd-*` YAML/prompt pairs | Explicit `kimi --agent-file ~/.agents/agents/gsd.yaml`; no GSD hooks or statusline | +| Kimi CLI | First-existing generic root: `~/.config/agents` recommended, then `~/.agents` when `~/.agents/skills` exists and `~/.config/agents/skills` does not | Deferred and guarded | `skills/gsd-*/SKILL.md` invoked as `/skill:gsd-*` | `agents/gsd.yaml`, `agents/gsd.md`, and `agents/subagents/gsd-*` YAML/prompt pairs | Explicit `kimi --agent-file /agents/gsd.yaml`; no GSD hooks or statusline | | Codex | `~/.codex` | `./.codex` | `skills/gsd-*/SKILL.md` | `agents/` source markdown plus per-agent TOML | `config.toml` `[agents.gsd-*]`, `[features].hooks` (canonical; legacy alias `codex_hooks` is recognized and migrated forward on reinstall, #3566), and hook tables | | GitHub Copilot | `~/.copilot` | `./.github` | `skills/gsd-*/SKILL.md` and `copilot-instructions.md` | `.agent.md` files | No GSD hooks or statusline | | Antigravity | auto-detected: `~/.gemini/antigravity`, `~/.gemini/antigravity-ide`, or `~/.gemini/antigravity-cli` | `./.agent` | `skills/gsd-*/SKILL.md` | `agents/gsd-*.md` | Gemini-style `settings.json` hook entries when installed by GSD | @@ -819,16 +819,16 @@ The migration-specific ownership and source snapshots live in Runtime install expectations are checked against primary documentation where available. The current source snapshot is 2026-05-11, with Kimi CLI rechecked -on 2026-06-06: +on 2026-06-07: - Claude Code: Anthropic slash commands, settings, hooks, and subagents docs. - OpenCode and Kilo: OpenCode config docs and Kilo custom subagent docs. - Gemini CLI and Qwen Code: command/config docs; Qwen command docs were last updated 2026-05-06. -- Kimi CLI: Agent Skills docs for `~/.kimi-code/skills/` and - `~/.agents/skills/` user-level discovery, plus Agents docs for YAML files, - `system_prompt_path`, `kimi_cli.tools.*` module paths, and explicit - `kimi --agent-file` launch. +- Kimi CLI: Agent Skills docs for user-level brand roots and first-existing + generic roots (`~/.config/agents/skills/` recommended, then + `~/.agents/skills/`), plus Agents docs for YAML files, `system_prompt_path`, + `kimi_cli.tools.*` module paths, and explicit `kimi --agent-file` launch. - Codex: OpenAI Codex docs and `config-schema.json`; the installer also carries Codex 0.124.0 compatibility for agent table shape. - Copilot, Cursor, Cline, Augment, Hermes, and CodeBuddy: vendor docs for diff --git a/docs/how-to/install-on-your-runtime.md b/docs/how-to/install-on-your-runtime.md index cfad88a00..55074ba5f 100644 --- a/docs/how-to/install-on-your-runtime.md +++ b/docs/how-to/install-on-your-runtime.md @@ -110,21 +110,32 @@ Skills land in `~/.codex/skills/gsd-*/SKILL.md`. Agents are written with per-age npx @opengsd/gsd-core@latest --kimi --global ``` -Skills land in `~/.agents/skills/gsd-*/SKILL.md`. Start a new Kimi CLI session after install, then invoke GSD skills with `/skill:gsd-*`, for example: +Skills land in Kimi's first existing generic user skills root: + +- `~/.config/agents/skills/gsd-*/SKILL.md` when `~/.config/agents/skills` already exists, or when neither generic root exists yet +- `~/.agents/skills/gsd-*/SKILL.md` when `~/.agents/skills` already exists and `~/.config/agents/skills` does not + +Start a new Kimi CLI session after install, then invoke GSD skills with `/skill:gsd-*`, for example: ```text /skill:gsd-new-project ``` -The installer also writes the GSD custom agent definition to `~/.agents/agents/gsd.yaml` with its prompt at `~/.agents/agents/gsd.md`; subagents land under `~/.agents/agents/subagents/gsd-*.yaml` and `~/.agents/agents/subagents/gsd-*.md`. +The installer also writes the GSD custom agent definition to the same selected config root: `/agents/gsd.yaml` with its prompt at `/agents/gsd.md`; subagents land under `/agents/subagents/gsd-*.yaml` and `/agents/subagents/gsd-*.md`. Kimi custom agents do not auto-activate just because the files exist. Launch Kimi with the generated agent file when you want the GSD agent surface: +```bash +kimi --agent-file ~/.config/agents/agents/gsd.yaml +``` + +If your machine already uses `~/.agents/skills` and does not have `~/.config/agents/skills`, GSD installs there instead and the launch command becomes: + ```bash kimi --agent-file ~/.agents/agents/gsd.yaml ``` -GSD uses Kimi's generic Agent Skills root (`~/.agents`) as the default so the install follows the shared agents discovery convention. Kimi also discovers user skills from the brand-specific `~/.kimi-code` directory. If your Kimi setup is already centered on `~/.kimi-code`, install there explicitly: +Kimi also discovers user skills from the brand-specific `~/.kimi-code` directory. If your Kimi setup is already centered on `~/.kimi-code`, install there explicitly: ```bash npx @opengsd/gsd-core@latest --kimi --global --config-dir ~/.kimi-code diff --git a/docs/installer-migrations.md b/docs/installer-migrations.md index 88f4e96b0..7de591e25 100644 --- a/docs/installer-migrations.md +++ b/docs/installer-migrations.md @@ -345,7 +345,8 @@ only the owned portion. ## Runtime Configuration Contract Registry -Last upstream documentation check: 2026-05-11. +Last upstream documentation check: 2026-05-11. Kimi CLI was rechecked on +2026-06-07 against the MoonshotAI docs. This registry is the source of truth for migrations that touch host runtime configuration. Each row records: @@ -367,6 +368,7 @@ for the new shape before changing migration behavior. | OpenCode | Flat markdown commands in `command/gsd-*.md`; agents in `agents/gsd-*.md`; config updates in `opencode.json` or `opencode.jsonc` | Global `OPENCODE_CONFIG_DIR`, `dirname(OPENCODE_CONFIG)`, `XDG_CONFIG_HOME/opencode`, or `~/.config/opencode`; local `./.opencode` | GSD owns generated command/agent files and GSD entries in structured config only | [Config](https://opencode.ai/docs/config/); docs published 2026-05, checked 2026-05-11 | | Kilo | OpenCode-style flat markdown commands in `command/gsd-*.md`; agents in `agents/gsd-*.md`; config updates in `kilo.json` or `kilo.jsonc` | Global `KILO_CONFIG_DIR`, `dirname(KILO_CONFIG)`, `XDG_CONFIG_HOME/kilo`, or `~/.config/kilo`; local `./.kilo` | GSD owns generated command/agent files and GSD entries in structured config only | [Custom subagents](https://docs.kilo.ai/docs/customize/custom-subagents); docs not versioned, checked 2026-05-11 | | Gemini CLI | TOML slash commands in `commands/gsd/*.toml`; agents in `agents/gsd-*.md`; `settings.json` feature flag, hooks, and statusline | Global `GEMINI_CONFIG_DIR` or `~/.gemini`; local `./.gemini` | GSD owns generated commands/agents/hooks and only GSD settings entries; local command copy may be skipped when global GSD commands already exist | [Custom commands](https://google-gemini.github.io/gemini-cli/docs/cli/custom-commands.html), [configuration](https://google-gemini.github.io/gemini-cli/docs/cli/configuration.html); docs checked 2026-05-11 | +| Kimi CLI | Agent Skills in `skills/gsd-*/SKILL.md`; explicit custom agent YAML/prompt artifacts in `agents/gsd.yaml`, `agents/gsd.md`, and `agents/subagents/gsd-*`; `gsd-core/` payload files referenced by generated skills; manifest, pristine, local-patch, and migration journal files from the normal installer safety pipeline | Global `KIMI_CONFIG_DIR`, explicit `--config-dir`, or first-existing generic skills root: `~/.config/agents` when `~/.config/agents/skills` exists or no generic skills root exists yet, otherwise `~/.agents` when `~/.agents/skills` exists and `~/.config/agents/skills` does not; local `--kimi --local` is guarded and writes no project-level artifacts | GSD owns only generated `skills/gsd-*`, `agents/gsd.*`, `agents/subagents/gsd-*`, installed `gsd-core/` payload files, and manifest/preservation/migration records. GSD does not own Kimi config files, hooks, settings, rules, statusline, update-banner registration, or non-GSD Kimi skills/agents. Reinstall/update must preserve locally modified generated Kimi artifacts through manifest-backed `gsd-local-patches/`; uninstall removes only GSD-owned Kimi artifacts and preserves non-GSD user content. | [Agent Skills](https://moonshotai.github.io/kimi-cli/en/customization/skills.html), [Agents and Subagents](https://moonshotai.github.io/kimi-cli/en/customization/agents.html), [Tools](https://moonshotai.github.io/kimi-code/en/reference/tools.html); docs checked 2026-06-07 | | Codex | Skills in `skills/gsd-*/SKILL.md`; agents as source markdown plus per-agent TOML in `agents/`; `[agents.gsd-*]` and hooks in `config.toml` | Global `CODEX_HOME` or `~/.codex`; local `./.codex` | GSD owns generated skills, generated agent TOML, `agents.gsd-*` config sections, `[features].hooks` when added by GSD (canonical; legacy alias `codex_hooks` is recognized and migrated forward, #3566), and GSD hook entries | [Codex config schema](https://developers.openai.com/codex/config-schema.json), [Codex developer docs](https://developers.openai.com/codex/); docs not versioned, checked 2026-05-15; installer compatibility sentinel: Codex 0.130.0 features.hooks key (legacy `codex_hooks` recognized) | | GitHub Copilot | Skills in `skills/gsd-*/SKILL.md`; agents as `.agent.md`; repository instructions in `copilot-instructions.md` | Global `COPILOT_CONFIG_DIR` or `~/.copilot`; local `./.github` | GSD owns generated skill/agent files and GSD-authored instruction files; no hook/statusline ownership | [Repository custom instructions](https://docs.github.com/en/copilot/how-tos/configure-custom-instructions/add-repository-instructions), [Copilot CLI custom instructions](https://docs.github.com/en/copilot/how-tos/copilot-cli/add-custom-instructions); GitHub Docs product docs, checked 2026-05-11 | | Antigravity | Skills in `skills/gsd-*/SKILL.md`; agents in `agents/`; Gemini-style `settings.json` hooks when installed by GSD | Global `ANTIGRAVITY_CONFIG_DIR` or `~/.gemini/antigravity`; local `./.agent` | GSD owns generated skills/agents/hooks and GSD settings entries only | Public Antigravity install/config docs for this file layout were not stable or complete as of 2026-05-11; installer compatibility therefore uses GSD's Gemini-compatible settings policy, documented shim baseline. | diff --git a/src/runtime-homes.cts b/src/runtime-homes.cts index 30ac2cd26..df572ffe1 100644 --- a/src/runtime-homes.cts +++ b/src/runtime-homes.cts @@ -14,9 +14,11 @@ * cline — Rules-based; commands are embedded in .clinerules. Cline does * not use a skills/ directory. getGlobalSkillDir() returns null * for cline so the caller can emit an appropriate warning. - * kimi — Agent Skills are discovered from Kimi's generic user root: - * ~/.agents/skills. ~/.kimi-code/skills is compatible with - * Kimi Code CLI and can be selected with KIMI_CONFIG_DIR. + * kimi — Agent Skills are discovered from Kimi's generic user roots: + * ~/.config/agents/skills (recommended) then ~/.agents/skills, + * with Kimi selecting the first existing generic skills directory. + * ~/.kimi-code/skills is brand-specific and can be selected with + * KIMI_CONFIG_DIR. */ import os from 'node:os'; @@ -38,6 +40,12 @@ export interface ResolveAntigravityOpts { existsSync?: (p: string) => boolean; } +export interface ResolveKimiOpts { + env?: Record; + home?: string; + existsSync?: (p: string) => boolean; +} + /** * Resolve Antigravity global config dir across 1.x and 2.x layouts. */ @@ -61,6 +69,34 @@ export function resolveAntigravityGlobalDir(opts: ResolveAntigravityOpts = {}): return path.join(base, 'antigravity'); } +/** + * Resolve Kimi's generic user root using Kimi CLI's documented first-existing + * generic skills directory policy: + * + * 1. ~/.config/agents/skills (recommended) + * 2. ~/.agents/skills + * + * If neither generic skills directory exists yet, install to the recommended + * ~/.config/agents root so the generated skills become the first generic + * candidate Kimi discovers. + */ +export function resolveKimiGlobalDir(opts: ResolveKimiOpts = {}): string { + const env: Record = opts.env ?? process.env; + const home = opts.home ?? os.homedir(); + const existsSyncFn = opts.existsSync ?? fs.existsSync; + + if (env['KIMI_CONFIG_DIR']) return expandTilde(env['KIMI_CONFIG_DIR']); + + const recommendedRoot = path.join(home, '.config', 'agents'); + const fallbackRoot = path.join(home, '.agents'); + const candidates = [recommendedRoot, fallbackRoot]; + for (const candidate of candidates) { + if (existsSyncFn(path.join(candidate, 'skills'))) return candidate; + } + + return recommendedRoot; +} + /** * Return the global config base directory for the given runtime. * Respects the same env-var overrides as bin/install.js getGlobalDir(). @@ -130,8 +166,7 @@ export function getGlobalConfigDir(runtime: string): string { // ── Kimi CLI (generic agents user root) ──────────────────────────────── case 'kimi': { - if (env['KIMI_CONFIG_DIR']) return expandTilde(env['KIMI_CONFIG_DIR']); - return path.join(home, '.agents'); + return resolveKimiGlobalDir({ env, home }); } // ── OpenCode (XDG) ─────────────────────────────────────────────────────── diff --git a/tests/bug-kimi-path-layout-local-guard.test.cjs b/tests/bug-kimi-path-layout-local-guard.test.cjs index 0d6fe1668..9e2a11a4d 100644 --- a/tests/bug-kimi-path-layout-local-guard.test.cjs +++ b/tests/bug-kimi-path-layout-local-guard.test.cjs @@ -19,6 +19,7 @@ const { getGlobalConfigDir, getGlobalSkillsBase, getGlobalSkillDir, + resolveKimiGlobalDir, } = require(path.join(ROOT, 'gsd-core', 'bin', 'lib', 'runtime-homes.cjs')); const { resolveRuntimeArtifactLayout, @@ -47,25 +48,56 @@ function withEnv(updates, fn) { } describe('Kimi runtime homes', () => { - test('canonical global skills base is ~/.agents/skills, not ~/.kimi-code/skills', () => { - withEnv({ KIMI_CONFIG_DIR: undefined, XDG_CONFIG_HOME: undefined }, () => { + test('default Kimi global root is recommended ~/.config/agents when no generic skills root exists', () => { + const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-kimi-home-default-')); + try { + withEnv({ KIMI_CONFIG_DIR: undefined, XDG_CONFIG_HOME: undefined, HOME: tmpHome, USERPROFILE: tmpHome }, () => { + assert.strictEqual( + getGlobalConfigDir('kimi'), + path.join(tmpHome, '.config', 'agents'), + ); + assert.strictEqual( + getGlobalSkillsBase('kimi'), + path.join(tmpHome, '.config', 'agents', 'skills'), + ); + assert.strictEqual( + getGlobalSkillDir('kimi', 'gsd-help'), + path.join(tmpHome, '.config', 'agents', 'skills', 'gsd-help'), + ); + assert.notStrictEqual( + getGlobalSkillsBase('kimi'), + path.join(tmpHome, '.kimi-code', 'skills'), + ); + }); + } finally { + cleanup(tmpHome); + } + }); + + test('Kimi root resolution follows first-existing generic skills directory order', () => { + const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-kimi-home-existing-')); + try { + const recommendedRoot = path.join(tmpHome, '.config', 'agents'); + const fallbackRoot = path.join(tmpHome, '.agents'); assert.strictEqual( - getGlobalConfigDir('kimi'), - path.join(os.homedir(), '.agents'), + resolveKimiGlobalDir({ env: {}, home: tmpHome, existsSync: fs.existsSync }), + recommendedRoot, ); + + fs.mkdirSync(path.join(fallbackRoot, 'skills'), { recursive: true }); assert.strictEqual( - getGlobalSkillsBase('kimi'), - path.join(os.homedir(), '.agents', 'skills'), + resolveKimiGlobalDir({ env: {}, home: tmpHome, existsSync: fs.existsSync }), + fallbackRoot, ); + + fs.mkdirSync(path.join(recommendedRoot, 'skills'), { recursive: true }); assert.strictEqual( - getGlobalSkillDir('kimi', 'gsd-help'), - path.join(os.homedir(), '.agents', 'skills', 'gsd-help'), + resolveKimiGlobalDir({ env: {}, home: tmpHome, existsSync: fs.existsSync }), + recommendedRoot, ); - assert.notStrictEqual( - getGlobalSkillsBase('kimi'), - path.join(os.homedir(), '.kimi-code', 'skills'), - ); - }); + } finally { + cleanup(tmpHome); + } }); test('KIMI_CONFIG_DIR can select the brand-specific ~/.kimi-code root', () => { @@ -80,16 +112,55 @@ describe('Kimi runtime homes', () => { }); test('XDG_CONFIG_HOME does not change Kimi default root', () => { - withEnv({ KIMI_CONFIG_DIR: undefined, XDG_CONFIG_HOME: '/tmp/xdg-home' }, () => { + const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-kimi-home-xdg-')); + try { + withEnv({ KIMI_CONFIG_DIR: undefined, XDG_CONFIG_HOME: '/tmp/xdg-home', HOME: tmpHome, USERPROFILE: tmpHome }, () => { + assert.strictEqual( + getGlobalConfigDir('kimi'), + path.join(tmpHome, '.config', 'agents'), + ); + assert.strictEqual( + getConfigDirFromHome('kimi', true), + "'.config', 'agents'", + ); + }); + } finally { + cleanup(tmpHome); + } + }); + + test('Kimi global install reuses existing ~/.agents/skills when recommended root is absent', () => { + const tmpProject = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-kimi-existing-agents-project-')); + const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-kimi-existing-agents-home-')); + try { + fs.mkdirSync(path.join(tmpHome, '.agents', 'skills'), { recursive: true }); + const result = spawnSync( + process.execPath, + [INSTALL_SCRIPT, '--kimi', '--global', '--no-sdk'], + { + cwd: tmpProject, + encoding: 'utf8', + env: installerEnv({ HOME: tmpHome, USERPROFILE: tmpHome }), + }, + ); + assert.strictEqual( - getGlobalConfigDir('kimi'), - path.join(os.homedir(), '.agents'), + result.status, + 0, + `expected --kimi --global to reuse existing ~/.agents/skills\nstdout: ${result.stdout}\nstderr: ${result.stderr}`, ); assert.strictEqual( - getConfigDirFromHome('kimi', true), - "'.agents'", + fs.existsSync(path.join(tmpHome, '.agents', 'skills', 'gsd-new-project', 'SKILL.md')), + true, ); - }); + assert.strictEqual( + fs.existsSync(path.join(tmpHome, '.config', 'agents', 'skills', 'gsd-new-project', 'SKILL.md')), + false, + ); + } finally { + cleanup(tmpProject); + cleanup(tmpHome); + } }); }); diff --git a/tests/helpers/install-shared.cjs b/tests/helpers/install-shared.cjs index fb499452b..a56b2539a 100644 --- a/tests/helpers/install-shared.cjs +++ b/tests/helpers/install-shared.cjs @@ -48,7 +48,7 @@ const RUNTIME_META = { cursor: { localDir: '.cursor', globalSuffix: '.cursor' }, gemini: { localDir: '.gemini', globalSuffix: '.gemini' }, hermes: { localDir: '.hermes', globalSuffix: '.hermes' }, - kimi: { localDir: '.kimi-code', globalSuffix: '.agents' }, + kimi: { localDir: '.kimi-code', globalSuffix: path.join('.config', 'agents') }, kilo: { localDir: '.kilo', globalSuffix: path.join('.config', 'kilo') }, opencode: { localDir: '.opencode', globalSuffix: path.join('.config', 'opencode') }, qwen: { localDir: '.qwen', globalSuffix: '.qwen' }, diff --git a/tests/multi-runtime-select.test.cjs b/tests/multi-runtime-select.test.cjs index 771440a52..66eaa1050 100644 --- a/tests/multi-runtime-select.test.cjs +++ b/tests/multi-runtime-select.test.cjs @@ -187,8 +187,8 @@ describe('install.js exports multi-select runtime metadata', () => { 'prompt lists Hermes Agent as option 10'); assert.ok(/\b11\)\s*Kimi\b/.test(prompt), 'prompt lists Kimi as option 11'); - assert.ok(/Kimi\s+\(~\/\.agents\)/.test(prompt), - 'prompt shows the canonical Kimi global root'); + assert.ok(/Kimi\s+\(~\/\.config\/agents, then ~\/\.agents if existing\)/.test(prompt), + 'prompt shows the Kimi first-existing generic root policy'); assert.ok(/\b14\)\s*Qwen Code\b/.test(prompt), 'prompt lists Qwen Code as option 14'); assert.ok(/\b15\)\s*Trae\b/.test(prompt),