fix: normalize Kilo skill paths and guard OpenCode permissions
Keep Kilo skill path rewrites consistent and avoid rewriting valid string-valued OpenCode permission configs while preserving resolved config-dir handling.
This commit is contained in:
@@ -2895,8 +2895,9 @@ function convertClaudeToKiloFrontmatter(content, { isAgent = false } = {}) {
|
||||
convertedContent = convertedContent.replace(/~\/\.claude\b/g, '~/.config/kilo');
|
||||
convertedContent = convertedContent.replace(/\$HOME\/\.claude\b/g, '$HOME/.config/kilo');
|
||||
convertedContent = convertedContent.replace(/\.\/\.claude\//g, './.kilo/');
|
||||
// Normalize both Claude skill directory variants to Kilo's canonical skills dir.
|
||||
convertedContent = replaceRelativePathReference(convertedContent, '.claude/skills/', '.kilo/skills/');
|
||||
convertedContent = replaceRelativePathReference(convertedContent, '.agents/skills/', '.kilo/skill/');
|
||||
convertedContent = replaceRelativePathReference(convertedContent, '.agents/skills/', '.kilo/skills/');
|
||||
convertedContent = replaceRelativePathReference(convertedContent, '.claude/agents/', '.kilo/agents/');
|
||||
// Replace general-purpose subagent type with Kilo's equivalent "general"
|
||||
convertedContent = convertedContent.replace(/subagent_type="general-purpose"/g, 'subagent_type="general"');
|
||||
@@ -4337,8 +4338,14 @@ function configureOpencodePermissions(isGlobal = true, configDir = null) {
|
||||
}
|
||||
}
|
||||
|
||||
// OpenCode also supports a top-level string permission like "allow".
|
||||
// In that case, path-specific permission entries are unnecessary.
|
||||
if (typeof config.permission === 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure permission structure exists
|
||||
if (!config.permission) {
|
||||
if (!config.permission || typeof config.permission !== 'object') {
|
||||
config.permission = {};
|
||||
}
|
||||
|
||||
@@ -5619,6 +5626,7 @@ if (process.env.GSD_TEST_MODE) {
|
||||
convertClaudeCommandToCodexSkill,
|
||||
convertClaudeToOpencodeFrontmatter,
|
||||
convertClaudeToKiloFrontmatter,
|
||||
configureOpencodePermissions,
|
||||
neutralizeAgentReferences,
|
||||
GSD_CODEX_MARKER,
|
||||
CODEX_AGENT_SANDBOX,
|
||||
|
||||
61
tests/opencode-permissions.test.cjs
Normal file
61
tests/opencode-permissions.test.cjs
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* GSD Tools Tests - OpenCode permission config
|
||||
*
|
||||
* Regression tests for OpenCode permission handling, including the valid
|
||||
* top-level string form: "permission": "allow".
|
||||
*/
|
||||
|
||||
process.env.GSD_TEST_MODE = '1';
|
||||
|
||||
const { test, describe, beforeEach, afterEach } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const { createTempDir, cleanup } = require('./helpers.cjs');
|
||||
const { configureOpencodePermissions } = require('../bin/install.js');
|
||||
|
||||
const installSrc = fs.readFileSync(path.join(__dirname, '..', 'bin', 'install.js'), 'utf8');
|
||||
|
||||
describe('configureOpencodePermissions', () => {
|
||||
let configDir;
|
||||
|
||||
beforeEach(() => {
|
||||
configDir = createTempDir('gsd-opencode-');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup(configDir);
|
||||
});
|
||||
|
||||
test('does not rewrite top-level string permissions', () => {
|
||||
const configPath = path.join(configDir, 'opencode.json');
|
||||
const original = JSON.stringify({
|
||||
$schema: 'https://opencode.ai/config.json',
|
||||
permission: 'allow',
|
||||
skills: { paths: ['/tmp/skills'] },
|
||||
}, null, 2) + '\n';
|
||||
|
||||
fs.writeFileSync(configPath, original);
|
||||
|
||||
assert.doesNotThrow(() => configureOpencodePermissions(true, configDir));
|
||||
assert.strictEqual(fs.readFileSync(configPath, 'utf8'), original);
|
||||
});
|
||||
|
||||
test('adds path-specific read and external_directory permissions for object configs', () => {
|
||||
const configPath = path.join(configDir, 'opencode.json');
|
||||
fs.writeFileSync(configPath, JSON.stringify({ permission: {} }, null, 2) + '\n');
|
||||
|
||||
configureOpencodePermissions(true, configDir);
|
||||
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
const gsdPath = `${configDir.replace(/\\/g, '/')}/get-shit-done/*`;
|
||||
|
||||
assert.strictEqual(config.permission.read[gsdPath], 'allow');
|
||||
assert.strictEqual(config.permission.external_directory[gsdPath], 'allow');
|
||||
});
|
||||
|
||||
test('finishInstall passes the actual config dir to OpenCode permissions', () => {
|
||||
assert.ok(installSrc.includes('configureOpencodePermissions(isGlobal, configDir);'), 'OpenCode permission config uses actual install dir');
|
||||
});
|
||||
});
|
||||
@@ -158,7 +158,8 @@ Fallback skills live in .agents/skills/.`;
|
||||
assert.ok(result.includes('.kilo/skills/'), '.claude/skills should be replaced for Kilo');
|
||||
assert.ok(result.includes('.kilo/agents/'), '.claude/agents should be replaced for Kilo');
|
||||
assert.ok(result.includes('./.kilo/hooks/'), './.claude should be replaced for Kilo');
|
||||
assert.ok(result.includes('.kilo/skill/'), '.agents/skills should be rewritten to Kilo skill dir');
|
||||
assert.ok(result.includes('Fallback skills live in .kilo/skills/.'), '.agents/skills should be rewritten to Kilo skills dir');
|
||||
assert.ok(!result.includes('.kilo/skill/'), 'singular Kilo skill dir should not be emitted');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user