fix: clarify browser routing and repair relocated skill links

This commit is contained in:
jb
2026-09-20 19:53:44 -04:00
parent 89474017e1
commit 773c628512
5 changed files with 135 additions and 16 deletions
+63 -1
View File
@@ -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(