Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions apps/server/src/provider/Drivers/ClaudeSkills.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,71 @@ it.layer(NodeServices.layer)("discoverClaudeSkills", (it) => {
}),
);

it.effect("resolves each install record's project repository once per discovery", () =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
// Real path: macOS temp dirs sit behind a /var -> /private/var symlink.
const tempDir = yield* fs.realPath(
yield* fs.makeTempDirectoryScoped({ prefix: "t3-claude-skills-" }),
);
const configDir = path.join(tempDir, "claude-home");
const workspace = path.join(tempDir, "workspace");
const otherProject = path.join(tempDir, "other", "packages", "app");
const sameRepoProject = path.join(workspace, "packages", "app");
yield* fs.makeDirectory(path.join(workspace, ".git"), { recursive: true });
yield* fs.makeDirectory(path.join(tempDir, "other", ".git"), { recursive: true });
yield* fs.makeDirectory(otherProject, { recursive: true });
yield* fs.makeDirectory(sameRepoProject, { recursive: true });
const elsewhereInstall = path.join(configDir, "plugins", "elsewhere");
const sameRepoInstall = path.join(configDir, "plugins", "same-repo");
yield* writeSkill(path.join(elsewhereInstall, "skills"), "elsewhere", "# Elsewhere");
yield* writeSkill(path.join(sameRepoInstall, "skills"), "same-repo", "# Same repo");
const records = Array.from({ length: 4 }, () => ({
scope: "project",
projectPath: otherProject,
installPath: elsewhereInstall,
}));
const plugins = ["a@shop", "b@shop", "c@shop"];
yield* fs.writeFileString(
path.join(configDir, "plugins", "installed_plugins.json"),
JSON.stringify({
plugins: {
...Object.fromEntries(plugins.map((key) => [key, records])),
"d@shop": [
{ scope: "project", projectPath: sameRepoProject, installPath: sameRepoInstall },
],
},
}),
);
yield* fs.writeFileString(
path.join(configDir, "settings.json"),
JSON.stringify({
enabledPlugins: Object.fromEntries([...plugins, "d@shop"].map((key) => [key, true])),
}),
);

const probed: Array<string> = [];
const skills = yield* discoverClaudeSkills({ homePath: configDir }, workspace).pipe(
Effect.provideService(FileSystem.FileSystem, {
...fs,
exists: (target) => {
probed.push(target);
return fs.exists(target);
},
}),
);
assert.strictEqual(
probed.filter((target) => target === path.join(otherProject, ".git")).length,
1,
);
assert.deepEqual(
skills.map((skill) => skill.name),
["d:same-repo"],
);
}),
);

it.effect("publishes and dispatches canonical plugin folder names regardless of aliases", () =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
Expand Down
19 changes: 12 additions & 7 deletions apps/server/src/provider/Drivers/ClaudeSkills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ function claudeSettingsPaths(
];
}

const findRepositoryRoot = Effect.fn("findRepositoryRoot")(function* (
// Untraced: skill discovery resolves every distinct plugin project path, too often for spans.
const findRepositoryRoot = Effect.fnUntraced(function* (
cwd: string,
): Effect.fn.Return<string | undefined, never, FileSystem.FileSystem | Path.Path> {
const fileSystem = yield* FileSystem.FileSystem;
Expand All @@ -164,7 +165,7 @@ const findRepositoryRoot = Effect.fn("findRepositoryRoot")(function* (
}
});

const findPluginRepositoryRoot = Effect.fn("findPluginRepositoryRoot")(function* (cwd: string) {
const findPluginRepositoryRoot = Effect.fnUntraced(function* (cwd: string) {
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const root = yield* findRepositoryRoot(cwd);
Expand Down Expand Up @@ -336,6 +337,8 @@ const discoverPluginSkillRoots = Effect.fn("discoverPluginSkillRoots")(function*
cwd === undefined
? undefined
: yield* fileSystem.realPath(cwd).pipe(Effect.orElseSucceed(() => path.resolve(cwd)));
// Install records repeat project paths, often hundreds of times per discovery.
const projectRepositoryRoots = new Map<string, string | undefined>();
const repositoryRoot =
workspace === undefined ? undefined : yield* findPluginRepositoryRoot(workspace);

Expand All @@ -347,11 +350,13 @@ const discoverPluginSkillRoots = Effect.fn("discoverPluginSkillRoots")(function*
Effect.gen(function* () {
if (record.scope === "user" || record.scope === "managed") return true;
if (record.projectPath === undefined || workspace === undefined) return false;
if (path.resolve(record.projectPath) === workspace) return true;
return (
repositoryRoot !== undefined &&
(yield* findPluginRepositoryRoot(record.projectPath)) === repositoryRoot
);
const projectPath = path.resolve(record.projectPath);
if (projectPath === workspace) return true;
if (repositoryRoot === undefined) return false;
if (!projectRepositoryRoots.has(projectPath)) {
projectRepositoryRoots.set(projectPath, yield* findPluginRepositoryRoot(projectPath));
}
return projectRepositoryRoots.get(projectPath) === repositoryRoot;
}),
);
// The native loader keeps registry order, preferring the first install that exists.
Expand Down
Loading