From cc447fcd6685ff7bb99a8480f30a78eb3f9318c9 Mon Sep 17 00:00:00 2001 From: Viktorplus <36795799+viktorplus@users.noreply.github.com> Date: Sat, 6 Jun 2026 14:03:07 +0200 Subject: [PATCH] feat(02-02): install Kimi global skills only - Wire Kimi global layout to convertClaudeCommandToKimiSkill - Keep --kimi --local guarded as a no-op - Add Kimi self-invocation hint without agent or tool artifacts --- bin/install.js | 42 +++++++++++++++++++++-------- src/runtime-artifact-layout.cts | 6 ++--- tests/kimi-skill-converter.test.cjs | 4 +-- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/bin/install.js b/bin/install.js index 7e560cae5..d2cbe2b6a 100755 --- a/bin/install.js +++ b/bin/install.js @@ -2179,7 +2179,7 @@ function convertClaudeCommandToKimiSkill(content, skillName, _runtime = null, cm names ); - return `---\nname: ${kimiSkillName}\ndescription: ${yamlQuote(toSingleLine(description))}\n---\n${normalizedBody}`; + return `---\nname: ${kimiSkillName}\ndescription: ${yamlQuote(toSingleLine(description))}\n---\nInvoke this Kimi skill with \`/skill:${kimiSkillName}\`.\n\n${normalizedBody}`; } /** @@ -8291,19 +8291,14 @@ function install(isGlobal, runtime = 'claude', options = {}) { const dirName = getDirName(runtime); const src = path.join(__dirname, '..'); - if (isKimi) { - const scopeLabel = isGlobal ? 'global' : 'local'; - console.log(` ${yellow}⚠${reset} Kimi ${scopeLabel} install is deferred for Phase 1.`); - if (isGlobal) { - console.log(` No Kimi skills, agents, hooks, or workflow payload artifacts were written.`); - } else { - console.log(` No .kimi/skills or .agents/skills project artifacts were written.`); - } - console.log(` Phase 1 only registers Kimi selection, paths, and layout guards.`); + if (isKimi && !isGlobal) { + console.log(` ${yellow}⚠${reset} Kimi local install is deferred for Phase 2.`); + console.log(` No .kimi/skills or .agents/skills project artifacts were written.`); + console.log(` Project-level Kimi install semantics remain deferred.`); return { runtime, skipped: true, - reason: isGlobal ? 'kimi_global_deferred' : 'kimi_local_deferred', + reason: 'kimi_local_deferred', configDir: null, settingsPath: null, settings: null, @@ -8455,6 +8450,31 @@ function install(isGlobal, runtime = 'claude', options = {}) { rollback(); }; + if (isKimi && isGlobal) { + installRuntimeArtifacts(runtime, targetDir, 'global', _resolvedProfile); + const skillsDir = path.join(targetDir, 'skills'); + const count = fs.existsSync(skillsDir) + ? fs.readdirSync(skillsDir, { withFileTypes: true }) + .filter(e => e.isDirectory() && e.name.startsWith('gsd-')).length + : 0; + if (count > 0) { + console.log(` ${green}✓${reset} Installed ${count} skills to skills/`); + } else { + throw new Error('Kimi global install produced no skills/gsd-* entries'); + } + return { + runtime, + skipped: true, + reason: 'kimi_global_skills_only', + configDir: targetDir, + settingsPath: null, + settings: null, + statuslineCommand: null, + updateBannerCommand: null, + rollbackInstallerMigrations, + }; + } + // Save any locally modified GSD files before they get wiped. // The pristine context lets saveLocalPatches populate gsd-pristine/ via // the install transform pipeline, giving the reapply-patches Step 5 diff --git a/src/runtime-artifact-layout.cts b/src/runtime-artifact-layout.cts index b85544ea2..dfe874a87 100644 --- a/src/runtime-artifact-layout.cts +++ b/src/runtime-artifact-layout.cts @@ -305,9 +305,9 @@ function resolveRuntimeArtifactLayout(runtime: string, configDir: string, scope: break; case 'kimi': - // Phase 1 skeleton only: Kimi is recognized by the layout seam, but - // SKILL.md conversion and local project semantics are deferred. - kinds = []; + kinds = scope === 'global' + ? [skillsKind('skills', 'gsd-', 'convertClaudeCommandToKimiSkill', 'kimi', configDir)] + : []; break; case 'opencode': diff --git a/tests/kimi-skill-converter.test.cjs b/tests/kimi-skill-converter.test.cjs index 3d07f6b96..dc5974321 100644 --- a/tests/kimi-skill-converter.test.cjs +++ b/tests/kimi-skill-converter.test.cjs @@ -71,8 +71,8 @@ describe('convertClaudeCommandToKimiSkill', () => { assert.equal( (result.match(/\/skill:gsd-new-project/g) || []).length, - 4, - 'all supported source invocation forms are rewritten' + 5, + 'self invocation hint and all supported source invocation forms are emitted' ); assert.ok(!result.includes('/gsd:new-project'), 'slash colon form is removed'); assert.ok(!result.includes('gsd:new-project'), 'bare colon form is removed');