From d1c8b326892cad65c6a4c8af0632340f272aec57 Mon Sep 17 00:00:00 2001 From: 0xdhx Date: Mon, 3 Aug 2026 18:24:10 -0500 Subject: [PATCH] fix(#2665): watch kimi-code's config.toml, and pin it by name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rebase onto next brought in #2755, which added a SECOND Kimi config home — kimi-code's `~/.kimi-code`, overridden by KIMI_CODE_HOME — declared as an inline object literal inside resolveKimiHooksTomlDir's body. That is the resolvable-but-not-enumerable shape round 3 hoisted KIMI_SHARE_DIR out of, so the hoist is extended to cover both descriptors rather than reverting #2755's parameterization. The scrub set was already complete: KIMI_CODE_HOME is declared in capabilities/kimi-code/capability.json, so the registry rung covered it and CONFIG_LOCATION_ENV_KEYS is 28 keys both before and after the rebase. What was NOT covered is the guard — resolveExtraWatchTargets iterates NON_REGISTRY_CONFIG_HOME_DESCRIPTORS, so with only one entry it watched Kimi CLI's config.toml and never Kimi Code's. Targets go 2 -> 3. The existing 'extra targets are DERIVED from the descriptor array' test cannot catch this: it builds its expectation FROM the array, so removing an entry shrinks the expectation with it. Verified — with the kimi-code descriptor removed that test still passes while the new named test fails. This is the enumeration-relative scope boundary the suite already documents one layer down, biting one layer up. Also rewrites NON_REGISTRY_OWNED_FILE's docblock, which asserted "today's only such descriptor is kimi's ~/.kimi". There are now two, and its named residual is load-bearing rather than vacuous. --- scripts/live-config-guard.cjs | 16 +++++++++------- tests/live-config-guard.test.cjs | 25 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/scripts/live-config-guard.cjs b/scripts/live-config-guard.cjs index eec8f65f5..3577b329c 100644 --- a/scripts/live-config-guard.cjs +++ b/scripts/live-config-guard.cjs @@ -80,13 +80,15 @@ const GSD_ARTIFACT_PREFIX = 'gsd-'; /** * The file GSD writes into a NON-REGISTRY config home. * - * Today's only such descriptor is kimi's `~/.kimi` (KIMI_SHARE_DIR), where GSD - * writes its native `[[hooks]]` block into `config.toml`. NAMED RESIDUAL: this - * assumes every non-registry descriptor is written the same way. A future - * descriptor whose owned file differs needs a per-descriptor mapping here — the - * consequence of getting it wrong is under-watching (a missed leak), not a false - * positive, so it fails in the quiet direction and is called out rather than - * left to be discovered. + * Both current descriptors are Kimi's — Kimi CLI's `~/.kimi` (KIMI_SHARE_DIR) and + * Kimi Code's `~/.kimi-code` (KIMI_CODE_HOME) — and GSD writes its native + * `[[hooks]]` block into `config.toml` in each, so the single filename below holds + * for both. NAMED RESIDUAL: this assumes every non-registry descriptor is written + * the same way. That assumption is now load-bearing rather than vacuous — it is + * carrying two descriptors, not one — and a future descriptor whose owned file + * differs needs a per-descriptor mapping here. The consequence of getting it wrong + * is under-watching (a missed leak), not a false positive, so it fails in the quiet + * direction and is called out rather than left to be discovered. */ const NON_REGISTRY_OWNED_FILE = 'config.toml'; diff --git a/tests/live-config-guard.test.cjs b/tests/live-config-guard.test.cjs index a12c46006..d54f836cc 100644 --- a/tests/live-config-guard.test.cjs +++ b/tests/live-config-guard.test.cjs @@ -229,6 +229,31 @@ describe('#2665: guard watches non-root write surfaces', () => { } }); + test('#2755: kimi-code config.toml is watched — named, not enumeration-relative', () => { + // The test above derives its expectation FROM the descriptor array, so it + // passes for whatever that array happens to contain and cannot see a + // descriptor that was never added — the enumeration-relative scope boundary + // this suite already calls out one layer down. #2755 landed kimi-code's + // `~/.kimi-code` (KIMI_CODE_HOME) on `next` as an inline literal inside + // resolveKimiHooksTomlDir's body; until it was hoisted into + // NON_REGISTRY_CONFIG_HOME_DESCRIPTORS the guard watched Kimi CLI's + // config.toml and not Kimi Code's. Naming the path is what makes dropping + // the descriptor fail loudly instead of quietly shrinking the expectation. + const home = tmpRoot(); + const codeHome = tmpRoot(); + try { + const env = { GSD_HOME: home, KIMI_CODE_HOME: codeHome }; + const targets = resolveExtraWatchTargets({ env, os: { homedir: () => home } }); + assert.ok( + targets.includes(path.resolve(path.join(codeHome, 'config.toml'))), + `expected kimi-code config.toml in ${JSON.stringify(targets)}`, + ); + } finally { + cleanup(home); + cleanup(codeHome); + } + }); + test('GSD_HOME falls back to homedir when unset', () => { const home = tmpRoot(); try {