From 8669dcba1879bfb713ec5ba3cce8bbd2fcb380f5 Mon Sep 17 00:00:00 2001 From: mathias-heide Date: Fri, 11 Sep 2026 10:28:42 -0700 Subject: [PATCH] feat(skills): install once to the shared agentskills.io folder Agents whose docs read ~/.agents/skills (and .agents/skills in a project) now all install there: Codex, Cursor, Zed, OpenCode, Copilot in VS Code, Devin Desktop, Amp, Crush, Warp, Kimi Code, Factory Droid, Rovo Dev, Grok Build; Antigravity, Goose, Hermes and Mistral Vibe at project scope. One install serves all of them. The summary names the other agents covered. --force removes the copies 3.1.0 wrote in each agent's own folder so no agent loads a skill twice. Claude Code, Copilot CLI, Kiro, Qwen, Kilo and Cline keep their native folders (their docs do not list the shared one). Co-Authored-By: Claude Fable 5.1 --- CHANGELOG.md | 5 ++ docs/OVERVIEW.md | 28 +++++----- integrations/amp/README.md | 2 +- integrations/crush/README.md | 2 +- integrations/grok-build/README.md | 2 +- integrations/hermes/README.md | 2 +- integrations/kimi-code/README.md | 2 +- integrations/mistral-vibe/README.md | 2 +- integrations/opencode/README.md | 2 +- integrations/rovo-dev/README.md | 2 +- integrations/vscode-copilot/README.md | 2 +- integrations/warp/README.md | 2 +- integrations/windsurf/README.md | 2 +- src/cli/commands/skills.ts | 29 +++++++++- src/installer/agent-table.test.ts | 39 +++++++++++--- src/installer/agent-table.ts | 77 +++++++++++++++++++++------ 16 files changed, 149 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee870e0..b010de4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to summer-engine will be documented here. Following [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [Semantic Versioning](https://semver.org/). +## [Unreleased] + +### Changed +- Agents whose docs read the agentskills.io folder now install skills to `~/.agents/skills` (project: `.agents/skills`) instead of each agent's own folder: Codex, Cursor, Zed, OpenCode, Copilot in VS Code, Devin Desktop, Amp, Crush, Warp, Kimi Code, Factory Droid, Rovo Dev, Grok Build; Antigravity, Goose, Hermes and Mistral Vibe at project scope. One install serves all of them and "where are the skills" has one answer. `skills install --force` removes the copies 3.1.0 wrote in the old per-agent folders. + ## [3.1.0] - 2026-09-11 ### Added diff --git a/docs/OVERVIEW.md b/docs/OVERVIEW.md index bd51099..277594e 100644 --- a/docs/OVERVIEW.md +++ b/docs/OVERVIEW.md @@ -45,15 +45,15 @@ Or download from [summerengine.com/download](https://summerengine.com/download). ## Where skills live per agent -Each agent has its own home for skills (`/SKILL.md` unless noted). Agents with no skills folder (Claude Desktop, Copilot in Visual Studio and JetBrains, Trae, Junie, LM Studio) get the MCP server only and pull guidance in-chat via `summer_get_agent_playbook`. +Skills install as `/SKILL.md`. Agents whose docs read the agentskills.io folder (`~/.agents/skills`, `.agents/skills`) share it, so one install covers Codex, Cursor, Zed, OpenCode, Copilot in VS Code, Devin Desktop, Amp, Crush, Warp, Kimi, Factory, Rovo Dev and Grok Build; the rest keep their own folder. Agents with no skills folder (Claude Desktop, Copilot in Visual Studio and JetBrains, Trae, Junie, LM Studio) get the MCP server only and pull guidance in-chat via `summer_get_agent_playbook`. | Agent | User scope | Project scope | |---|---|---| | `summer` | `~/.summer/skills` | `.summer/skills` | | `claude-code` | `~/.claude/skills` | `.claude/skills` | | `codex` | `~/.agents/skills` | `.agents/skills` | -| `cursor` | `~/.cursor/skills` | `.cursor/skills` | -| `windsurf` | `~/.codeium/windsurf/skills` | `.windsurf/skills` | +| `cursor` | `~/.agents/skills` | `.agents/skills` | +| `windsurf` | `~/.agents/skills` | `.agents/skills` | | `antigravity` | `~/.gemini/config/skills` | `.agents/skills` | | `gemini` (legacy) | `~/.gemini/extensions/summer-engine/skills` | n/a | | `cline` | `~/.cline/skills` | `.cline/skills` | @@ -61,22 +61,22 @@ Each agent has its own home for skills (`/SKILL.md` unless noted). Agents | `roo-code` (legacy) | `~/Documents/Roo/Rules` (rule files `summer-.md`) | `.clinerules` | | `kilo-code` | `~/.kilo/skills` | `.kilo/skills` | | `github-copilot` | `~/.copilot/skills` | `.github/skills` | -| `vscode-copilot` | `~/.copilot/skills` | `.github/skills` | -| `opencode` | `~/.config/opencode/skills` | `.opencode/skills` | +| `vscode-copilot` | `~/.agents/skills` | `.agents/skills` | +| `opencode` | `~/.agents/skills` | `.agents/skills` | | `zed` | `~/.agents/skills` | `.agents/skills` | | `kiro` | `~/.kiro/skills` | `.kiro/skills` | | `goose` | `~/.config/agents/skills` | `.agents/skills` | -| `hermes` | `~/.hermes/skills` | `.hermes/skills` | +| `hermes` | `~/.hermes/skills` | `.agents/skills` | | `qwen-code` | `~/.qwen/skills` | `.qwen/skills` | -| `kimi-code` | `~/.kimi-code/skills` | `.kimi-code/skills` | -| `crush` | `~/.config/crush/skills` | `.crush/skills` | -| `amp` | `~/.config/amp/skills` | `.agents/skills` | -| `factory` | `~/.factory/skills` | `.factory/skills` | -| `warp` | `~/.warp/skills` | `.warp/skills` | -| `rovo-dev` | `~/.rovodev/skills` | `.rovodev/skills` | +| `kimi-code` | `~/.agents/skills` | `.agents/skills` | +| `crush` | `~/.agents/skills` | `.agents/skills` | +| `amp` | `~/.agents/skills` | `.agents/skills` | +| `factory` | `~/.agents/skills` | `.agents/skills` | +| `warp` | `~/.agents/skills` | `.agents/skills` | +| `rovo-dev` | `~/.agents/skills` | `.agents/skills` | | `qoder` | `~/.qoder/skills` | `.qoder/skills` | -| `grok-build` | `~/.grok/skills` | `.grok/skills` | -| `mistral-vibe` | `~/.vibe/skills` | `.vibe/skills` | +| `grok-build` | `~/.agents/skills` | `.agents/skills` | +| `mistral-vibe` | `~/.vibe/skills` | `.agents/skills` | Use `--scope project` when you want the skills committed with the game: diff --git a/integrations/amp/README.md b/integrations/amp/README.md index 150b57c..932b0fc 100644 --- a/integrations/amp/README.md +++ b/integrations/amp/README.md @@ -6,7 +6,7 @@ is intentionally empty. Support is delivered at install time by - MCP config: `~/.config/amp/settings.json`; Windows `%APPDATA%/amp/settings.json` (user); user scope only (project requests fall back with a warning). Shape: `"amp.mcpServers".summer-engine = { command, args }`. -- Skills: `~/.config/amp/skills` (user) or `.agents/skills` (project) as `/SKILL.md`. +- Skills: `~/.agents/skills` (user) or `.agents/skills` (project) as `/SKILL.md`. - After: Restart Amp so it reloads settings.json. Source of truth: `src/installer/agent-table.ts` (one row per agent). diff --git a/integrations/crush/README.md b/integrations/crush/README.md index c35a832..41b0ad1 100644 --- a/integrations/crush/README.md +++ b/integrations/crush/README.md @@ -6,7 +6,7 @@ is intentionally empty. Support is delivered at install time by - MCP config: `~/.config/crush/crushrc`; Windows `%USERPROFILE%/.config/crush/crushrc` (user); `.crushrc` (project). Shape: `mcp.summer-engine = { type: "stdio", command, args }`. -- Skills: `~/.config/crush/skills` (user) or `.crush/skills` (project) as `/SKILL.md`. +- Skills: `~/.agents/skills` (user) or `.agents/skills` (project) as `/SKILL.md`. - After: Restart Crush so it reloads crushrc. Source of truth: `src/installer/agent-table.ts` (one row per agent). diff --git a/integrations/grok-build/README.md b/integrations/grok-build/README.md index 1601fbd..a960a32 100644 --- a/integrations/grok-build/README.md +++ b/integrations/grok-build/README.md @@ -6,7 +6,7 @@ is intentionally empty. Support is delivered at install time by - MCP config: `~/.grok/config.toml`; Windows `%USERPROFILE%/.grok/config.toml` (user); `.grok/config.toml` (project). Shape: `[mcp_servers.summer-engine]` table (TOML). -- Skills: `~/.grok/skills` (user) or `.grok/skills` (project) as `/SKILL.md`. +- Skills: `~/.agents/skills` (user) or `.agents/skills` (project) as `/SKILL.md`. - After: Restart Grok Build so it reloads config.toml (it also reads ~/.claude.json and .cursor/mcp.json). Source of truth: `src/installer/agent-table.ts` (one row per agent). diff --git a/integrations/hermes/README.md b/integrations/hermes/README.md index 821ffce..bd68995 100644 --- a/integrations/hermes/README.md +++ b/integrations/hermes/README.md @@ -6,7 +6,7 @@ is intentionally empty. Support is delivered at install time by - MCP config: `~/.hermes/config.yaml`; Windows `%USERPROFILE%/.hermes/config.yaml` (user); user scope only (project requests fall back with a warning). Shape: `mcp_servers.summer-engine = { command, args }` (YAML). -- Skills: `~/.hermes/skills` (user) or `.hermes/skills` (project) as `/SKILL.md`. Project skills need `hermes skills trust` before Hermes loads them. +- Skills: `~/.hermes/skills` (user) or `.agents/skills` (project) as `/SKILL.md`. Project skills need `hermes skills trust` before Hermes loads them. - After: Run /reload-mcp in Hermes Agent (or restart it). Source of truth: `src/installer/agent-table.ts` (one row per agent). diff --git a/integrations/kimi-code/README.md b/integrations/kimi-code/README.md index a526c42..099e9d3 100644 --- a/integrations/kimi-code/README.md +++ b/integrations/kimi-code/README.md @@ -6,7 +6,7 @@ is intentionally empty. Support is delivered at install time by - MCP config: `~/.kimi-code/mcp.json`; Windows `%USERPROFILE%/.kimi-code/mcp.json` (user); `.kimi-code/mcp.json` (project). Shape: `mcpServers.summer-engine = { command, args }`. -- Skills: `~/.kimi-code/skills` (user) or `.kimi-code/skills` (project) as `/SKILL.md`. +- Skills: `~/.agents/skills` (user) or `.agents/skills` (project) as `/SKILL.md`. - After: Restart Kimi Code CLI so it reconnects its MCP servers. Source of truth: `src/installer/agent-table.ts` (one row per agent). diff --git a/integrations/mistral-vibe/README.md b/integrations/mistral-vibe/README.md index 2e3fa7f..655a0fc 100644 --- a/integrations/mistral-vibe/README.md +++ b/integrations/mistral-vibe/README.md @@ -6,7 +6,7 @@ is intentionally empty. Support is delivered at install time by - MCP config: `~/.vibe/config.toml`; Windows `%USERPROFILE%/.vibe/config.toml` (user); `.vibe/config.toml` (project). Shape: undefined. -- Skills: `~/.vibe/skills` (user) or `.vibe/skills` (project) as `/SKILL.md`. +- Skills: `~/.vibe/skills` (user) or `.agents/skills` (project) as `/SKILL.md`. - After: Restart Vibe so it reloads config.toml. Source of truth: `src/installer/agent-table.ts` (one row per agent). diff --git a/integrations/opencode/README.md b/integrations/opencode/README.md index 92c3c23..3bcca72 100644 --- a/integrations/opencode/README.md +++ b/integrations/opencode/README.md @@ -6,7 +6,7 @@ is intentionally empty. Support is delivered at install time by - MCP config: `~/.config/opencode/opencode.json`; Windows `%APPDATA%/opencode/opencode.json` (user); `opencode.json` (project). Shape: `mcp.summer-engine = { type: "local", command: [npx, ...], enabled: true }`. -- Skills: `~/.config/opencode/skills` (user) or `.opencode/skills` (project) as `/SKILL.md`. OpenCode loads skills from this folder on the next session. Summer 3.0 and earlier wrote markdown under agents/summer; `--force` removes those. +- Skills: `~/.agents/skills` (user) or `.agents/skills` (project) as `/SKILL.md`. OpenCode loads skills from this folder on the next session. Summer 3.0 wrote markdown under agents/summer and 3.1.0 wrote opencode/skills; `--force` removes both. - After: Restart OpenCode so it reloads opencode.json. Source of truth: `src/installer/agent-table.ts` (one row per agent). diff --git a/integrations/rovo-dev/README.md b/integrations/rovo-dev/README.md index 1f4ab45..fcec9ec 100644 --- a/integrations/rovo-dev/README.md +++ b/integrations/rovo-dev/README.md @@ -6,7 +6,7 @@ is intentionally empty. Support is delivered at install time by - MCP config: `~/.rovodev/mcp.json`; Windows `%USERPROFILE%/.rovodev/mcp.json` (user); user scope only (project requests fall back with a warning). Shape: undefined. -- Skills: `~/.rovodev/skills` (user) or `.rovodev/skills` (project) as `/SKILL.md`. +- Skills: `~/.agents/skills` (user) or `.agents/skills` (project) as `/SKILL.md`. - After: Restart Rovo Dev CLI so it reconnects its MCP servers. Source of truth: `src/installer/agent-table.ts` (one row per agent). diff --git a/integrations/vscode-copilot/README.md b/integrations/vscode-copilot/README.md index b6f603a..b2d6e2d 100644 --- a/integrations/vscode-copilot/README.md +++ b/integrations/vscode-copilot/README.md @@ -6,7 +6,7 @@ is intentionally empty. Support is delivered at install time by - MCP config: `~/Library/Application Support/Code/User/mcp.json`; Linux `~/.config/Code/User/mcp.json`; Windows `%APPDATA%/Code/User/mcp.json` (user); `.vscode/mcp.json` (project). Shape: `servers.summer-engine = { type: "stdio", command, args }`. -- Skills: `~/.copilot/skills` (user) or `.github/skills` (project) as `/SKILL.md`. +- Skills: `~/.agents/skills` (user) or `.agents/skills` (project) as `/SKILL.md`. - After: Restart VS Code or run MCP: List Servers, then start summer-engine in Copilot Agent mode. Source of truth: `src/installer/agent-table.ts` (one row per agent). diff --git a/integrations/warp/README.md b/integrations/warp/README.md index 36b41ab..3755ba3 100644 --- a/integrations/warp/README.md +++ b/integrations/warp/README.md @@ -6,7 +6,7 @@ is intentionally empty. Support is delivered at install time by - MCP config: `~/.warp/.mcp.json`; Windows `%USERPROFILE%/.warp/.mcp.json` (user); `.warp/.mcp.json` (project). Shape: `mcpServers.summer-engine = { command, args }`. -- Skills: `~/.warp/skills` (user) or `.warp/skills` (project) as `/SKILL.md`. +- Skills: `~/.agents/skills` (user) or `.agents/skills` (project) as `/SKILL.md`. - After: Warp detects the file and spawns the server; check Settings > AI > MCP servers. Source of truth: `src/installer/agent-table.ts` (one row per agent). diff --git a/integrations/windsurf/README.md b/integrations/windsurf/README.md index 0978325..26ef4a5 100644 --- a/integrations/windsurf/README.md +++ b/integrations/windsurf/README.md @@ -6,7 +6,7 @@ is intentionally empty. Support is delivered at install time by - MCP config: `~/.codeium/windsurf/mcp_config.json`; Windows `%USERPROFILE%/.codeium/windsurf/mcp_config.json` (user); user scope only (project requests fall back with a warning). Shape: `mcpServers.summer-engine = { command, args }`. -- Skills: `~/.codeium/windsurf/skills` (user) or `.windsurf/skills` (project) as `/SKILL.md`. +- Skills: `~/.agents/skills` (user) or `.agents/skills` (project) as `/SKILL.md`. - After: Restart Devin Desktop (formerly Windsurf) and refresh MCP servers from the agent settings. Devin's docs say mcp_config.json configures the Cascade agent; for the Devin agent add the server in the app's MCP settings with the same command. Source of truth: `src/installer/agent-table.ts` (one row per agent). diff --git a/src/cli/commands/skills.ts b/src/cli/commands/skills.ts index dc12338..bae187c 100644 --- a/src/cli/commands/skills.ts +++ b/src/cli/commands/skills.ts @@ -31,7 +31,15 @@ import { } from "../../installer/skill-locations.js"; import { PACKAGE_ROOT } from "../../core/package-root.js"; -import { agentLabel as tableAgentLabel, agentSpec, defaultPathContext, legacyRuleFiles } from "../../installer/agent-table.js"; +import { + agentLabel as tableAgentLabel, + agentSpec, + agentsSharingSkills, + defaultPathContext, + isSharedSkillsPath, + legacyRuleFiles, + legacySkillDirs, +} from "../../installer/agent-table.js"; import { TOOLKIT_VERSION as cliVersion } from "../../core/version.js"; // Skill files live in library/skills// and are resolved through the @@ -288,6 +296,14 @@ function printInstallSummary( const reloadHint = agent === "summer" ? undefined : agentSpec(agent).skills?.reloadHint; if (location.kind === "skill-dir") { console.log(`${label} can read skills from ${tildeified}//SKILL.md`); + if (isSharedSkillsPath(location.path)) { + const others = agentsSharingSkills(scope, defaultPathContext()) + .map((spec) => spec.label) + .filter((name) => name !== label); + if (others.length > 0) { + console.log(`This is the shared agentskills.io folder; ${others.join(", ")} read it too, so they are covered by this install.`); + } + } if (reloadHint) console.log(reloadHint); } else if (location.kind === "cursor-rule-dir") { console.log(`Cursor rules are in ${tildeified}/summer-.mdc`); @@ -409,12 +425,21 @@ skillsCommand // (Cursor, OpenCode): drop the rule files 3.0 wrote so the agent does not // load both for the same slug. Only files with Summer's own naming go. if (agent !== "summer" && !process.env.SUMMER_SKILLS_DIR) { - const legacy = legacyRuleFiles(agentSpec(agent), scope, defaultPathContext(), skills.map((skill) => skill.name)) ?? []; + const names = skills.map((skill) => skill.name); + const legacy = legacyRuleFiles(agentSpec(agent), scope, defaultPathContext(), names) ?? []; for (const file of legacy) { if (!existsSync(file)) continue; rmSync(file, { force: true }); console.log(` Removed ${tildeify(file)} (rule file from Summer 3.0; now a skill)`); } + // 3.1.0 wrote skills into each agent's own folder; agents that read + // the shared ~/.agents/skills now install there. Drop Summer's copies + // (dirs with a SKILL.md and a library name) from the old folder. + for (const dir of legacySkillDirs(agentSpec(agent), scope, defaultPathContext(), names) ?? []) { + if (!existsSync(join(dir, "SKILL.md"))) continue; + rmSync(dir, { recursive: true, force: true }); + console.log(` Removed ${tildeify(dir)} (moved to the shared skills folder)`); + } } } if (name && skills[0]?.status === "preview") { diff --git a/src/installer/agent-table.test.ts b/src/installer/agent-table.test.ts index 5208da5..044389d 100644 --- a/src/installer/agent-table.test.ts +++ b/src/installer/agent-table.test.ts @@ -11,7 +11,10 @@ import { AGENT_IDS, agentAliasMap, agentSpec, + agentsSharingSkills, allAgentSpecs, + isSharedSkillsPath, + legacySkillDirs, resolveMcpPath, resolveSkillPath, skillCapableAgents, @@ -128,13 +131,35 @@ describe("agent table paths", () => { ); }); - it("Cursor and Devin Desktop now get SKILL.md skills, not rule files", () => { - expect(resolveSkillPath(agentSpec("cursor"), "user", ctx("darwin"))).toEqual({ - kind: "skill-dir", - path: "/home/dev/.cursor/skills", - scope: "user", - }); - expect(resolveSkillPath(agentSpec("windsurf"), "user", ctx("darwin"))?.path).toBe("/home/dev/.codeium/windsurf/skills"); + it("agents whose docs read the agentskills.io folder all install to ~/.agents/skills", () => { + const shared = agentsSharingSkills("user", ctx("darwin")).map((s) => s.id).sort(); + expect(shared).toEqual([ + "amp", "codex", "crush", "cursor", "factory", "grok-build", "kimi-code", "opencode", + "rovo-dev", "vscode-copilot", "warp", "windsurf", "zed", + ]); + for (const id of shared) { + expect(resolveSkillPath(agentSpec(id), "user", ctx("darwin"))?.path).toBe("/home/dev/.agents/skills"); + } + // Project scope adds the agents that only read .agents/skills inside a repo. + const project = agentsSharingSkills("project", ctx("darwin")).map((s) => s.id); + for (const id of ["antigravity", "goose", "hermes", "mistral-vibe"]) expect(project).toContain(id); + // Native-only agents stay where their docs say. + expect(resolveSkillPath(agentSpec("claude-code"), "user", ctx("darwin"))?.path).toBe("/home/dev/.claude/skills"); + expect(resolveSkillPath(agentSpec("github-copilot"), "user", ctx("darwin"))?.path).toBe("/home/dev/.copilot/skills"); + expect(isSharedSkillsPath("/home/dev/.agents/skills")).toBe(true); + expect(isSharedSkillsPath("/home/dev/.claude/skills")).toBe(false); + }); + + it("legacySkillDirs names the 3.1.0 native folders for moved agents and nothing for agents that never moved", () => { + expect(legacySkillDirs(agentSpec("cursor"), "user", ctx("darwin"), ["debug", "play"])).toEqual([ + "/home/dev/.cursor/skills/debug", + "/home/dev/.cursor/skills/play", + ]); + expect(legacySkillDirs(agentSpec("amp"), "project", ctx("darwin"), ["debug"])).toEqual([]); + expect(legacySkillDirs(agentSpec("claude-code"), "user", ctx("darwin"), ["debug"])).toBeNull(); + // Hermes only moved at project scope; its user folder is still current. + expect(legacySkillDirs(agentSpec("hermes"), "user", ctx("darwin"), ["debug"])).toEqual([]); + expect(legacySkillDirs(agentSpec("hermes"), "project", ctx("darwin"), ["debug"])).toEqual(["/work/game/.hermes/skills/debug"]); }); }); diff --git a/src/installer/agent-table.ts b/src/installer/agent-table.ts index 55b8fea..7109492 100644 --- a/src/installer/agent-table.ts +++ b/src/installer/agent-table.ts @@ -118,6 +118,13 @@ export interface SkillHome { * finds there so the agent does not load a rule and a skill for the same slug. */ legacyRules?: { user: (ctx: PathContext) => string; project: ((ctx: PathContext) => string) | null; suffix: string }; + /** + * Skill folders Summer 3.1.0 wrote for this agent before it moved to the + * shared agentskills.io folder (~/.agents/skills). `skills install --force` + * removes Summer's own skill dirs there so the agent does not load the same + * skill twice. + */ + legacyDirs?: { user: (ctx: PathContext) => string; project: ((ctx: PathContext) => string) | null }; } export interface AgentSpec { @@ -179,6 +186,28 @@ function vsCodeGlobalStorage(ctx: PathContext, extensionId: string, fileName: st const home = (...parts: string[]) => (ctx: PathContext) => join(ctx.home, ...parts); const project = (...parts: string[]) => (ctx: PathContext) => join(ctx.cwd, ...parts); +/** + * The agentskills.io shared folder: `~/.agents/skills` and `/.agents/skills`. + * Every agent whose docs say it reads this folder installs here, so one + * install serves them all and "where are the skills" has one answer. + */ +const SHARED_SKILLS_USER = home(".agents", "skills"); +const SHARED_SKILLS_PROJECT = project(".agents", "skills"); +function sharedSkills(extra: Partial = {}): SkillHome { + return { kind: "skill-dir", user: SHARED_SKILLS_USER, project: SHARED_SKILLS_PROJECT, ...extra }; +} + +/** True when this location is the shared agentskills.io folder. */ +export function isSharedSkillsPath(path: string): boolean { + return /[\\/]\.agents[\\/]skills$/.test(path); +} + +/** Agents that read the shared folder at the given scope (for the install summary). */ +export function agentsSharingSkills(scope: ConfigScope, ctx: PathContext): AgentSpec[] { + const shared = (scope === "user" ? SHARED_SKILLS_USER : SHARED_SKILLS_PROJECT)(ctx); + return SPECS.filter((spec) => resolveSkillPath(spec, scope, ctx)?.path === shared); +} + /** agentskills.io layout (`//SKILL.md`) under a user dir and a project dir. */ function skillDirs(user: (ctx: PathContext) => string, proj: ((ctx: PathContext) => string) | null, extra: Partial = {}): SkillHome { return { kind: "skill-dir", user, project: proj, ...extra }; @@ -227,7 +256,7 @@ const SPECS: readonly AgentSpec[] = [ projectNote: "Codex only loads project .codex/config.toml from trusted projects.", }, restart: "Restart Codex or run /mcp in a new session.", - skills: skillDirs(home(".agents", "skills"), project(".agents", "skills")), + skills: sharedSkills(), }, { id: "cursor", @@ -238,9 +267,10 @@ const SPECS: readonly AgentSpec[] = [ format: "json-stdio", mcp: { user: home(".cursor", "mcp.json"), project: project(".cursor", "mcp.json") }, restart: "Restart Cursor and enable the summer-engine MCP server if prompted.", - skills: skillDirs(home(".cursor", "skills"), project(".cursor", "skills"), { - reloadHint: "Cursor loads skills in the next chat. Summer 3.0 and earlier wrote rule files to .cursor/rules; `--force` removes those.", + skills: sharedSkills({ + reloadHint: "Cursor loads skills in the next chat. Summer 3.0 wrote rule files to .cursor/rules and 3.1.0 wrote .cursor/skills; `--force` removes both.", legacyRules: { user: home(".cursor", "rules"), project: project(".cursor", "rules"), suffix: ".mdc" }, + legacyDirs: { user: home(".cursor", "skills"), project: project(".cursor", "skills") }, }), }, { @@ -255,7 +285,7 @@ const SPECS: readonly AgentSpec[] = [ project: null, }, restart: "Restart Devin Desktop (formerly Windsurf) and refresh MCP servers from the agent settings. Devin's docs say mcp_config.json configures the Cascade agent; for the Devin agent add the server in the app's MCP settings with the same command.", - skills: skillDirs(home(".codeium", "windsurf", "skills"), project(".windsurf", "skills")), + skills: sharedSkills({ legacyDirs: { user: home(".codeium", "windsurf", "skills"), project: project(".windsurf", "skills") } }), }, { id: "antigravity", @@ -367,7 +397,7 @@ const SPECS: readonly AgentSpec[] = [ format: "json-vscode", mcp: { user: (ctx) => join(vsCodeUserDir(ctx), "mcp.json"), project: project(".vscode", "mcp.json") }, restart: "Restart VS Code or run MCP: List Servers, then start summer-engine in Copilot Agent mode.", - skills: skillDirs(home(".copilot", "skills"), project(".github", "skills")), + skills: sharedSkills({ legacyDirs: { user: home(".copilot", "skills"), project: project(".github", "skills") } }), }, { id: "visual-studio", @@ -408,9 +438,10 @@ const SPECS: readonly AgentSpec[] = [ project: project("opencode.json"), }, restart: "Restart OpenCode so it reloads opencode.json.", - skills: skillDirs((ctx) => join(xdgOrAppData(ctx), "opencode", "skills"), project(".opencode", "skills"), { - reloadHint: "OpenCode loads skills from this folder on the next session. Summer 3.0 and earlier wrote markdown under agents/summer; `--force` removes those.", + skills: sharedSkills({ + reloadHint: "OpenCode loads skills from this folder on the next session. Summer 3.0 wrote markdown under agents/summer and 3.1.0 wrote opencode/skills; `--force` removes both.", legacyRules: { user: (ctx) => join(xdgOrAppData(ctx), "opencode", "agents", "summer"), project: project(".opencode", "agents", "summer"), suffix: ".md" }, + legacyDirs: { user: (ctx) => join(xdgOrAppData(ctx), "opencode", "skills"), project: project(".opencode", "skills") }, }), }, { @@ -425,7 +456,7 @@ const SPECS: readonly AgentSpec[] = [ project: null, }, restart: "Zed reloads settings.json live; open the Agent panel and check summer-engine under MCP servers.", - skills: skillDirs(home(".agents", "skills"), project(".agents", "skills")), + skills: sharedSkills(), }, { id: "kiro", @@ -464,8 +495,9 @@ const SPECS: readonly AgentSpec[] = [ format: "yaml-hermes", mcp: { user: home(".hermes", "config.yaml"), project: null }, restart: "Run /reload-mcp in Hermes Agent (or restart it).", - skills: skillDirs(home(".hermes", "skills"), project(".hermes", "skills"), { + skills: skillDirs(home(".hermes", "skills"), SHARED_SKILLS_PROJECT, { reloadHint: "Project skills need `hermes skills trust` before Hermes loads them.", + legacyDirs: { user: home(".hermes", "skills"), project: project(".hermes", "skills") }, }), }, { @@ -504,7 +536,7 @@ const SPECS: readonly AgentSpec[] = [ format: "json", mcp: { user: home(".kimi-code", "mcp.json"), project: project(".kimi-code", "mcp.json") }, restart: "Restart Kimi Code CLI so it reconnects its MCP servers.", - skills: skillDirs(home(".kimi-code", "skills"), project(".kimi-code", "skills")), + skills: sharedSkills({ legacyDirs: { user: home(".kimi-code", "skills"), project: project(".kimi-code", "skills") } }), }, { id: "crush", @@ -518,7 +550,7 @@ const SPECS: readonly AgentSpec[] = [ project: project(".crushrc"), }, restart: "Restart Crush so it reloads crushrc.", - skills: skillDirs(home(".config", "crush", "skills"), project(".crush", "skills")), + skills: sharedSkills({ legacyDirs: { user: home(".config", "crush", "skills"), project: project(".crush", "skills") } }), }, { id: "amp", @@ -529,7 +561,7 @@ const SPECS: readonly AgentSpec[] = [ format: "json-amp", mcp: { user: (ctx) => join(xdgOrAppData(ctx), "amp", "settings.json"), project: null }, restart: "Restart Amp so it reloads settings.json.", - skills: skillDirs((ctx) => join(xdgOrAppData(ctx), "amp", "skills"), project(".agents", "skills")), + skills: sharedSkills({ legacyDirs: { user: (ctx) => join(xdgOrAppData(ctx), "amp", "skills"), project: null } }), }, { id: "factory", @@ -540,7 +572,7 @@ const SPECS: readonly AgentSpec[] = [ format: "json-stdio", mcp: { user: home(".factory", "mcp.json"), project: project(".factory", "mcp.json") }, restart: "Restart droid or run /mcp in the active session.", - skills: skillDirs(home(".factory", "skills"), project(".factory", "skills")), + skills: sharedSkills({ legacyDirs: { user: home(".factory", "skills"), project: project(".factory", "skills") } }), }, { id: "junie", @@ -563,7 +595,7 @@ const SPECS: readonly AgentSpec[] = [ format: "json", mcp: { user: home(".warp", ".mcp.json"), project: project(".warp", ".mcp.json") }, restart: "Warp detects the file and spawns the server; check Settings > AI > MCP servers.", - skills: skillDirs(home(".warp", "skills"), project(".warp", "skills")), + skills: sharedSkills({ legacyDirs: { user: home(".warp", "skills"), project: project(".warp", "skills") } }), }, { id: "rovo-dev", @@ -574,7 +606,7 @@ const SPECS: readonly AgentSpec[] = [ format: "json-transport", mcp: { user: home(".rovodev", "mcp.json"), project: null }, restart: "Restart Rovo Dev CLI so it reconnects its MCP servers.", - skills: skillDirs(home(".rovodev", "skills"), project(".rovodev", "skills")), + skills: sharedSkills({ legacyDirs: { user: home(".rovodev", "skills"), project: project(".rovodev", "skills") } }), }, { id: "qoder", @@ -596,7 +628,7 @@ const SPECS: readonly AgentSpec[] = [ format: "toml", mcp: { user: home(".grok", "config.toml"), project: project(".grok", "config.toml") }, restart: "Restart Grok Build so it reloads config.toml (it also reads ~/.claude.json and .cursor/mcp.json).", - skills: skillDirs(home(".grok", "skills"), project(".grok", "skills")), + skills: sharedSkills({ legacyDirs: { user: home(".grok", "skills"), project: project(".grok", "skills") } }), }, { id: "mistral-vibe", @@ -607,7 +639,7 @@ const SPECS: readonly AgentSpec[] = [ format: "toml-array", mcp: { user: home(".vibe", "config.toml"), project: project(".vibe", "config.toml") }, restart: "Restart Vibe so it reloads config.toml.", - skills: skillDirs(home(".vibe", "skills"), project(".vibe", "skills")), + skills: skillDirs(home(".vibe", "skills"), SHARED_SKILLS_PROJECT, { legacyDirs: { user: home(".vibe", "skills"), project: project(".vibe", "skills") } }), }, { id: "lm-studio", @@ -699,6 +731,17 @@ export function legacyRuleFiles(spec: AgentSpec, scope: ConfigScope, ctx: PathCo return [...skillNames].map((name) => join(dir, `${prefix}${name}${legacy.suffix}`)); } +/** Skill dirs a Summer 3.1.0 install may have written at this agent's old native location; `null` when it never moved. */ +export function legacySkillDirs(spec: AgentSpec, scope: ConfigScope, ctx: PathContext, skillNames: Iterable): string[] | null { + const legacy = spec.skills?.legacyDirs; + if (!legacy) return null; + const dir = scope === "project" ? (legacy.project ? legacy.project(ctx) : null) : legacy.user(ctx); + if (!dir) return []; + const current = resolveSkillPath(spec, scope, ctx)?.path; + if (current === dir) return []; + return [...skillNames].map((name) => join(dir, name)); +} + export interface ResolvedSkillPath { kind: SkillLocationKind; path: string;