Merge remote-tracking branch 'upstream/next' into kimi-runtime-support

# Conflicts:
#	bin/install.js
#	src/runtime-homes.cts
This commit is contained in:
Viktorplus
2026-06-07 22:27:05 +02:00
33 changed files with 3273 additions and 124 deletions

View File

@@ -28,6 +28,7 @@ const { createTempDir, cleanup } = require('./helpers.cjs');
const {
installRuntimeArtifacts,
installOpencodeFamilySkills,
parseRuntimeInput,
allRuntimes,
} = require('../bin/install.js');
@@ -165,14 +166,44 @@ describe('installRuntimeArtifacts — gemini commands layout', () => {
});
});
describe('installRuntimeArtifacts — cline no-op', () => {
test('cline: no kinds — call succeeds, no dirs created', (t) => {
describe('installRuntimeArtifacts — cursor commands layout (#785)', () => {
test('cursor: skills/ AND commands/ both created; commands/gsd-help.md is plain markdown', (t) => {
const configDir = createTempDir('gsd-ial-cursor-cmds-');
t.after(() => cleanup(configDir));
installRuntimeArtifacts('cursor', configDir, 'global', RESOLVED_CORE);
// Existing skills kind still present
const skillsDir = path.join(configDir, 'skills');
assert.ok(fs.existsSync(skillsDir), 'skills/ must exist');
assert.ok(fs.existsSync(path.join(skillsDir, 'gsd-help', 'SKILL.md')),
'skills/gsd-help/SKILL.md must exist');
// New commands kind (#785)
const commandsDir = path.join(configDir, 'commands');
assert.ok(fs.existsSync(commandsDir), 'commands/ must exist (#785)');
assert.ok(fs.existsSync(path.join(commandsDir, 'gsd-help.md')),
'commands/gsd-help.md must exist (#785)');
// Cursor commands are plain markdown — no YAML frontmatter
const helpContent = fs.readFileSync(path.join(commandsDir, 'gsd-help.md'), 'utf8');
assert.ok(!helpContent.startsWith('---'), 'cursor commands must not start with YAML frontmatter');
});
});
describe('installRuntimeArtifacts — cline skills (#782)', () => {
test('cline: global install writes gsd-prefixed skill dirs under skills/', (t) => {
const configDir = createTempDir('gsd-ial-cline-');
t.after(() => cleanup(configDir));
assert.doesNotThrow(() => installRuntimeArtifacts('cline', configDir, 'global', RESOLVED_CORE));
assert.ok(!fs.existsSync(path.join(configDir, 'skills')));
assert.ok(!fs.existsSync(path.join(configDir, 'commands')));
const skillsDir = path.join(configDir, 'skills');
assert.ok(fs.existsSync(skillsDir), 'skills/ must be created for global cline install');
assert.ok(
fs.existsSync(path.join(skillsDir, 'gsd-help', 'SKILL.md')),
'gsd-help/SKILL.md must exist'
);
});
});
@@ -191,6 +222,83 @@ describe('installRuntimeArtifacts — opencode / kilo flat commands', () => {
}
});
// ─── #784: installOpencodeFamilySkills — skills + path rewrite + preservation ─
// Stage the raw command set the way the installer's _stageSkills() does, so the
// skills writer receives the same input as the flattened-command writer.
function stageRawCommands(runtime, configDir) {
const layout = resolveRuntimeArtifactLayout(runtime, configDir, 'global');
const commandsKind = layout.kinds.find((k) => k.kind === 'commands');
return commandsKind.stage(RESOLVED_CORE);
}
describe('installOpencodeFamilySkills — emits skills/<name>/SKILL.md (#784)', () => {
for (const runtime of ['opencode', 'kilo']) {
test(`${runtime}: writes gsd-help/SKILL.md with name + description`, (t) => {
const configDir = createTempDir(`gsd-ocs-${runtime}-`);
t.after(() => cleanup(configDir));
const raw = stageRawCommands(runtime, configDir);
const count = installOpencodeFamilySkills(runtime, configDir, raw, `${configDir}/`);
assert.ok(count >= 1, 'should report installed skills');
const skillMd = path.join(configDir, 'skills', 'gsd-help', 'SKILL.md');
assert.ok(fs.existsSync(skillMd), 'gsd-help/SKILL.md must exist');
const content = fs.readFileSync(skillMd, 'utf8');
assert.match(content, /^name: gsd-help$/m, 'name matches dir');
assert.match(content, /^description: /m, 'description present');
assert.ok(!/\/gsd:/.test(content), 'no /gsd: colon refs in body');
});
test(`${runtime}: rewrites body paths to the actual install target (#784 path fix)`, (t) => {
const configDir = createTempDir(`gsd-ocp-${runtime}-`);
t.after(() => cleanup(configDir));
// Simulate a custom/local install: pathPrefix points at configDir, NOT the
// runtime's default global config dir. Body refs must use pathPrefix.
const pathPrefix = `${configDir}/`;
installOpencodeFamilySkills(runtime, configDir, stageRawCommands(runtime, configDir), pathPrefix);
const defaultBase = runtime === 'kilo' ? '.config/kilo' : '.config/opencode';
const help = fs.readFileSync(path.join(configDir, 'skills', 'gsd-help', 'SKILL.md'), 'utf8');
// gsd-help references gsd-core workflow files via @<configDir>/gsd-core/...
assert.ok(
help.includes(`${configDir}/gsd-core/`),
'gsd-help body must reference the actual install target via pathPrefix',
);
for (const skillName of fs.readdirSync(path.join(configDir, 'skills'))) {
const body = fs.readFileSync(path.join(configDir, 'skills', skillName, 'SKILL.md'), 'utf8');
assert.ok(
!body.includes(`~/${defaultBase}/`),
`${skillName}: must not leak hardcoded ~/${defaultBase}/ — should use install target`,
);
// Regression guard for the prefix-overlap double-rewrite (e.g. kilo-alt-alt).
assert.ok(
!new RegExp(`${defaultBase.replace(/[\\.*+?^${}()|[\]]/g, '\\$&')}-[^/\\s]*-`).test(body),
`${skillName}: must not contain a doubled config-dir suffix`,
);
}
});
test(`${runtime}: preserves user-owned gsd-dev-preferences across reinstall (#784)`, (t) => {
const configDir = createTempDir(`gsd-ocd-${runtime}-`);
t.after(() => cleanup(configDir));
const userSkill = path.join(configDir, 'skills', 'gsd-dev-preferences');
fs.mkdirSync(userSkill, { recursive: true });
const marker = '---\nname: gsd-dev-preferences\ndescription: mine\n---\nKEEP ME\n';
fs.writeFileSync(path.join(userSkill, 'SKILL.md'), marker);
installOpencodeFamilySkills(runtime, configDir, stageRawCommands(runtime, configDir), `${configDir}/`);
const after = fs.readFileSync(path.join(userSkill, 'SKILL.md'), 'utf8');
assert.ok(after.includes('KEEP ME'), 'user-owned dev-preferences must survive reinstall');
// GSD-managed skills should also be present.
assert.ok(fs.existsSync(path.join(configDir, 'skills', 'gsd-help', 'SKILL.md')));
});
}
});
// ─── Section 7: uninstallRuntimeArtifacts — all runtimes ─────────────────────
describe('uninstallRuntimeArtifacts — removes gsd-owned entries, preserves foreign', () => {