mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-19 09:36:59 +03:00
Add Veto harness support (#675)
Adds Veto detection, provider transforms, installation paths, documentation, and regression coverage.\n\nAI-assisted maintainer repair, review, and validation by Codex under maintainer direction.
This commit is contained in:
@@ -9,8 +9,8 @@
|
||||
*/
|
||||
|
||||
import { execSync } from 'node:child_process';
|
||||
import { existsSync, readFileSync, readdirSync, statSync, lstatSync, unlinkSync, mkdirSync, mkdtempSync, writeFileSync, rmSync, rmdirSync, renameSync, createWriteStream, realpathSync, symlinkSync, readlinkSync, cpSync } from 'node:fs';
|
||||
import { join, resolve, dirname, relative, isAbsolute, sep } from 'node:path';
|
||||
import { existsSync, readFileSync, readdirSync, statSync, accessSync, constants, lstatSync, unlinkSync, mkdirSync, mkdtempSync, writeFileSync, rmSync, rmdirSync, renameSync, createWriteStream, realpathSync, symlinkSync, readlinkSync, cpSync } from 'node:fs';
|
||||
import { join, resolve, dirname, relative, isAbsolute, sep, delimiter } from 'node:path';
|
||||
import { createInterface, emitKeypressEvents } from 'node:readline';
|
||||
import { Readable } from 'node:stream';
|
||||
import { pipeline } from 'node:stream/promises';
|
||||
@@ -24,7 +24,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const API_BASE = 'https://impeccable.style';
|
||||
|
||||
// Provider folder names in project roots
|
||||
const PROVIDER_DIRS = ['.claude', '.cursor', '.gemini', '.agents', '.agent', '.github', '.grok', '.hermes', '.kiro', '.opencode', '.pi', '.qoder', '.trae', '.trae-cn', '.rovodev', '.vibe'];
|
||||
const PROVIDER_DIRS = ['.claude', '.cursor', '.gemini', '.agents', '.agent', '.github', '.grok', '.hermes', '.kiro', '.opencode', '.pi', '.qoder', '.trae', '.trae-cn', '.rovodev', '.vibe', '.veto'];
|
||||
const PROVIDER_ALIASES = {
|
||||
agent: '.agent',
|
||||
agents: '.agents',
|
||||
@@ -49,6 +49,7 @@ const PROVIDER_ALIASES = {
|
||||
trae: '.trae',
|
||||
'trae-cn': '.trae-cn',
|
||||
vibe: '.vibe',
|
||||
veto: '.veto',
|
||||
};
|
||||
|
||||
const PROVIDER_DISPLAY = {
|
||||
@@ -68,8 +69,9 @@ const PROVIDER_DISPLAY = {
|
||||
'.trae': { name: 'Trae', input: 'trae' },
|
||||
'.trae-cn': { name: 'Trae CN', input: 'trae-cn' },
|
||||
'.vibe': { name: 'Mistral Vibe', input: 'vibe' },
|
||||
'.veto': { name: 'Veto', input: 'veto' },
|
||||
};
|
||||
const PROVIDER_INPUT_ORDER = ['antigravity', 'claude', 'codex', 'cursor', 'gemini', 'github', 'grok', 'hermes', 'kiro', 'opencode', 'pi', 'qoder', 'trae', 'trae-cn', 'rovo-dev', 'vibe'];
|
||||
const PROVIDER_INPUT_ORDER = ['antigravity', 'claude', 'codex', 'cursor', 'gemini', 'github', 'grok', 'hermes', 'kiro', 'opencode', 'pi', 'qoder', 'trae', 'trae-cn', 'rovo-dev', 'vibe', 'veto'];
|
||||
|
||||
// OpenCode reads global skills from its config directory, not ~/.opencode:
|
||||
// $OPENCODE_CONFIG_DIR, else $XDG_CONFIG_HOME/opencode, else
|
||||
@@ -164,6 +166,10 @@ const GLOBAL_HARNESS_HINTS = [
|
||||
{ home: '.qoder', provider: '.qoder' },
|
||||
{ home: '.rovodev', provider: '.rovodev' },
|
||||
{ home: '.vibe', provider: '.vibe' },
|
||||
// Veto is a CLI harness whose managed skill directory is ~/.veto/skills.
|
||||
// Require its managed state directory as well as the executable so an
|
||||
// unrelated `veto` binary on PATH does not change project install defaults.
|
||||
{ command: 'veto', provider: '.veto' },
|
||||
];
|
||||
|
||||
// Last-resort default when nothing is detected: Claude Code + the universal
|
||||
@@ -666,7 +672,7 @@ async function copyOrExtractLocalBundle(sourceValue) {
|
||||
*/
|
||||
function normalizeForHash(content) {
|
||||
return content
|
||||
.replace(/\.(claude|cursor|agents|agent|github|gemini|codex|grok|hermes|kiro|opencode|pi|qoder|trae|trae-cn|rovodev|vibe)\/skills\//g, '.PROVIDER/skills/');
|
||||
.replace(/\.(claude|cursor|agents|agent|github|gemini|codex|grok|hermes|kiro|opencode|pi|qoder|trae|trae-cn|rovodev|vibe|veto)\/skills\//g, '.PROVIDER/skills/');
|
||||
}
|
||||
|
||||
function hashSkillFile(filePath) {
|
||||
@@ -932,6 +938,25 @@ function userSkillProbePaths(home, harnessDir, provider) {
|
||||
]);
|
||||
}
|
||||
|
||||
function commandOnPath(command) {
|
||||
const candidates = process.platform === 'win32'
|
||||
? [`${command}.exe`, `${command}.cmd`, `${command}.bat`]
|
||||
: [command];
|
||||
for (const directory of String(process.env.PATH || '').split(delimiter)) {
|
||||
if (!directory) continue;
|
||||
for (const candidate of candidates) {
|
||||
const path = resolve(directory, candidate);
|
||||
try {
|
||||
if (statSync(path).isFile()) {
|
||||
accessSync(path, constants.X_OK);
|
||||
return path;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function collectInstallDetections(root, home = homedir()) {
|
||||
const detections = [];
|
||||
for (const provider of PROVIDER_DIRS) {
|
||||
@@ -952,9 +977,14 @@ function collectInstallDetections(root, home = homedir()) {
|
||||
const { provider } = hint;
|
||||
// A hint is either a fixed dir under home or a resolver for harnesses
|
||||
// whose location depends on the environment (OpenCode's config dir).
|
||||
const foundPath = hint.resolve ? hint.resolve(home) : join(home, hint.home);
|
||||
if (!existsSync(foundPath)) continue;
|
||||
const skillProbePaths = hint.resolve
|
||||
const foundPath = hint.command
|
||||
? commandOnPath(hint.command)
|
||||
: hint.resolve ? hint.resolve(home) : join(home, hint.home);
|
||||
if (!foundPath || (!hint.command && !existsSync(foundPath))) continue;
|
||||
if (hint.command && !existsSync(join(home, '.veto'))) continue;
|
||||
const skillProbePaths = hint.command
|
||||
? [userProviderSkillsDir(home, provider)]
|
||||
: hint.resolve
|
||||
? uniquePaths([userProviderSkillsDir(home, provider), join(foundPath, 'skills')])
|
||||
: userSkillProbePaths(home, hint.home, provider);
|
||||
detections.push({
|
||||
@@ -965,7 +995,7 @@ function collectInstallDetections(root, home = homedir()) {
|
||||
installPath: userProviderSkillsDir(home, provider),
|
||||
skillProbePaths,
|
||||
hasRealSkills: skillProbePaths.some(hasRealSkillEntries),
|
||||
reason: 'user harness folder',
|
||||
reason: hint.command ? 'CLI on PATH' : 'user harness folder',
|
||||
});
|
||||
}
|
||||
return detections;
|
||||
|
||||
@@ -18,6 +18,7 @@ export const FILE_DOWNLOAD_PROVIDER_CONFIG_DIRS = Object.freeze({
|
||||
pi: '.pi',
|
||||
qoder: '.qoder',
|
||||
vibe: '.vibe',
|
||||
veto: '.veto',
|
||||
});
|
||||
|
||||
export const FILE_DOWNLOAD_PROVIDERS = Object.freeze(
|
||||
|
||||
Reference in New Issue
Block a user