agentloom 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ import type { Provider } from "../types.js";
2
+ type ProviderEntity = "agent" | "command";
3
+ export declare function isProviderEntityFileName(options: {
4
+ provider: Provider;
5
+ entity: ProviderEntity;
6
+ fileName: string;
7
+ }): boolean;
8
+ export {};
@@ -0,0 +1,34 @@
1
+ import path from "node:path";
2
+ const COPILOT_AGENT_FILE = /\.agent\.md$/i;
3
+ const COPILOT_COMMAND_FILE = /\.prompt\.md$/i;
4
+ const GENERIC_AGENT_FILE = /\.md$/i;
5
+ const GENERIC_COMMAND_FILE = /(?:\.prompt)?\.(md|mdc)$/i;
6
+ const GEMINI_COMMAND_FILE = /\.(toml|md|mdc)$/i;
7
+ const EXCLUDED_ENTITY_STEMS = new Set(["readme"]);
8
+ export function isProviderEntityFileName(options) {
9
+ const normalizedName = path.basename(options.fileName);
10
+ const stem = normalizeEntityStem(normalizedName);
11
+ if (!stem || EXCLUDED_ENTITY_STEMS.has(stem)) {
12
+ return false;
13
+ }
14
+ if (options.provider === "copilot") {
15
+ if (options.entity === "agent") {
16
+ return COPILOT_AGENT_FILE.test(normalizedName);
17
+ }
18
+ return COPILOT_COMMAND_FILE.test(normalizedName);
19
+ }
20
+ if (options.entity === "agent") {
21
+ return GENERIC_AGENT_FILE.test(normalizedName);
22
+ }
23
+ if (options.provider === "gemini") {
24
+ return GEMINI_COMMAND_FILE.test(normalizedName);
25
+ }
26
+ return GENERIC_COMMAND_FILE.test(normalizedName);
27
+ }
28
+ function normalizeEntityStem(fileName) {
29
+ return fileName
30
+ .toLowerCase()
31
+ .replace(/\.agent\.md$/i, "")
32
+ .replace(/\.prompt\.(md|mdc)$/i, "")
33
+ .replace(/\.(md|mdc|toml)$/i, "");
34
+ }
@@ -1,7 +1,14 @@
1
1
  import type { Provider, ScopePaths } from "../types.js";
2
2
  export declare function getProviderAgentsDir(paths: ScopePaths, provider: Provider): string;
3
3
  export declare function getProviderCommandsDir(paths: ScopePaths, provider: Provider): string;
4
- export declare function getProviderSkillsPaths(paths: ScopePaths, providers: Provider[]): string[];
4
+ export declare function getProviderSkillsPaths(paths: ScopePaths, providers: readonly Provider[]): string[];
5
+ export declare function getCursorRulesDir(paths: ScopePaths): string;
6
+ export declare function getAgentsInstructionPath(paths: ScopePaths): string;
7
+ export declare function getClaudeInstructionPath(paths: ScopePaths): string;
8
+ export declare function getGeminiInstructionPath(paths: ScopePaths): string;
9
+ export declare function getCopilotInstructionPath(paths: ScopePaths): string;
10
+ export declare function getOpenCodeInstructionPath(paths: ScopePaths): string;
11
+ export declare function getRuleInstructionPaths(paths: ScopePaths, providers: readonly Provider[]): string[];
5
12
  export declare function getCursorMcpPath(paths: ScopePaths): string;
6
13
  export declare function getClaudeMcpPath(paths: ScopePaths): string;
7
14
  export declare function getClaudeSettingsPath(paths: ScopePaths): string;
@@ -25,7 +25,7 @@ export function getProviderAgentsDir(paths, provider) {
25
25
  case "copilot":
26
26
  return paths.scope === "local"
27
27
  ? path.join(workspaceRoot, ".github", "agents")
28
- : path.join(home, ".vscode", "chatmodes");
28
+ : path.join(home, ".copilot", "agents");
29
29
  case "pi":
30
30
  return paths.scope === "local"
31
31
  ? path.join(workspaceRoot, ".pi", "agents")
@@ -59,7 +59,7 @@ export function getProviderCommandsDir(paths, provider) {
59
59
  case "copilot":
60
60
  return paths.scope === "local"
61
61
  ? path.join(workspaceRoot, ".github", "prompts")
62
- : path.join(home, ".github", "prompts");
62
+ : path.join(home, ".copilot", "prompts");
63
63
  case "pi":
64
64
  return paths.scope === "local"
65
65
  ? path.join(workspaceRoot, ".pi", "prompts")
@@ -70,12 +70,16 @@ export function getProviderCommandsDir(paths, provider) {
70
70
  }
71
71
  export function getProviderSkillsPaths(paths, providers) {
72
72
  const targets = new Set();
73
- const hasClaudeStyleProvider = providers.includes("claude") || providers.includes("copilot");
74
- if (hasClaudeStyleProvider) {
73
+ if (providers.includes("claude")) {
75
74
  targets.add(paths.scope === "local"
76
75
  ? path.join(paths.workspaceRoot, ".claude", "skills")
77
76
  : path.join(paths.homeDir, ".claude", "skills"));
78
77
  }
78
+ if (providers.includes("copilot")) {
79
+ targets.add(paths.scope === "local"
80
+ ? path.join(paths.workspaceRoot, ".github", "skills")
81
+ : path.join(paths.homeDir, ".copilot", "skills"));
82
+ }
79
83
  if (providers.includes("cursor")) {
80
84
  targets.add(paths.scope === "local"
81
85
  ? path.join(paths.workspaceRoot, ".cursor", "skills")
@@ -88,6 +92,66 @@ export function getProviderSkillsPaths(paths, providers) {
88
92
  }
89
93
  return [...targets];
90
94
  }
95
+ export function getCursorRulesDir(paths) {
96
+ return paths.scope === "local"
97
+ ? path.join(paths.workspaceRoot, ".cursor", "rules")
98
+ : path.join(paths.homeDir, ".cursor", "rules");
99
+ }
100
+ export function getAgentsInstructionPath(paths) {
101
+ return path.join(paths.workspaceRoot, "AGENTS.md");
102
+ }
103
+ export function getClaudeInstructionPath(paths) {
104
+ return paths.scope === "local"
105
+ ? path.join(paths.workspaceRoot, "CLAUDE.md")
106
+ : path.join(paths.homeDir, ".claude", "CLAUDE.md");
107
+ }
108
+ export function getGeminiInstructionPath(paths) {
109
+ return paths.scope === "local"
110
+ ? path.join(paths.workspaceRoot, "GEMINI.md")
111
+ : path.join(paths.homeDir, ".gemini", "GEMINI.md");
112
+ }
113
+ export function getCopilotInstructionPath(paths) {
114
+ return paths.scope === "local"
115
+ ? path.join(paths.workspaceRoot, ".github", "copilot-instructions.md")
116
+ : path.join(paths.homeDir, ".copilot", "copilot-instructions.md");
117
+ }
118
+ export function getOpenCodeInstructionPath(paths) {
119
+ return paths.scope === "local"
120
+ ? path.join(paths.workspaceRoot, "AGENTS.md")
121
+ : path.join(paths.homeDir, ".config", "opencode", "AGENTS.md");
122
+ }
123
+ export function getRuleInstructionPaths(paths, providers) {
124
+ const targets = new Set();
125
+ if (paths.scope === "local") {
126
+ targets.add(getAgentsInstructionPath(paths));
127
+ }
128
+ for (const provider of providers) {
129
+ if (provider === "claude") {
130
+ targets.add(getClaudeInstructionPath(paths));
131
+ continue;
132
+ }
133
+ if (provider === "gemini") {
134
+ targets.add(getGeminiInstructionPath(paths));
135
+ continue;
136
+ }
137
+ if (provider === "copilot") {
138
+ targets.add(getCopilotInstructionPath(paths));
139
+ continue;
140
+ }
141
+ if (provider === "opencode") {
142
+ targets.add(getOpenCodeInstructionPath(paths));
143
+ continue;
144
+ }
145
+ if (provider === "codex" && paths.scope === "local") {
146
+ targets.add(getAgentsInstructionPath(paths));
147
+ continue;
148
+ }
149
+ if (provider === "pi" && paths.scope === "local") {
150
+ targets.add(getAgentsInstructionPath(paths));
151
+ }
152
+ }
153
+ return [...targets];
154
+ }
91
155
  export function getCursorMcpPath(paths) {
92
156
  return paths.scope === "local"
93
157
  ? path.join(paths.workspaceRoot, ".cursor", "mcp.json")
@@ -101,7 +165,7 @@ export function getClaudeMcpPath(paths) {
101
165
  export function getClaudeSettingsPath(paths) {
102
166
  return paths.scope === "local"
103
167
  ? path.join(paths.workspaceRoot, ".claude", "settings.json")
104
- : path.join(paths.homeDir, ".claude.json");
168
+ : path.join(paths.homeDir, ".claude", "settings.json");
105
169
  }
106
170
  export function getOpenCodeConfigPath(paths) {
107
171
  return paths.scope === "local"
@@ -7,7 +7,20 @@ const AGGREGATE_VERBS = new Set([
7
7
  "delete",
8
8
  "init",
9
9
  ]);
10
- const ENTITY_NOUNS = new Set(["agent", "command", "mcp", "skill"]);
10
+ const ENTITY_NOUNS = new Set([
11
+ "agent",
12
+ "command",
13
+ "mcp",
14
+ "rule",
15
+ "skill",
16
+ ]);
17
+ const ENTITY_NOUN_ALIASES = {
18
+ agents: "agent",
19
+ commands: "command",
20
+ mcps: "mcp",
21
+ rules: "rule",
22
+ skills: "skill",
23
+ };
11
24
  const ENTITY_VERBS = new Set([
12
25
  "add",
13
26
  "list",
@@ -18,9 +31,10 @@ const ENTITY_VERBS = new Set([
18
31
  ]);
19
32
  const MCP_SERVER_VERBS = new Set(["add", "list", "delete"]);
20
33
  export function parseCommandRoute(argv) {
21
- const root = argv[0]?.trim().toLowerCase();
22
- if (!root)
34
+ const rawRoot = argv[0]?.trim().toLowerCase();
35
+ if (!rawRoot)
23
36
  return null;
37
+ const root = ENTITY_NOUN_ALIASES[rawRoot] ?? rawRoot;
24
38
  if (AGGREGATE_VERBS.has(root)) {
25
39
  return {
26
40
  mode: "aggregate",
@@ -0,0 +1,34 @@
1
+ export interface CanonicalRuleFile {
2
+ id: string;
3
+ name: string;
4
+ fileName: string;
5
+ sourcePath: string;
6
+ content: string;
7
+ body: string;
8
+ frontmatter: Record<string, unknown> & {
9
+ name: string;
10
+ };
11
+ }
12
+ export interface ManagedRuleBlock {
13
+ id: string;
14
+ name: string;
15
+ body: string;
16
+ }
17
+ export declare function parseRulesDir(rulesDir: string): CanonicalRuleFile[];
18
+ export declare function parseRuleMarkdown(content: string, sourcePath?: string): {
19
+ name: string;
20
+ body: string;
21
+ frontmatter: Record<string, unknown> & {
22
+ name: string;
23
+ };
24
+ };
25
+ export declare function normalizeRuleSelector(selector: string): string;
26
+ export declare function resolveRuleSelections(rules: CanonicalRuleFile[], selectors: string[]): {
27
+ selected: CanonicalRuleFile[];
28
+ unmatched: string[];
29
+ };
30
+ export declare function renderRuleForCursor(rule: CanonicalRuleFile): string;
31
+ export declare function renderManagedRuleBlock(rule: CanonicalRuleFile): string;
32
+ export declare function parseManagedRuleBlocks(content: string): ManagedRuleBlock[];
33
+ export declare function upsertManagedRuleBlocks(existingContent: string, rules: CanonicalRuleFile[]): string;
34
+ export declare function stripRuleFileExtension(fileName: string): string;
@@ -0,0 +1,149 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import matter from "gray-matter";
4
+ import YAML from "yaml";
5
+ import { listMarkdownFiles, slugify } from "./fs.js";
6
+ const MANAGED_BLOCK_PATTERN = /<!--\s*agentloom:([a-z0-9._-]+):start\s*-->[\s\S]*?<!--\s*agentloom:\1:end\s*-->\s*/gi;
7
+ const MANAGED_BLOCK_CAPTURE_PATTERN = /<!--\s*agentloom:([a-z0-9._-]+):start\s*-->\s*([\s\S]*?)\s*<!--\s*agentloom:\1:end\s*-->/gi;
8
+ export function parseRulesDir(rulesDir) {
9
+ if (!fs.existsSync(rulesDir))
10
+ return [];
11
+ return listMarkdownFiles(rulesDir)
12
+ .sort((a, b) => a.localeCompare(b))
13
+ .map((sourcePath) => {
14
+ const content = fs.readFileSync(sourcePath, "utf8");
15
+ const fileName = path.basename(sourcePath);
16
+ const parsed = parseRuleMarkdown(content, sourcePath);
17
+ return {
18
+ id: stripRuleFileExtension(fileName),
19
+ name: parsed.name,
20
+ fileName,
21
+ sourcePath,
22
+ content,
23
+ body: parsed.body,
24
+ frontmatter: parsed.frontmatter,
25
+ };
26
+ });
27
+ }
28
+ export function parseRuleMarkdown(content, sourcePath = "<rule>") {
29
+ const parsed = matter(content);
30
+ if (!parsed.data ||
31
+ typeof parsed.data !== "object" ||
32
+ Array.isArray(parsed.data)) {
33
+ throw new Error(`Rule "${sourcePath}" must include YAML frontmatter with required "name".`);
34
+ }
35
+ const frontmatter = parsed.data;
36
+ if (typeof frontmatter.name !== "string" || frontmatter.name.trim() === "") {
37
+ throw new Error(`Rule "${sourcePath}" is missing required frontmatter.name.`);
38
+ }
39
+ return {
40
+ name: frontmatter.name.trim(),
41
+ body: parsed.content.trimStart(),
42
+ frontmatter: {
43
+ ...frontmatter,
44
+ name: frontmatter.name.trim(),
45
+ },
46
+ };
47
+ }
48
+ export function normalizeRuleSelector(selector) {
49
+ const trimmed = selector.trim().replace(/^\/+/, "");
50
+ const withoutExt = stripRuleFileExtension(trimmed);
51
+ const normalized = slugify(withoutExt);
52
+ return normalized || withoutExt.toLowerCase();
53
+ }
54
+ export function resolveRuleSelections(rules, selectors) {
55
+ const selectedById = new Map();
56
+ const unmatched = [];
57
+ for (const selector of selectors) {
58
+ const normalizedSelector = normalizeRuleSelector(selector);
59
+ if (!normalizedSelector) {
60
+ continue;
61
+ }
62
+ const matches = rules.filter((rule) => {
63
+ const exactCandidates = new Set([
64
+ rule.id.toLowerCase(),
65
+ stripRuleFileExtension(rule.fileName).toLowerCase(),
66
+ ]);
67
+ const slugCandidates = new Set([
68
+ normalizeRuleSelector(rule.id),
69
+ normalizeRuleSelector(stripRuleFileExtension(rule.fileName)),
70
+ normalizeRuleSelector(rule.name),
71
+ ]);
72
+ return (exactCandidates.has(normalizedSelector) ||
73
+ slugCandidates.has(normalizedSelector));
74
+ });
75
+ if (matches.length === 0) {
76
+ unmatched.push(selector);
77
+ continue;
78
+ }
79
+ for (const match of matches) {
80
+ selectedById.set(match.id, match);
81
+ }
82
+ }
83
+ return {
84
+ selected: [...selectedById.values()],
85
+ unmatched,
86
+ };
87
+ }
88
+ export function renderRuleForCursor(rule) {
89
+ const fm = YAML.stringify(rule.frontmatter, { lineWidth: 0 }).trimEnd();
90
+ const body = rule.body.trimStart();
91
+ return `---\n${fm}\n---\n\n${body}${body.endsWith("\n") ? "" : "\n"}`;
92
+ }
93
+ export function renderManagedRuleBlock(rule) {
94
+ const body = rule.body.trim();
95
+ const heading = `## ${rule.name}`;
96
+ const content = body ? `${heading}\n\n${body}` : heading;
97
+ return `<!-- agentloom:${rule.id}:start -->\n${content}\n<!-- agentloom:${rule.id}:end -->`;
98
+ }
99
+ export function parseManagedRuleBlocks(content) {
100
+ const blocks = [];
101
+ content.replace(MANAGED_BLOCK_CAPTURE_PATTERN, (_match, rawId, rawInner) => {
102
+ const id = String(rawId).trim();
103
+ const inner = String(rawInner).trim();
104
+ const headingMatch = inner.match(/^##\s+(.+?)(?:\r?\n([\s\S]*))?$/);
105
+ const name = headingMatch?.[1]?.trim() || id;
106
+ const body = (headingMatch?.[2] ?? inner).replace(/^\r?\n/, "").trim();
107
+ blocks.push({
108
+ id,
109
+ name,
110
+ body: headingMatch ? body : inner,
111
+ });
112
+ return "";
113
+ });
114
+ return blocks;
115
+ }
116
+ export function upsertManagedRuleBlocks(existingContent, rules) {
117
+ const byId = new Map(rules.map((rule) => [rule.id, rule]));
118
+ const seen = new Set();
119
+ let nextContent = existingContent.replace(MANAGED_BLOCK_PATTERN, (match, id) => {
120
+ const normalizedId = String(id).trim();
121
+ const rule = byId.get(normalizedId);
122
+ if (!rule) {
123
+ return "";
124
+ }
125
+ if (seen.has(normalizedId)) {
126
+ return "";
127
+ }
128
+ seen.add(normalizedId);
129
+ const suffix = /\n$/.test(match) ? "\n" : "";
130
+ return `${renderManagedRuleBlock(rule)}${suffix}`;
131
+ });
132
+ const missing = rules
133
+ .filter((rule) => !seen.has(rule.id))
134
+ .map((rule) => renderManagedRuleBlock(rule));
135
+ if (missing.length === 0) {
136
+ return nextContent;
137
+ }
138
+ if (nextContent.trim().length === 0) {
139
+ return `${missing.join("\n\n")}\n`;
140
+ }
141
+ const trailingNewline = nextContent.endsWith("\n") ? "" : "\n";
142
+ return `${nextContent}${trailingNewline}\n${missing.join("\n\n")}\n`;
143
+ }
144
+ export function stripRuleFileExtension(fileName) {
145
+ const ext = path.extname(fileName);
146
+ if (!ext)
147
+ return fileName;
148
+ return fileName.slice(0, -ext.length);
149
+ }
@@ -15,6 +15,7 @@ export function buildScopePaths(cwd, scope, homeDir = os.homedir()) {
15
15
  agentsRoot,
16
16
  agentsDir: path.join(agentsRoot, "agents"),
17
17
  commandsDir: path.join(agentsRoot, "commands"),
18
+ rulesDir: path.join(agentsRoot, "rules"),
18
19
  skillsDir: path.join(agentsRoot, "skills"),
19
20
  mcpPath: path.join(agentsRoot, "mcp.json"),
20
21
  lockPath: path.join(agentsRoot, "agents.lock.json"),
@@ -1,6 +1,8 @@
1
1
  import type { Provider, ScopePaths } from "../types.js";
2
2
  export interface CanonicalSkill {
3
3
  name: string;
4
+ aliases: string[];
5
+ sourceDirName: string;
4
6
  sourcePath: string;
5
7
  skillPath: string;
6
8
  layout: "nested" | "root";
@@ -8,6 +10,7 @@ export interface CanonicalSkill {
8
10
  export declare const ROOT_SKILL_ARTIFACT_DIRS: readonly ["references", "assets", "scripts", "templates", "examples"];
9
11
  export declare function parseSkillsDir(skillsDir: string): CanonicalSkill[];
10
12
  export declare function normalizeSkillSelector(value: string): string;
13
+ export declare function resolveSkillSelector(skills: CanonicalSkill[], selector: string): CanonicalSkill | null;
11
14
  export declare function resolveSkillSelections(skills: CanonicalSkill[], selectors: string[]): {
12
15
  selected: CanonicalSkill[];
13
16
  unmatched: string[];
@@ -21,3 +24,4 @@ export declare function applySkillProviderSideEffects(options: {
21
24
  dryRun?: boolean;
22
25
  warn?: (message: string) => void;
23
26
  }): void;
27
+ export declare function getLegacyCopilotSkillDirs(paths: ScopePaths): string[];
@@ -22,8 +22,12 @@ export function parseSkillsDir(skillsDir) {
22
22
  const skillFile = path.join(skillDir, "SKILL.md");
23
23
  if (!fs.existsSync(skillFile))
24
24
  continue;
25
+ const raw = fs.readFileSync(skillFile, "utf8");
26
+ const canonicalName = extractSkillName(raw) || entry.name;
25
27
  skills.push({
26
- name: entry.name,
28
+ name: canonicalName,
29
+ aliases: buildSkillAliases(canonicalName, entry.name),
30
+ sourceDirName: entry.name,
27
31
  sourcePath: skillDir,
28
32
  skillPath: skillFile,
29
33
  layout: "nested",
@@ -37,9 +41,12 @@ export function parseSkillsDir(skillsDir) {
37
41
  return [];
38
42
  }
39
43
  const raw = fs.readFileSync(rootSkillFile, "utf8");
44
+ const canonicalName = extractSkillName(raw) || path.basename(skillsDir);
40
45
  return [
41
46
  {
42
- name: extractSkillName(raw) || path.basename(skillsDir),
47
+ name: canonicalName,
48
+ aliases: buildSkillAliases(canonicalName, path.basename(skillsDir)),
49
+ sourceDirName: path.basename(skillsDir),
43
50
  sourcePath: skillsDir,
44
51
  skillPath: rootSkillFile,
45
52
  layout: "root",
@@ -61,6 +68,16 @@ function extractSkillName(raw) {
61
68
  export function normalizeSkillSelector(value) {
62
69
  return slugify(value.trim().replace(/\/+$/, "")).toLowerCase();
63
70
  }
71
+ export function resolveSkillSelector(skills, selector) {
72
+ const normalizedSelector = normalizeSkillSelector(selector);
73
+ if (!normalizedSelector)
74
+ return null;
75
+ const canonicalMatch = skills.find((skill) => normalizeSkillSelector(skill.name) === normalizedSelector) ?? null;
76
+ if (canonicalMatch) {
77
+ return canonicalMatch;
78
+ }
79
+ return (skills.find((skill) => normalizeSkillSelector(skill.sourceDirName) === normalizedSelector) ?? null);
80
+ }
64
81
  export function resolveSkillSelections(skills, selectors) {
65
82
  const normalizedSelectors = selectors
66
83
  .map((item) => item.trim())
@@ -70,14 +87,12 @@ export function resolveSkillSelections(skills, selectors) {
70
87
  const selectedMap = new Map();
71
88
  const unmatched = [];
72
89
  for (const selector of normalizedSelectors) {
73
- const matches = skills.filter((skill) => normalizeSkillSelector(skill.name) === selector);
74
- if (matches.length === 0) {
90
+ const match = resolveSkillSelector(skills, selector);
91
+ if (!match) {
75
92
  unmatched.push(selector);
76
93
  continue;
77
94
  }
78
- for (const match of matches) {
79
- selectedMap.set(match.name, match);
80
- }
95
+ selectedMap.set(match.name, match);
81
96
  }
82
97
  return {
83
98
  selected: [...selectedMap.values()],
@@ -125,13 +140,21 @@ export function skillContentMatchesTarget(skill, targetDir) {
125
140
  }
126
141
  export function applySkillProviderSideEffects(options) {
127
142
  const pathsToSymlink = getProviderSkillsPaths(options.paths, options.providers);
128
- if (pathsToSymlink.length === 0)
143
+ if (options.providers.includes("copilot")) {
144
+ const copilotTargets = getProviderSkillsPaths(options.paths, ["copilot"]);
145
+ const hasCurrentCopilotTarget = copilotTargets.some((targetPath) => fs.existsSync(targetPath));
146
+ if (!hasCurrentCopilotTarget) {
147
+ pathsToSymlink.push(...getLegacyCopilotSkillDirs(options.paths));
148
+ }
149
+ }
150
+ const uniquePathsToSymlink = [...new Set(pathsToSymlink)];
151
+ if (uniquePathsToSymlink.length === 0)
129
152
  return;
130
153
  const canonicalSkillsDir = options.paths.skillsDir;
131
154
  if (!options.dryRun) {
132
155
  ensureDir(canonicalSkillsDir);
133
156
  }
134
- for (const targetSkillsDir of pathsToSymlink) {
157
+ for (const targetSkillsDir of uniquePathsToSymlink) {
135
158
  enforceProviderSkillsSymlink({
136
159
  targetSkillsDir,
137
160
  canonicalSkillsDir,
@@ -140,6 +163,17 @@ export function applySkillProviderSideEffects(options) {
140
163
  });
141
164
  }
142
165
  }
166
+ export function getLegacyCopilotSkillDirs(paths) {
167
+ const legacyDirs = [
168
+ paths.scope === "local"
169
+ ? path.join(paths.workspaceRoot, ".claude", "skills")
170
+ : path.join(paths.homeDir, ".claude", "skills"),
171
+ ];
172
+ if (paths.scope === "global") {
173
+ legacyDirs.push(path.join(paths.homeDir, ".github", "skills"));
174
+ }
175
+ return legacyDirs;
176
+ }
143
177
  function enforceProviderSkillsSymlink(options) {
144
178
  const resolvedCanonical = realPathOrResolved(options.canonicalSkillsDir);
145
179
  const targetDir = options.targetSkillsDir;
@@ -176,9 +210,7 @@ function enforceProviderSkillsSymlink(options) {
176
210
  function migrateProviderSkillsIntoCanonical(options) {
177
211
  const providerSkills = parseSkillsDir(options.providerSkillsDir);
178
212
  for (const skill of providerSkills) {
179
- const targetSkillDirName = skill.layout === "nested"
180
- ? path.basename(skill.sourcePath)
181
- : slugify(skill.name) || "skill";
213
+ const targetSkillDirName = slugify(skill.name) || "skill";
182
214
  const targetSkillDir = path.join(options.canonicalSkillsDir, targetSkillDirName);
183
215
  if (fs.existsSync(targetSkillDir)) {
184
216
  const sameContent = skill.layout === "nested"
@@ -199,6 +231,9 @@ function migrateProviderSkillsIntoCanonical(options) {
199
231
  copyRootSkillArtifacts(skill.sourcePath, targetSkillDir);
200
232
  }
201
233
  }
234
+ function buildSkillAliases(name, sourceDirName) {
235
+ return [...new Set([name, sourceDirName].filter(Boolean))];
236
+ }
202
237
  function moveDirectory(sourceDir, targetDir) {
203
238
  ensureDir(path.dirname(targetDir));
204
239
  try {
@@ -16,7 +16,14 @@ export declare function prepareSource(options: {
16
16
  ref?: string;
17
17
  subdir?: string;
18
18
  }): PreparedSource;
19
+ export declare function discoverPluginSourceRoots(importRoot: string): string[];
19
20
  export declare function discoverSourceAgentsDir(importRoot: string): string | null;
21
+ export declare function discoverSourceAgentsDirs(importRoot: string): string[];
20
22
  export declare function discoverSourceMcpPath(importRoot: string): string | null;
23
+ export declare function discoverSourceMcpPaths(importRoot: string): string[];
24
+ export declare function discoverSourceCommandsDirs(importRoot: string): string[];
21
25
  export declare function discoverSourceCommandsDir(importRoot: string): string | null;
22
26
  export declare function discoverSourceSkillsDir(importRoot: string): string | null;
27
+ export declare function discoverSourceSkillsDirs(importRoot: string): string[];
28
+ export declare function discoverSourceRulesDir(importRoot: string): string | null;
29
+ export declare function discoverSourceRulesDirs(importRoot: string): string[];