agentloom 0.1.5 → 0.1.6

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,13 @@ 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
+ ]);
11
17
  const ENTITY_VERBS = new Set([
12
18
  "add",
13
19
  "list",
@@ -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"),
@@ -21,3 +21,4 @@ export declare function applySkillProviderSideEffects(options: {
21
21
  dryRun?: boolean;
22
22
  warn?: (message: string) => void;
23
23
  }): void;
24
+ export declare function getLegacyCopilotSkillDirs(paths: ScopePaths): string[];
@@ -125,13 +125,21 @@ export function skillContentMatchesTarget(skill, targetDir) {
125
125
  }
126
126
  export function applySkillProviderSideEffects(options) {
127
127
  const pathsToSymlink = getProviderSkillsPaths(options.paths, options.providers);
128
- if (pathsToSymlink.length === 0)
128
+ if (options.providers.includes("copilot")) {
129
+ const copilotTargets = getProviderSkillsPaths(options.paths, ["copilot"]);
130
+ const hasCurrentCopilotTarget = copilotTargets.some((targetPath) => fs.existsSync(targetPath));
131
+ if (!hasCurrentCopilotTarget) {
132
+ pathsToSymlink.push(...getLegacyCopilotSkillDirs(options.paths));
133
+ }
134
+ }
135
+ const uniquePathsToSymlink = [...new Set(pathsToSymlink)];
136
+ if (uniquePathsToSymlink.length === 0)
129
137
  return;
130
138
  const canonicalSkillsDir = options.paths.skillsDir;
131
139
  if (!options.dryRun) {
132
140
  ensureDir(canonicalSkillsDir);
133
141
  }
134
- for (const targetSkillsDir of pathsToSymlink) {
142
+ for (const targetSkillsDir of uniquePathsToSymlink) {
135
143
  enforceProviderSkillsSymlink({
136
144
  targetSkillsDir,
137
145
  canonicalSkillsDir,
@@ -140,6 +148,17 @@ export function applySkillProviderSideEffects(options) {
140
148
  });
141
149
  }
142
150
  }
151
+ export function getLegacyCopilotSkillDirs(paths) {
152
+ const legacyDirs = [
153
+ paths.scope === "local"
154
+ ? path.join(paths.workspaceRoot, ".claude", "skills")
155
+ : path.join(paths.homeDir, ".claude", "skills"),
156
+ ];
157
+ if (paths.scope === "global") {
158
+ legacyDirs.push(path.join(paths.homeDir, ".github", "skills"));
159
+ }
160
+ return legacyDirs;
161
+ }
143
162
  function enforceProviderSkillsSymlink(options) {
144
163
  const resolvedCanonical = realPathOrResolved(options.canonicalSkillsDir);
145
164
  const targetDir = options.targetSkillsDir;
@@ -18,5 +18,7 @@ export declare function prepareSource(options: {
18
18
  }): PreparedSource;
19
19
  export declare function discoverSourceAgentsDir(importRoot: string): string | null;
20
20
  export declare function discoverSourceMcpPath(importRoot: string): string | null;
21
+ export declare function discoverSourceCommandsDirs(importRoot: string): string[];
21
22
  export declare function discoverSourceCommandsDir(importRoot: string): string | null;
22
23
  export declare function discoverSourceSkillsDir(importRoot: string): string | null;
24
+ export declare function discoverSourceRulesDir(importRoot: string): string | null;
@@ -65,6 +65,10 @@ export function discoverSourceAgentsDir(importRoot) {
65
65
  if (fs.existsSync(nested) && fs.statSync(nested).isDirectory()) {
66
66
  return nested;
67
67
  }
68
+ const githubAgents = path.join(importRoot, ".github", "agents");
69
+ if (fs.existsSync(githubAgents) && fs.statSync(githubAgents).isDirectory()) {
70
+ return githubAgents;
71
+ }
68
72
  return null;
69
73
  }
70
74
  export function discoverSourceMcpPath(importRoot) {
@@ -76,20 +80,34 @@ export function discoverSourceMcpPath(importRoot) {
76
80
  return direct;
77
81
  return null;
78
82
  }
79
- export function discoverSourceCommandsDir(importRoot) {
83
+ export function discoverSourceCommandsDirs(importRoot) {
80
84
  const nested = path.join(importRoot, ".agents", "commands");
81
85
  if (fs.existsSync(nested) && fs.statSync(nested).isDirectory()) {
82
- return nested;
86
+ return [nested];
83
87
  }
84
88
  const direct = path.join(importRoot, "commands");
85
89
  if (fs.existsSync(direct) && fs.statSync(direct).isDirectory()) {
86
- return direct;
90
+ return [direct];
87
91
  }
88
92
  const prompts = path.join(importRoot, "prompts");
89
93
  if (fs.existsSync(prompts) && fs.statSync(prompts).isDirectory()) {
90
- return prompts;
94
+ return [prompts];
91
95
  }
92
- return null;
96
+ const providerFallbacks = [];
97
+ const githubPrompts = path.join(importRoot, ".github", "prompts");
98
+ if (fs.existsSync(githubPrompts) &&
99
+ fs.statSync(githubPrompts).isDirectory()) {
100
+ providerFallbacks.push(githubPrompts);
101
+ }
102
+ const geminiCommands = path.join(importRoot, ".gemini", "commands");
103
+ if (fs.existsSync(geminiCommands) &&
104
+ fs.statSync(geminiCommands).isDirectory()) {
105
+ providerFallbacks.push(geminiCommands);
106
+ }
107
+ return providerFallbacks;
108
+ }
109
+ export function discoverSourceCommandsDir(importRoot) {
110
+ return discoverSourceCommandsDirs(importRoot)[0] ?? null;
93
111
  }
94
112
  export function discoverSourceSkillsDir(importRoot) {
95
113
  const nested = path.join(importRoot, ".agents", "skills");
@@ -106,6 +124,17 @@ export function discoverSourceSkillsDir(importRoot) {
106
124
  }
107
125
  return null;
108
126
  }
127
+ export function discoverSourceRulesDir(importRoot) {
128
+ const nested = path.join(importRoot, ".agents", "rules");
129
+ if (fs.existsSync(nested) && fs.statSync(nested).isDirectory()) {
130
+ return nested;
131
+ }
132
+ const direct = path.join(importRoot, "rules");
133
+ if (fs.existsSync(direct) && fs.statSync(direct).isDirectory()) {
134
+ return direct;
135
+ }
136
+ return null;
137
+ }
109
138
  function resolveImportRoot(rootPath, subdir) {
110
139
  if (!subdir)
111
140
  return rootPath;
@@ -4,7 +4,7 @@ export type TelemetrySource = {
4
4
  repo: string;
5
5
  };
6
6
  export type TelemetryItem = {
7
- entityType: "agent" | "skill" | "command" | "mcp";
7
+ entityType: "agent" | "skill" | "command" | "mcp" | "rule";
8
8
  name: string;
9
9
  filePath: string;
10
10
  };
@@ -47,6 +47,7 @@ export function parseGitHubSource(input) {
47
47
  }
48
48
  export function buildTelemetryItems(summary) {
49
49
  const items = [];
50
+ const importedRules = summary.importedRules ?? [];
50
51
  for (const filePath of summary.importedAgents) {
51
52
  const name = path.basename(filePath, path.extname(filePath));
52
53
  items.push({ entityType: "agent", name, filePath });
@@ -58,6 +59,21 @@ export function buildTelemetryItems(summary) {
58
59
  for (const serverName of summary.importedMcpServers) {
59
60
  items.push({ entityType: "mcp", name: serverName, filePath: "mcp.json" });
60
61
  }
62
+ if (summary.telemetryRules && summary.telemetryRules.length > 0) {
63
+ for (const rule of summary.telemetryRules) {
64
+ items.push({
65
+ entityType: "rule",
66
+ name: rule.name,
67
+ filePath: rule.filePath.replace(/^\/+/, ""),
68
+ });
69
+ }
70
+ }
71
+ else {
72
+ for (const filePath of importedRules) {
73
+ const name = path.basename(filePath, path.extname(filePath));
74
+ items.push({ entityType: "rule", name, filePath });
75
+ }
76
+ }
61
77
  if (summary.telemetrySkills && summary.telemetrySkills.length > 0) {
62
78
  for (const skill of summary.telemetrySkills) {
63
79
  items.push({