From da1b6111e7fe2d76f9f4e8297a6f49903ae6af94 Mon Sep 17 00:00:00 2001 From: Viktorplus <36795799+viktorplus@users.noreply.github.com> Date: Sat, 6 Jun 2026 13:59:55 +0200 Subject: [PATCH] test(02-02): add failing Kimi global skill install tests - Expect Kimi global layout to stage skills/gsd-*/SKILL.md - Assert --kimi --global writes gsd-new-project SKILL.md - Preserve explicit --kimi --local no-op guard --- .../bug-kimi-path-layout-local-guard.test.cjs | 26 +++++++++----- tests/install-runtime-artifacts.test.cjs | 13 +++++-- tests/runtime-artifact-layout.test.cjs | 35 +++++++++++++++++++ 3 files changed, 64 insertions(+), 10 deletions(-) diff --git a/tests/bug-kimi-path-layout-local-guard.test.cjs b/tests/bug-kimi-path-layout-local-guard.test.cjs index 5fad93f0b..2846e82b7 100644 --- a/tests/bug-kimi-path-layout-local-guard.test.cjs +++ b/tests/bug-kimi-path-layout-local-guard.test.cjs @@ -94,11 +94,15 @@ describe('Kimi runtime homes', () => { }); describe('Kimi runtime artifact layout', () => { - test('known runtime with empty Phase 1 layout placeholder', () => { + test('global layout stages Kimi skills while local layout remains guarded', () => { const globalLayout = resolveRuntimeArtifactLayout('kimi', '/tmp/kimi-config', 'global'); assert.strictEqual(globalLayout.runtime, 'kimi'); assert.strictEqual(globalLayout.configDir, '/tmp/kimi-config'); - assert.deepStrictEqual(globalLayout.kinds, []); + assert.strictEqual(globalLayout.kinds.length, 1); + assert.strictEqual(globalLayout.kinds[0].kind, 'skills'); + assert.strictEqual(globalLayout.kinds[0].destSubpath, 'skills'); + assert.strictEqual(globalLayout.kinds[0].prefix, 'gsd-'); + assert.strictEqual(typeof globalLayout.kinds[0].stage, 'function'); const localLayout = resolveRuntimeArtifactLayout('kimi', '/tmp/kimi-config', 'local'); assert.strictEqual(localLayout.runtime, 'kimi'); @@ -139,7 +143,7 @@ describe('Kimi local install guard', () => { } }); - test('--kimi --global exits successfully without writing unconverted Kimi artifacts', () => { + test('--kimi --global writes converted Kimi skills only', () => { const tmpProject = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-kimi-global-project-')); const tmpConfig = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-kimi-global-config-')); const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-kimi-global-home-')); @@ -157,14 +161,20 @@ describe('Kimi local install guard', () => { assert.strictEqual( result.status, 0, - `expected --kimi --global skeleton guard to no-op successfully\nstdout: ${result.stdout}\nstderr: ${result.stderr}`, + `expected --kimi --global to install Kimi skills successfully\nstdout: ${result.stdout}\nstderr: ${result.stderr}`, ); const combined = `${result.stdout}\n${result.stderr}`; - assert.match(combined, /Kimi global install/i); - assert.match(combined, /deferred/i); + assert.match(combined, /Installing for .*Kimi/i); + assert.match(combined, /Installed \d+ skills to skills\//i); - assert.ok(!fs.existsSync(path.join(tmpConfig, 'skills')), 'must not write unconverted Kimi skills'); - assert.ok(!fs.existsSync(path.join(tmpConfig, 'agents')), 'must not write unconverted Kimi agents'); + const skillFile = path.join(tmpConfig, 'skills', 'gsd-new-project', 'SKILL.md'); + assert.ok(fs.existsSync(skillFile), 'must write gsd-new-project/SKILL.md'); + const skillContent = fs.readFileSync(skillFile, 'utf8'); + assert.match(skillContent, /^name: gsd-new-project$/m); + assert.match(skillContent, /\/skill:gsd-new-project/); + assert.doesNotMatch(skillContent, /kimi_cli\.tools|system_prompt_path|^version: 1$/m); + + assert.ok(!fs.existsSync(path.join(tmpConfig, 'agents')), 'must not write Kimi agents'); assert.ok(!fs.existsSync(path.join(tmpConfig, 'gsd-core')), 'must not write workflow payloads as Kimi artifacts'); assert.ok(!fs.existsSync(path.join(tmpConfig, 'hooks')), 'must not write hooks under the Kimi root'); } finally { diff --git a/tests/install-runtime-artifacts.test.cjs b/tests/install-runtime-artifacts.test.cjs index 76fd3d566..e5e867f7d 100644 --- a/tests/install-runtime-artifacts.test.cjs +++ b/tests/install-runtime-artifacts.test.cjs @@ -49,13 +49,13 @@ const RESOLVED_CORE = resolveProfile({ modes: ['core'], manifest: MANIFEST }); const SKILLS_RUNTIMES_LAYOUT = [ 'claude', 'cursor', 'codex', 'copilot', 'antigravity', - 'windsurf', 'augment', 'trae', 'qwen', 'codebuddy', + 'windsurf', 'augment', 'trae', 'qwen', 'kimi', 'codebuddy', ]; const ALL_RUNTIMES_LAYOUT = [ 'claude', 'cursor', 'gemini', 'codex', 'copilot', 'antigravity', 'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy', - 'cline', 'opencode', 'kilo', + 'cline', 'kimi', 'opencode', 'kilo', ]; function countPrefixedEntries(destDir, prefix) { @@ -94,6 +94,15 @@ describe('installRuntimeArtifacts — skills runtimes write gsd-prefixed skill d `${runtime}: ${skillsKind.prefix}help/SKILL.md must exist` ); + if (runtime === 'kimi') { + const newProjectSkill = path.join(destDir, 'gsd-new-project', 'SKILL.md'); + assert.ok(fs.existsSync(newProjectSkill), 'kimi: gsd-new-project/SKILL.md must exist'); + const content = fs.readFileSync(newProjectSkill, 'utf8'); + assert.match(content, /^name: gsd-new-project$/m); + assert.match(content, /\/skill:gsd-new-project/); + assert.doesNotMatch(content, /kimi_cli\.tools|system_prompt_path|^version: 1$/m); + } + if (RESOLVED_CORE.skills !== '*') { const prefixedCount = countPrefixedEntries(destDir, skillsKind.prefix || 'gsd-'); assert.strictEqual(prefixedCount, RESOLVED_CORE.skills.size, diff --git a/tests/runtime-artifact-layout.test.cjs b/tests/runtime-artifact-layout.test.cjs index 01c72657e..415b70c3e 100644 --- a/tests/runtime-artifact-layout.test.cjs +++ b/tests/runtime-artifact-layout.test.cjs @@ -175,6 +175,24 @@ describe('resolveRuntimeArtifactLayout — qwen', () => { }); }); +describe('resolveRuntimeArtifactLayout — kimi', () => { + test('returns global skills layout and guarded empty local layout for kimi', () => { + const globalLayout = resolveRuntimeArtifactLayout('kimi', FAKE_DIR, 'global'); + assert.strictEqual(globalLayout.runtime, 'kimi'); + assert.strictEqual(globalLayout.configDir, FAKE_DIR); + assert.strictEqual(globalLayout.kinds.length, 1); + assert.strictEqual(globalLayout.kinds[0].kind, 'skills'); + assert.strictEqual(globalLayout.kinds[0].destSubpath, 'skills'); + assert.strictEqual(globalLayout.kinds[0].prefix, 'gsd-'); + assert.strictEqual(typeof globalLayout.kinds[0].stage, 'function'); + + const localLayout = resolveRuntimeArtifactLayout('kimi', FAKE_DIR, 'local'); + assert.strictEqual(localLayout.runtime, 'kimi'); + assert.strictEqual(localLayout.configDir, FAKE_DIR); + assert.deepStrictEqual(localLayout.kinds, []); + }); +}); + describe('resolveRuntimeArtifactLayout — hermes', () => { test('returns correct layout for hermes', () => { const layout = resolveRuntimeArtifactLayout('hermes', FAKE_DIR); @@ -379,6 +397,23 @@ describe('stage — skills kind (claude global)', () => { }); }); +describe('stage — skills kind (kimi global)', () => { + test('stage returns Kimi SKILL.md dirs with /skill:gsd-* invocations', () => { + const layout = resolveRuntimeArtifactLayout('kimi', FAKE_STAGE_DIR, 'global'); + const skillsKind = layout.kinds.find(k => k.kind === 'skills'); + assert.ok(skillsKind, 'should have a skills kind'); + + const stagedDir = skillsKind.stage(PROFILE_CORE); + assert.ok(fs.existsSync(stagedDir), 'stagedDir must exist'); + const skillMd = path.join(stagedDir, 'gsd-new-project', 'SKILL.md'); + assert.ok(fs.existsSync(skillMd), 'gsd-new-project/SKILL.md must exist'); + const content = fs.readFileSync(skillMd, 'utf8'); + assert.match(content, /^name: gsd-new-project$/m); + assert.match(content, /\/skill:gsd-new-project/); + assert.doesNotMatch(content, /kimi_cli\.tools|system_prompt_path|^version: 1$/m); + }); +}); + describe('stage — opencode commands kind', () => { test('opencode stage returns directory with .md files for selected skills', () => { const layout = resolveRuntimeArtifactLayout('opencode', FAKE_STAGE_DIR);