mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-08-31 02:41:44 +00:00
fix(skills): skip mirror fan-out to agents that read the universal store (#3325)
This commit is contained in:
@@ -177,6 +177,30 @@ describe("mirrorGlobalSkills", () => {
|
||||
const link = join(home, ".cursor", "skills", "hyperframes");
|
||||
expect(realpathSync(link)).toBe(realpathSync(join(home, ".claude", "skills", "hyperframes")));
|
||||
});
|
||||
|
||||
// Pi natively discovers BOTH ~/.pi/agent/skills and the universal
|
||||
// ~/.agents/skills (pi's packages/coding-agent/docs/skills.md#locations).
|
||||
// A mirrored per-agent copy collides with the universal one and Pi skips
|
||||
// the universal entry on name conflict (#3294), so the mirror must not fan
|
||||
// out to it.
|
||||
it("skips agents that natively read the universal store (pi, #3294)", () => {
|
||||
const home = makeHome();
|
||||
seedStore(home, ["hyperframes"]);
|
||||
installMarker(home, ".pi/agent"); // Pi present
|
||||
installMarker(home, ".cursor"); // a regular per-dir agent, for contrast
|
||||
|
||||
const { mirrored } = mirrorGlobalSkills({
|
||||
skills: ["hyperframes"],
|
||||
home,
|
||||
platform: "linux",
|
||||
env: ENV,
|
||||
});
|
||||
const agents = mirrored.map((m) => m.agent);
|
||||
expect(agents).not.toContain("pi");
|
||||
expect(agents).toContain("cursor");
|
||||
// no per-agent copy created where the universal store already serves Pi
|
||||
expect(existsSync(join(home, ".pi", "agent", "skills", "hyperframes"))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("AGENT_GLOBAL_DIRS (generated table)", () => {
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
// populate.
|
||||
//
|
||||
// So we mirror the canonical Claude store into each of those per-agent dirs, but
|
||||
// only for agents the machine actually has (their marker dir exists). On Unix
|
||||
// only for agents the machine actually has (their marker dir exists). Agents
|
||||
// that already consume the universal ~/.agents/skills store globally (Pi) are
|
||||
// skipped: their universal copy is authoritative and a per-agent copy would
|
||||
// collide with it (#3294). On Unix
|
||||
// each skill is a relative symlink back into the store (one source of truth,
|
||||
// near-zero size, auto-fresh on update); on Windows it's a copy, because
|
||||
// symlinks there need admin / Developer Mode and otherwise silently dangle —
|
||||
@@ -24,6 +27,22 @@ import { homedir } from "node:os";
|
||||
import { dirname, isAbsolute, join, relative } from "node:path";
|
||||
import { AGENT_GLOBAL_DIRS, type AgentDirBase } from "./agentDirs.generated.js";
|
||||
|
||||
/**
|
||||
* Agents that natively discover the universal `~/.agents/skills` store globally
|
||||
* in ADDITION to their own agent-specific directory. Mirroring into their own
|
||||
* dir makes every skill discoverable twice.
|
||||
*
|
||||
* Pi is the known case (earendil-works/pi): it reads both `~/.pi/agent/skills/`
|
||||
* and `~/.agents/skills/` as global locations (pi's packages/coding-agent/docs/
|
||||
* skills.md#locations), so a mirrored entry collides with the universal copy
|
||||
* and Pi skips the universal one on name conflict (#3294).
|
||||
*
|
||||
* The generated table cannot carry this capability — it is a plain
|
||||
* (agent, base, sub) list synced from vercel-labs/skills — so the set lives
|
||||
* here next to the mirror logic that needs it.
|
||||
*/
|
||||
const UNIVERSAL_STORE_READERS = new Set(["pi"]);
|
||||
|
||||
export interface MirrorResult {
|
||||
/** The store mirrored from, or null when no global Claude store was found. */
|
||||
source: string | null;
|
||||
@@ -128,6 +147,7 @@ export function mirrorGlobalSkills(opts: {
|
||||
for (const { agent, base, sub } of AGENT_GLOBAL_DIRS) {
|
||||
const targetDir = join(bases[base], ...sub.split("/").filter(Boolean));
|
||||
if (targetDir === source || targetDir === universalStore) continue; // install-owned
|
||||
if (UNIVERSAL_STORE_READERS.has(agent)) continue; // already reads the universal store (#3294)
|
||||
if (!existsSync(dirname(targetDir))) continue; // agent not installed (no marker)
|
||||
if (mirrorInto(targetDir, source, skills, platform)) mirrored.push({ agent, dir: targetDir });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user