mirror of
https://github.com/sickn33/agentic-awesome-skills.git
synced 2026-09-14 18:06:35 +08:00
5f1cc811a1
The supported path now carries a reviewed selection through manifest/plan inspection to the direct installer's own preview. Single-target plans infer their target and verify the cached runtime's catalog against the manifest before planning. The installer verifies release identity before materializing a sparse canonical skill tree, preserving complete bundles while avoiding plugin/docs/app checkout. On the same published release, temporary worktree storage fell 70.9%; every canonical entry matched. A freshly packed installer preview and real isolated installation preserved all 22 files across three selected trees. npm payload size is unchanged by this optimization. These changes are explicitly documented as unreleased. The source-only classifier also treats a Git copy as changing its destination, preserving raw-record safety checks. Negative tests still reject generated-file renames, copies into generated paths and real generated modifications. This fixes the unchanged copy-origin classification currently blocking #1346; that PR will be revalidated against the merged protected implementation. Co-authored-by: sck_0 <188885273+sck000@users.noreply.github.com>
304 lines
12 KiB
JavaScript
304 lines
12 KiB
JavaScript
const assert = require("assert");
|
|
const fs = require("fs");
|
|
const os = require("os");
|
|
const path = require("path");
|
|
const { spawnSync } = require("child_process");
|
|
|
|
const installer = require(path.resolve(__dirname, "..", "..", "bin", "install.js"));
|
|
const packageVersion = require(path.resolve(__dirname, "..", "..", "..", "package.json")).version;
|
|
const pinnedRef = `v${packageVersion}`;
|
|
|
|
function writeSkill(repoRoot, skillPath, frontmatter) {
|
|
const skillDir = path.join(repoRoot, "skills", skillPath);
|
|
fs.mkdirSync(skillDir, { recursive: true });
|
|
fs.writeFileSync(
|
|
path.join(skillDir, "SKILL.md"),
|
|
`---\n${frontmatter}\n---\n\n# ${skillPath}\n`,
|
|
"utf8",
|
|
);
|
|
}
|
|
|
|
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "installer-exact-selection-"));
|
|
|
|
try {
|
|
const repoRoot = path.join(tmpRoot, "repo");
|
|
fs.mkdirSync(path.join(repoRoot, "skills"), { recursive: true });
|
|
fs.mkdirSync(path.join(repoRoot, "docs"), { recursive: true });
|
|
fs.writeFileSync(path.join(repoRoot, "docs", "README.md"), "# Docs\n", "utf8");
|
|
|
|
writeSkill(
|
|
repoRoot,
|
|
"frontend-design",
|
|
"name: frontend-design\ncategory: development\nrisk: safe\ntags: [frontend]",
|
|
);
|
|
writeSkill(
|
|
repoRoot,
|
|
path.join("game-development", "2d-games"),
|
|
"name: 2D Games\ncategory: games\nrisk: safe\ntags: [game]",
|
|
);
|
|
writeSkill(
|
|
repoRoot,
|
|
path.join("legacy-games", "2d-games"),
|
|
"name: Legacy 2D Games\ncategory: games\nrisk: safe\ntags: [game]",
|
|
);
|
|
writeSkill(
|
|
repoRoot,
|
|
"shared-one",
|
|
"name: Shared Skill\ncategory: development\nrisk: safe\ntags: [shared]",
|
|
);
|
|
writeSkill(
|
|
repoRoot,
|
|
"shared-two",
|
|
"name: Shared Skill\ncategory: development\nrisk: safe\ntags: [shared]",
|
|
);
|
|
|
|
const requested = installer.parseExactSkillArg("frontend-design,game-development/2d-games");
|
|
assert.deepStrictEqual(
|
|
installer.getInstallEntries(repoRoot, installer.buildInstallSelectors({}), requested),
|
|
["frontend-design", "game-development/2d-games", "docs"],
|
|
"--skills should resolve exact root ids and full nested paths",
|
|
);
|
|
|
|
assert.deepStrictEqual(
|
|
installer.getInstallEntries(
|
|
repoRoot,
|
|
installer.buildInstallSelectors({ categoryArg: "development" }),
|
|
requested,
|
|
),
|
|
["frontend-design", "docs"],
|
|
"--skills must combine with metadata filters using AND",
|
|
);
|
|
|
|
assert.throws(
|
|
() => installer.getInstallEntries(
|
|
repoRoot,
|
|
installer.buildInstallSelectors({ categoryArg: "games" }),
|
|
installer.parseExactSkillArg("frontend-design"),
|
|
),
|
|
/No skills matched/i,
|
|
"an empty exact-selection/filter intersection must fail instead of installing docs or a broad fallback",
|
|
);
|
|
|
|
assert.throws(
|
|
() => installer.getInstallEntries(
|
|
repoRoot,
|
|
installer.buildInstallSelectors({}),
|
|
installer.parseExactSkillArg("does-not-exist"),
|
|
),
|
|
/unknown skill requested/i,
|
|
"unknown exact skill selections must fail",
|
|
);
|
|
assert.throws(
|
|
() => installer.getInstallEntries(
|
|
repoRoot,
|
|
installer.buildInstallSelectors({}),
|
|
installer.parseExactSkillArg("2d-games"),
|
|
),
|
|
/ambiguous skill requested/i,
|
|
"ambiguous basename selections must require a full nested path",
|
|
);
|
|
assert.throws(
|
|
() => installer.getInstallEntries(
|
|
repoRoot,
|
|
installer.buildInstallSelectors({}),
|
|
installer.parseExactSkillArg("Shared Skill"),
|
|
),
|
|
/ambiguous skill requested/i,
|
|
"ambiguous canonical names must fail",
|
|
);
|
|
|
|
const absentTarget = path.join(tmpRoot, "absent-target");
|
|
assert.throws(
|
|
() => installer.installForTarget(
|
|
repoRoot,
|
|
{ name: "Absent", path: absentTarget },
|
|
installer.buildInstallSelectors({}),
|
|
null,
|
|
installer.parseExactSkillArg("does-not-exist"),
|
|
),
|
|
/unknown skill requested/i,
|
|
);
|
|
assert.strictEqual(
|
|
fs.existsSync(absentTarget),
|
|
false,
|
|
"invalid --skills selection must fail before an absent target is created",
|
|
);
|
|
|
|
const existingTarget = path.join(tmpRoot, "existing-target");
|
|
fs.mkdirSync(existingTarget, { recursive: true });
|
|
const sentinelPath = path.join(existingTarget, "sentinel.txt");
|
|
fs.writeFileSync(sentinelPath, "keep", "utf8");
|
|
const manifestPath = path.join(existingTarget, ".antigravity-install-manifest.json");
|
|
fs.writeFileSync(
|
|
manifestPath,
|
|
JSON.stringify({
|
|
schemaVersion: 1,
|
|
entries: ["frontend-design", "legacy-skill", "docs", "../outside"],
|
|
}),
|
|
"utf8",
|
|
);
|
|
const plan = installer.buildDryRunPlan(
|
|
pinnedRef,
|
|
[
|
|
{ name: "Existing", path: existingTarget },
|
|
{ name: "Absent", path: absentTarget },
|
|
],
|
|
["game-development/2d-games", "frontend-design", "docs"],
|
|
);
|
|
assert.deepStrictEqual(plan.targets[0].remove, ["legacy-skill"]);
|
|
assert.deepStrictEqual(plan.targets[0].ignoredUnsafeManifestEntries, ["../outside"]);
|
|
assert.strictEqual(plan.targets[1].targetExists, false);
|
|
const output = [];
|
|
const originalLog = console.log;
|
|
console.log = (message) => output.push(String(message));
|
|
try {
|
|
installer.printDryRunPlan(plan);
|
|
} finally {
|
|
console.log = originalLog;
|
|
}
|
|
assert.strictEqual(fs.readFileSync(sentinelPath, "utf8"), "keep");
|
|
assert.strictEqual(fs.existsSync(absentTarget), false);
|
|
assert.match(output.join("\n"), new RegExp(`Ref: ${pinnedRef.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`));
|
|
assert.match(output.join("\n"), new RegExp(existingTarget.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
|
|
assert.match(output.join("\n"), /Exact skill set \(2\):/);
|
|
assert.match(output.join("\n"), /frontend-design/);
|
|
assert.match(output.join("\n"), /remove stale managed entries \(1\)/i);
|
|
assert.match(output.join("\n"), /legacy-skill/);
|
|
assert.match(output.join("\n"), /ignored unsafe manifest entry: \.\.\/outside/i);
|
|
|
|
// Exercise the real CLI boundary with a deterministic fake git clone. This
|
|
// proves --dry-run returns before installForTarget can mutate either an
|
|
// existing target or a missing one.
|
|
const fakeBin = path.join(tmpRoot, "fake-bin");
|
|
fs.mkdirSync(fakeBin, { recursive: true });
|
|
const fakeGit = path.join(fakeBin, "git");
|
|
fs.writeFileSync(
|
|
fakeGit,
|
|
`#!/usr/bin/env node
|
|
const fs = require("fs");
|
|
const args = process.argv.slice(2);
|
|
if (args[0] === "clone") {
|
|
const destination = args[args.length - 1];
|
|
fs.appendFileSync(process.env.FAKE_GIT_LOG, JSON.stringify(args) + "\\n");
|
|
fs.cpSync(process.env.FAKE_GIT_SOURCE, destination, { recursive: true, force: true });
|
|
} else if (args[0] === "-C" && args[2] === "rev-parse" && args[3] === "HEAD") {
|
|
process.stdout.write(process.env.FAKE_GIT_HEAD + "\\n");
|
|
} else if (args[0] === "-C" && args.slice(2).join(" ") === "sparse-checkout set --cone skills") {
|
|
fs.appendFileSync(process.env.FAKE_GIT_LOG, JSON.stringify(args) + "\\n");
|
|
if (process.env.FAKE_SPARSE_FAILURE === "1") process.exitCode = 2;
|
|
} else {
|
|
process.exitCode = 1;
|
|
}
|
|
`,
|
|
"utf8",
|
|
);
|
|
fs.chmodSync(fakeGit, 0o755);
|
|
|
|
const fakeNpm = path.join(fakeBin, "npm");
|
|
fs.writeFileSync(
|
|
fakeNpm,
|
|
`#!/usr/bin/env node
|
|
if (process.argv[2] !== "view" || !process.argv.includes("gitHead")) {
|
|
process.exitCode = 1;
|
|
} else {
|
|
process.stdout.write(JSON.stringify(process.env.FAKE_NPM_GIT_HEAD) + "\\n");
|
|
}
|
|
`,
|
|
"utf8",
|
|
);
|
|
fs.chmodSync(fakeNpm, 0o755);
|
|
|
|
const fakeGitLog = path.join(tmpRoot, "fake-git-log.jsonl");
|
|
const fakeReleaseHead = "1111111111111111111111111111111111111111";
|
|
const cliEnv = {
|
|
...process.env,
|
|
FAKE_GIT_SOURCE: repoRoot,
|
|
FAKE_GIT_LOG: fakeGitLog,
|
|
FAKE_GIT_HEAD: fakeReleaseHead,
|
|
FAKE_NPM_GIT_HEAD: fakeReleaseHead,
|
|
PATH: `${fakeBin}${path.delimiter}${process.env.PATH || ""}`,
|
|
};
|
|
const installerPath = path.resolve(__dirname, "..", "..", "bin", "install.js");
|
|
const beforeDryRunEntries = fs.readdirSync(existingTarget).sort();
|
|
const dryRun = spawnSync(
|
|
process.execPath,
|
|
[installerPath, "--path", existingTarget, "--release", packageVersion, "--skills", "frontend-design", "--dry-run"],
|
|
{ encoding: "utf8", env: cliEnv },
|
|
);
|
|
assert.strictEqual(dryRun.status, 0, dryRun.stderr);
|
|
assert.match(dryRun.stdout, new RegExp(`Ref: ${pinnedRef.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`));
|
|
assert.match(dryRun.stdout, /Exact skill set \(1\):/);
|
|
assert.match(dryRun.stdout, /remove stale managed entries \(1\)/i);
|
|
assert.match(dryRun.stdout, /legacy-skill/);
|
|
assert.deepStrictEqual(fs.readdirSync(existingTarget).sort(), beforeDryRunEntries);
|
|
assert.strictEqual(fs.readFileSync(sentinelPath, "utf8"), "keep");
|
|
const cloneArgs = JSON.parse(fs.readFileSync(fakeGitLog, "utf8").trim().split("\n")[0]);
|
|
assert.deepStrictEqual(
|
|
cloneArgs.slice(0, 5),
|
|
["clone", "--depth", "1", "--branch", pinnedRef],
|
|
"the real CLI dry run must clone the explicitly pinned release",
|
|
);
|
|
assert.ok(cloneArgs.includes("--filter=blob:none"));
|
|
assert.ok(cloneArgs.includes("--sparse"));
|
|
const sparseArgs = JSON.parse(fs.readFileSync(fakeGitLog, "utf8").trim().split("\n")[1]);
|
|
assert.deepStrictEqual(sparseArgs.slice(2), ["sparse-checkout", "set", "--cone", "skills"]);
|
|
const checkoutFailure = spawnSync(process.execPath, [installerPath, "--path", existingTarget, "--skills", "frontend-design"], {
|
|
encoding: "utf8", env: { ...cliEnv, FAKE_SPARSE_FAILURE: "1" },
|
|
});
|
|
assert.equal(checkoutFailure.status, 1);
|
|
assert.match(checkoutFailure.stderr, /failed/);
|
|
assert.deepStrictEqual(fs.readdirSync(existingTarget).sort(), beforeDryRunEntries);
|
|
assert.equal(fs.existsSync(sparseArgs[1]), false, "successful preview cleans its temporary source");
|
|
const lastInvocation = JSON.parse(fs.readFileSync(fakeGitLog, "utf8").trim().split("\n").at(-1));
|
|
assert.equal(fs.existsSync(lastInvocation[1]), false, "failed sparse checkout also cleans temporary source");
|
|
|
|
const outsideSentinel = path.join(tmpRoot, "outside");
|
|
fs.writeFileSync(outsideSentinel, "outside stays", "utf8");
|
|
assert.doesNotThrow(() => installer.pruneRemovedEntries(existingTarget, ["../outside"], []));
|
|
assert.strictEqual(fs.readFileSync(outsideSentinel, "utf8"), "outside stays");
|
|
|
|
const unknownTarget = path.join(tmpRoot, "unknown-target");
|
|
const unknown = spawnSync(
|
|
process.execPath,
|
|
[installerPath, "--path", unknownTarget, "--skills", "does-not-exist", "--dry-run"],
|
|
{ encoding: "utf8", env: cliEnv },
|
|
);
|
|
assert.strictEqual(unknown.status, 1);
|
|
assert.match(unknown.stderr, /Unknown skill requested/);
|
|
assert.strictEqual(fs.existsSync(unknownTarget), false);
|
|
|
|
const multiHome = path.join(tmpRoot, "multi-home");
|
|
const firstTarget = path.join(multiHome, ".claude", "skills");
|
|
const codexHome = path.join(tmpRoot, "codex-home");
|
|
const unsafeTarget = path.join(codexHome, "skills");
|
|
const unsafeRealTarget = path.join(tmpRoot, "unsafe-real-target");
|
|
fs.mkdirSync(firstTarget, { recursive: true });
|
|
fs.mkdirSync(codexHome, { recursive: true });
|
|
fs.mkdirSync(unsafeRealTarget, { recursive: true });
|
|
const firstSentinel = path.join(firstTarget, "first-sentinel.txt");
|
|
fs.writeFileSync(firstSentinel, "first stays", "utf8");
|
|
let createdUnsafeSymlink = false;
|
|
try {
|
|
fs.symlinkSync(unsafeRealTarget, unsafeTarget, "dir");
|
|
createdUnsafeSymlink = true;
|
|
} catch (error) {
|
|
// Some platforms disallow unprivileged directory symlinks.
|
|
}
|
|
if (createdUnsafeSymlink) {
|
|
const multiTarget = spawnSync(
|
|
process.execPath,
|
|
[installerPath, "--claude", "--codex", "--skills", "frontend-design"],
|
|
{
|
|
encoding: "utf8",
|
|
env: { ...cliEnv, HOME: multiHome, CODEX_HOME: codexHome },
|
|
},
|
|
);
|
|
assert.strictEqual(multiTarget.status, 1);
|
|
assert.match(multiTarget.stderr, /symlinked target/i);
|
|
assert.deepStrictEqual(fs.readdirSync(firstTarget), ["first-sentinel.txt"]);
|
|
assert.strictEqual(fs.readFileSync(firstSentinel, "utf8"), "first stays");
|
|
}
|
|
} finally {
|
|
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
}
|