mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-16 16:16:32 +03:00
Fix OpenCode slash command bridge (#483)
Add a first-class OpenCode command bridge across builds, installs, updates, linked installs, and pinned shortcuts. Preserve current provider behavior while backfilling missing or drifted command files.\n\nAI assistance: contributor and maintainer work used AI tools as disclosed in the PR discussion and commits.
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { createTransformer } from '../../../scripts/lib/transformers/factory.js';
|
||||
import { PROVIDERS } from '../../../scripts/lib/transformers/providers.js';
|
||||
|
||||
const config = PROVIDERS.opencode;
|
||||
const transform = createTransformer(config);
|
||||
|
||||
const TEST_DIR = path.join(process.cwd(), 'test-tmp-opencode-commands');
|
||||
const COMMAND_PATH = path.join(
|
||||
TEST_DIR,
|
||||
`${config.provider}/${config.configDir}/commands/impeccable.md`,
|
||||
);
|
||||
|
||||
const SAMPLE_SKILL = {
|
||||
name: 'impeccable',
|
||||
description: 'Use when the user wants to design, redesign, shape, critique, audit, polish, clarify, distill, harden, optimize, adapt, animate, colorize, extract, or otherwise improve a frontend interface.',
|
||||
body: '# Impeccable\n\nSkill body here.',
|
||||
references: [],
|
||||
scripts: [],
|
||||
agents: [],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
if (fs.existsSync(TEST_DIR)) {
|
||||
fs.rmSync(TEST_DIR, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (fs.existsSync(TEST_DIR)) {
|
||||
fs.rmSync(TEST_DIR, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
describe('opencode commands bridge', () => {
|
||||
test('emits .opencode/commands/impeccable.md alongside the skill', () => {
|
||||
transform([SAMPLE_SKILL], TEST_DIR);
|
||||
expect(fs.existsSync(COMMAND_PATH)).toBe(true);
|
||||
});
|
||||
|
||||
test('command frontmatter uses only fields OpenCode recognises', () => {
|
||||
transform([SAMPLE_SKILL], TEST_DIR);
|
||||
const content = fs.readFileSync(COMMAND_PATH, 'utf-8');
|
||||
const fm = content.match(/^---\n([\s\S]*?)\n---/);
|
||||
expect(fm).not.toBeNull();
|
||||
const lines = fm[1].split('\n').map(l => l.trim()).filter(Boolean);
|
||||
const keys = lines.map(l => l.split(':')[0]);
|
||||
// OpenCode only recognises: description, agent, model, variant, subtask (per
|
||||
// opencode/packages/core/src/v1/config/command.ts:5-13).
|
||||
const allowed = new Set(['description', 'agent', 'model', 'variant', 'subtask']);
|
||||
for (const key of keys) {
|
||||
expect(allowed.has(key)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test('command description mirrors the skill description exactly', () => {
|
||||
transform([SAMPLE_SKILL], TEST_DIR);
|
||||
const content = fs.readFileSync(COMMAND_PATH, 'utf-8');
|
||||
const fm = content.match(/^---\n([\s\S]*?)\n---/)[1];
|
||||
const line = fm.split('\n').find(l => l.startsWith('description:'));
|
||||
const value = line.slice('description:'.length).trim().replace(/^"(.*)"$/, '$1');
|
||||
expect(value).toBe(SAMPLE_SKILL.description);
|
||||
});
|
||||
|
||||
test('command body delegates to the impeccable skill', () => {
|
||||
transform([SAMPLE_SKILL], TEST_DIR);
|
||||
const content = fs.readFileSync(COMMAND_PATH, 'utf-8');
|
||||
const body = content.replace(/^---\n[\s\S]*?\n---\n/, '');
|
||||
expect(body).toContain('skill({');
|
||||
expect(body).toContain("name: \"impeccable\"");
|
||||
expect(body).toContain('Setup');
|
||||
expect(body).toContain('Commands');
|
||||
expect(body).toContain('$ARGUMENTS');
|
||||
});
|
||||
|
||||
test('command declares agent: build and subtask: true', () => {
|
||||
transform([SAMPLE_SKILL], TEST_DIR);
|
||||
const content = fs.readFileSync(COMMAND_PATH, 'utf-8');
|
||||
expect(content).toMatch(/^agent: build$/m);
|
||||
expect(content).toMatch(/^subtask: true$/m);
|
||||
});
|
||||
|
||||
test('does not emit Claude-only frontmatter fields on the command', () => {
|
||||
transform([SAMPLE_SKILL], TEST_DIR);
|
||||
const content = fs.readFileSync(COMMAND_PATH, 'utf-8');
|
||||
const fm = content.match(/^---\n([\s\S]*?)\n---/)[1];
|
||||
expect(fm).not.toMatch(/^version:/m);
|
||||
expect(fm).not.toMatch(/^user-invocable:/m);
|
||||
expect(fm).not.toMatch(/^argument-hint:/m);
|
||||
expect(fm).not.toMatch(/^allowed-tools:/m);
|
||||
});
|
||||
|
||||
test('emits no command when the skill is empty', () => {
|
||||
transform([], TEST_DIR);
|
||||
expect(fs.existsSync(path.dirname(COMMAND_PATH))).toBe(false);
|
||||
});
|
||||
|
||||
test('keeps emitting the skill alongside the command', () => {
|
||||
transform([SAMPLE_SKILL], TEST_DIR);
|
||||
const skillPath = path.join(
|
||||
TEST_DIR,
|
||||
`${config.provider}/${config.configDir}/skills/impeccable/SKILL.md`,
|
||||
);
|
||||
expect(fs.existsSync(skillPath)).toBe(true);
|
||||
expect(fs.existsSync(COMMAND_PATH)).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user