Files
tea-sdlc/scripts/runtime-verify.js
T

135 lines
4.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}