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.
package/README.md CHANGED
@@ -10,7 +10,7 @@ For monorepo-level documentation and architecture context, see the [root README]
10
10
  npx agentloom init
11
11
  ```
12
12
 
13
- That's all you need. Agentloom picks up your existing provider configs, migrates them into a unified `.agents/` directory, and syncs everything back out to all your tools. From here on, manage your agents, commands, skills, and MCP servers in one place and run `agentloom sync` whenever you make changes.
13
+ That's all you need. Agentloom picks up your existing provider configs, migrates them into a unified `.agents/` directory, and syncs everything back out to all your tools. From here on, manage your agents, commands, rules, skills, and MCP servers in one place and run `agentloom sync` whenever you make changes.
14
14
 
15
15
  ## Install
16
16
 
@@ -32,6 +32,9 @@ Project scope:
32
32
  commands/
33
33
  review.md
34
34
  ship.md
35
+ rules/
36
+ always-test.md
37
+ enforce-logs.md
35
38
  skills/
36
39
  reviewing/
37
40
  SKILL.md
@@ -59,7 +62,7 @@ Global scope uses `~/.agents` with the same file layout.
59
62
  - `agentloom sync`
60
63
  - `agentloom delete <source|name>`
61
64
 
62
- Aggregate `add` imports discoverable entities from a source (agents, commands, MCP servers, skills). In interactive sessions, each entity supports two tracking modes:
65
+ Aggregate `add` imports discoverable entities from a source (agents, commands, rules, MCP servers, skills). In interactive sessions, each entity supports two tracking modes:
63
66
 
64
67
  - `Sync everything from source` (default): updates include newly added source items.
65
68
  - `Use custom selection`: updates stay pinned to the selected items, even if all current items were selected.
@@ -67,7 +70,8 @@ Aggregate `add` imports discoverable entities from a source (agents, commands, M
67
70
  Source path resolution is additive and priority-ordered:
68
71
 
69
72
  - Agents: `.agents/agents` -> `agents`
70
- - Commands: `.agents/commands` -> `commands` -> `prompts`
73
+ - Commands: `.agents/commands` -> `commands` -> `prompts` -> provider fallbacks `.github/prompts` + `.gemini/commands`
74
+ - Rules: `.agents/rules` -> `rules`
71
75
  - Skills: `.agents/skills` -> `skills` -> root `SKILL.md` fallback
72
76
  - MCP: `.agents/mcp.json` -> `mcp.json`
73
77
 
@@ -78,6 +82,7 @@ Aggregate `agentloom add <source>` can import command/skill/MCP-only repositorie
78
82
  - `agentloom agent <add|list|delete|find|update|sync>`
79
83
  - `agentloom command <add|list|delete|find|update|sync>`
80
84
  - `agentloom mcp <add|list|delete|find|update|sync>`
85
+ - `agentloom rule <add|list|delete|find|update|sync>`
81
86
  - `agentloom skill <add|list|delete|find|update|sync>`
82
87
 
83
88
  ### Selector flags
@@ -85,11 +90,13 @@ Aggregate `agentloom add <source>` can import command/skill/MCP-only repositorie
85
90
  - `--agents <csv>`
86
91
  - `--commands <csv>`
87
92
  - `--mcps <csv>`
93
+ - `--rule <csv>` (alias)
94
+ - `--rules <csv>`
88
95
  - `--skills <csv>`
89
96
  - `--selection-mode <all|sync-all|custom>`
90
97
  - `--source <value>`
91
98
  - `--name <value>`
92
- - `--entity <agent|command|mcp|skill>`
99
+ - `--entity <agent|command|mcp|rule|skill>`
93
100
 
94
101
  ### MCP manual server mode
95
102
 
@@ -107,6 +114,7 @@ agentloom add farnoodma/agents
107
114
  agentloom agent add farnoodma/agents --agents issue-creator
108
115
  agentloom command add farnoodma/agents --commands review
109
116
  agentloom mcp add farnoodma/agents --mcps browser
117
+ agentloom rule add farnoodma/agents --rules always-test
110
118
  agentloom skill add farnoodma/agents --skills pr-review
111
119
  agentloom delete farnoodma/agents
112
120
  agentloom mcp server add browser-tools --command npx --arg browser-tools-mcp
@@ -128,6 +136,7 @@ agentloom command --help
128
136
  agentloom command add --help
129
137
  agentloom mcp --help
130
138
  agentloom mcp add --help
139
+ agentloom rule --help
131
140
  agentloom mcp server --help
132
141
  ```
133
142
 
@@ -166,6 +175,7 @@ The install runs after the requested command completes so scope/provider selecti
166
175
  Successful GitHub-based `agentloom add` imports can send anonymous telemetry
167
176
  to the Agentloom directory API.
168
177
 
178
+ - tracked entities: agents, commands, rules, skills, MCP servers
169
179
  - disable telemetry via `AGENTLOOM_DISABLE_TELEMETRY=1`
170
180
  - override endpoint via `AGENTLOOM_TELEMETRY_ENDPOINT`
171
181
 
@@ -188,14 +198,67 @@ description: Review changes and report issues.
188
198
  claude:
189
199
  model: sonnet
190
200
  codex:
191
- model: gpt-5.3-codex
192
- reasoningEffort: low
201
+ model: gpt-5-codex
202
+ reasoningEffort: medium
193
203
  webSearch: true
194
204
  ---
195
205
 
196
206
  You are a strict reviewer...
197
207
  ```
198
208
 
209
+ ## Command schema
210
+
211
+ Canonical commands are markdown files. Frontmatter is optional. When present,
212
+ provider-specific command config can be nested per provider:
213
+
214
+ ```md
215
+ ---
216
+ copilot:
217
+ description: Review current changes
218
+ agent: agent
219
+ tools:
220
+ - codebase
221
+ model: gpt-5
222
+ argument-hint: "<scope>"
223
+ ---
224
+
225
+ # /review
226
+
227
+ Review active changes with scope $ARGUMENTS.
228
+ ```
229
+
230
+ Notes:
231
+
232
+ - Provider configs follow the same pattern as agents:
233
+ - omit provider key for default behavior
234
+ - `provider: { ... }` to add provider-specific overrides
235
+ - `provider: false` to disable output for that provider
236
+ - Provider-specific frontmatter keys are passed through as-is to that provider output.
237
+ - Canonical command bodies can use `$ARGUMENTS`; provider-specific placeholder translation is applied during sync (for example Copilot receives `${input:args}` and Gemini receives `{{args}}`).
238
+
239
+ ## Rule schema
240
+
241
+ Canonical rules are markdown files under `.agents/rules/*.md`.
242
+ Rules require `frontmatter.name`. Additional frontmatter keys are preserved.
243
+
244
+ ```md
245
+ ---
246
+ name: Always run tests
247
+ description: Require tests before merge
248
+ globs:
249
+ - "**/*.ts"
250
+ alwaysApply: true
251
+ ---
252
+
253
+ Before finishing any change, run the project checks and include the result.
254
+ ```
255
+
256
+ Notes:
257
+
258
+ - Rule ID is the canonical filename stem (for example `.agents/rules/always-test.md` -> `always-test`).
259
+ - Managed instruction-block rendering uses only `frontmatter.name` + body.
260
+ - Extra frontmatter keys are used only for Cursor `.cursor/rules/*.mdc` rendering.
261
+
199
262
  ## MCP schema
200
263
 
201
264
  Canonical MCP file format:
@@ -234,6 +297,29 @@ For canonical commands (`.agents/commands`), Codex output is always written to
234
297
  global prompts under `~/.codex/prompts` (Codex prompts are global-only), even
235
298
  when syncing local scope.
236
299
 
300
+ ## Rule sync behavior
301
+
302
+ Rule sync is additive on top of agent/command/MCP/skill sync:
303
+
304
+ - Local scope always updates managed rule blocks in `AGENTS.md`.
305
+ - Local + Cursor writes `.cursor/rules/<rule-id>.mdc`.
306
+ - Local + Claude writes managed blocks to `CLAUDE.md`.
307
+ - Local + Gemini writes managed blocks to `GEMINI.md`.
308
+ - Local + Copilot writes managed blocks to `.github/copilot-instructions.md`.
309
+ - Local + OpenCode/Codex/Pi use the same managed `AGENTS.md` baseline blocks.
310
+ - Global + Claude writes `~/.claude/CLAUDE.md`.
311
+ - Global + Gemini writes `~/.gemini/GEMINI.md`.
312
+ - Global + Copilot writes `~/.copilot/copilot-instructions.md` and adds that location to VS Code `chat.instructionsFilesLocations`.
313
+ - Global + OpenCode writes `~/.config/opencode/AGENTS.md`.
314
+ - Global Cursor/Codex/Pi rule settings are no-op unless a stable provider settings key exists.
315
+
316
+ ## Provider contract notes
317
+
318
+ - Cursor provider output uses Cursor subagents (`.cursor/agents/*.md`).
319
+ - Gemini command sync writes TOML files (`.gemini/commands/*.toml`).
320
+ - Copilot global output uses `~/.copilot/{agents,prompts,skills}` and `~/.copilot/copilot-instructions.md` (workspace output remains `.github/*`).
321
+ - Claude global user-scope MCP is not file-synced by Agentloom. Project MCP remains synced via `.mcp.json`.
322
+
237
323
  ## Development
238
324
 
239
325
  ```bash
package/dist/cli.js CHANGED
@@ -7,6 +7,7 @@ import { runDeleteCommand } from "./commands/delete.js";
7
7
  import { runFindCommand } from "./commands/find.js";
8
8
  import { runInitCommand } from "./commands/init.js";
9
9
  import { runMcpCommand } from "./commands/mcp.js";
10
+ import { runRuleCommand } from "./commands/rule.js";
10
11
  import { runSkillCommand } from "./commands/skills.js";
11
12
  import { runSyncCommand } from "./commands/sync.js";
12
13
  import { runUpgradeCommand } from "./commands/upgrade.js";
@@ -54,9 +55,11 @@ export async function runCli(argv) {
54
55
  yes: Boolean(parsed.yes),
55
56
  cwd,
56
57
  });
58
+ const bootstrapArgs = shouldBootstrapManageAgents
59
+ ? buildManageAgentsBootstrapArgs(parsed, cwd)
60
+ : undefined;
57
61
  await runRoutedCommand(route, parsed, cwd, command, version);
58
- if (shouldBootstrapManageAgents) {
59
- const bootstrapArgs = buildManageAgentsBootstrapArgs(parsed, cwd);
62
+ if (bootstrapArgs) {
60
63
  await runSkillCommand(parseArgs(bootstrapArgs), cwd);
61
64
  }
62
65
  }
@@ -111,13 +114,17 @@ async function runRoutedCommand(route, parsed, cwd, command, version) {
111
114
  case "skill":
112
115
  await runSkillCommand(parsed, cwd);
113
116
  return;
117
+ case "rule":
118
+ await runRuleCommand(parsed, cwd);
119
+ return;
114
120
  default:
115
121
  throw new Error(formatUnknownCommandError(command));
116
122
  }
117
123
  }
118
124
  function buildManageAgentsBootstrapArgs(parsed, cwd) {
119
- const scope = resolveBootstrapScope(parsed);
120
- const providers = resolveBootstrapProviders(parsed, cwd, scope);
125
+ const inferredScope = resolveBootstrapScope(parsed);
126
+ const scope = inferredScope ?? "global";
127
+ const providers = resolveBootstrapProviders(parsed, cwd, inferredScope);
121
128
  const args = [
122
129
  "skill",
123
130
  "add",
@@ -40,10 +40,12 @@ async function runEntityAwareAdd(options) {
40
40
  const importAgents = options.target === "all" || options.target === "agent";
41
41
  const importCommands = options.target === "all" || options.target === "command";
42
42
  const importMcp = options.target === "all" || options.target === "mcp";
43
+ const importRules = options.target === "all" || options.target === "rule";
43
44
  const importSkills = options.target === "all" || options.target === "skill";
44
45
  const agentSelectors = getEntitySelectors(options.argv, "agent");
45
46
  const commandSelectors = getEntitySelectors(options.argv, "command");
46
47
  const mcpSelectors = getEntitySelectors(options.argv, "mcp");
48
+ const ruleSelectors = getEntitySelectors(options.argv, "rule");
47
49
  const skillSelectors = getEntitySelectors(options.argv, "skill");
48
50
  let resolvedSkillProviders;
49
51
  const resolveProvidersForSkills = async () => {
@@ -82,6 +84,10 @@ async function runEntityAwareAdd(options) {
82
84
  requireMcp: options.target === "mcp",
83
85
  mcpSelectors,
84
86
  promptForMcp: mcpSelectors.length === 0,
87
+ importRules,
88
+ requireRules: options.target === "rule",
89
+ ruleSelectors,
90
+ promptForRules: ruleSelectors.length === 0,
85
91
  importSkills,
86
92
  requireSkills: options.target === "skill",
87
93
  skillSelectors,
@@ -95,12 +101,14 @@ async function runEntityAwareAdd(options) {
95
101
  promptForAgentSelection: agentSelectors.length === 0,
96
102
  selectionMode,
97
103
  });
104
+ const importedRules = summary.importedRules ?? [];
98
105
  console.log(`Imported source: ${summary.source}`);
99
106
  console.log(`Source type: ${summary.sourceType}`);
100
107
  console.log(`Resolved commit: ${summary.resolvedCommit}`);
101
108
  console.log(`Imported agents: ${summary.importedAgents.length}`);
102
109
  console.log(`Imported commands: ${summary.importedCommands.length}`);
103
110
  console.log(`Imported MCP servers: ${summary.importedMcpServers.length}`);
111
+ console.log(`Imported rules: ${importedRules.length}`);
104
112
  console.log(`Imported skills: ${summary.importedSkills.length}`);
105
113
  await sendAddTelemetryEvent({
106
114
  rawSource: source,
@@ -131,6 +139,9 @@ function buildAddUsage(target) {
131
139
  if (target === "mcp") {
132
140
  return "agentloom mcp add <source> [--ref <ref>] [--subdir <path>] [--mcps <name>] [options]";
133
141
  }
142
+ if (target === "rule") {
143
+ return "agentloom rule add <source> [--ref <ref>] [--subdir <path>] [--rules <name>] [options]";
144
+ }
134
145
  if (target === "skill") {
135
146
  return "agentloom skill add <source> [--ref <ref>] [--subdir <path>] [--skills <name>] [options]";
136
147
  }
@@ -146,6 +157,9 @@ function buildAddExample(target) {
146
157
  if (target === "mcp") {
147
158
  return "agentloom mcp add farnoodma/agents --mcps browser";
148
159
  }
160
+ if (target === "rule") {
161
+ return "agentloom rule add farnoodma/agents --rules always-test";
162
+ }
149
163
  if (target === "skill") {
150
164
  return "agentloom skill add farnoodma/agents --skills code-review";
151
165
  }
@@ -6,10 +6,11 @@ import { parseCommandsDir } from "../core/commands.js";
6
6
  import { formatUsageError } from "../core/copy.js";
7
7
  import { readLockfile, writeLockfile } from "../core/lockfile.js";
8
8
  import { readCanonicalMcp, writeCanonicalMcp } from "../core/mcp.js";
9
+ import { normalizeRuleSelector, parseRulesDir, stripRuleFileExtension, } from "../core/rules.js";
9
10
  import { parseSourceSpec } from "../core/sources.js";
10
11
  import { parseSkillsDir } from "../core/skills.js";
11
12
  import { getNonInteractiveMode, resolvePathsForCommand, runPostMutationSync, } from "./entity-utils.js";
12
- const ALL_ENTITIES = ["agent", "command", "mcp", "skill"];
13
+ const ALL_ENTITIES = ["agent", "command", "mcp", "rule", "skill"];
13
14
  const MULTISELECT_HELP_TEXT = "↑↓ move, space select, enter confirm";
14
15
  function withMultiselectHelp(message) {
15
16
  return `${message}\n${MULTISELECT_HELP_TEXT}`;
@@ -165,6 +166,11 @@ async function deleteBySource(options) {
165
166
  delete mcp.mcpServers[server];
166
167
  }
167
168
  }
169
+ if (options.entities.includes("rule")) {
170
+ for (const imported of entry.importedRules) {
171
+ removeIfExists(path.join(options.paths.agentsRoot, imported));
172
+ }
173
+ }
168
174
  if (options.entities.includes("skill")) {
169
175
  for (const skill of entry.importedSkills) {
170
176
  removeIfExists(path.join(options.paths.skillsDir, skill));
@@ -185,6 +191,7 @@ async function deleteBySource(options) {
185
191
  }
186
192
  async function deleteByName(options) {
187
193
  let selectedEntities = options.entities;
194
+ let deletedRuleIdentifiers;
188
195
  if (selectedEntities.length > 1) {
189
196
  const matches = detectNameMatches(options.paths, options.candidate);
190
197
  const matchingEntities = selectedEntities.filter((entity) => matches.includes(entity));
@@ -227,6 +234,14 @@ async function deleteByName(options) {
227
234
  }
228
235
  delete mcp.mcpServers[existing];
229
236
  }
237
+ else if (entity === "rule") {
238
+ const deletedRule = deleteRuleByName(options.paths, options.candidate);
239
+ deletedRuleIdentifiers = [
240
+ deletedRule.name,
241
+ deletedRule.fileName,
242
+ stripRuleFileExtension(deletedRule.fileName),
243
+ ];
244
+ }
230
245
  else if (entity === "skill") {
231
246
  deleteSkillByName(options.paths, options.candidate);
232
247
  }
@@ -236,7 +251,7 @@ async function deleteByName(options) {
236
251
  }
237
252
  const lockfile = readLockfile(options.paths);
238
253
  lockfile.entries = lockfile.entries
239
- .map((entry) => removeNameFromEntry(entry, selectedEntities, options.candidate))
254
+ .map((entry) => removeNameFromEntry(entry, selectedEntities, options.candidate, deletedRuleIdentifiers))
240
255
  .filter((entry) => Boolean(entry));
241
256
  writeLockfile(options.paths, lockfile);
242
257
  }
@@ -269,6 +284,15 @@ function deleteSkillByName(paths, name) {
269
284
  }
270
285
  removeIfExists(target.sourcePath);
271
286
  }
287
+ function deleteRuleByName(paths, name) {
288
+ const rules = parseRulesDir(paths.rulesDir);
289
+ const target = rules.find((rule) => ruleMatchesCandidate(rule, name));
290
+ if (!target) {
291
+ throw new Error(`Rule "${name}" was not found in canonical rules.`);
292
+ }
293
+ removeIfExists(target.sourcePath);
294
+ return target;
295
+ }
272
296
  function removeIfExists(filePath) {
273
297
  if (!fs.existsSync(filePath))
274
298
  return;
@@ -293,6 +317,9 @@ function detectNameMatches(paths, candidate) {
293
317
  const hasMcp = Object.keys(mcp.mcpServers).some((name) => normalizeName(name) === normalized);
294
318
  if (hasMcp)
295
319
  matches.push("mcp");
320
+ const hasRule = parseRulesDir(paths.rulesDir).some((rule) => ruleMatchesCandidate(rule, candidate));
321
+ if (hasRule)
322
+ matches.push("rule");
296
323
  const hasSkill = parseSkillsDir(paths.skillsDir).some((skill) => normalizeName(skill.name) === normalized);
297
324
  if (hasSkill)
298
325
  matches.push("skill");
@@ -322,6 +349,14 @@ function removeEntityDataFromEntry(entry, entities) {
322
349
  selectedSourceMcpServers: undefined,
323
350
  };
324
351
  }
352
+ if (entities.includes("rule")) {
353
+ next = {
354
+ ...next,
355
+ importedRules: [],
356
+ selectedSourceRules: undefined,
357
+ ruleRenameMap: undefined,
358
+ };
359
+ }
325
360
  if (entities.includes("skill")) {
326
361
  next = {
327
362
  ...next,
@@ -333,7 +368,7 @@ function removeEntityDataFromEntry(entry, entities) {
333
368
  }
334
369
  return finalizeEntry(next);
335
370
  }
336
- function removeNameFromEntry(entry, entities, candidate) {
371
+ function removeNameFromEntry(entry, entities, candidate, deletedRuleIdentifiers) {
337
372
  let next = { ...entry };
338
373
  const normalized = normalizeName(candidate);
339
374
  if (entities.includes("agent")) {
@@ -373,6 +408,37 @@ function removeNameFromEntry(entry, entities, candidate) {
373
408
  next.selectedSourceMcpServers = [...next.importedMcpServers];
374
409
  }
375
410
  }
411
+ if (entities.includes("rule")) {
412
+ const before = [...next.importedRules];
413
+ const ruleMatchKeys = new Set([candidate, ...(deletedRuleIdentifiers ?? [])].flatMap(getRuleMatchKeys));
414
+ const matchesDeletedRule = (value) => getRuleMatchKeys(value).some((key) => ruleMatchKeys.has(key));
415
+ next.importedRules = next.importedRules.filter((item) => {
416
+ const base = path.basename(item).replace(/\.(md|mdc)$/i, "");
417
+ return !matchesDeletedRule(base);
418
+ });
419
+ if (next.importedRules.length !== before.length) {
420
+ const remainingImportedNames = new Set(next.importedRules.map((item) => path.basename(item)));
421
+ if (next.ruleRenameMap) {
422
+ const filteredRenameEntries = Object.entries(next.ruleRenameMap).filter(([, importedName]) => remainingImportedNames.has(path.basename(importedName)));
423
+ next.ruleRenameMap =
424
+ filteredRenameEntries.length > 0
425
+ ? Object.fromEntries(filteredRenameEntries)
426
+ : undefined;
427
+ }
428
+ const selectorsFromRenameMap = next.ruleRenameMap
429
+ ? Object.keys(next.ruleRenameMap)
430
+ : [];
431
+ const renamedRuleTargets = new Set(Object.values(next.ruleRenameMap ?? {}).map((importedName) => normalizeName(path.basename(importedName))));
432
+ const selectorsFromUnrenamedRules = next.importedRules
433
+ .map((item) => path.basename(item))
434
+ .filter((item) => !renamedRuleTargets.has(normalizeName(path.basename(item))));
435
+ next.selectedSourceRules =
436
+ selectorsFromRenameMap.length > 0 ||
437
+ selectorsFromUnrenamedRules.length > 0
438
+ ? [...selectorsFromRenameMap, ...selectorsFromUnrenamedRules]
439
+ : [];
440
+ }
441
+ }
376
442
  if (entities.includes("skill")) {
377
443
  const renameEntriesBeforeRemoval = Object.entries(next.skillRenameMap ?? {});
378
444
  const removedSkillTargets = next.importedSkills.filter((name) => normalizeName(name) === normalized);
@@ -400,9 +466,19 @@ function removeNameFromEntry(entry, entities, candidate) {
400
466
  }
401
467
  return finalizeEntry(next);
402
468
  }
469
+ function ruleMatchesCandidate(rule, candidate) {
470
+ const candidateKeys = new Set(getRuleMatchKeys(candidate));
471
+ return [rule.name, rule.fileName, stripRuleFileExtension(rule.fileName)].some((value) => getRuleMatchKeys(value).some((key) => candidateKeys.has(key)));
472
+ }
473
+ function getRuleMatchKeys(value) {
474
+ const normalized = normalizeName(value);
475
+ const selector = normalizeRuleSelector(value);
476
+ return [...new Set([normalized, selector].filter(Boolean))];
477
+ }
403
478
  function finalizeEntry(entry) {
404
479
  const hasOtherEntities = entry.importedAgents.length > 0 ||
405
480
  entry.importedMcpServers.length > 0 ||
481
+ entry.importedRules.length > 0 ||
406
482
  entry.importedSkills.length > 0;
407
483
  if (entry.importedCommands.length === 0) {
408
484
  entry.commandRenameMap = undefined;
@@ -412,6 +488,10 @@ function finalizeEntry(entry) {
412
488
  entry.skillRenameMap = undefined;
413
489
  entry.skillsProviders = undefined;
414
490
  }
491
+ if (entry.importedRules.length === 0) {
492
+ entry.ruleRenameMap = undefined;
493
+ entry.selectedSourceRules = hasOtherEntities ? [] : undefined;
494
+ }
415
495
  const trackedEntities = (entry.trackedEntities ?? []).filter((entity) => {
416
496
  if (entity === "agent") {
417
497
  return entry.importedAgents.length > 0 || Boolean(entry.requestedAgents);
@@ -425,6 +505,11 @@ function finalizeEntry(entry) {
425
505
  return (entry.importedMcpServers.length > 0 ||
426
506
  Boolean(entry.selectedSourceMcpServers));
427
507
  }
508
+ if (entity === "rule") {
509
+ return (entry.importedRules.length > 0 ||
510
+ Boolean(entry.selectedSourceRules) ||
511
+ Boolean(entry.ruleRenameMap));
512
+ }
428
513
  return (entry.importedSkills.length > 0 ||
429
514
  Boolean(entry.selectedSourceSkills) ||
430
515
  Boolean(entry.skillsProviders) ||
@@ -437,6 +522,7 @@ function finalizeEntry(entry) {
437
522
  if (next.importedAgents.length === 0 &&
438
523
  next.importedCommands.length === 0 &&
439
524
  next.importedMcpServers.length === 0 &&
525
+ next.importedRules.length === 0 &&
440
526
  next.importedSkills.length === 0) {
441
527
  return null;
442
528
  }
@@ -32,6 +32,9 @@ export function getEntitySelectors(argv, entity) {
32
32
  if (entity === "mcp") {
33
33
  return getStringArrayFlag(argsRecord.mcps, getStringArrayFlag(argsRecord.mcp));
34
34
  }
35
+ if (entity === "rule") {
36
+ return getStringArrayFlag(argsRecord.rules, getStringArrayFlag(argsRecord.rule));
37
+ }
35
38
  return getStringArrayFlag(argsRecord.skills, getStringArrayFlag(argsRecord.skill));
36
39
  }
37
40
  export async function runPostMutationSync(options) {