mirror of
https://github.com/morluto/rea.git
synced 2026-11-05 14:26:07 +00:00
104 lines
4.7 KiB
TypeScript
104 lines
4.7 KiB
TypeScript
import { spawnSync } from "node:child_process";
|
|
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { expect, it } from "vitest";
|
|
|
|
it.each(
|
|
(
|
|
[
|
|
["gemini", "relative path", false],
|
|
["gemini", "profile traversal", false],
|
|
["gemini", "profile root alias", false],
|
|
["gemini", "skill root alias", false],
|
|
["gemini", "skill traversal", false],
|
|
["gemini", "owned child", true],
|
|
["copilot", "relative path", false],
|
|
["copilot", "sibling", false],
|
|
["copilot", "skill traversal", false],
|
|
["copilot", "owned child", true],
|
|
["qwen", "relative path", false],
|
|
["qwen", "profile traversal", false],
|
|
["qwen", "profile root alias", false],
|
|
["qwen", "skill root alias", false],
|
|
["qwen", "skill traversal", false],
|
|
["qwen", "profile child", true],
|
|
["qwen", "skill child", true],
|
|
] as const
|
|
).filter(([client]) => process.platform !== "win32" || client !== "copilot"),
|
|
)(
|
|
"%s checks %s setup-plan targets before writing",
|
|
async (client, kind, allowed) => {
|
|
const state = await mkdtemp(join(tmpdir(), "rea-client-plan-"));
|
|
try {
|
|
const commands = join(state, "commands.mjs");
|
|
const fixture = join(state, "fixture.mjs");
|
|
const hook = join(state, "hook.mjs");
|
|
await writeFile(
|
|
commands,
|
|
`import {writeFile} from 'node:fs/promises'; import {join} from 'node:path';
|
|
export async function createVerifierCommands(){return {
|
|
async exec(command,args,options={}){
|
|
if(command==='git')return {stdout:'',stderr:''};
|
|
if(args.includes('--version'))return {stdout:'fake client',stderr:''};
|
|
if(args.includes('setup') && args.includes('--dry-run')){
|
|
const client=process.env.CLIENT; const kind=process.env.KIND;
|
|
const profile=client==='gemini'?options.env.GEMINI_CLI_HOME:client==='copilot'?options.env.COPILOT_HOME:options.env.QWEN_HOME;
|
|
const skills=join(client==='gemini'?profile:options.env.HOME,'.agents','skills');
|
|
const skill=join(skills,'reverse-engineer-anything');
|
|
const config=join(profile,client==='gemini'?'.gemini/settings.json':client==='copilot'?'mcp-config.json':'settings.json');
|
|
const candidate=kind==='relative path'?'relative/AGENTS.md':kind==='profile root alias'?profile+'/child/..':kind==='skill root alias'?skills+'/child/..':kind==='profile traversal'?profile+'/../outside/AGENTS.md':kind==='skill traversal'?(client==='copilot'?skill:skills)+'/../outside/AGENTS.md':kind==='sibling'?skill+'-sibling/AGENTS.md':kind==='profile child'?config:join(skill,'SKILL.md');
|
|
const selectedSkill=(kind==='skill traversal'||kind==='skill root alias')&&client==='gemini'?candidate:join(skill,'SKILL.md');
|
|
const actions=client==='qwen'?[{target:candidate}]:[{target:config},{target:client==='copilot'?skill:selectedSkill},{target:candidate}];
|
|
return {stdout:JSON.stringify({status:'planned',plannedActions:actions}),stderr:''};
|
|
}
|
|
await writeFile(process.env.STATE+'/setup-advanced','yes'); throw Error('intentional stop before setup mutation');
|
|
}, async close(){} };}`,
|
|
);
|
|
await writeFile(
|
|
fixture,
|
|
`export async function createOpenAiModelFixture(){return {baseUrl:'http://127.0.0.1:1',async close(){}};}`,
|
|
);
|
|
await writeFile(
|
|
hook,
|
|
`import {registerHooks} from 'node:module';import{pathToFileURL}from'node:url';
|
|
registerHooks({resolve(specifier,context,next){if(context.parentURL?.endsWith('/scripts/verify-'+process.env.CLIENT+'-client.mjs')){let f;if(specifier==='./lib/verify-commands.mjs')f='commands.mjs';if(specifier==='./verify/clients/model-fixture.mjs')f='fixture.mjs';if(f)return{url:pathToFileURL(process.env.STATE+'/'+f).href,shortCircuit:true};}return next(specifier,context);}});`,
|
|
);
|
|
const script = fileURLToPath(
|
|
new URL(
|
|
`../../../scripts/verify-${client}-client.mjs`,
|
|
import.meta.url,
|
|
),
|
|
);
|
|
const result = spawnSync(process.execPath, ["--import", hook, script], {
|
|
encoding: "utf8",
|
|
timeout: 10_000,
|
|
env: {
|
|
...process.env,
|
|
STATE: state,
|
|
CLIENT: client,
|
|
KIND: kind,
|
|
TMPDIR: state,
|
|
TMP: state,
|
|
TEMP: state,
|
|
},
|
|
});
|
|
expect(result.status).not.toBe(0);
|
|
const advanced = await readFile(
|
|
join(state, "setup-advanced"),
|
|
"utf8",
|
|
).then(
|
|
() => true,
|
|
() => false,
|
|
);
|
|
expect(advanced).toBe(allowed);
|
|
expect(result.stderr).toContain(
|
|
allowed ? "intentional stop before setup mutation" : "AssertionError",
|
|
);
|
|
} finally {
|
|
await rm(state, { recursive: true, force: true });
|
|
}
|
|
},
|
|
);
|