fix: clarify browser routing and repair relocated skill links
This commit is contained in:
@@ -11,7 +11,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import test from "node:test";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { buildPlan, run } from "./pi-links.mjs";
|
||||
import { buildPlan, main, run } from "./pi-links.mjs";
|
||||
|
||||
const repositoryRoot = path.resolve(
|
||||
path.dirname(fileURLToPath(import.meta.url)),
|
||||
@@ -295,6 +295,68 @@ test("normal exposure and package evidence retain the Slice 0 security boundary"
|
||||
);
|
||||
});
|
||||
|
||||
test("explicit relocation repairs only exact manifest links, including dangling targets", async () => {
|
||||
const options = await fixture();
|
||||
const oldRoot = path.join(options.root, "old-repo");
|
||||
const initial = await buildPlan(options);
|
||||
for (const link of initial.state.links) {
|
||||
await mkdir(path.dirname(link.destination), { recursive: true });
|
||||
const oldTarget = path.join(oldRoot, path.relative(options.repoRoot, link.source));
|
||||
await symlink(link.relative ? path.relative(path.dirname(link.destination), oldTarget) : oldTarget, link.destination);
|
||||
}
|
||||
assert.equal((await buildPlan(options)).conflicts.length, initial.state.links.length);
|
||||
const migration = { ...options, migrateFrom: oldRoot };
|
||||
const plan = await buildPlan(migration);
|
||||
assert.equal(plan.conflicts.length, 0);
|
||||
assert.equal(plan.operations.length, initial.state.links.length);
|
||||
assert.ok(plan.operations.every(op => op.type === "replace-link"));
|
||||
assert.equal(await run("sync", { ...migration, dryRun: true }), 0);
|
||||
assert.equal((await buildPlan(options)).conflicts.length, initial.state.links.length);
|
||||
assert.equal(await run("sync", migration), 0);
|
||||
assert.equal(await run("check", options), 0);
|
||||
});
|
||||
|
||||
test("relocation preserves wrong-path, unrelated, prefix-lookalike and regular-file collisions", async () => {
|
||||
for (const kind of ["wrong-path", "unrelated", "prefix", "file"]) {
|
||||
const options = await fixture();
|
||||
const oldRoot = path.join(options.root, "old-repo");
|
||||
const destination = path.join(options.homeDir, ".agents", "skills", "global-skill");
|
||||
await mkdir(path.dirname(destination), { recursive: true });
|
||||
const target = kind === "wrong-path" ? path.join(oldRoot, "skills/wrong")
|
||||
: kind === "prefix" ? `${oldRoot}-other/skills/group/global-skill`
|
||||
: path.join(options.root, "unrelated");
|
||||
if (kind === "file") await writeFile(destination, "keep");
|
||||
else await symlink(target, destination);
|
||||
const migration = { ...options, migrateFrom: oldRoot };
|
||||
assert.equal((await buildPlan(migration)).conflicts.length, 1);
|
||||
assert.equal(await run("sync", migration), 2);
|
||||
assert.equal(kind === "file" ? await readFile(destination, "utf8") : await readlink(destination), kind === "file" ? "keep" : target);
|
||||
}
|
||||
});
|
||||
|
||||
test("relocation never claims stale old-root links or local packages for pruning", async () => {
|
||||
const options = await fixture();
|
||||
const oldRoot = path.join(options.root, "old-repo");
|
||||
await run("sync", options);
|
||||
const stale = path.join(options.homeDir, ".agents", "skills", "old-excluded");
|
||||
await symlink(path.join(oldRoot, "skills/old-excluded"), stale);
|
||||
const settings = JSON.parse(await readFile(options.settingsFile, "utf8"));
|
||||
settings.packages.push(path.join(oldRoot, "extensions/old/index.ts"));
|
||||
await writeJson(options.settingsFile, settings);
|
||||
const plan = await buildPlan({ ...options, migrateFrom: oldRoot });
|
||||
assert.deepEqual(plan.operations, []);
|
||||
await assert.rejects(() => run("prune", { ...options, migrateFrom: oldRoot }), /never authorizes pruning/);
|
||||
assert.equal(await readlink(stale), path.join(oldRoot, "skills/old-excluded"));
|
||||
});
|
||||
|
||||
test("migration rejects invalid roots and malformed CLI options", async () => {
|
||||
const options = await fixture();
|
||||
for (const migrateFrom of ["relative", "", options.repoRoot, options.root, path.join(options.repoRoot, "nested")])
|
||||
await assert.rejects(() => buildPlan({ ...options, migrateFrom }), /migrate-from/);
|
||||
for (const args of [["sync", "--migrate-from"], ["sync", "unexpected"], ["sync", "--migrate-from", "/old", "--migrate-from", "/other"]])
|
||||
assert.equal(await main(args), 2);
|
||||
});
|
||||
|
||||
test("duplicate declared skill names fail before planning changes", async () => {
|
||||
const options = await fixture();
|
||||
await writeSkill(
|
||||
|
||||
Reference in New Issue
Block a user