mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
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.
260 lines
10 KiB
JavaScript
260 lines
10 KiB
JavaScript
import { afterEach, beforeEach, describe, it } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { spawnSync } from 'node:child_process';
|
|
|
|
const ROOT = process.cwd();
|
|
const PIN_SCRIPT = path.join(ROOT, 'skill', 'scripts', 'pin.mjs');
|
|
|
|
// Neutralize any real user-scope OpenCode config so tests never write into the
|
|
// developer's actual global install. Points the resolution at a path that does
|
|
// not exist unless a test creates it.
|
|
function cleanEnv(overrides = {}) {
|
|
return {
|
|
...process.env,
|
|
OPENCODE_CONFIG_DIR: path.join(os.tmpdir(), 'impeccable-pin-no-config'),
|
|
XDG_CONFIG_HOME: path.join(os.tmpdir(), 'impeccable-pin-no-xdg'),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('pin command provider syntax', () => {
|
|
let project;
|
|
|
|
beforeEach(() => {
|
|
project = fs.mkdtempSync(path.join(os.tmpdir(), 'impeccable-pin-'));
|
|
fs.writeFileSync(path.join(project, 'package.json'), '{}\n');
|
|
for (const harness of ['.claude', '.cursor', '.agents', '.codex']) {
|
|
fs.mkdirSync(path.join(project, harness, 'skills', 'impeccable'), { recursive: true });
|
|
}
|
|
});
|
|
|
|
afterEach(() => {
|
|
fs.rmSync(project, { recursive: true, force: true });
|
|
});
|
|
|
|
it('renders each pinned shortcut for its target harness', () => {
|
|
const result = spawnSync(process.execPath, [PIN_SCRIPT, 'pin', 'audit'], {
|
|
cwd: project,
|
|
encoding: 'utf8',
|
|
env: cleanEnv(),
|
|
});
|
|
|
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
|
|
for (const harness of ['.claude', '.cursor']) {
|
|
const skill = fs.readFileSync(path.join(project, harness, 'skills', 'audit', 'SKILL.md'), 'utf8');
|
|
assert.match(skill, /\/impeccable audit/);
|
|
assert.doesNotMatch(skill, /\$impeccable audit/);
|
|
assert.match(skill, /^argument-hint:/m);
|
|
assert.match(skill, /^user-invocable: true$/m);
|
|
}
|
|
|
|
for (const harness of ['.agents', '.codex']) {
|
|
const skill = fs.readFileSync(path.join(project, harness, 'skills', 'audit', 'SKILL.md'), 'utf8');
|
|
assert.match(skill, /\$impeccable audit/);
|
|
assert.doesNotMatch(skill, /\/impeccable audit/);
|
|
assert.doesNotMatch(skill, /^argument-hint:/m);
|
|
assert.doesNotMatch(skill, /^user-invocable:/m);
|
|
assert.match(skill, /^metadata:\n argument-hint:/m);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('pin command OpenCode target', () => {
|
|
let project;
|
|
|
|
beforeEach(() => {
|
|
project = fs.mkdtempSync(path.join(os.tmpdir(), 'impeccable-pin-oc-'));
|
|
fs.writeFileSync(path.join(project, 'package.json'), '{}\n');
|
|
fs.mkdirSync(path.join(project, '.opencode', 'skills', 'impeccable'), { recursive: true });
|
|
});
|
|
|
|
afterEach(() => {
|
|
fs.rmSync(project, { recursive: true, force: true });
|
|
});
|
|
|
|
it('writes a slash command bridge for OpenCode, not a skill shortcut', () => {
|
|
const result = spawnSync(process.execPath, [PIN_SCRIPT, 'pin', 'audit'], {
|
|
cwd: project,
|
|
encoding: 'utf8',
|
|
env: cleanEnv(),
|
|
});
|
|
|
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
|
|
const commandPath = path.join(project, '.opencode', 'commands', 'impeccable-audit.md');
|
|
assert.ok(fs.existsSync(commandPath), `expected ${commandPath}`);
|
|
const content = fs.readFileSync(commandPath, 'utf8');
|
|
assert.match(content, /---\ndescription:.*audit/);
|
|
assert.match(content, /agent: build/);
|
|
assert.match(content, /subtask: true/);
|
|
assert.match(content, /<skill-base-dir>\/reference\/audit\.md/);
|
|
assert.doesNotMatch(content, /user-invocable:/);
|
|
assert.doesNotMatch(content, /argument-hint:/);
|
|
|
|
const skillPath = path.join(project, '.opencode', 'skills', 'audit', 'SKILL.md');
|
|
assert.equal(fs.existsSync(skillPath), false, 'OpenCode pin must not create a skill shortcut');
|
|
});
|
|
|
|
it('unpin removes only the OpenCode command bridge', () => {
|
|
spawnSync(process.execPath, [PIN_SCRIPT, 'pin', 'audit'], { cwd: project, encoding: 'utf8', env: cleanEnv() });
|
|
const commandPath = path.join(project, '.opencode', 'commands', 'impeccable-audit.md');
|
|
assert.ok(fs.existsSync(commandPath));
|
|
|
|
const result = spawnSync(process.execPath, [PIN_SCRIPT, 'unpin', 'audit'], {
|
|
cwd: project,
|
|
encoding: 'utf8',
|
|
env: cleanEnv(),
|
|
});
|
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
assert.equal(fs.existsSync(commandPath), false);
|
|
});
|
|
|
|
it('unpin cleans the project command after the skill was removed', () => {
|
|
spawnSync(process.execPath, [PIN_SCRIPT, 'pin', 'audit'], { cwd: project, encoding: 'utf8', env: cleanEnv() });
|
|
const commandPath = path.join(project, '.opencode', 'commands', 'impeccable-audit.md');
|
|
assert.ok(fs.existsSync(commandPath));
|
|
|
|
// Skill removed before unpin (e.g. uninstall): cleanup must still find the pin.
|
|
fs.rmSync(path.join(project, '.opencode', 'skills', 'impeccable'), { recursive: true, force: true });
|
|
|
|
const result = spawnSync(process.execPath, [PIN_SCRIPT, 'unpin', 'audit'], {
|
|
cwd: project,
|
|
encoding: 'utf8',
|
|
env: cleanEnv(),
|
|
});
|
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
assert.equal(fs.existsSync(commandPath), false, 'stale pin must be removed after skill removal');
|
|
});
|
|
|
|
it('unpin after skill removal leaves non-pinned user commands alone', () => {
|
|
const commandsDir = path.join(project, '.opencode', 'commands');
|
|
fs.mkdirSync(commandsDir, { recursive: true });
|
|
const commandPath = path.join(commandsDir, 'impeccable-audit.md');
|
|
fs.writeFileSync(commandPath, 'my own command, not a pin\n');
|
|
fs.rmSync(path.join(project, '.opencode', 'skills', 'impeccable'), { recursive: true, force: true });
|
|
|
|
const result = spawnSync(process.execPath, [PIN_SCRIPT, 'unpin', 'audit'], {
|
|
cwd: project,
|
|
encoding: 'utf8',
|
|
env: cleanEnv(),
|
|
});
|
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
assert.ok(fs.existsSync(commandPath), 'non-pinned user command must survive cleanup');
|
|
});
|
|
});
|
|
|
|
describe('pin command OpenCode user scope', () => {
|
|
let project;
|
|
let config;
|
|
|
|
beforeEach(() => {
|
|
project = fs.mkdtempSync(path.join(os.tmpdir(), 'impeccable-pin-usr-'));
|
|
fs.writeFileSync(path.join(project, 'package.json'), '{}\n');
|
|
config = fs.mkdtempSync(path.join(os.tmpdir(), 'impeccable-pin-cfg-'));
|
|
});
|
|
|
|
afterEach(() => {
|
|
fs.rmSync(project, { recursive: true, force: true });
|
|
fs.rmSync(config, { recursive: true, force: true });
|
|
});
|
|
|
|
function installUserScopeSkill(dir = config) {
|
|
fs.mkdirSync(path.join(dir, 'skills', 'impeccable'), { recursive: true });
|
|
}
|
|
|
|
it('pins into the user config dir when only a global install exists', () => {
|
|
installUserScopeSkill();
|
|
const result = spawnSync(process.execPath, [PIN_SCRIPT, 'pin', 'audit'], {
|
|
cwd: project,
|
|
encoding: 'utf8',
|
|
env: cleanEnv({ OPENCODE_CONFIG_DIR: config }),
|
|
});
|
|
|
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
assert.doesNotMatch(result.stdout, /No harness directories/);
|
|
const commandPath = path.join(config, 'commands', 'impeccable-audit.md');
|
|
assert.ok(fs.existsSync(commandPath), `expected ${commandPath}`);
|
|
assert.match(fs.readFileSync(commandPath, 'utf8'), /impeccable-pinned-command/);
|
|
assert.equal(
|
|
fs.existsSync(path.join(project, '.opencode', 'commands')),
|
|
false,
|
|
'must not create a project commands dir for a user-scope install',
|
|
);
|
|
});
|
|
|
|
it('unpin removes the user-scope pinned command', () => {
|
|
installUserScopeSkill();
|
|
spawnSync(process.execPath, [PIN_SCRIPT, 'pin', 'audit'], {
|
|
cwd: project,
|
|
encoding: 'utf8',
|
|
env: cleanEnv({ OPENCODE_CONFIG_DIR: config }),
|
|
});
|
|
const commandPath = path.join(config, 'commands', 'impeccable-audit.md');
|
|
assert.ok(fs.existsSync(commandPath));
|
|
|
|
const result = spawnSync(process.execPath, [PIN_SCRIPT, 'unpin', 'audit'], {
|
|
cwd: project,
|
|
encoding: 'utf8',
|
|
env: cleanEnv({ OPENCODE_CONFIG_DIR: config }),
|
|
});
|
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
assert.equal(fs.existsSync(commandPath), false);
|
|
});
|
|
|
|
it('unpin removes the user-scope pinned command after the global skill was removed', () => {
|
|
installUserScopeSkill();
|
|
spawnSync(process.execPath, [PIN_SCRIPT, 'pin', 'audit'], {
|
|
cwd: project,
|
|
encoding: 'utf8',
|
|
env: cleanEnv({ OPENCODE_CONFIG_DIR: config }),
|
|
});
|
|
const commandPath = path.join(config, 'commands', 'impeccable-audit.md');
|
|
assert.ok(fs.existsSync(commandPath));
|
|
|
|
// Global skill removed before unpin: cleanup must still find the pin.
|
|
fs.rmSync(path.join(config, 'skills', 'impeccable'), { recursive: true, force: true });
|
|
|
|
const result = spawnSync(process.execPath, [PIN_SCRIPT, 'unpin', 'audit'], {
|
|
cwd: project,
|
|
encoding: 'utf8',
|
|
env: cleanEnv({ OPENCODE_CONFIG_DIR: config }),
|
|
});
|
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
assert.equal(fs.existsSync(commandPath), false, 'stale user-scope pin must be removed after skill removal');
|
|
});
|
|
|
|
it('pins in both scopes when project and user installs coexist', () => {
|
|
installUserScopeSkill();
|
|
fs.mkdirSync(path.join(project, '.opencode', 'skills', 'impeccable'), { recursive: true });
|
|
const result = spawnSync(process.execPath, [PIN_SCRIPT, 'pin', 'audit'], {
|
|
cwd: project,
|
|
encoding: 'utf8',
|
|
env: cleanEnv({ OPENCODE_CONFIG_DIR: config }),
|
|
});
|
|
|
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
assert.ok(fs.existsSync(path.join(config, 'commands', 'impeccable-audit.md')), 'user-scope pin');
|
|
assert.ok(fs.existsSync(path.join(project, '.opencode', 'commands', 'impeccable-audit.md')), 'project pin');
|
|
});
|
|
|
|
it('honours XDG_CONFIG_HOME when OPENCODE_CONFIG_DIR is unset', () => {
|
|
const xdg = fs.mkdtempSync(path.join(os.tmpdir(), 'impeccable-pin-xdg-'));
|
|
installUserScopeSkill(path.join(xdg, 'opencode'));
|
|
try {
|
|
const result = spawnSync(process.execPath, [PIN_SCRIPT, 'pin', 'audit'], {
|
|
cwd: project,
|
|
encoding: 'utf8',
|
|
env: cleanEnv({ OPENCODE_CONFIG_DIR: undefined, XDG_CONFIG_HOME: xdg }),
|
|
});
|
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
assert.ok(fs.existsSync(path.join(xdg, 'opencode', 'commands', 'impeccable-audit.md')));
|
|
} finally {
|
|
fs.rmSync(xdg, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|