mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
205 lines
7.9 KiB
TypeScript
205 lines
7.9 KiB
TypeScript
import {
|
|
access,
|
|
mkdir,
|
|
readFile,
|
|
readdir,
|
|
symlink,
|
|
writeFile,
|
|
} from "node:fs/promises";
|
|
import { dirname, join } from "node:path";
|
|
|
|
import { expect } from "vitest";
|
|
|
|
import { parseClientConfiguration } from "../../../src/application/ClientConfigurationDocument.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
|
|
cliTest.skipIf(process.platform === "win32")(
|
|
"configures Codex in the filesystem-selected CODEX_HOME and shared skills in HOME with backups and repeat setup",
|
|
async ({ cli }) => {
|
|
const root = await createTestTempDirectory("rea-selected-client-home-");
|
|
const selected = join(root, "selected");
|
|
await mkdir(join(selected, "deep"), { recursive: true });
|
|
await symlink(join(selected, "deep"), join(root, "link"));
|
|
const home = join(root, "home");
|
|
await mkdir(home);
|
|
const codexHome = `${root}/link/..`;
|
|
const config = join(selected, "config.toml");
|
|
const lexicalConfig = join(root, "config.toml");
|
|
const original =
|
|
'model = "selected-model"\n[mcp_servers.other]\ncommand = "other-server"\n';
|
|
const sentinel = 'model = "unselected-model"\n';
|
|
await mkdir(dirname(config), { recursive: true });
|
|
await mkdir(dirname(lexicalConfig), { recursive: true });
|
|
await writeFile(config, original);
|
|
await writeFile(lexicalConfig, sentinel);
|
|
const manifest = join(
|
|
home,
|
|
".agents/skills/reverse-engineer-anything/SKILL.md",
|
|
);
|
|
const lexicalManifest = join(
|
|
root,
|
|
".agents/skills/reverse-engineer-anything/SKILL.md",
|
|
);
|
|
await mkdir(dirname(manifest), { recursive: true });
|
|
await mkdir(dirname(lexicalManifest), { recursive: true });
|
|
const originalSkill = "previous selected instructions\n";
|
|
const sentinelSkill = "unselected instructions\n";
|
|
await writeFile(manifest, originalSkill);
|
|
await writeFile(lexicalManifest, sentinelSkill);
|
|
const run = () =>
|
|
cli.run({
|
|
arguments: [
|
|
"setup",
|
|
"--client",
|
|
"codex",
|
|
"--skill-client",
|
|
"codex",
|
|
"--yes",
|
|
"--json",
|
|
],
|
|
cwd: root,
|
|
environment: {
|
|
HOME: home,
|
|
USERPROFILE: home,
|
|
CODEX_HOME: codexHome,
|
|
HOPPER_LAUNCHER_PATH: join(root, "missing-hopper"),
|
|
GHIDRA_INSTALL_DIR: join(root, "missing-ghidra"),
|
|
JAVA_HOME: join(root, "missing-java"),
|
|
},
|
|
});
|
|
const configured = await run();
|
|
expect(configured.exitCode, configured.stdout + configured.stderr).toBe(0);
|
|
const registered = parseClientConfiguration(
|
|
await readFile(config, "utf8"),
|
|
"toml",
|
|
);
|
|
expect(registered.document.model).toBe("selected-model");
|
|
expect(registered.servers.other).toMatchObject({ command: "other-server" });
|
|
expect(registered.servers.rea).toBeDefined();
|
|
const installed = await readFile(manifest, "utf8");
|
|
expect(installed).toContain("name: reverse-engineer-anything");
|
|
expect(await readFile(`${config}.rea.backup`, "utf8")).toBe(original);
|
|
expect(await readFile(`${manifest}.rea.backup`, "utf8")).toBe(
|
|
originalSkill,
|
|
);
|
|
expect(await readFile(lexicalConfig, "utf8")).toBe(sentinel);
|
|
expect(await readFile(lexicalManifest, "utf8")).toBe(sentinelSkill);
|
|
await expect(access(`${lexicalConfig}.rea.backup`)).rejects.toMatchObject({
|
|
code: "ENOENT",
|
|
});
|
|
await expect(access(`${lexicalManifest}.rea.backup`)).rejects.toMatchObject(
|
|
{ code: "ENOENT" },
|
|
);
|
|
const before = await readFile(config);
|
|
const repeated = await run();
|
|
expect(repeated.exitCode, repeated.stdout + repeated.stderr).toBe(0);
|
|
expect(repeated.json).toMatchObject({
|
|
status: "ready",
|
|
appliedActions: [],
|
|
});
|
|
expect((await readFile(config)).equals(before)).toBe(true);
|
|
expect(await readFile(manifest, "utf8")).toBe(installed);
|
|
expect(await readFile(lexicalConfig, "utf8")).toBe(sentinel);
|
|
expect(await readFile(lexicalManifest, "utf8")).toBe(sentinelSkill);
|
|
expect(
|
|
(await readdir(dirname(manifest))).filter((name) =>
|
|
name.startsWith(".rea-skill-"),
|
|
),
|
|
).toEqual([]);
|
|
},
|
|
);
|
|
|
|
cliTest.skipIf(process.platform === "win32")(
|
|
"installs shared skills in the native HOME selection with backups and repeat setup",
|
|
async ({ cli }) => {
|
|
const root = await createTestTempDirectory("rea-selected-skill-home-");
|
|
const selected = join(root, "selected");
|
|
await mkdir(join(selected, "nested"), { recursive: true });
|
|
await symlink(join(selected, "nested"), join(root, "alias"));
|
|
const home = `${root}/alias/..`;
|
|
const relative = ".agents/skills/reverse-engineer-anything";
|
|
const skill = join(selected, relative);
|
|
const lexicalSkill = join(root, relative);
|
|
const original = "previous selected instructions\n";
|
|
const oldReference = "previous selected reference\n";
|
|
const sentinel = "unselected instructions\n";
|
|
const sentinelReference = "unselected reference\n";
|
|
const reference = "references/connection-and-recovery.md";
|
|
for (const directory of [skill, lexicalSkill])
|
|
await mkdir(join(directory, "references"), { recursive: true });
|
|
await writeFile(join(skill, "SKILL.md"), original);
|
|
await writeFile(join(skill, reference), oldReference);
|
|
await writeFile(join(lexicalSkill, "SKILL.md"), sentinel);
|
|
await writeFile(join(lexicalSkill, reference), sentinelReference);
|
|
const run = (arguments_: readonly string[]) =>
|
|
cli.run({
|
|
arguments: [...arguments_, "--json"],
|
|
cwd: root,
|
|
environment: { HOME: home, USERPROFILE: home },
|
|
});
|
|
const configured = await run([
|
|
"setup",
|
|
"--skill-client",
|
|
"shared",
|
|
"--yes",
|
|
]);
|
|
expect(configured.exitCode, configured.stdout + configured.stderr).toBe(0);
|
|
expect(configured.json).toMatchObject({
|
|
status: "ready",
|
|
appliedActions: ["installed_skill"],
|
|
});
|
|
const installed = await readFile(join(skill, "SKILL.md"), "utf8");
|
|
expect(installed).toContain("name: reverse-engineer-anything");
|
|
const installedReference = await readFile(join(skill, reference), "utf8");
|
|
expect(installedReference).not.toBe(oldReference);
|
|
expect(await readFile(join(skill, "SKILL.md.rea.backup"), "utf8")).toBe(
|
|
original,
|
|
);
|
|
expect(await readFile(`${join(skill, reference)}.rea.backup`, "utf8")).toBe(
|
|
oldReference,
|
|
);
|
|
expect(await readFile(join(lexicalSkill, "SKILL.md"), "utf8")).toBe(
|
|
sentinel,
|
|
);
|
|
expect(await readFile(join(lexicalSkill, reference), "utf8")).toBe(
|
|
sentinelReference,
|
|
);
|
|
for (const path of [
|
|
join(lexicalSkill, "SKILL.md.rea.backup"),
|
|
`${join(lexicalSkill, reference)}.rea.backup`,
|
|
])
|
|
await expect(access(path)).rejects.toMatchObject({ code: "ENOENT" });
|
|
const repeated = await run(["setup", "--skill-client", "shared", "--yes"]);
|
|
expect(repeated.exitCode, repeated.stdout + repeated.stderr).toBe(0);
|
|
expect(repeated.json).toMatchObject({
|
|
status: "ready",
|
|
appliedActions: [],
|
|
});
|
|
expect(await readFile(join(skill, "SKILL.md"), "utf8")).toBe(installed);
|
|
expect(await readFile(join(skill, reference), "utf8")).toBe(
|
|
installedReference,
|
|
);
|
|
const doctor = await run(["doctor", "--skill"]);
|
|
// Doctor also discovers client-specific locations. Gemini uses the lexical
|
|
// HOME sibling, whose deliberately stale sentinel must remain untouched.
|
|
expect(doctor.exitCode, doctor.stdout + doctor.stderr).toBe(1);
|
|
expect(doctor.json).toMatchObject({
|
|
identity: {
|
|
skill: {
|
|
state: "stale",
|
|
issues: [{ client: "gemini_cli", path: lexicalSkill, kind: "stale" }],
|
|
},
|
|
},
|
|
});
|
|
expect(
|
|
(await readdir(skill)).filter((name) => name.startsWith(".rea-skill-")),
|
|
).toEqual([]);
|
|
expect(
|
|
(await readdir(join(skill, "references"))).filter((name) =>
|
|
name.startsWith(".rea-skill-"),
|
|
),
|
|
).toEqual([]);
|
|
},
|
|
);
|