agentloom 0.1.4 → 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.
@@ -6,6 +6,7 @@ import { parseAgentMarkdown, parseAgentsDir } from "../core/agents.js";
6
6
  import { parseCommandsDir } from "../core/commands.js";
7
7
  import { formatUsageError, getFindHelpText } from "../core/copy.js";
8
8
  import { readCanonicalMcp } from "../core/mcp.js";
9
+ import { parseRulesDir } from "../core/rules.js";
9
10
  import { buildScopePaths } from "../core/scope.js";
10
11
  import { parseSkillsDir } from "../core/skills.js";
11
12
  const FIND_API_BASE = process.env.AGENTLOOM_FIND_API_BASE || "https://api.github.com";
@@ -34,6 +35,7 @@ export async function runScopedFindCommand(argv, target, searchClient = searchAg
34
35
  const shouldSearchAgents = target === "all" || target === "agent";
35
36
  const shouldSearchCommands = target === "all" || target === "command";
36
37
  const shouldSearchMcp = target === "all" || target === "mcp";
38
+ const shouldSearchRules = target === "all" || target === "rule";
37
39
  const shouldSearchSkills = target === "all" || target === "skill";
38
40
  const agentResult = shouldSearchAgents
39
41
  ? normalizeSearchResult(await searchClient(query, DEFAULT_RESULT_LIMIT))
@@ -44,6 +46,9 @@ export async function runScopedFindCommand(argv, target, searchClient = searchAg
44
46
  const mcpResult = shouldSearchMcp
45
47
  ? await searchMcpWithDiagnostics(query, DEFAULT_RESULT_LIMIT, FIND_API_BASE)
46
48
  : { items: [], failures: [] };
49
+ const ruleResult = shouldSearchRules
50
+ ? await searchRulesWithDiagnostics(query, DEFAULT_RESULT_LIMIT, FIND_API_BASE)
51
+ : { items: [], failures: [] };
47
52
  const skillResult = shouldSearchSkills
48
53
  ? await searchSkillsWithDiagnostics(query, DEFAULT_RESULT_LIMIT, FIND_API_BASE)
49
54
  : { items: [], failures: [] };
@@ -85,12 +90,14 @@ export async function runScopedFindCommand(argv, target, searchClient = searchAg
85
90
  const total = agentResult.agents.length +
86
91
  commandResult.items.length +
87
92
  mcpResult.items.length +
93
+ ruleResult.items.length +
88
94
  skillResult.items.length +
89
95
  local.total;
90
96
  if (total === 0) {
91
97
  const failureCount = agentResult.failures.length +
92
98
  commandResult.failures.length +
93
99
  mcpResult.failures.length +
100
+ ruleResult.failures.length +
94
101
  skillResult.failures.length;
95
102
  if (failureCount > 0) {
96
103
  throw new Error(`Search returned no results and encountered ${failureCount} remote scan failure(s).`);
@@ -134,6 +141,15 @@ export async function runScopedFindCommand(argv, target, searchClient = searchAg
134
141
  }
135
142
  console.log("");
136
143
  }
144
+ if (ruleResult.items.length > 0) {
145
+ console.log("Rule matches:");
146
+ for (const result of ruleResult.items) {
147
+ console.log(` - ${result.repo}@${result.ruleName}${formatStars(result.stars)} (${result.filePath})`);
148
+ console.log(` ${result.fileUrl}`);
149
+ console.log(` Install: ${buildRuleInstallCommand(result)}`);
150
+ }
151
+ console.log("");
152
+ }
137
153
  if (skillResult.items.length > 0) {
138
154
  console.log("Skill matches:");
139
155
  for (const result of skillResult.items) {
@@ -147,6 +163,7 @@ export async function runScopedFindCommand(argv, target, searchClient = searchAg
147
163
  ...agentResult.failures,
148
164
  ...commandResult.failures,
149
165
  ...mcpResult.failures,
166
+ ...ruleResult.failures,
150
167
  ...skillResult.failures,
151
168
  ];
152
169
  if (failures.length > 0) {
@@ -351,6 +368,42 @@ async function searchSkillsWithDiagnostics(query, limit, apiBase) {
351
368
  }));
352
369
  return normalizeEntityScanResults(scanned, limit);
353
370
  }
371
+ async function searchRulesWithDiagnostics(query, limit, apiBase) {
372
+ const tokens = tokenizeQuery(query);
373
+ const repos = await searchReposByQuery(query, apiBase);
374
+ const candidates = repos.slice(0, DEFAULT_REPO_SCAN_LIMIT);
375
+ const scanned = await Promise.allSettled(candidates.map(async (repo) => {
376
+ const tree = await getRepoTree(repo, apiBase);
377
+ const matches = [];
378
+ for (const entry of tree) {
379
+ if (entry.type !== "blob")
380
+ continue;
381
+ const filePath = toTrimmedString(entry.path);
382
+ if (!filePath)
383
+ continue;
384
+ const parsed = parseRulePath(filePath);
385
+ if (!parsed)
386
+ continue;
387
+ const haystack = `${repo.fullName} ${filePath} ${parsed.ruleName}`.toLowerCase();
388
+ if (tokens.length > 0 &&
389
+ !tokens.some((token) => haystack.includes(token))) {
390
+ continue;
391
+ }
392
+ matches.push({
393
+ repo: repo.fullName,
394
+ ruleName: parsed.ruleName,
395
+ installRuleSelector: parsed.installRuleSelector,
396
+ filePath,
397
+ fileUrl: `${repo.repoWebUrl}/blob/${repo.defaultBranch}/${filePath}`,
398
+ source: repo.installSource,
399
+ stars: repo.stars,
400
+ subdir: parsed.subdir,
401
+ });
402
+ }
403
+ return matches;
404
+ }));
405
+ return normalizeEntityScanResults(scanned, limit);
406
+ }
354
407
  async function searchReposByQuery(query, apiBase) {
355
408
  const url = buildApiUrl(apiBase, "search/repositories");
356
409
  url.searchParams.set("q", `${query} in:name,description,readme`);
@@ -433,6 +486,17 @@ async function getRepoTree(repo, apiBase) {
433
486
  return payload.tree;
434
487
  }
435
488
  function parseAgentPath(filePath) {
489
+ const directGitHub = filePath.match(/^\.github\/agents\/([^/]+)\.agent\.md$/i);
490
+ if (directGitHub) {
491
+ return { agentName: directGitHub[1] };
492
+ }
493
+ const nestedGitHub = filePath.match(/^(.+)\/\.github\/agents\/([^/]+)\.agent\.md$/i);
494
+ if (nestedGitHub) {
495
+ return {
496
+ subdir: nestedGitHub[1],
497
+ agentName: nestedGitHub[2],
498
+ };
499
+ }
436
500
  const directAgentloom = filePath.match(/^\.agents\/agents\/([^/]+)\.md$/);
437
501
  if (directAgentloom) {
438
502
  return { agentName: directAgentloom[1] };
@@ -458,41 +522,55 @@ function parseAgentPath(filePath) {
458
522
  return null;
459
523
  }
460
524
  function parseCommandPath(filePath) {
461
- const directAgentloom = filePath.match(/^\.agents\/commands\/([^/]+)\.(md|mdc)$/i);
525
+ const directGitHub = filePath.match(/^\.github\/prompts\/([^/]+)\.prompt\.md$/i);
526
+ if (directGitHub) {
527
+ return { commandName: stripPromptSuffix(directGitHub[1]) };
528
+ }
529
+ const nestedGitHub = filePath.match(/^(.+)\/\.github\/prompts\/([^/]+)\.prompt\.md$/i);
530
+ if (nestedGitHub) {
531
+ return {
532
+ subdir: nestedGitHub[1],
533
+ commandName: stripPromptSuffix(nestedGitHub[2]),
534
+ };
535
+ }
536
+ const directAgentloom = filePath.match(/^\.agents\/commands\/([^/]+)(?:\.prompt)?\.(md|mdc)$/i);
462
537
  if (directAgentloom) {
463
- return { commandName: directAgentloom[1] };
538
+ return { commandName: stripPromptSuffix(directAgentloom[1]) };
464
539
  }
465
- const nestedAgentloom = filePath.match(/^(.+)\/\.agents\/commands\/([^/]+)\.(md|mdc)$/i);
540
+ const nestedAgentloom = filePath.match(/^(.+)\/\.agents\/commands\/([^/]+)(?:\.prompt)?\.(md|mdc)$/i);
466
541
  if (nestedAgentloom) {
467
542
  return {
468
543
  subdir: nestedAgentloom[1],
469
- commandName: nestedAgentloom[2],
544
+ commandName: stripPromptSuffix(nestedAgentloom[2]),
470
545
  };
471
546
  }
472
- const directCommands = filePath.match(/^commands\/([^/]+)\.(md|mdc)$/i);
547
+ const directCommands = filePath.match(/^commands\/([^/]+)(?:\.prompt)?\.(md|mdc)$/i);
473
548
  if (directCommands) {
474
- return { commandName: directCommands[1] };
549
+ return { commandName: stripPromptSuffix(directCommands[1]) };
475
550
  }
476
- const nestedCommands = filePath.match(/^(.+)\/commands\/([^/]+)\.(md|mdc)$/i);
551
+ const nestedCommands = filePath.match(/^(.+)\/commands\/([^/]+)(?:\.prompt)?\.(md|mdc)$/i);
477
552
  if (nestedCommands) {
478
553
  return {
479
554
  subdir: nestedCommands[1],
480
- commandName: nestedCommands[2],
555
+ commandName: stripPromptSuffix(nestedCommands[2]),
481
556
  };
482
557
  }
483
- const directPrompts = filePath.match(/^prompts\/([^/]+)\.(md|mdc)$/i);
558
+ const directPrompts = filePath.match(/^prompts\/([^/]+)(?:\.prompt)?\.(md|mdc)$/i);
484
559
  if (directPrompts) {
485
- return { commandName: directPrompts[1] };
560
+ return { commandName: stripPromptSuffix(directPrompts[1]) };
486
561
  }
487
- const nestedPrompts = filePath.match(/^(.+)\/prompts\/([^/]+)\.(md|mdc)$/i);
562
+ const nestedPrompts = filePath.match(/^(.+)\/prompts\/([^/]+)(?:\.prompt)?\.(md|mdc)$/i);
488
563
  if (nestedPrompts) {
489
564
  return {
490
565
  subdir: nestedPrompts[1],
491
- commandName: nestedPrompts[2],
566
+ commandName: stripPromptSuffix(nestedPrompts[2]),
492
567
  };
493
568
  }
494
569
  return null;
495
570
  }
571
+ function stripPromptSuffix(commandName) {
572
+ return commandName.replace(/\.prompt$/i, "");
573
+ }
496
574
  function parseSkillPath(filePath) {
497
575
  const directAgentloom = filePath.match(/^\.agents\/skills\/([^/]+)\/SKILL\.md$/i);
498
576
  if (directAgentloom) {
@@ -533,6 +611,39 @@ function parseSkillPath(filePath) {
533
611
  }
534
612
  return null;
535
613
  }
614
+ function parseRulePath(filePath) {
615
+ const directCanonical = filePath.match(/^\.agents\/rules\/([^/]+)\.md$/i);
616
+ if (directCanonical) {
617
+ return {
618
+ ruleName: directCanonical[1],
619
+ installRuleSelector: directCanonical[1],
620
+ };
621
+ }
622
+ const nestedCanonical = filePath.match(/^(.+)\/\.agents\/rules\/([^/]+)\.md$/i);
623
+ if (nestedCanonical) {
624
+ return {
625
+ subdir: nestedCanonical[1],
626
+ ruleName: nestedCanonical[2],
627
+ installRuleSelector: nestedCanonical[2],
628
+ };
629
+ }
630
+ const directRules = filePath.match(/^rules\/([^/]+)\.md$/i);
631
+ if (directRules) {
632
+ return {
633
+ ruleName: directRules[1],
634
+ installRuleSelector: directRules[1],
635
+ };
636
+ }
637
+ const nestedRules = filePath.match(/^(.+)\/rules\/([^/]+)\.md$/i);
638
+ if (nestedRules) {
639
+ return {
640
+ subdir: nestedRules[1],
641
+ ruleName: nestedRules[2],
642
+ installRuleSelector: nestedRules[2],
643
+ };
644
+ }
645
+ return null;
646
+ }
536
647
  function isMcpPath(filePath) {
537
648
  return (/^mcp\.json$/i.test(filePath) ||
538
649
  /^\.agents\/mcp\.json$/i.test(filePath) ||
@@ -701,6 +812,21 @@ function buildSkillInstallCommand(result) {
701
812
  }
702
813
  return `agentloom skill add ${repoArg} --skills ${quoteShellArg(selector)}`;
703
814
  }
815
+ function buildRuleInstallCommand(result) {
816
+ const source = result.source?.trim() || result.repo;
817
+ const repoArg = quoteShellArg(source);
818
+ const selector = result.installRuleSelector?.trim();
819
+ if (result.subdir && result.subdir.trim()) {
820
+ if (!selector) {
821
+ return `agentloom rule add ${repoArg} --subdir ${quoteShellArg(result.subdir)}`;
822
+ }
823
+ return `agentloom rule add ${repoArg} --subdir ${quoteShellArg(result.subdir)} --rules ${quoteShellArg(selector)}`;
824
+ }
825
+ if (!selector) {
826
+ return `agentloom rule add ${repoArg}`;
827
+ }
828
+ return `agentloom rule add ${repoArg} --rules ${quoteShellArg(selector)}`;
829
+ }
704
830
  function tokenizeQuery(query) {
705
831
  return query
706
832
  .toLowerCase()
@@ -749,6 +875,14 @@ function searchLocalMatches(query, target) {
749
875
  lines.push(`mcp: ${name}`);
750
876
  }
751
877
  }
878
+ if ((target === "all" || target === "rule") && fsExists(paths.rulesDir)) {
879
+ const rules = parseRulesDir(paths.rulesDir);
880
+ for (const rule of rules) {
881
+ if (!matchesTokens(`${rule.name} ${rule.fileName}`, tokens))
882
+ continue;
883
+ lines.push(`rule: ${rule.name} (${rule.fileName})`);
884
+ }
885
+ }
752
886
  if ((target === "all" || target === "skill") && fsExists(paths.skillsDir)) {
753
887
  const skills = parseSkillsDir(paths.skillsDir);
754
888
  for (const skill of skills) {
@@ -0,0 +1,2 @@
1
+ import type { ParsedArgs } from "minimist";
2
+ export declare function runRuleCommand(argv: ParsedArgs, cwd: string): Promise<void>;
@@ -0,0 +1,86 @@
1
+ import { formatUsageError } from "../core/copy.js";
2
+ import { parseRulesDir } from "../core/rules.js";
3
+ import { runScopedAddCommand } from "./add.js";
4
+ import { runScopedDeleteCommand } from "./delete.js";
5
+ import { resolvePathsForCommand } from "./entity-utils.js";
6
+ import { runScopedFindCommand } from "./find.js";
7
+ import { runScopedSyncCommand } from "./sync.js";
8
+ import { runScopedUpdateCommand } from "./update.js";
9
+ export async function runRuleCommand(argv, cwd) {
10
+ const action = argv._[1];
11
+ if (argv.help || !action) {
12
+ console.log("Usage:\n agentloom rule <add|list|delete|find|update|sync> [options]");
13
+ return;
14
+ }
15
+ if (action !== "add" &&
16
+ action !== "list" &&
17
+ action !== "delete" &&
18
+ action !== "find" &&
19
+ action !== "update" &&
20
+ action !== "sync") {
21
+ throw new Error(formatUsageError({
22
+ issue: "Invalid rule command.",
23
+ usage: "agentloom rule <add|list|delete|find|update|sync> [options]",
24
+ example: "agentloom rule add farnoodma/agents",
25
+ }));
26
+ }
27
+ if (action === "list") {
28
+ const paths = await resolvePathsForCommand(argv, cwd);
29
+ const rules = parseRulesDir(paths.rulesDir);
30
+ if (Boolean(argv.json)) {
31
+ console.log(JSON.stringify({
32
+ version: 1,
33
+ rules: rules.map((rule) => ({
34
+ id: rule.id,
35
+ name: rule.name,
36
+ fileName: rule.fileName,
37
+ })),
38
+ }, null, 2));
39
+ return;
40
+ }
41
+ if (rules.length === 0) {
42
+ console.log("No canonical rules configured.");
43
+ return;
44
+ }
45
+ for (const rule of rules) {
46
+ console.log(`${rule.name} (${rule.fileName})`);
47
+ }
48
+ return;
49
+ }
50
+ if (action === "add") {
51
+ await runScopedAddCommand({
52
+ argv,
53
+ cwd,
54
+ entity: "rule",
55
+ sourceIndex: 2,
56
+ });
57
+ return;
58
+ }
59
+ if (action === "delete") {
60
+ await runScopedDeleteCommand({
61
+ argv,
62
+ cwd,
63
+ entity: "rule",
64
+ sourceIndex: 2,
65
+ });
66
+ return;
67
+ }
68
+ if (action === "find") {
69
+ await runScopedFindCommand(argv, "rule");
70
+ return;
71
+ }
72
+ if (action === "update") {
73
+ await runScopedUpdateCommand({
74
+ argv,
75
+ cwd,
76
+ entity: "rule",
77
+ sourceIndex: 2,
78
+ });
79
+ return;
80
+ }
81
+ await runScopedSyncCommand({
82
+ argv,
83
+ cwd,
84
+ target: "rule",
85
+ });
86
+ }
@@ -74,10 +74,18 @@ function createDryRunCanonicalPaths(paths) {
74
74
  const tempAgentsRoot = path.join(tempRoot, ".agents");
75
75
  if (fs.existsSync(paths.agentsRoot) &&
76
76
  fs.statSync(paths.agentsRoot).isDirectory()) {
77
- fs.cpSync(paths.agentsRoot, tempAgentsRoot, {
78
- recursive: true,
79
- force: true,
80
- });
77
+ try {
78
+ fs.cpSync(paths.agentsRoot, tempAgentsRoot, {
79
+ recursive: true,
80
+ force: true,
81
+ });
82
+ }
83
+ catch (error) {
84
+ const code = error.code;
85
+ if (code !== "ENOENT") {
86
+ throw error;
87
+ }
88
+ }
81
89
  }
82
90
  return {
83
91
  paths: {
@@ -85,6 +93,7 @@ function createDryRunCanonicalPaths(paths) {
85
93
  agentsRoot: tempAgentsRoot,
86
94
  agentsDir: path.join(tempAgentsRoot, "agents"),
87
95
  commandsDir: path.join(tempAgentsRoot, "commands"),
96
+ rulesDir: path.join(tempAgentsRoot, "rules"),
88
97
  skillsDir: path.join(tempAgentsRoot, "skills"),
89
98
  mcpPath: path.join(tempAgentsRoot, "mcp.json"),
90
99
  lockPath: path.join(tempAgentsRoot, "agents.lock.json"),
@@ -2,6 +2,7 @@ import path from "node:path";
2
2
  import { parseProvidersFlag } from "../core/argv.js";
3
3
  import { importSource, NonInteractiveConflictError } from "../core/importer.js";
4
4
  import { normalizeCommandSelector, stripCommandFileExtension, } from "../core/commands.js";
5
+ import { normalizeRuleSelector, stripRuleFileExtension, } from "../core/rules.js";
5
6
  import { readLockfile } from "../core/lockfile.js";
6
7
  import { prepareSource, parseSourceSpec } from "../core/sources.js";
7
8
  import { getUpdateHelpText } from "../core/copy.js";
@@ -79,6 +80,7 @@ async function runEntityAwareUpdate(options) {
79
80
  if (!updatePlan.importAgents &&
80
81
  !updatePlan.importCommands &&
81
82
  !updatePlan.importMcp &&
83
+ !updatePlan.importRules &&
82
84
  !updatePlan.importSkills) {
83
85
  skipped += 1;
84
86
  continue;
@@ -92,6 +94,7 @@ async function runEntityAwareUpdate(options) {
92
94
  promptForAgentSelection: false,
93
95
  promptForCommands: false,
94
96
  promptForMcp: false,
97
+ promptForRules: false,
95
98
  promptForSkills: false,
96
99
  yes: Boolean(options.argv.yes),
97
100
  nonInteractive,
@@ -100,6 +103,7 @@ async function runEntityAwareUpdate(options) {
100
103
  requireAgents: updatePlan.importAgents,
101
104
  importCommands: updatePlan.importCommands,
102
105
  importMcp: updatePlan.importMcp,
106
+ importRules: updatePlan.importRules,
103
107
  };
104
108
  if (updatePlan.importSkills) {
105
109
  importOptions.importSkills = true;
@@ -120,6 +124,12 @@ async function runEntityAwareUpdate(options) {
120
124
  if (updatePlan.mcpSelectors) {
121
125
  importOptions.mcpSelectors = updatePlan.mcpSelectors;
122
126
  }
127
+ if (updatePlan.ruleSelectors) {
128
+ importOptions.ruleSelectors = updatePlan.ruleSelectors;
129
+ }
130
+ if (updatePlan.ruleRenameMap) {
131
+ importOptions.ruleRenameMap = updatePlan.ruleRenameMap;
132
+ }
123
133
  if (updatePlan.skillSelectors) {
124
134
  importOptions.skillSelectors = updatePlan.skillSelectors;
125
135
  }
@@ -152,19 +162,24 @@ function buildEntryUpdatePlan(entry, target) {
152
162
  const includeAgents = shouldUpdateEntity(entry, "agent", target);
153
163
  const includeCommands = shouldUpdateEntity(entry, "command", target);
154
164
  const includeMcp = shouldUpdateEntity(entry, "mcp", target);
165
+ const includeRules = shouldUpdateEntity(entry, "rule", target);
155
166
  const includeSkills = shouldUpdateEntity(entry, "skill", target);
156
167
  const commandOptions = getUpdateCommandOptions(entry, includeCommands);
157
168
  const mcpOptions = getUpdateMcpOptions(entry, includeMcp);
169
+ const ruleOptions = getUpdateRuleOptions(entry, includeRules);
158
170
  const skillOptions = getUpdateSkillOptions(entry, includeSkills);
159
171
  return {
160
172
  importAgents: includeAgents,
161
173
  importCommands: commandOptions.importCommands,
162
174
  importMcp: mcpOptions.importMcp,
175
+ importRules: ruleOptions.importRules,
163
176
  importSkills: skillOptions.importSkills,
164
177
  requestedAgents: includeAgents ? entry.requestedAgents : undefined,
165
178
  commandSelectors: commandOptions.commandSelectors,
166
179
  commandRenameMap: commandOptions.commandRenameMap,
167
180
  mcpSelectors: mcpOptions.mcpSelectors,
181
+ ruleSelectors: ruleOptions.ruleSelectors,
182
+ ruleRenameMap: ruleOptions.ruleRenameMap,
168
183
  skillSelectors: skillOptions.skillSelectors,
169
184
  skillsProviders: skillOptions.skillsProviders,
170
185
  skillRenameMap: skillOptions.skillRenameMap,
@@ -204,7 +219,7 @@ function shouldUpdateEntity(entry, entity, target) {
204
219
  if (target !== "all" && target !== entity)
205
220
  return false;
206
221
  if (target === "all" && !entry.trackedEntities) {
207
- if (entity === "skill") {
222
+ if (entity === "skill" || entity === "rule") {
208
223
  return tracksEntity(entry, entity);
209
224
  }
210
225
  return true;
@@ -221,6 +236,9 @@ function tracksEntity(entry, entity) {
221
236
  const importedMcpServers = Array.isArray(entry.importedMcpServers)
222
237
  ? entry.importedMcpServers
223
238
  : [];
239
+ const importedRules = Array.isArray(entry.importedRules)
240
+ ? entry.importedRules
241
+ : [];
224
242
  const importedSkills = Array.isArray(entry.importedSkills)
225
243
  ? entry.importedSkills
226
244
  : [];
@@ -237,6 +255,11 @@ function tracksEntity(entry, entity) {
237
255
  if (entity === "mcp") {
238
256
  return (importedMcpServers.length > 0 || Boolean(entry.selectedSourceMcpServers));
239
257
  }
258
+ if (entity === "rule") {
259
+ return (importedRules.length > 0 ||
260
+ Boolean(entry.selectedSourceRules) ||
261
+ Boolean(entry.ruleRenameMap));
262
+ }
240
263
  return (importedSkills.length > 0 ||
241
264
  Boolean(entry.selectedSourceSkills) ||
242
265
  Boolean(entry.skillsProviders) ||
@@ -272,6 +295,26 @@ function getUpdateMcpOptions(entry, includeMcp) {
272
295
  mcpSelectors: entry.selectedSourceMcpServers,
273
296
  };
274
297
  }
298
+ function getUpdateRuleOptions(entry, includeRules) {
299
+ if (!includeRules) {
300
+ return { importRules: false };
301
+ }
302
+ const ruleSelectors = getUpdateRuleSelectors(entry);
303
+ if (ruleSelectors && ruleSelectors.length === 0) {
304
+ return { importRules: false };
305
+ }
306
+ if (!ruleSelectors) {
307
+ return {
308
+ importRules: true,
309
+ ruleRenameMap: entry.ruleRenameMap,
310
+ };
311
+ }
312
+ return {
313
+ importRules: true,
314
+ ruleSelectors,
315
+ ruleRenameMap: getUpdateRuleRenameMap(entry, ruleSelectors),
316
+ };
317
+ }
275
318
  function getUpdateSkillOptions(entry, includeSkills) {
276
319
  if (!includeSkills) {
277
320
  return { importSkills: false };
@@ -294,7 +337,11 @@ function getUpdateCommandSelectors(entry) {
294
337
  return undefined;
295
338
  }
296
339
  function getUpdateCommandRenameMap(entry, commandSelectors) {
297
- const renameMapFromLock = filterRenameMapBySelectors(entry.commandRenameMap, commandSelectors);
340
+ const renameMapFromLock = filterRenameMapBySelectors({
341
+ renameMap: entry.commandRenameMap,
342
+ selectors: commandSelectors,
343
+ normalizeSelector: normalizeCommandSelector,
344
+ });
298
345
  if (renameMapFromLock) {
299
346
  return renameMapFromLock;
300
347
  }
@@ -319,12 +366,48 @@ function inferUpdateCommandRename(entry, commandSelectors) {
319
366
  }
320
367
  return importedName;
321
368
  }
322
- function filterRenameMapBySelectors(renameMap, selectors) {
323
- if (!renameMap)
369
+ function getUpdateRuleSelectors(entry) {
370
+ if (entry.selectedSourceRules !== undefined) {
371
+ return entry.selectedSourceRules;
372
+ }
373
+ return undefined;
374
+ }
375
+ function getUpdateRuleRenameMap(entry, ruleSelectors) {
376
+ const renameMapFromLock = filterRenameMapBySelectors({
377
+ renameMap: entry.ruleRenameMap,
378
+ selectors: ruleSelectors,
379
+ normalizeSelector: normalizeRuleSelector,
380
+ });
381
+ if (renameMapFromLock) {
382
+ return renameMapFromLock;
383
+ }
384
+ const inferredRename = inferUpdateRuleRename(entry, ruleSelectors);
385
+ if (!inferredRename)
386
+ return undefined;
387
+ return {
388
+ [ruleSelectors[0]]: inferredRename,
389
+ };
390
+ }
391
+ function inferUpdateRuleRename(entry, ruleSelectors) {
392
+ if (!ruleSelectors || ruleSelectors.length !== 1) {
393
+ return undefined;
394
+ }
395
+ if (entry.importedRules.length !== 1) {
396
+ return undefined;
397
+ }
398
+ const sourceName = stripRuleFileExtension(ruleSelectors[0]);
399
+ const importedName = stripRuleFileExtension(path.basename(entry.importedRules[0]));
400
+ if (!sourceName || !importedName || sourceName === importedName) {
401
+ return undefined;
402
+ }
403
+ return importedName;
404
+ }
405
+ function filterRenameMapBySelectors(options) {
406
+ if (!options.renameMap)
324
407
  return undefined;
325
- const selectorSet = new Set(selectors.map(normalizeCommandSelector));
326
- const filteredEntries = Object.entries(renameMap)
327
- .filter(([sourceSelector]) => selectorSet.has(normalizeCommandSelector(sourceSelector)))
408
+ const selectorSet = new Set(options.selectors.map(options.normalizeSelector));
409
+ const filteredEntries = Object.entries(options.renameMap)
410
+ .filter(([sourceSelector]) => selectorSet.has(options.normalizeSelector(sourceSelector)))
328
411
  .map(([sourceSelector, importedFileName]) => [
329
412
  sourceSelector,
330
413
  path.basename(importedFileName),
@@ -46,7 +46,7 @@ export function parseAgentMarkdown(raw, sourcePath, fileName = path.basename(sou
46
46
  };
47
47
  }
48
48
  export function buildAgentMarkdown(frontmatter, body) {
49
- const fm = YAML.stringify(frontmatter).trimEnd();
49
+ const fm = YAML.stringify(frontmatter, { lineWidth: 0 }).trimEnd();
50
50
  const normalizedBody = body.trimStart();
51
51
  return `---\n${fm}\n---\n\n${normalizedBody}${normalizedBody.endsWith("\n") ? "" : "\n"}`;
52
52
  }
package/dist/core/argv.js CHANGED
@@ -14,6 +14,8 @@ export function parseArgs(argv) {
14
14
  "commands",
15
15
  "mcp",
16
16
  "mcps",
17
+ "rule",
18
+ "rules",
17
19
  "skill",
18
20
  "skills",
19
21
  "url",
@@ -1,9 +1,21 @@
1
+ import type { Provider } from "../types.js";
1
2
  export interface CanonicalCommandFile {
2
3
  fileName: string;
3
4
  sourcePath: string;
4
5
  content: string;
6
+ body: string;
7
+ frontmatter?: Record<string, unknown>;
5
8
  }
6
9
  export declare function parseCommandsDir(commandsDir: string): CanonicalCommandFile[];
10
+ export declare function parseCommandContent(content: string): {
11
+ body: string;
12
+ frontmatter?: Record<string, unknown>;
13
+ };
14
+ export declare function getCommandProviderConfig(command: CanonicalCommandFile, provider: Provider): Record<string, unknown> | null;
15
+ export declare function isCommandProviderEnabled(command: CanonicalCommandFile, provider: Provider): boolean;
16
+ export declare function renderCommandForProvider(command: CanonicalCommandFile, provider: Provider): string | null;
17
+ export declare function normalizeCommandArgumentsForProvider(body: string, provider: Provider): string;
18
+ export declare function normalizeCommandArgumentsForCanonical(body: string, provider: Provider): string;
7
19
  export declare function stripCommandFileExtension(fileName: string): string;
8
20
  export declare function commandFileMatchesSelector(fileName: string, selector: string): boolean;
9
21
  export declare function normalizeCommandSelector(selector: string): string;