UNPKG

rigjs

Version:

A multi-repos dev tool based on yarn and git.Rigjs is intended to be the simplest way to develop,share and deliver codes between different developers or different projects.

104 lines (95 loc) 4 kB
import print from '../print'; import { requireCrew } from './config'; export type Engine = 'claude' | 'codex' | 'pi'; const VALID_ENGINES: Engine[] = ['claude', 'codex', 'pi']; export type EngineSource = 'explicit' | 'project' | 'crew' | 'host' | 'unresolved'; export interface EngineResolution { engine: Engine | null; source: EngineSource; detail?: string; } export function isEngine(v: unknown): v is Engine { return typeof v === 'string' && (VALID_ENGINES as string[]).includes(v); } /** * Host engine self-detection from the environment (design §2.2). * - claude: `CLAUDECODE`, any `CLAUDE_CODE*`, or `AI_AGENT` containing "claude" * - codex: any `CODEX*`, or `AI_AGENT` containing "codex" * Returns null when neither is present, or both are (ambiguous → caller asks). */ export function detectHostEngine(env: NodeJS.ProcessEnv = process.env): Engine | null { const keys = Object.keys(env); const ai = (env.AI_AGENT || '').toLowerCase(); const claude = !!env.CLAUDECODE || keys.some(k => k.startsWith('CLAUDE_CODE')) || ai.includes('claude'); const codex = keys.some(k => k.startsWith('CODEX')) || ai.includes('codex'); if (claude && !codex) return 'claude'; if (codex && !claude) return 'codex'; return null; } /** * 5-level engine resolution (design §2.2). Highest priority first: * 1. explicit — task `engine:` field / conversation directive (override) * 2. project — project config `defaultExecutor` * 3. crew — crew config `defaultExecutor` * 4. host — host self-detection (`detectHostEngine`) * 5. unresolved — caller raises a pending question; agent never invents an engine * * An invalid explicit value throws; invalid project/crew values are skipped * (treated as unset) so a typo in config can't silently pin the wrong engine. */ export function resolveEngine(opts: { explicit?: string | null; project?: { defaultExecutor?: string } | null; crew?: { defaultExecutor?: string } | null; env?: NodeJS.ProcessEnv; }): EngineResolution { const { explicit, project, crew, env } = opts; if (explicit != null && explicit !== '') { if (!isEngine(explicit)) { throw new Error(`invalid engine "${explicit}". Use one of: ${VALID_ENGINES.join(', ')}.`); } return { engine: explicit, source: 'explicit' }; } if (project && isEngine(project.defaultExecutor)) { return { engine: project.defaultExecutor as Engine, source: 'project' }; } if (crew && isEngine(crew.defaultExecutor)) { return { engine: crew.defaultExecutor as Engine, source: 'crew' }; } const host = detectHostEngine(env); if (host) return { engine: host, source: 'host' }; return { engine: null, source: 'unresolved', detail: 'no explicit/project/crew engine and host undetected — raise a pending question instead of guessing', }; } interface EngineOpts { crew?: string; project?: string; engine?: string; json?: boolean; } /** `rig orchestrate engine` — debug view of which engine resolves and why. */ export default function crewEngine(opts: EngineOpts): void { const crew = requireCrew(opts.crew); const project = opts.project ? (crew.projects || []).find(p => p.name === opts.project) : undefined; if (opts.project && !project) { print.error(`unknown project: ${opts.project}`); process.exit(1); } let res: EngineResolution; try { res = resolveEngine({ explicit: opts.engine, project, crew }); } catch (e: any) { print.error(e.message); process.exit(1); return; } if (opts.json) { // eslint-disable-next-line no-console console.log(JSON.stringify({ ok: res.engine != null, engine: res.engine, source: res.source, project: opts.project || null, detail: res.detail }, null, 2)); return; } if (res.engine == null) { print.warn(`engine unresolved — ${res.detail}.`); process.exitCode = 1; return; } print.info(`engine: ${res.engine} (source: ${res.source}${opts.project ? `, project: ${opts.project}` : ''})`); }