fix(#2755): resolve the kimi hooks-TOML root per runtime (#3032)

* test(#2755): failing-first coverage for per-runtime kimi hooks root

Install/uninstall filesystem-shape rows over a sandbox HOME (no permission
tricks) plus resolver unit rows. Covers both uninstall directions, which is
where a fix applied only to the install call site would drift.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(#2755): resolve the kimi hooks-TOML root per runtime

resolveKimiHooksTomlDir took no runtime argument and hardcoded ~/.kimi, but
both kimi and kimi-code route through the single hooksSurface=kimi-hooks-toml
branch. A --kimi-code install therefore wrote its [[hooks]] block, hook bundle
and CommonJS marker into Kimi CLI's config file, and a --kimi-code uninstall
stripped Kimi CLI's block.

Adds a runtime selector to the resolver -- kimi keeps ~/.kimi + KIMI_SHARE_DIR,
kimi-code gets ~/.kimi-code + KIMI_CODE_HOME, per Kimi Code's own upstream
data-locations and hooks docs -- and passes the runtime at both the install and
uninstall call sites. An omitted or unrecognized runtime still resolves ~/.kimi,
so the exported no-arg contract is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(#2755): use centralized helpers and add a divergence guard

Review findings, all fixed in-PR:

- The new test block reimplemented runMinimalInstall, createTempDir and
  toPosixPath. Extends runMinimalInstall with optional root/extraEnv instead
  (back-compat: every existing caller passes neither) and uses the centralized
  helpers, per CONTRIBUTING's Use Centralized Test Helpers rule.

- Adds a parity assertion between the capability registry and the resolver: a
  third runtime declaring hooksSurface kimi-hooks-toml would silently inherit
  ~/.kimi, re-creating this very defect. The guard fires the moment those two
  surfaces drift.

- Adds an installer-level test proving KIMI_SHARE_DIR and KIMI_CODE_HOME do not
  interfere when both are set, which only the resolver unit covered before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(#2755): track the kimi-code hooks root in the emitted-artifact gates

The remote runner caught a real ripple: moving kimi-code hooks to ~/.kimi-code
made 31 emitted paths unattributable and 58 emitted hashes unexplained, because
three parallel surfaces keyed on the literal .kimi path.

- HOOK_CONFIG_RELATIVE_PATHS excluded only .kimi/config.toml, so kimi-code's
  config.toml became manifest-visible; it embeds a platform-varying node-runner
  command and must stay out for both products.
- HOOKS_ROOTS, the package.json-marker branch and the synthesized-install-metadata
  pattern each named .kimi only.
- tests/fixtures/install-tree/kimi-code.json still recorded the old paths;
  regenerated via gen:install-tree.

Adds the per-PR drift acknowledgment for the 58 paths whose bytes are unchanged
but whose destination moved - a ripple no source diff can show, since no hook
script was edited.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(#2755): clear production-tree security advisories

The remote runner's npm-integrity gate reported 2 high advisories in the
production dependency tree. My diff touches neither package.json nor
package-lock.json, so these come from the base -- but a red gate is not
something to wave off as pre-existing, so it is fixed here rather than deferred.

Lockfile-only, semver-in-range, via npm audit fix:
  fast-uri   3.1.4  -> 3.1.5   (host confusion via backslash authority introducer)
  ip-address 10.2.0 -> 10.4.0  (three SSRF / trust-boundary bypasses)
  hono       4.12.31 -> 4.13.0 (moderate; reverting it traded a high for a
                                moderate, so the full remedy is taken)

npm audit now reports 0 vulnerabilities at every severity, npm ci installs
clean from the updated lockfile, and the build and the kimi behavior both
re-verified afterwards.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore(#2755): backfill changeset pr numbers

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-08-03 18:56:42 -04:00
committed by GitHub
parent fd64389616
commit c6ce4d1d9a
12 changed files with 451 additions and 74 deletions

View File

@@ -8082,11 +8082,12 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
// 1a-kimi. Non-layout Kimi side-effect (#2095 EoS/kimi Upgrade 1): kimi's
// native config.toml lives outside targetDir entirely (resolveKimiHooksTomlDir
// resolves ~/.kimi, a sibling of targetDir's ~/.config/agents), so its
// resolves ~/.kimi for kimi and ~/.kimi-code for kimi-code (#2755), a sibling
// of targetDir's ~/.config/agents), so its
// cleanup can't be driven by anything under targetDir the way every other
// hook surface above is.
if (resolveInstallPlan(runtime).hooksSurface === 'kimi-hooks-toml') {
const kimiHooksRoot = resolveKimiHooksTomlDir();
const kimiHooksRoot = resolveKimiHooksTomlDir({ runtime });
const kimiHooksTomlPath = path.join(kimiHooksRoot, 'config.toml');
const kimiHooksCleanup = removeKimiHooksToml(kimiHooksTomlPath);
if (kimiHooksCleanup.changed) {
@@ -11924,7 +11925,8 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
// hooks needed. Kimi is also artifact-only for its INSTALL surface (skills +
// kimi-agents, no settings.json) but #2095 Upgrade 1 gives it its own
// independent hooksSurface: kimi's native config.toml [[hooks]] array, which
// lives outside targetDir entirely (resolveKimiHooksTomlDir resolves ~/.kimi,
// lives outside targetDir entirely (resolveKimiHooksTomlDir resolves the
// per-runtime root — ~/.kimi for kimi, ~/.kimi-code for kimi-code, #2755 —
// a sibling of targetDir's ~/.config/agents) — hence writing it here, inside
// this early-return, rather than requiring installSurface to change.
//
@@ -11945,7 +11947,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
// ~/.kimi/hooks/<script> rather than a script that doesn't exist under
// targetDir/hooks (which kimi no longer receives).
if (plan.hooksSurface === 'kimi-hooks-toml' && isGlobal) {
const kimiHooksRoot = resolveKimiHooksTomlDir();
const kimiHooksRoot = resolveKimiHooksTomlDir({ runtime });
// Note: the `failures` array's hard-fail gate (`if (failures.length > 0)
// process.exit(1)`) runs earlier in this function, before this
// profile-marker-only branch is ever reached — pushing to it here would
@@ -11961,7 +11963,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
//
// Done HERE rather than in installer-migration 007 (which retires the same
// stale marker for every other runtime) because kimi's hook root is
// ~/.kimi — resolved by resolveKimiHooksTomlDir, OUTSIDE kimi's configDir.
// the per-runtime Kimi root — resolved by resolveKimiHooksTomlDir, OUTSIDE the configDir.
// Migration relPaths are structurally confined to configDir, so the
// framework cannot address this path at all. Same exact-content predicate
// either way, so a user-authored ~/.kimi/package.json is never touched.