feat(install): validate agent CLI runtime registries
This commit is contained in:
@@ -9,8 +9,7 @@ import assert from 'node:assert/strict';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { repoRoot, manifest, runBin } from './helpers/run-script.js';
|
||||
|
||||
/** 入口必須認得的四個名字,缺一個就有轉接檔或文件會指向不存在的子指令 */
|
||||
const SUBCOMMANDS = ['install', 'uninstall', 'prompt', 'status'];
|
||||
const SUBCOMMANDS = ['install', 'uninstall', 'prompt', 'status', 'sdlc-version', 'verify'];
|
||||
|
||||
|
||||
// ── 子指令解析 ─────────────────────────────────────────────────────
|
||||
|
||||
@@ -82,9 +82,16 @@ test('轉接檔寫完就驗一次真實的叫用鏈,逐平台回報 pass', asy
|
||||
assert.equal(code, 0);
|
||||
assert.equal(json.ok, true);
|
||||
assert.equal(json.data.verify.ok, true);
|
||||
// 逐平台 pass/fail,而不是只有一個總結
|
||||
assert.deepEqual(byName(json.data.verify).claude, { name: 'claude', ok: true, checked: 2, failures: [] });
|
||||
assert.deepEqual(byName(json.data.verify).kiro, { name: 'kiro', ok: true, checked: 2, failures: [] });
|
||||
const claude = byName(json.data.verify).claude;
|
||||
assert.equal(claude.name, 'claude');
|
||||
assert.equal(claude.ok, true);
|
||||
assert.equal(claude.status, 'pass');
|
||||
assert.deepEqual(claude.runtime.commands, ['sdlc-feat', 'sdlc-plan']);
|
||||
const kiro = byName(json.data.verify).kiro;
|
||||
assert.equal(kiro.name, 'kiro');
|
||||
assert.equal(kiro.ok, true);
|
||||
assert.equal(kiro.status, 'not-supported');
|
||||
assert.deepEqual(kiro.failures, []);
|
||||
});
|
||||
|
||||
test('驗證是真的把 PATH 上的 tea-sdlc 找出來執行,不是查有沒有這個檔', async (t) => {
|
||||
|
||||
@@ -170,7 +170,7 @@ test('支援 command 的四個平台產生 command 轉接檔,另外三個產
|
||||
assert.deepEqual(filesUnder(join(home, '.claude')), ['commands/sdlc-plan.md']);
|
||||
assert.deepEqual(filesUnder(join(home, '.codex')), ['prompts/sdlc-plan.md']);
|
||||
assert.deepEqual(filesUnder(join(home, '.config', 'opencode')), ['command/sdlc-plan.md']);
|
||||
assert.deepEqual(filesUnder(join(home, '.omp')), ['commands/sdlc-plan.md']);
|
||||
assert.deepEqual(filesUnder(join(home, '.omp')), ['agent/commands/sdlc-plan.md']);
|
||||
assert.deepEqual(filesUnder(join(home, '.gemini')), ['skills/sdlc-plan/SKILL.md']);
|
||||
assert.deepEqual(filesUnder(join(home, '.kiro')), ['skills/sdlc-plan/SKILL.md']);
|
||||
assert.deepEqual(filesUnder(join(home, 'work', '.github')), ['skills/sdlc-plan/SKILL.md']);
|
||||
@@ -213,7 +213,7 @@ test('支援關閉自動觸發的 command 平台設上旗標;三個 skill 平
|
||||
join(home, '.claude', 'commands', 'sdlc-plan.md'),
|
||||
join(home, '.codex', 'prompts', 'sdlc-plan.md'),
|
||||
join(home, '.config', 'opencode', 'command', 'sdlc-plan.md'),
|
||||
join(home, '.omp', 'commands', 'sdlc-plan.md'),
|
||||
join(home, '.omp', 'agent', 'commands', 'sdlc-plan.md'),
|
||||
];
|
||||
const skills = [
|
||||
join(home, '.gemini', 'skills', 'sdlc-plan', 'SKILL.md'),
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { existsSync, mkdirSync, rmSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { fakePrompt, makeFakePlugin } from './helpers/fake-plugin.js';
|
||||
import { tmpRoot } from './helpers/run-script.js';
|
||||
|
||||
const PROMPTS = {
|
||||
'sdlc-analyze': fakePrompt('sdlc-analyze'),
|
||||
'sdlc-plan': fakePrompt('sdlc-plan'),
|
||||
};
|
||||
|
||||
function homeWith(t, platform) {
|
||||
mkdirSync(tmpRoot, { recursive: true });
|
||||
const home = join(tmpRoot, `runtime-home-${platform}`);
|
||||
rmSync(home, { recursive: true, force: true });
|
||||
mkdirSync(join(home, 'work'), { recursive: true });
|
||||
const roots = {
|
||||
'oh-my-pi': ['.omp'],
|
||||
claude: ['.claude'],
|
||||
codex: ['.codex'],
|
||||
kiro: ['.kiro'],
|
||||
};
|
||||
mkdirSync(join(home, ...roots[platform]), { recursive: true });
|
||||
t.after(() => rmSync(home, { recursive: true, force: true }));
|
||||
return home;
|
||||
}
|
||||
|
||||
const runInHome = (plugin, home) => (args) =>
|
||||
plugin.run(args, { env: { HOME: home }, cwd: join(home, 'work') });
|
||||
|
||||
test('sdlc-version 回報版本、實際 tea-sdlc executable 與六個流程', async (t) => {
|
||||
const plugin = makeFakePlugin(t, { prompts: PROMPTS });
|
||||
const { json } = await plugin.run(['sdlc-version']);
|
||||
|
||||
assert.equal(json.ok, true);
|
||||
assert.equal(json.data.name, 'tea-sdlc');
|
||||
assert.equal(json.data.version, '0.0.1');
|
||||
assert.equal(json.data.executable, join(plugin.shim, 'tea-sdlc'));
|
||||
assert.deepEqual(json.data.commands, ['sdlc-analyze', 'sdlc-plan']);
|
||||
});
|
||||
|
||||
test('OMP 使用 ~/.omp/agent/commands 作為 runtime registry,六個 command 全部找到才 pass', async (t) => {
|
||||
const plugin = makeFakePlugin(t, { prompts: PROMPTS });
|
||||
const home = homeWith(t, 'oh-my-pi');
|
||||
const run = runInHome(plugin, home);
|
||||
const installed = await run(['install']);
|
||||
|
||||
assert.equal(installed.code, 0);
|
||||
assert.ok(existsSync(join(home, '.omp', 'agent', 'commands', 'sdlc-analyze.md')));
|
||||
assert.equal(installed.json.data.verify.platforms[0].runtime.status, 'pass');
|
||||
assert.deepEqual(installed.json.data.verify.platforms[0].runtime.commands, ['sdlc-analyze', 'sdlc-plan']);
|
||||
});
|
||||
|
||||
test('runtime registry 缺少 command 時 verify exit 1 並列出 missing', async (t) => {
|
||||
const plugin = makeFakePlugin(t, { prompts: PROMPTS });
|
||||
const home = homeWith(t, 'claude');
|
||||
const run = runInHome(plugin, home);
|
||||
await run(['install']);
|
||||
rmSync(join(home, '.claude', 'commands', 'sdlc-plan.md'));
|
||||
|
||||
const result = await run(['verify', '--platform', 'claude']);
|
||||
assert.equal(result.code, 1);
|
||||
assert.equal(result.json.error.code, 'RUNTIME_VERIFY_FAILED');
|
||||
assert.deepEqual(result.json.data.platforms[0].runtime.missing, ['sdlc-plan']);
|
||||
assert.equal(result.json.data.platforms[0].runtime.status, 'fail');
|
||||
});
|
||||
|
||||
test('沒有安全 registry probe 的平台回報 not-supported 而不阻止 install', async (t) => {
|
||||
const plugin = makeFakePlugin(t, { prompts: PROMPTS });
|
||||
const home = homeWith(t, 'kiro');
|
||||
const result = await runInHome(plugin, home)(['install']);
|
||||
|
||||
assert.equal(result.code, 0);
|
||||
assert.equal(result.json.data.verify.platforms[0].runtime.status, 'not-supported');
|
||||
assert.deepEqual(result.json.data.verify.platforms[0].runtime.failures, []);
|
||||
});
|
||||
Reference in New Issue
Block a user