Files
msd-core/docs/migration/kimi-to-kimi-code.md
Tom Boucher 72819a4616 fix(#3031): opt-in reclaim of GSD hooks orphaned in ~/.kimi (#3731)
* test(#3031): failing-first coverage for opt-in ~/.kimi legacy reclaim

Drives the user-reachable installer surface against a sandbox HOME seeded
with the pre-#2755 wreckage: a GSD [[hooks]] block, hooks bundle and
CommonJS marker orphaned in ~/.kimi by a --kimi-code install.

Covers the reclaim itself plus the four guards the diagnosis identified as
negative space: opt-in only (no flag, no deletion), user-authored TOML and
hook files preserved, a --kimi install never reclaiming its own root, and
the KIMI_SHARE_DIR/KIMI_CODE_HOME collision where both roots resolve to one
directory. Adds a fast-check property that stripping the block never
destroys user content.

Red until --reclaim-kimi-legacy exists.

Refs #3031

* fix(#3031): opt-in reclaim of GSD hooks orphaned in ~/.kimi

A --kimi-code install older than 1.10.0 wrote its GSD [[hooks]] block, hook
bundle and CommonJS marker into Kimi CLI's ~/.kimi. #2755 fixed the
destination but could not reclaim what the old bug already wrote: the stale
block is byte-identical to a legitimate Kimi CLI one — both runtimes render
the same bytes for the same root, since the command paths derive from the
hooks root, not the runtime — so no inspection can tell litter from a working
install.

Cleanup is therefore opt-in. `--reclaim-kimi-legacy` on a --kimi-code install
removes GSD's own artifacts from the legacy root; without it nothing is
touched, so a dual-product machine keeps Kimi CLI's hooks and #2755's
acceptance criterion holds.

Extracts the uninstall path's removal sequence into reclaimKimiHooksRoot() and
drives both callers through it, so the reclaim removes precisely what a real
uninstall removes rather than a hand-copied second implementation. Guards the
wrong-runtime case (a --kimi install would delete its own hooks) and the
KIMI_SHARE_DIR/KIMI_CODE_HOME collision where both roots resolve to one
directory.

Also corrects two pre-#2755 leftovers in the same surface that told users to
run `--kimi --config-dir ~/.kimi-code` — the form that produces this very
defect, since --config-dir moves only the skills root — and adds the missing
--kimi-code entry to the installer's own help.

Regression coverage folded into tests/kimi-upgrades.test.cjs beside the #2755
cases, per the regression-test-naming lint.

Fixes #3031

* fix(#3031): never reclaim ~/.kimi when this run also installs kimi

Found by the isolated adversarial review pass and independently while tracing
--all ordering, then reproduced.

selectRuntimesFromArgs orders 'kimi' before 'kimi-code' in both --all and an
explicit --kimi --kimi-code, and installAllRuntimes installs in that order. So
--all --reclaim-kimi-legacy installed a fresh, legitimate Kimi CLI hooks block
into ~/.kimi and then deleted it moments later from the kimi-code leg — exiting
0 and reporting success while leaving the user with no Kimi CLI hooks at all.
The collision guard could not catch it: kimi-code's own root is ~/.kimi-code, a
genuinely different directory.

The flag asserts "I only use Kimi Code"; installing kimi in the same invocation
falsifies that, so the reclaim is skipped with a notice.

Also hardens the collision guard itself. It compared path.resolve strings,
which returns false for two spellings of ONE directory — measured, not assumed:
a symlinked alias and a case variant on a case-insensitive filesystem both
compared unequal, so the guard would not have fired and the install would have
deleted its own freshly-written hooks. isSameDirectory now compares directories
via resolve, then dev+ino identity, then realpath.

Regression tests for all three cases; the two alias tests probe the real
filesystem and t.skip() where the alias cannot exist.

Refs #3031

* docs(#3031): reattach reclaimKimiHooksRoot's JSDoc to its own function

Inserting isSameDirectory anchored on the function name, which placed the
helper between reclaimKimiHooksRoot's doc block and the function it documents.
isSameDirectory ended up with two stacked doc blocks above it and
reclaimKimiHooksRoot with none.

Refs #3031

* fix(#3031): warn when --reclaim-kimi-legacy cannot apply

The flag only acts inside the kimi-code GLOBAL install branch. Passed with any
other runtime, or with --local, it was consumed in silence: exit 0, no cleanup,
no message. For a cleanup the user explicitly asked for, silence is
indistinguishable from "it ran and found nothing".

The scope warning is raised at argument-resolution time rather than inside
install(). kimi-code declares hostBehaviors.localInstallDeferred, so install()
returns early at the deferral check long before the kimi-hooks-toml branch — a
guard placed there is unreachable, which is both dead code and a linted drift
shape in this repo. Verified reachable by spawning the real installer.

Neither case is a hard error: the flag stays composable with --all, where it is
legitimately inert for the other seventeen runtimes.

Refs #3031

* docs(#3031): document every case where --reclaim-kimi-legacy skips

Refs #3031

* fix(#3031): resolve local config dirs from RUNTIME_META alone in the install harness

The remote runner surfaced this: the #3031 warning test drives a local
kimi-code install and died with "The path argument must be of type string.
Received undefined".

runMinimalInstall carried a SECOND, hand-maintained local-dir map beside
RUNTIME_META, and it had drifted — four runtimes present in RUNTIME_META
(hermes, kimi, kimi-code, zcode) were missing from it, so scope:'local' for any
of them resolved path.join(root, undefined) and threw a bare TypeError naming
neither the runtime nor the map at fault. #3023 had already hit exactly this
for pi and fixed it by adding one more entry, which left the divergence itself
in place for the next runtime to rediscover.

Local scope now reads RUNTIME_META.localDir, the same table the global branch
already reads, with the same loud named error the global branch raises. Parity
verified for all 14 previously-supported runtimes: every one resolves to a
byte-identical configDir. cline keeps its ternary — its local artifacts land at
the project root itself, which is a real exception, not a directory name.

Guarded in golden-parity-single-source.test.cjs beside the buildParityManifest
anti-divergence test, and both arms of that guard were proven able to fail.

Refs #3031

* chore(#3031): backfill changeset pr number

---------

Co-authored-by: sim <sim@local>
2026-08-21 03:05:58 -04:00

98 lines
7.8 KiB
Markdown

# Migrating from `--kimi` to `--kimi-code`
> **When:** you installed GSD via `--kimi --global` but you're actually running **Kimi Code** (Moonshot's Node CLI, `~/.kimi-code/config.toml`), not **Kimi CLI** (Moonshot's Python CLI, `~/.kimi/config.toml`).
## Symptom
Before the Phase 1 descriptor split (epic #2505), GSD conflated both products under a single `kimi` runtime. If you ran `--kimi --global` on Kimi Code:
- `gsd-tools query agent-skills <name>` returned **empty** (the Python kimi-cli agent YAMLs are inert on Kimi Code).
- Every workflow that called a named GSD subagent (`gsd-planner`, `gsd-executor`, …) **failed at dispatch** (Kimi Code only recognizes `coder`, `explore`, `plan`).
- Every GSD guard hook (the `PreToolUse` guards `gsd-prompt-guard`, `gsd-read-guard`, `gsd-worktree-path-guard`, `gsd-workflow-guard`, and the `PostToolUse` scanner `gsd-read-injection-scanner`) was **silently dormant** (#2304) — the matcher was translated but the payload check wasn't, so the hooks exited 0 on every Kimi-vocabulary tool call.
> **Scope after the fix (#2547):** normalization makes each hook's *checks* run. It does not make all of them *enforceable* on Kimi. Only **PreToolUse** results are consulted by kimi-cli, so the enforceable blocks are the worktree cross-root write block and the workflow force-add block. `gsd-read-injection-scanner` is **PostToolUse**, whose results kimi-cli discards, so its prompt-injection block does not apply on Kimi regardless of what it emits.
## Which product am I on?
| Check | Kimi CLI (Python) | Kimi Code (Node) |
|---|---|---|
| Config file | `~/.kimi/config.toml` | `~/.kimi-code/config.toml` (`KIMI_CODE_HOME`) |
| Built-in subagents | Custom via YAML (`extend:`, `system_prompt_path`) | Three only: `coder`, `explore`, `plan` |
| Skills discovery | `~/.config/agents/skills` or `~/.agents/skills` | `~/.kimi-code/skills/` (auto, `merge_all_available_skills = true`) |
| Language | Python (`kimi-cli`) | Node |
If `~/.kimi-code/config.toml` exists and `~/.kimi/config.toml` does not, you're on Kimi Code.
## Migration steps
### 1. Re-install with `--kimi-code`
```bash
npx @opengsd/gsd-core --kimi-code --global
```
This installs the correct Agent Skills surface at `~/.kimi-code/skills/gsd-*/SKILL.md` (Phase 2) and activates the Phase 0 guard normalization (the dormant-guard fix). The Phase 5 installer will warn you if you accidentally pick the wrong variant.
### 2. Remove inert Python-kimi-cli artifacts (if any)
If your prior `--kimi` install wrote agent YAMLs (the `kimi-agents` artifact layout) into your config dir, they're inert on Kimi Code — Kimi Code cannot read them. Safe to remove:
```bash
# Only if you previously installed via --kimi and are now on --kimi-code:
rm -rf ~/.config/agents/agents/gsd-*.yaml ~/.agents/agents/gsd-*.yaml 2>/dev/null || true
```
### 3. Reclaim GSD hooks a pre-1.10.0 install left in `~/.kimi`
Before 1.10.0 (#2755), a `--kimi-code` install wrote its GSD `[[hooks]]` block, hook bundle and CommonJS marker into Kimi CLI's `~/.kimi/` instead of Kimi Code's own root. Upgrading fixes the destination but cannot clean up what the old bug already wrote, so those artifacts stay in `~/.kimi/` indefinitely — nothing reads them, and no uninstall path reaches them.
Reclaim them by adding `--reclaim-kimi-legacy` to the re-install:
```bash
npx @opengsd/gsd-core --kimi-code --global --reclaim-kimi-legacy
```
This removes GSD's managed `[[hooks]]` block from `~/.kimi/config.toml`, plus GSD's own hook scripts, `hooks/lib/` helpers and CommonJS marker under `~/.kimi/`. Only exact GSD-owned filenames are touched: your own `config.toml` sections, your own scripts, and any `package.json` you wrote yourself are left alone, and directories are removed only when that cleanup leaves them empty.
> **Do not pass this flag if you also use Kimi CLI.** GSD wraps its entries in the same `# GSD Hooks BEGIN`/`END` markers whichever product it installed for, and the command paths inside them are derived from the hooks root — so a block the old bug wrote for Kimi Code is **byte-identical** to the one a legitimate `--kimi` install writes. Nothing on disk can tell them apart, which is exactly why this cleanup is opt-in rather than automatic: on a machine with both products, the flag would remove Kimi CLI's working hooks. If you use both, leave `~/.kimi` alone — the leftovers are inert for Kimi Code and harmless for Kimi CLI. To remove them later, uninstall Kimi CLI's install properly instead: `npx @opengsd/gsd-core --kimi --global --uninstall`.
The flag never acts silently. It is skipped, with a notice saying so, in each case where reclaiming would be wrong or impossible:
| Situation | What happens |
|---|---|
| The install is not `--kimi-code`, or is `--local` | Warns that the flag was ignored — nothing in `~/.kimi` is touched. |
| The same invocation also installs `--kimi` (including via `--all`) | Skipped: that run is creating a live Kimi CLI install in `~/.kimi`, so the flag's premise does not hold. |
| `KIMI_SHARE_DIR` and `KIMI_CODE_HOME` name the same directory | Skipped: there is no separate legacy root, and reclaiming would delete the hooks this install just wrote. Aliases count — a symlink or a case variant on a case-insensitive filesystem is recognized as the same directory. |
| No GSD artifacts are found in `~/.kimi` | Reports that there was nothing to reclaim. |
### 4. Verify skills are discovered
After re-install, launch Kimi Code and confirm the GSD skills appear in the `/skill:` menu (or whatever surface Kimi Code uses for auto-discovered Agent Skills). Each `gsd-*` skill should be present at `~/.kimi-code/skills/gsd-*/SKILL.md`.
### 5. Verify agent-skills query
```bash
gsd-tools query agent-skills gsd-planner
```
Should return the planner's prompt content (non-empty) — Phase 3's fallback reads the installed agent prompt on non-Claude runtimes.
## What about workflows that dispatch named subagents?
Phase 4 (epic #2505) added runtime-aware dispatch. Workflows now resolve the subagent type via `gsd_run query resolve-dispatch-type --requested <role> --raw` before dispatching. On Kimi Code, a role like `gsd-planner` resolves to the `plan` built-in; the persona rides `${AGENT_SKILLS_PLANNER}` (Phase 3's fallback) regardless of the resolved type. You do not need to edit any workflow files — the resolution is automatic.
## What about the dormant guards?
Phase 0 (#2304 / PR #2518) made all seven Kimi-surface hooks read Kimi's payload shape, so their checks now run instead of exiting 0 on every call. Re-installing via `--kimi-code --global` picks up the fix automatically — the normalized guard scripts are part of the standard install.
What that does and does not buy you (#2547):
- **Enforceable on Kimi** — the `PreToolUse` blocks: the worktree cross-root write block (`gsd-worktree-path-guard`) and the workflow force-add block (`gsd-workflow-guard`). Kimi awaits `PreToolUse` results and honours a `block`.
- **Not enforceable on Kimi** — `gsd-read-injection-scanner`'s prompt-injection block. It is a `PostToolUse` hook, and kimi-cli's dispatch never inspects `PostToolUse` results, so the block cannot take effect there no matter what the hook emits. On Kimi, treat the read-injection scanner as advisory-only and rely on the prompt-level untrusted-input boundary instead.
## Questions
- **Can I keep both `--kimi` and `--kimi-code` installs?** Yes — they install to separate config dirs (`~/.kimi/` vs `~/.kimi-code/`). Run both if you genuinely use both products.
- **I only ever used Kimi Code — why is there anything in `~/.kimi` at all?** A GSD install older than 1.10.0 put it there (#2755). See step 3 above to reclaim it.
- **Do I need to uninstall the old `--kimi` install first?** No — `--kimi-code --global` writes to `~/.kimi-code/`, which is separate. But if you no longer use Python kimi-cli, uninstalling the old install keeps things clean: `npx @opengsd/gsd-core --kimi --global --uninstall`.