fix(cli): mirror skills into every installed agent on Windows (#5367)

* fix(cli): mirror skills into every installed agent on Windows

* fix(cli): motion shots can overwrite their previous png on Windows
This commit is contained in:
Miguel Ángel
2026-10-09 19:51:24 +00:00
committed by GitHub
parent ce359d91cb
commit ce73339b1a
4 changed files with 57 additions and 5 deletions
+2 -2
View File
@@ -43,8 +43,8 @@ function pathsReferToSameFile(firstPath: string, secondPath: string): boolean {
const second = resolve(secondPath);
if (first === second) return true;
try {
const firstStat = statSync(first);
const secondStat = statSync(second);
const firstStat = statSync(first, { bigint: true });
const secondStat = statSync(second, { bigint: true });
return firstStat.dev === secondStat.dev && firstStat.ino === secondStat.ino;
} catch {
return false;
+51
View File
@@ -0,0 +1,51 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
// Number-mode stats report one identity for every path, as Windows file ids above 2^53 can.
vi.mock("node:fs", async (importOriginal) => {
const actual = await importOriginal<typeof import("node:fs")>();
const statSync = ((path: string, options?: { bigint?: boolean }) =>
options?.bigint
? actual.statSync(path, options as never)
: { ...actual.statSync(path), dev: 1, ino: 2 ** 53 }) as typeof actual.statSync;
return { ...actual, statSync };
});
const { mirrorGlobalSkills } = await import("./utils/skillsMirror.js");
const { ensureShotOutputDir } = await import("./commands/motionShot.js");
const homes: string[] = [];
afterEach(() => {
for (const home of homes.splice(0)) rmSync(home, { recursive: true, force: true });
});
describe("same-file checks on colliding number inodes", () => {
it("still mirrors an agent whose dir only collides with the store in number precision", () => {
const home = mkdtempSync(join(tmpdir(), "mirror-ino-"));
homes.push(home);
mkdirSync(join(home, ".claude", "skills", "hyperframes"), { recursive: true });
writeFileSync(join(home, ".claude", "skills", "hyperframes", "SKILL.md"), "# hf\n");
mkdirSync(join(home, ".config", "goose"), { recursive: true });
const { mirrored, skipped } = mirrorGlobalSkills({
skills: ["hyperframes"],
home,
platform: "linux",
env: {},
});
expect(skipped).toEqual([]);
expect(mirrored.map((m) => m.agent)).toContain("goose");
});
it("lets a rerun overwrite its previous shot next to the composition", () => {
const home = mkdtempSync(join(tmpdir(), "shot-ino-"));
homes.push(home);
writeFileSync(join(home, "index.html"), "<html></html>");
writeFileSync(join(home, "shot.png"), "png");
expect(() =>
ensureShotOutputDir(join(home, "shot.png"), join(home, "index.html")),
).not.toThrow();
});
});
+2 -1
View File
@@ -132,12 +132,13 @@ describe("mirrorGlobalSkills", () => {
installMarker(home, ".config/goose"); // goose present (XDG base)
// windsurf NOT installed (no ~/.codeium/windsurf)
const { mirrored } = mirrorGlobalSkills({
const { mirrored, skipped } = mirrorGlobalSkills({
skills: ["hyperframes", "hyperframes-core"],
home,
platform: "linux",
env: ENV,
});
expect(skipped).toEqual([]);
const agents = mirrored.map((m) => m.agent);
expect(agents).toContain("cursor");
expect(agents).toContain("bob");
+2 -2
View File
@@ -106,8 +106,8 @@ function pathsOverlap(left: string, right: string): boolean {
function sameExistingNode(left: string, right: string): boolean {
try {
const leftStat = statSync(left);
const rightStat = statSync(right);
const leftStat = statSync(left, { bigint: true });
const rightStat = statSync(right, { bigint: true });
return leftStat.dev === rightStat.dev && leftStat.ino === rightStat.ino;
} catch {
return false;