304 lines · 10.6 KB · TypeScript
RawDownload
import * as NodeServices from "@effect/platform-node/NodeServices";import { assert, it } from "@effect/vitest";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Path from "effect/Path"; import { discoverClaudeSkills } from "./ClaudeSkills.ts"; const writeSkill = Effect.fn(function* (  skillsDir: string,  directoryName: string,  contents: string,) {  const fs = yield* FileSystem.FileSystem;  const path = yield* Path.Path;  const skillDir = path.join(skillsDir, directoryName);  yield* fs.makeDirectory(skillDir, { recursive: true });  yield* fs.writeFileString(path.join(skillDir, "SKILL.md"), contents);}); it.layer(NodeServices.layer)("discoverClaudeSkills", (it) => {  it.effect("discovers user and project skills with frontmatter metadata", () =>    Effect.gen(function* () {      const fs = yield* FileSystem.FileSystem;      const path = yield* Path.Path;      const tempDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-claude-skills-" });      const configDir = path.join(tempDir, "claude-home");      const workspace = path.join(tempDir, "workspace");       yield* writeSkill(        path.join(configDir, "skills"),        "codex-review",        [          "---",          "name: codex-review",          "description: Ask Codex for a review.",          "---",          "",          "# Body",        ].join("\n"),      );      yield* writeSkill(        path.join(workspace, ".claude", "skills"),        "deploy",        ["---", "name: deploy", "description: Deploy the app.", "---", "", "# Deploy"].join("\n"),      );       const skills = yield* discoverClaudeSkills({ homePath: configDir }, workspace);       assert.deepEqual(skills, [        {          name: "codex-review",          path: path.join(configDir, "skills", "codex-review", "SKILL.md"),          enabled: true,          scope: "user",          description: "Ask Codex for a review.",        },        {          name: "deploy",          path: path.join(workspace, ".claude", "skills", "deploy", "SKILL.md"),          enabled: true,          scope: "project",          description: "Deploy the app.",        },      ]);    }),  );   it.effect("discovers project skills from the workspace .agents directory", () =>    Effect.gen(function* () {      const fs = yield* FileSystem.FileSystem;      const path = yield* Path.Path;      const tempDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-claude-skills-" });      const configDir = path.join(tempDir, "claude-home");      const workspace = path.join(tempDir, "workspace");       yield* writeSkill(        path.join(workspace, ".agents", "skills"),        "review",        ["---", "name: review", "description: Review the changes.", "---"].join("\n"),      );       const skills = yield* discoverClaudeSkills({ homePath: configDir }, workspace);       assert.deepEqual(skills, [        {          name: "review",          path: path.join(workspace, ".agents", "skills", "review", "SKILL.md"),          enabled: true,          scope: "project",          description: "Review the changes.",        },      ]);    }),  );   it.effect("prefers workspace .claude skills on three-way name collisions", () =>    Effect.gen(function* () {      const fs = yield* FileSystem.FileSystem;      const path = yield* Path.Path;      const tempDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-claude-skills-" });      const configDir = path.join(tempDir, "claude-home");      const workspace = path.join(tempDir, "workspace");       yield* writeSkill(        path.join(configDir, "skills"),        "deploy",        ["---", "name: deploy", "description: User deploy.", "---"].join("\n"),      );      yield* writeSkill(        path.join(workspace, ".agents", "skills"),        "deploy",        ["---", "name: deploy", "description: Agents deploy.", "---"].join("\n"),      );      yield* writeSkill(        path.join(workspace, ".claude", "skills"),        "deploy",        ["---", "name: deploy", "description: Claude deploy.", "---"].join("\n"),      );       const skills = yield* discoverClaudeSkills({ homePath: configDir }, workspace);       assert.deepEqual(skills, [        {          name: "deploy",          path: path.join(workspace, ".claude", "skills", "deploy", "SKILL.md"),          enabled: true,          scope: "project",          description: "Claude deploy.",        },      ]);    }),  );   it.effect("prefers workspace .agents skills over user skills on name collisions", () =>    Effect.gen(function* () {      const fs = yield* FileSystem.FileSystem;      const path = yield* Path.Path;      const tempDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-claude-skills-" });      const configDir = path.join(tempDir, "claude-home");      const workspace = path.join(tempDir, "workspace");       yield* writeSkill(        path.join(configDir, "skills"),        "deploy",        ["---", "name: deploy", "description: User deploy.", "---"].join("\n"),      );      yield* writeSkill(        path.join(workspace, ".agents", "skills"),        "deploy",        ["---", "name: deploy", "description: Agents deploy.", "---"].join("\n"),      );       const skills = yield* discoverClaudeSkills({ homePath: configDir }, workspace);       assert.deepEqual(skills, [        {          name: "deploy",          path: path.join(workspace, ".agents", "skills", "deploy", "SKILL.md"),          enabled: true,          scope: "project",          description: "Agents deploy.",        },      ]);    }),  );   it.effect("prefers project skills over user skills on name collisions", () =>    Effect.gen(function* () {      const fs = yield* FileSystem.FileSystem;      const path = yield* Path.Path;      const tempDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-claude-skills-" });      const configDir = path.join(tempDir, "claude-home");      const workspace = path.join(tempDir, "workspace");       yield* writeSkill(        path.join(configDir, "skills"),        "deploy",        ["---", "name: deploy", "description: User deploy.", "---"].join("\n"),      );      yield* writeSkill(        path.join(workspace, ".claude", "skills"),        "deploy",        ["---", "name: deploy", "description: Project deploy.", "---"].join("\n"),      );       const skills = yield* discoverClaudeSkills({ homePath: configDir }, workspace);       assert.equal(skills.length, 1);      assert.equal(skills[0]?.scope, "project");      assert.equal(skills[0]?.description, "Project deploy.");    }),  );   it.effect("falls back to the directory name and skips malformed frontmatter", () =>    Effect.gen(function* () {      const fs = yield* FileSystem.FileSystem;      const path = yield* Path.Path;      const tempDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-claude-skills-" });      const configDir = path.join(tempDir, "claude-home");      const skillsDir = path.join(configDir, "skills");       yield* writeSkill(skillsDir, "no-frontmatter", "# Just a heading\n");      yield* writeSkill(skillsDir, "broken-yaml", "---\nname: [unclosed\n---\n");      // A stray file (not a directory with SKILL.md) must be skipped.      yield* fs.makeDirectory(skillsDir, { recursive: true });      yield* fs.writeFileString(path.join(skillsDir, "README.md"), "not a skill");       const skills = yield* discoverClaudeSkills({ homePath: configDir }, undefined);       // A skill with no frontmatter falls back to its directory name; a skill      // whose frontmatter fails to parse is skipped entirely (Claude Code      // won't load it either).      assert.deepEqual(        skills.map((skill) => skill.name),        ["no-frontmatter"],      );      assert.equal(skills[0]?.description, undefined);    }),  );   it.effect("honors CLAUDE_CONFIG_DIR from the environment when homePath is unset", () =>    Effect.gen(function* () {      const fs = yield* FileSystem.FileSystem;      const path = yield* Path.Path;      const tempDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-claude-skills-" });      const environmentConfigDir = path.join(tempDir, "env-config");       yield* writeSkill(        path.join(environmentConfigDir, "skills"),        "env-skill",        ["---", "name: env-skill", "description: From env config dir.", "---"].join("\n"),      );       const skills = yield* discoverClaudeSkills({ homePath: "" }, undefined, {        CLAUDE_CONFIG_DIR: environmentConfigDir,      });       assert.deepEqual(        skills.map((skill) => skill.name),        ["env-skill"],      );       // An explicit homePath wins over the environment variable, matching      // makeClaudeEnvironment which overwrites CLAUDE_CONFIG_DIR for the CLI.      const explicitHome = path.join(tempDir, "explicit-home");      yield* writeSkill(        path.join(explicitHome, "skills"),        "explicit-skill",        ["---", "name: explicit-skill", "---"].join("\n"),      );      const explicitSkills = yield* discoverClaudeSkills({ homePath: explicitHome }, undefined, {        CLAUDE_CONFIG_DIR: environmentConfigDir,      });      assert.deepEqual(        explicitSkills.map((skill) => skill.name),        ["explicit-skill"],      );    }),  );   it.effect("resolves a relative CLAUDE_CONFIG_DIR against the workspace cwd", () =>    Effect.gen(function* () {      const fs = yield* FileSystem.FileSystem;      const path = yield* Path.Path;      const tempDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-claude-skills-" });      const workspace = path.join(tempDir, "workspace");      yield* fs.makeDirectory(workspace, { recursive: true });       // The spawned CLI resolves a relative CLAUDE_CONFIG_DIR against its own      // cwd (the workspace), so discovery must do the same.      yield* writeSkill(        path.join(workspace, "relative-config", "skills"),        "relative-skill",        ["---", "name: relative-skill", "---"].join("\n"),      );       const skills = yield* discoverClaudeSkills({ homePath: "" }, workspace, {        CLAUDE_CONFIG_DIR: "relative-config",      });       assert.deepEqual(        skills.map((skill) => skill.name),        ["relative-skill"],      );      assert.equal(skills[0]?.scope, "user");    }),  );   it.effect("returns an empty list when no skill roots exist", () =>    Effect.gen(function* () {      const fs = yield* FileSystem.FileSystem;      const path = yield* Path.Path;      const tempDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-claude-skills-" });       const skills = yield* discoverClaudeSkills(        { homePath: path.join(tempDir, "missing-home") },        path.join(tempDir, "missing-workspace"),      );       assert.deepEqual(skills, []);    }),  );});

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t