rulesync 8.8.1 → 8.10.0

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/dist/cli/index.js CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  JsonLogger,
17
17
  MAX_FILE_SIZE,
18
18
  McpProcessor,
19
+ PermissionsProcessor,
19
20
  RULESYNC_AIIGNORE_FILE_NAME,
20
21
  RULESYNC_AIIGNORE_RELATIVE_FILE_PATH,
21
22
  RULESYNC_COMMANDS_RELATIVE_DIR_PATH,
@@ -53,6 +54,7 @@ import {
53
54
  SKILL_FILE_NAME,
54
55
  SkillsProcessor,
55
56
  SubagentsProcessor,
57
+ ToolTargetSchema,
56
58
  checkPathTraversal,
57
59
  checkRulesyncDirExists,
58
60
  createTempDirectory,
@@ -77,7 +79,7 @@ import {
77
79
  stringifyFrontmatter,
78
80
  toPosixPath,
79
81
  writeFileContent
80
- } from "../chunk-XIMWQREW.js";
82
+ } from "../chunk-RMITDFVW.js";
81
83
 
82
84
  // src/cli/index.ts
83
85
  import { Command } from "commander";
@@ -85,6 +87,311 @@ import { Command } from "commander";
85
87
  // src/utils/parse-comma-separated-list.ts
86
88
  var parseCommaSeparatedList = (value) => value.split(",").map((s) => s.trim()).filter(Boolean);
87
89
 
90
+ // src/lib/convert.ts
91
+ async function convertFromTool(params) {
92
+ const ctx = params;
93
+ const [
94
+ rulesCount,
95
+ ignoreCount,
96
+ mcpCount,
97
+ commandsCount,
98
+ subagentsCount,
99
+ skillsCount,
100
+ hooksCount,
101
+ permissionsCount
102
+ ] = [
103
+ await runFeatureConvert(ctx, buildRulesStrategy(ctx)),
104
+ await runFeatureConvert(ctx, buildIgnoreStrategy(ctx)),
105
+ await runFeatureConvert(ctx, buildMcpStrategy(ctx)),
106
+ await runFeatureConvert(ctx, buildCommandsStrategy(ctx)),
107
+ await runFeatureConvert(ctx, buildSubagentsStrategy(ctx)),
108
+ await runFeatureConvert(ctx, buildSkillsStrategy(ctx)),
109
+ await runFeatureConvert(ctx, buildHooksStrategy(ctx)),
110
+ await runFeatureConvert(ctx, buildPermissionsStrategy(ctx))
111
+ ];
112
+ return {
113
+ rulesCount,
114
+ ignoreCount,
115
+ mcpCount,
116
+ commandsCount,
117
+ subagentsCount,
118
+ skillsCount,
119
+ hooksCount,
120
+ permissionsCount
121
+ };
122
+ }
123
+ async function runFeatureConvert(ctx, strategy) {
124
+ if (!strategy) return 0;
125
+ const { config, fromTool, toTools, logger: logger5 } = ctx;
126
+ const {
127
+ feature,
128
+ itemLabel,
129
+ allTargets,
130
+ importableTargets = allTargets,
131
+ createProcessor,
132
+ loadSource,
133
+ toRulesync,
134
+ fromRulesync,
135
+ write
136
+ } = strategy;
137
+ if (!config.getFeatures(fromTool).includes(feature)) {
138
+ return 0;
139
+ }
140
+ if (!allTargets.includes(fromTool)) {
141
+ logger5.warn(`Source tool '${fromTool}' does not support feature '${feature}'. Skipping.`);
142
+ return 0;
143
+ }
144
+ if (!importableTargets.includes(fromTool)) {
145
+ logger5.warn(`Conversion from ${fromTool} ${feature} is not supported. Skipping.`);
146
+ return 0;
147
+ }
148
+ const sourceProcessor = createProcessor({ toolTarget: fromTool, dryRun: false });
149
+ const sourceItems = await loadSource(sourceProcessor);
150
+ if (sourceItems.length === 0) {
151
+ logger5.warn(`No ${feature} files found for ${fromTool}. Skipping ${feature} conversion.`);
152
+ return 0;
153
+ }
154
+ const rulesyncItems = await toRulesync(sourceProcessor, sourceItems);
155
+ let totalCount = 0;
156
+ for (const toTool of toTools) {
157
+ if (!allTargets.includes(toTool)) {
158
+ logger5.warn(`Destination tool '${toTool}' does not support feature '${feature}'. Skipping.`);
159
+ continue;
160
+ }
161
+ const destProcessor = createProcessor({
162
+ toolTarget: toTool,
163
+ dryRun: config.isPreviewMode()
164
+ });
165
+ const destItems = await fromRulesync(destProcessor, rulesyncItems);
166
+ const { count } = await write(destProcessor, destItems);
167
+ totalCount += count;
168
+ if (config.getVerbose() && count > 0) {
169
+ const verb = config.isPreviewMode() ? "Would convert" : "Converted";
170
+ logger5.success(`${verb} ${count} ${itemLabel} for ${toTool}`);
171
+ }
172
+ }
173
+ return totalCount;
174
+ }
175
+ function getBaseDir(config) {
176
+ return config.getBaseDirs()[0] ?? ".";
177
+ }
178
+ function buildRulesStrategy(ctx) {
179
+ const { config, logger: logger5 } = ctx;
180
+ const global = config.getGlobal();
181
+ const baseDir = getBaseDir(config);
182
+ const allTargets = RulesProcessor.getToolTargets({ global });
183
+ return {
184
+ feature: "rules",
185
+ itemLabel: "rule file(s)",
186
+ allTargets,
187
+ createProcessor: ({ toolTarget, dryRun }) => new RulesProcessor({ baseDir, toolTarget, global, dryRun, logger: logger5 }),
188
+ loadSource: (p) => p.loadToolFiles(),
189
+ toRulesync: (p, files) => p.convertToolFilesToRulesyncFiles(files),
190
+ fromRulesync: (p, files) => p.convertRulesyncFilesToToolFiles(files),
191
+ write: (p, files) => p.writeAiFiles(files)
192
+ };
193
+ }
194
+ function buildIgnoreStrategy(ctx) {
195
+ const { config, logger: logger5 } = ctx;
196
+ if (config.getGlobal()) {
197
+ logger5.debug("Skipping ignore conversion (not supported in global mode)");
198
+ return null;
199
+ }
200
+ const baseDir = getBaseDir(config);
201
+ const allTargets = IgnoreProcessor.getToolTargets();
202
+ return {
203
+ feature: "ignore",
204
+ itemLabel: "ignore file(s)",
205
+ allTargets,
206
+ createProcessor: ({ toolTarget, dryRun }) => new IgnoreProcessor({
207
+ baseDir,
208
+ toolTarget,
209
+ dryRun,
210
+ logger: logger5,
211
+ featureOptions: config.getFeatureOptions(toolTarget, "ignore")
212
+ }),
213
+ loadSource: (p) => p.loadToolFiles(),
214
+ toRulesync: (p, files) => p.convertToolFilesToRulesyncFiles(files),
215
+ fromRulesync: (p, files) => p.convertRulesyncFilesToToolFiles(files),
216
+ write: (p, files) => p.writeAiFiles(files)
217
+ };
218
+ }
219
+ function buildMcpStrategy(ctx) {
220
+ const { config, logger: logger5 } = ctx;
221
+ const global = config.getGlobal();
222
+ const baseDir = getBaseDir(config);
223
+ const allTargets = McpProcessor.getToolTargets({ global });
224
+ return {
225
+ feature: "mcp",
226
+ itemLabel: "MCP file(s)",
227
+ allTargets,
228
+ createProcessor: ({ toolTarget, dryRun }) => new McpProcessor({ baseDir, toolTarget, global, dryRun, logger: logger5 }),
229
+ loadSource: (p) => p.loadToolFiles(),
230
+ toRulesync: (p, files) => p.convertToolFilesToRulesyncFiles(files),
231
+ fromRulesync: (p, files) => p.convertRulesyncFilesToToolFiles(files),
232
+ write: (p, files) => p.writeAiFiles(files)
233
+ };
234
+ }
235
+ function buildCommandsStrategy(ctx) {
236
+ const { config, logger: logger5 } = ctx;
237
+ const global = config.getGlobal();
238
+ const baseDir = getBaseDir(config);
239
+ const allTargets = CommandsProcessor.getToolTargets({ global, includeSimulated: false });
240
+ return {
241
+ feature: "commands",
242
+ itemLabel: "command file(s)",
243
+ allTargets,
244
+ createProcessor: ({ toolTarget, dryRun }) => new CommandsProcessor({ baseDir, toolTarget, global, dryRun, logger: logger5 }),
245
+ loadSource: (p) => p.loadToolFiles(),
246
+ toRulesync: (p, files) => p.convertToolFilesToRulesyncFiles(files),
247
+ fromRulesync: (p, files) => p.convertRulesyncFilesToToolFiles(files),
248
+ write: (p, files) => p.writeAiFiles(files)
249
+ };
250
+ }
251
+ function buildSubagentsStrategy(ctx) {
252
+ const { config, logger: logger5 } = ctx;
253
+ const global = config.getGlobal();
254
+ const baseDir = getBaseDir(config);
255
+ const allTargets = SubagentsProcessor.getToolTargets({ global, includeSimulated: false });
256
+ return {
257
+ feature: "subagents",
258
+ itemLabel: "subagent file(s)",
259
+ allTargets,
260
+ createProcessor: ({ toolTarget, dryRun }) => new SubagentsProcessor({ baseDir, toolTarget, global, dryRun, logger: logger5 }),
261
+ loadSource: (p) => p.loadToolFiles(),
262
+ toRulesync: (p, files) => p.convertToolFilesToRulesyncFiles(files),
263
+ fromRulesync: (p, files) => p.convertRulesyncFilesToToolFiles(files),
264
+ write: (p, files) => p.writeAiFiles(files)
265
+ };
266
+ }
267
+ function buildSkillsStrategy(ctx) {
268
+ const { config, logger: logger5 } = ctx;
269
+ const global = config.getGlobal();
270
+ const baseDir = getBaseDir(config);
271
+ const allTargets = SkillsProcessor.getToolTargets({ global });
272
+ return {
273
+ feature: "skills",
274
+ itemLabel: "skill(s)",
275
+ allTargets,
276
+ createProcessor: ({ toolTarget, dryRun }) => new SkillsProcessor({ baseDir, toolTarget, global, dryRun, logger: logger5 }),
277
+ loadSource: (p) => p.loadToolDirs(),
278
+ toRulesync: (p, dirs) => p.convertToolDirsToRulesyncDirs(dirs),
279
+ fromRulesync: (p, dirs) => p.convertRulesyncDirsToToolDirs(dirs),
280
+ write: (p, dirs) => p.writeAiDirs(dirs)
281
+ };
282
+ }
283
+ function buildHooksStrategy(ctx) {
284
+ const { config, logger: logger5 } = ctx;
285
+ const global = config.getGlobal();
286
+ const baseDir = getBaseDir(config);
287
+ const allTargets = HooksProcessor.getToolTargets({ global });
288
+ const importableTargets = HooksProcessor.getToolTargets({ global, importOnly: true });
289
+ return {
290
+ feature: "hooks",
291
+ itemLabel: "hooks file(s)",
292
+ allTargets,
293
+ importableTargets,
294
+ createProcessor: ({ toolTarget, dryRun }) => new HooksProcessor({ baseDir, toolTarget, global, dryRun, logger: logger5 }),
295
+ loadSource: (p) => p.loadToolFiles(),
296
+ toRulesync: (p, files) => p.convertToolFilesToRulesyncFiles(files),
297
+ fromRulesync: (p, files) => p.convertRulesyncFilesToToolFiles(files),
298
+ write: (p, files) => p.writeAiFiles(files)
299
+ };
300
+ }
301
+ function buildPermissionsStrategy(ctx) {
302
+ const { config, logger: logger5 } = ctx;
303
+ const global = config.getGlobal();
304
+ const baseDir = getBaseDir(config);
305
+ const allTargets = PermissionsProcessor.getToolTargets({ global });
306
+ const importableTargets = PermissionsProcessor.getToolTargets({ global, importOnly: true });
307
+ return {
308
+ feature: "permissions",
309
+ itemLabel: "permissions file(s)",
310
+ allTargets,
311
+ importableTargets,
312
+ createProcessor: ({ toolTarget, dryRun }) => new PermissionsProcessor({ baseDir, toolTarget, global, dryRun, logger: logger5 }),
313
+ loadSource: (p) => p.loadToolFiles(),
314
+ toRulesync: (p, files) => p.convertToolFilesToRulesyncFiles(files),
315
+ fromRulesync: (p, files) => p.convertRulesyncFilesToToolFiles(files),
316
+ write: (p, files) => p.writeAiFiles(files)
317
+ };
318
+ }
319
+
320
+ // src/utils/result.ts
321
+ function calculateTotalCount(result) {
322
+ return result.rulesCount + result.ignoreCount + result.mcpCount + result.commandsCount + result.subagentsCount + result.skillsCount + result.hooksCount + result.permissionsCount;
323
+ }
324
+
325
+ // src/cli/commands/convert.ts
326
+ function parseToolTarget(value, label) {
327
+ const result = ToolTargetSchema.safeParse(value);
328
+ if (!result.success) {
329
+ throw new CLIError(
330
+ `Invalid ${label} tool '${value}'. Must be one of: ${ALL_TOOL_TARGETS.join(", ")}`,
331
+ ErrorCodes.CONVERT_FAILED
332
+ );
333
+ }
334
+ return result.data;
335
+ }
336
+ async function convertCommand(logger5, options) {
337
+ const fromTool = parseToolTarget(options.from ?? "", "source");
338
+ const toToolsRaw = (options.to ?? []).map((t) => parseToolTarget(t, "destination"));
339
+ const toTools = Array.from(new Set(toToolsRaw));
340
+ if (toTools.includes(fromTool)) {
341
+ throw new CLIError(
342
+ `Destination tools must not include the source tool '${fromTool}'. Converting a tool onto itself is likely a mistake and may cause lossy round-trips.`,
343
+ ErrorCodes.CONVERT_FAILED
344
+ );
345
+ }
346
+ const config = await ConfigResolver.resolve({
347
+ ...options,
348
+ targets: [fromTool, ...toTools],
349
+ features: options.features ?? ["*"]
350
+ });
351
+ const isPreview = config.isPreviewMode();
352
+ const modePrefix = isPreview ? "[DRY RUN] " : "";
353
+ logger5.debug(`Converting files from ${fromTool} to ${toTools.join(", ")}...`);
354
+ const result = await convertFromTool({ config, fromTool, toTools, logger: logger5 });
355
+ const totalConverted = calculateTotalCount(result);
356
+ if (totalConverted === 0) {
357
+ const enabledFeatures = config.getFeatures(fromTool).join(", ");
358
+ logger5.warn(`No files converted for enabled features: ${enabledFeatures}`);
359
+ return;
360
+ }
361
+ if (logger5.jsonMode) {
362
+ logger5.captureData("from", fromTool);
363
+ logger5.captureData("to", toTools);
364
+ logger5.captureData("dryRun", isPreview);
365
+ logger5.captureData("features", {
366
+ rules: { count: result.rulesCount },
367
+ ignore: { count: result.ignoreCount },
368
+ mcp: { count: result.mcpCount },
369
+ commands: { count: result.commandsCount },
370
+ subagents: { count: result.subagentsCount },
371
+ skills: { count: result.skillsCount },
372
+ hooks: { count: result.hooksCount },
373
+ permissions: { count: result.permissionsCount }
374
+ });
375
+ logger5.captureData("totalFiles", totalConverted);
376
+ }
377
+ const parts = [];
378
+ if (result.rulesCount > 0) parts.push(`${result.rulesCount} rules`);
379
+ if (result.ignoreCount > 0) parts.push(`${result.ignoreCount} ignore files`);
380
+ if (result.mcpCount > 0) parts.push(`${result.mcpCount} MCP files`);
381
+ if (result.commandsCount > 0) parts.push(`${result.commandsCount} commands`);
382
+ if (result.subagentsCount > 0) parts.push(`${result.subagentsCount} subagents`);
383
+ if (result.skillsCount > 0) parts.push(`${result.skillsCount} skills`);
384
+ if (result.hooksCount > 0) parts.push(`${result.hooksCount} hooks`);
385
+ if (result.permissionsCount > 0) parts.push(`${result.permissionsCount} permissions`);
386
+ const verbPhrase = isPreview ? "Would convert" : "Converted";
387
+ const summary = `${modePrefix}${verbPhrase} ${totalConverted} file(s) total from ${fromTool} to ${toTools.join(", ")} (${parts.join(" + ")})`;
388
+ if (isPreview) {
389
+ logger5.info(summary);
390
+ } else {
391
+ logger5.success(summary);
392
+ }
393
+ }
394
+
88
395
  // src/lib/fetch.ts
89
396
  import { join, posix } from "path";
90
397
  import { Semaphore } from "es-toolkit/promise";
@@ -1032,11 +1339,6 @@ async function fetchCommand(logger5, options) {
1032
1339
  }
1033
1340
  }
1034
1341
 
1035
- // src/utils/result.ts
1036
- function calculateTotalCount(result) {
1037
- return result.rulesCount + result.ignoreCount + result.mcpCount + result.commandsCount + result.subagentsCount + result.skillsCount + result.hooksCount + result.permissionsCount;
1038
- }
1039
-
1040
1342
  // src/cli/commands/generate.ts
1041
1343
  function logFeatureResult(logger5, params) {
1042
1344
  const { count, paths, featureName, isPreview, modePrefix } = params;
@@ -1175,7 +1477,7 @@ var GITIGNORE_ENTRY_REGISTRY = [
1175
1477
  // This prevents accidental commits when a user disables the rovodev target.
1176
1478
  { target: "common", feature: "general", entry: "**/AGENTS.local.md" },
1177
1479
  // AGENTS.md
1178
- { target: "agentsmd", feature: "rules", entry: "**/AGENTS.md" },
1480
+ { target: ["agentsmd", "pi"], feature: "rules", entry: "**/AGENTS.md" },
1179
1481
  { target: "agentsmd", feature: "rules", entry: "**/.agents/" },
1180
1482
  // Augment Code
1181
1483
  { target: "augmentcode", feature: "rules", entry: "**/.augment/rules/" },
@@ -1315,6 +1617,10 @@ var GITIGNORE_ENTRY_REGISTRY = [
1315
1617
  feature: "general",
1316
1618
  entry: "**/.opencode/package-lock.json"
1317
1619
  },
1620
+ // Pi Coding Agent
1621
+ { target: "pi", feature: "rules", entry: "**/.agents/memories/" },
1622
+ { target: "pi", feature: "commands", entry: "**/.pi/prompts/" },
1623
+ { target: "pi", feature: "skills", entry: "**/.pi/skills/" },
1318
1624
  // Qwen Code
1319
1625
  { target: "qwencode", feature: "rules", entry: "**/QWEN.md" },
1320
1626
  { target: "qwencode", feature: "general", entry: "**/.qwen/memories/" },
@@ -6248,7 +6554,7 @@ function wrapCommand({
6248
6554
  }
6249
6555
 
6250
6556
  // src/cli/index.ts
6251
- var getVersion = () => "8.8.1";
6557
+ var getVersion = () => "8.10.0";
6252
6558
  function wrapCommand2(name, errorCode, handler) {
6253
6559
  return wrapCommand({ name, errorCode, handler, getVersion });
6254
6560
  }
@@ -6309,6 +6615,21 @@ var main = async () => {
6309
6615
  await importCommand(logger5, options);
6310
6616
  })
6311
6617
  );
6618
+ program.command("convert").description(
6619
+ "Convert configurations from one AI tool to other AI tools without writing .rulesync/ files"
6620
+ ).requiredOption("--from <tool>", "Source tool to convert from (e.g., 'cursor', 'claudecode')").requiredOption(
6621
+ "--to <tools>",
6622
+ "Comma-separated list of destination tools (e.g., 'copilot,claudecode')",
6623
+ parseCommaSeparatedList
6624
+ ).option(
6625
+ "-f, --features <features>",
6626
+ `Comma-separated list of features to convert (${ALL_FEATURES.join(",")}) or '*' for all`,
6627
+ parseCommaSeparatedList
6628
+ ).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").option("-g, --global", "Convert for global(user scope) configuration files").option("--dry-run", "Dry run: show changes without writing files").action(
6629
+ wrapCommand2("convert", "CONVERT_FAILED", async (logger5, options) => {
6630
+ await convertCommand(logger5, options);
6631
+ })
6632
+ );
6312
6633
  program.command("mcp").description("Start MCP server for rulesync").action(
6313
6634
  wrapCommand2("mcp", "MCP_FAILED", async (logger5, _options) => {
6314
6635
  await mcpCommand(logger5, { version });