import { existsSync, readdirSync } from 'node:fs'; import { homedir } from 'node:os'; import { basename, dirname, join } from 'node:path'; import { onPath } from './lib.js'; /** Runtime probes are deliberately local, non-interactive, and network-free. */ const SPECS = { 'oh-my-pi': { binary: 'omp', supported: true, root: (home) => join(home, '.omp', 'agent', 'commands') }, claude: { binary: 'claude', supported: true, root: (home) => join(home, '.claude', 'commands') }, codex: { binary: 'codex', supported: true, root: (home) => join(home, '.codex', 'prompts') }, opencode: { binary: 'opencode', supported: true, root: (home) => join(home, '.config', 'opencode', 'command') }, antigravity: { binary: 'gemini', supported: false }, kiro: { binary: 'kiro', supported: false }, copilot: { binary: 'gh', supported: false }, }; const PROBE_VERSION = 1; /** * @param {{name: string, adapters: {name: string, path: string}[]}[]} platforms * @returns {object[]} */ export function verifyRuntimePlatforms(platforms) { return platforms.map((platform) => verifyRuntimePlatform(platform)); } /** * @param {{name: string, adapters: {name: string, path: string}[]}} platform * @returns {object} */ function verifyRuntimePlatform(platform) { const spec = SPECS[platform.name]; if (!spec || !spec.supported) { return { name: platform.name, status: 'not-supported', probe: { command: spec?.binary ?? null, args: [], network: false, interactive: false, version: PROBE_VERSION }, resolved: spec ? onPath(spec.binary) : null, commands: [], missing: [], failures: [], remediation: '此 AI Agent CLI 沒有可供 tea-sdlc 安全呼叫的 command registry probe;請在該 CLI 內手動確認指令。', }; } const resolved = onPath(spec.binary); const probe = { command: 'filesystem-registry', args: [spec.binary], network: false, interactive: false, version: PROBE_VERSION, }; if (resolved === null) { return unsupportedRuntime( platform, probe, null, `找不到 ${spec.binary},沒有可安全執行的 runtime probe`, `請安裝 ${spec.binary} 並把它加入 PATH 後再重跑 tea-sdlc verify`, ); } const root = dirname(platform.adapters[0]?.path ?? spec.root(process.env.HOME || homedir())); const expected = platform.adapters.map(({ name }) => name); const available = new Set( existsSync(root) ? readdirSync(root, { withFileTypes: true }) .filter((entry) => entry.isFile() && entry.name.endsWith('.md')) .map((entry) => basename(entry.name, '.md')) : [], ); const commands = expected.filter((name) => available.has(name)); const missing = expected.filter((name) => !available.has(name)); if (missing.length > 0) { return failedRuntime( platform, { ...probe, registry: root }, resolved, `runtime registry 找不到:${missing.join('、')}`, `確認 ${root} 是 ${spec.binary} 的 command registry,然後重跑 tea-sdlc install`, commands, missing, ); } return { name: platform.name, status: 'pass', probe: { ...probe, registry: root }, resolved, commands, missing, failures: [], remediation: null, }; } function unsupportedRuntime(platform, probe, resolved, message, remediation) { return { name: platform.name, status: 'not-supported', probe, resolved, commands: [], missing: platform.adapters.map(({ name }) => name), failures: [], remediation: `${message};${remediation}`, }; } function failedRuntime(platform, probe, resolved, message, remediation, commands = [], missing = platform.adapters.map(({ name }) => name)) { const failure = { path: probe.registry ?? null, 病灶: message, 修復: remediation, }; return { name: platform.name, status: 'fail', probe, resolved, commands, missing, failures: [failure], remediation, }; } /** @returns {number} */ export function runtimeProbeVersion() { return PROBE_VERSION; }