rulesync 0.34.0 → 0.36.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/index.js CHANGED
@@ -6,6 +6,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
9
12
  var __copyProps = (to, from, except, desc) => {
10
13
  if (from && typeof from === "object" || typeof from === "function") {
11
14
  for (let key of __getOwnPropNames(from))
@@ -23,6 +26,307 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
26
  mod
24
27
  ));
25
28
 
29
+ // src/generators/mcp/claude.ts
30
+ function generateClaudeMcp(config, _target) {
31
+ const claudeSettings = {
32
+ mcpServers: {}
33
+ };
34
+ const shouldInclude = (server) => {
35
+ if (!server.rulesyncTargets || server.rulesyncTargets.length === 0) {
36
+ return true;
37
+ }
38
+ if (server.rulesyncTargets.length === 1 && server.rulesyncTargets[0] === "*") {
39
+ return true;
40
+ }
41
+ return server.rulesyncTargets.includes("claude");
42
+ };
43
+ for (const [serverName, server] of Object.entries(config.mcpServers)) {
44
+ if (!shouldInclude(server)) continue;
45
+ const claudeServer = {};
46
+ if (server.command) {
47
+ claudeServer.command = server.command;
48
+ if (server.args) claudeServer.args = server.args;
49
+ } else if (server.url || server.httpUrl) {
50
+ const url = server.httpUrl || server.url;
51
+ if (url) {
52
+ claudeServer.url = url;
53
+ }
54
+ if (server.httpUrl) {
55
+ claudeServer.transport = "http";
56
+ } else if (server.transport === "sse") {
57
+ claudeServer.transport = "sse";
58
+ }
59
+ }
60
+ if (server.env) {
61
+ claudeServer.env = server.env;
62
+ }
63
+ claudeSettings.mcpServers[serverName] = claudeServer;
64
+ }
65
+ return JSON.stringify(claudeSettings, null, 2);
66
+ }
67
+ var init_claude = __esm({
68
+ "src/generators/mcp/claude.ts"() {
69
+ "use strict";
70
+ }
71
+ });
72
+
73
+ // src/generators/mcp/cline.ts
74
+ function generateClineMcp(config, _target) {
75
+ const clineConfig = {
76
+ mcpServers: {}
77
+ };
78
+ const shouldInclude = (server) => {
79
+ const targets = server.rulesyncTargets;
80
+ if (!targets || targets.length === 0) return true;
81
+ if (targets.length === 1 && targets[0] === "*") return true;
82
+ return targets.includes("cline");
83
+ };
84
+ for (const [serverName, server] of Object.entries(config.mcpServers)) {
85
+ if (!shouldInclude(server)) continue;
86
+ const clineServer = {};
87
+ if (server.command) {
88
+ clineServer.command = server.command;
89
+ if (server.args) clineServer.args = server.args;
90
+ } else if (server.url) {
91
+ clineServer.url = server.url;
92
+ }
93
+ if (server.env) {
94
+ clineServer.env = server.env;
95
+ }
96
+ if (server.disabled !== void 0) {
97
+ clineServer.disabled = server.disabled;
98
+ }
99
+ if (server.alwaysAllow) {
100
+ clineServer.alwaysAllow = server.alwaysAllow;
101
+ }
102
+ if (server.networkTimeout !== void 0) {
103
+ clineServer.networkTimeout = server.networkTimeout;
104
+ }
105
+ clineConfig.mcpServers[serverName] = clineServer;
106
+ }
107
+ return JSON.stringify(clineConfig, null, 2);
108
+ }
109
+ var init_cline = __esm({
110
+ "src/generators/mcp/cline.ts"() {
111
+ "use strict";
112
+ }
113
+ });
114
+
115
+ // src/generators/mcp/copilot.ts
116
+ function generateCopilotMcp(config, target) {
117
+ const shouldInclude = (server) => {
118
+ const targets = server.rulesyncTargets;
119
+ if (!targets || targets.length === 0) return true;
120
+ if (targets.length === 1 && targets[0] === "*") return true;
121
+ return targets.includes("copilot");
122
+ };
123
+ const servers = {};
124
+ const inputs = [];
125
+ const inputMap = /* @__PURE__ */ new Map();
126
+ for (const [serverName, server] of Object.entries(config.mcpServers)) {
127
+ if (!shouldInclude(server)) continue;
128
+ const copilotServer = {};
129
+ if (server.command) {
130
+ copilotServer.command = server.command;
131
+ if (server.args) copilotServer.args = server.args;
132
+ } else if (server.url || server.httpUrl) {
133
+ const url = server.httpUrl || server.url;
134
+ if (url) {
135
+ copilotServer.url = url;
136
+ }
137
+ }
138
+ if (server.env) {
139
+ copilotServer.env = {};
140
+ for (const [key, value] of Object.entries(server.env)) {
141
+ if (target === "editor" && value.includes("SECRET")) {
142
+ const inputId = `${serverName}_${key}`;
143
+ inputMap.set(inputId, value);
144
+ copilotServer.env[key] = `\${input:${inputId}}`;
145
+ inputs.push({
146
+ id: inputId,
147
+ type: "password",
148
+ description: `${key} for ${serverName}`
149
+ });
150
+ } else {
151
+ copilotServer.env[key] = value;
152
+ }
153
+ }
154
+ }
155
+ if (server.tools) {
156
+ copilotServer.tools = server.tools;
157
+ } else if (server.alwaysAllow) {
158
+ copilotServer.tools = server.alwaysAllow;
159
+ }
160
+ servers[serverName] = copilotServer;
161
+ }
162
+ if (target === "codingAgent") {
163
+ const config2 = { mcpServers: servers };
164
+ return JSON.stringify(config2, null, 2);
165
+ } else {
166
+ const config2 = { servers };
167
+ if (inputs.length > 0) {
168
+ config2.inputs = inputs;
169
+ }
170
+ return JSON.stringify(config2, null, 2);
171
+ }
172
+ }
173
+ var init_copilot = __esm({
174
+ "src/generators/mcp/copilot.ts"() {
175
+ "use strict";
176
+ }
177
+ });
178
+
179
+ // src/generators/mcp/cursor.ts
180
+ function generateCursorMcp(config, _target) {
181
+ const cursorConfig = {
182
+ mcpServers: {}
183
+ };
184
+ const shouldInclude = (server) => {
185
+ const targets = server.rulesyncTargets;
186
+ if (!targets || targets.length === 0) return true;
187
+ if (targets.length === 1 && targets[0] === "*") return true;
188
+ return targets.includes("cursor");
189
+ };
190
+ for (const [serverName, server] of Object.entries(config.mcpServers)) {
191
+ if (!shouldInclude(server)) continue;
192
+ const cursorServer = {};
193
+ if (server.command) {
194
+ cursorServer.command = server.command;
195
+ if (server.args) cursorServer.args = server.args;
196
+ } else if (server.url || server.httpUrl) {
197
+ const url = server.httpUrl || server.url;
198
+ if (url) {
199
+ cursorServer.url = url;
200
+ }
201
+ if (server.httpUrl || server.transport === "http") {
202
+ cursorServer.type = "streamable-http";
203
+ } else if (server.transport === "sse" || server.type === "sse") {
204
+ cursorServer.type = "sse";
205
+ }
206
+ }
207
+ if (server.env) {
208
+ cursorServer.env = server.env;
209
+ }
210
+ if (server.cwd) {
211
+ cursorServer.cwd = server.cwd;
212
+ }
213
+ cursorConfig.mcpServers[serverName] = cursorServer;
214
+ }
215
+ return JSON.stringify(cursorConfig, null, 2);
216
+ }
217
+ var init_cursor = __esm({
218
+ "src/generators/mcp/cursor.ts"() {
219
+ "use strict";
220
+ }
221
+ });
222
+
223
+ // src/generators/mcp/geminicli.ts
224
+ function generateGeminiCliMcp(config, _target) {
225
+ const geminiSettings = {
226
+ mcpServers: {}
227
+ };
228
+ const shouldInclude = (server) => {
229
+ const targets = server.rulesyncTargets;
230
+ if (!targets || targets.length === 0) return true;
231
+ if (targets.length === 1 && targets[0] === "*") return true;
232
+ return targets.includes("geminicli");
233
+ };
234
+ for (const [serverName, server] of Object.entries(config.mcpServers)) {
235
+ if (!shouldInclude(server)) continue;
236
+ const geminiServer = {};
237
+ if (server.command) {
238
+ geminiServer.command = server.command;
239
+ if (server.args) geminiServer.args = server.args;
240
+ } else if (server.url || server.httpUrl) {
241
+ if (server.httpUrl) {
242
+ geminiServer.httpUrl = server.httpUrl;
243
+ } else if (server.url) {
244
+ geminiServer.url = server.url;
245
+ }
246
+ }
247
+ if (server.env) {
248
+ geminiServer.env = {};
249
+ for (const [key, value] of Object.entries(server.env)) {
250
+ if (value.startsWith("${") && value.endsWith("}")) {
251
+ geminiServer.env[key] = value;
252
+ } else {
253
+ geminiServer.env[key] = `\${${value}}`;
254
+ }
255
+ }
256
+ }
257
+ if (server.timeout !== void 0) {
258
+ geminiServer.timeout = server.timeout;
259
+ }
260
+ if (server.trust !== void 0) {
261
+ geminiServer.trust = server.trust;
262
+ }
263
+ geminiSettings.mcpServers[serverName] = geminiServer;
264
+ }
265
+ return JSON.stringify(geminiSettings, null, 2);
266
+ }
267
+ var init_geminicli = __esm({
268
+ "src/generators/mcp/geminicli.ts"() {
269
+ "use strict";
270
+ }
271
+ });
272
+
273
+ // src/generators/mcp/roo.ts
274
+ function generateRooMcp(config, _target) {
275
+ const rooConfig = {
276
+ mcpServers: {}
277
+ };
278
+ const shouldInclude = (server) => {
279
+ const targets = server.rulesyncTargets;
280
+ if (!targets || targets.length === 0) return true;
281
+ if (targets.length === 1 && targets[0] === "*") return true;
282
+ return targets.includes("roo");
283
+ };
284
+ for (const [serverName, server] of Object.entries(config.mcpServers)) {
285
+ if (!shouldInclude(server)) continue;
286
+ const rooServer = {};
287
+ if (server.command) {
288
+ rooServer.command = server.command;
289
+ if (server.args) rooServer.args = server.args;
290
+ } else if (server.url || server.httpUrl) {
291
+ const url = server.httpUrl || server.url;
292
+ if (url) {
293
+ rooServer.url = url;
294
+ }
295
+ if (server.httpUrl || server.transport === "http") {
296
+ rooServer.type = "streamable-http";
297
+ } else if (server.transport === "sse" || server.type === "sse") {
298
+ rooServer.type = "sse";
299
+ }
300
+ }
301
+ if (server.env) {
302
+ rooServer.env = {};
303
+ for (const [key, value] of Object.entries(server.env)) {
304
+ if (value.startsWith("${env:") && value.endsWith("}")) {
305
+ rooServer.env[key] = value;
306
+ } else {
307
+ rooServer.env[key] = `\${env:${value}}`;
308
+ }
309
+ }
310
+ }
311
+ if (server.disabled !== void 0) {
312
+ rooServer.disabled = server.disabled;
313
+ }
314
+ if (server.alwaysAllow) {
315
+ rooServer.alwaysAllow = server.alwaysAllow;
316
+ }
317
+ if (server.networkTimeout !== void 0) {
318
+ rooServer.networkTimeout = Math.max(3e4, Math.min(3e5, server.networkTimeout));
319
+ }
320
+ rooConfig.mcpServers[serverName] = rooServer;
321
+ }
322
+ return JSON.stringify(rooConfig, null, 2);
323
+ }
324
+ var init_roo = __esm({
325
+ "src/generators/mcp/roo.ts"() {
326
+ "use strict";
327
+ }
328
+ });
329
+
26
330
  // src/cli/index.ts
27
331
  var import_commander = require("commander");
28
332
 
@@ -39,11 +343,12 @@ function getDefaultConfig() {
39
343
  cursor: ".cursor/rules",
40
344
  cline: ".clinerules",
41
345
  claudecode: ".",
346
+ claude: ".",
42
347
  roo: ".roo/rules",
43
348
  geminicli: ".gemini/memories"
44
349
  },
45
350
  watchEnabled: false,
46
- defaultTargets: ["copilot", "cursor", "cline", "claudecode", "roo", "geminicli"]
351
+ defaultTargets: ["copilot", "cursor", "cline", "claudecode", "claude", "roo", "geminicli"]
47
352
  };
48
353
  }
49
354
  function resolveTargets(targets, config) {
@@ -89,27 +394,158 @@ async function addCommand(filename) {
89
394
  }
90
395
  }
91
396
 
92
- // src/generators/claudecode.ts
397
+ // src/generators/rules/claudecode.ts
398
+ var import_node_path4 = require("path");
399
+
400
+ // src/utils/file.ts
401
+ var import_promises2 = require("fs/promises");
402
+ var import_node_path3 = require("path");
403
+
404
+ // src/utils/ignore.ts
93
405
  var import_node_path2 = require("path");
406
+ var import_micromatch = __toESM(require("micromatch"));
407
+ var cachedIgnorePatterns = null;
408
+ async function loadIgnorePatterns(baseDir = process.cwd()) {
409
+ if (cachedIgnorePatterns) {
410
+ return cachedIgnorePatterns;
411
+ }
412
+ const ignorePath = (0, import_node_path2.join)(baseDir, ".rulesyncignore");
413
+ if (!await fileExists(ignorePath)) {
414
+ cachedIgnorePatterns = { patterns: [] };
415
+ return cachedIgnorePatterns;
416
+ }
417
+ try {
418
+ const content = await readFileContent(ignorePath);
419
+ const patterns = parseIgnoreFile(content);
420
+ cachedIgnorePatterns = { patterns };
421
+ return cachedIgnorePatterns;
422
+ } catch (error) {
423
+ console.warn(`Failed to read .rulesyncignore: ${error}`);
424
+ cachedIgnorePatterns = { patterns: [] };
425
+ return cachedIgnorePatterns;
426
+ }
427
+ }
428
+ function parseIgnoreFile(content) {
429
+ return content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
430
+ }
431
+ function isFileIgnored(filepath, ignorePatterns) {
432
+ if (ignorePatterns.length === 0) {
433
+ return false;
434
+ }
435
+ const negationPatterns = ignorePatterns.filter((p) => p.startsWith("!"));
436
+ const positivePatterns = ignorePatterns.filter((p) => !p.startsWith("!"));
437
+ const isIgnored = positivePatterns.length > 0 && import_micromatch.default.isMatch(filepath, positivePatterns, {
438
+ dot: true
439
+ });
440
+ if (isIgnored && negationPatterns.length > 0) {
441
+ const negationPatternsWithoutPrefix = negationPatterns.map((p) => p.substring(1));
442
+ return !import_micromatch.default.isMatch(filepath, negationPatternsWithoutPrefix, {
443
+ dot: true
444
+ });
445
+ }
446
+ return isIgnored;
447
+ }
448
+ function filterIgnoredFiles(files, ignorePatterns) {
449
+ if (ignorePatterns.length === 0) {
450
+ return files;
451
+ }
452
+ return files.filter((file) => !isFileIgnored(file, ignorePatterns));
453
+ }
454
+
455
+ // src/utils/file.ts
456
+ async function ensureDir(dirPath) {
457
+ try {
458
+ await (0, import_promises2.stat)(dirPath);
459
+ } catch {
460
+ await (0, import_promises2.mkdir)(dirPath, { recursive: true });
461
+ }
462
+ }
463
+ async function readFileContent(filepath) {
464
+ return (0, import_promises2.readFile)(filepath, "utf-8");
465
+ }
466
+ async function writeFileContent(filepath, content) {
467
+ await ensureDir((0, import_node_path3.dirname)(filepath));
468
+ await (0, import_promises2.writeFile)(filepath, content, "utf-8");
469
+ }
470
+ async function findFiles(dir, extension = ".md", ignorePatterns) {
471
+ try {
472
+ const files = await (0, import_promises2.readdir)(dir);
473
+ const filtered = files.filter((file) => file.endsWith(extension)).map((file) => (0, import_node_path3.join)(dir, file));
474
+ if (ignorePatterns && ignorePatterns.length > 0) {
475
+ return filterIgnoredFiles(filtered, ignorePatterns);
476
+ }
477
+ return filtered;
478
+ } catch {
479
+ return [];
480
+ }
481
+ }
482
+ async function fileExists(filepath) {
483
+ try {
484
+ await (0, import_promises2.stat)(filepath);
485
+ return true;
486
+ } catch {
487
+ return false;
488
+ }
489
+ }
490
+ async function removeDirectory(dirPath) {
491
+ const dangerousPaths = [".", "/", "~", "src", "node_modules"];
492
+ if (dangerousPaths.includes(dirPath) || dirPath === "") {
493
+ console.warn(`Skipping deletion of dangerous path: ${dirPath}`);
494
+ return;
495
+ }
496
+ try {
497
+ if (await fileExists(dirPath)) {
498
+ await (0, import_promises2.rm)(dirPath, { recursive: true, force: true });
499
+ }
500
+ } catch (error) {
501
+ console.warn(`Failed to remove directory ${dirPath}:`, error);
502
+ }
503
+ }
504
+ async function removeFile(filepath) {
505
+ try {
506
+ if (await fileExists(filepath)) {
507
+ await (0, import_promises2.rm)(filepath);
508
+ }
509
+ } catch (error) {
510
+ console.warn(`Failed to remove file ${filepath}:`, error);
511
+ }
512
+ }
513
+ async function removeClaudeGeneratedFiles() {
514
+ const filesToRemove = ["CLAUDE.md", ".claude/memories"];
515
+ for (const fileOrDir of filesToRemove) {
516
+ if (fileOrDir.endsWith("/memories")) {
517
+ await removeDirectory(fileOrDir);
518
+ } else {
519
+ await removeFile(fileOrDir);
520
+ }
521
+ }
522
+ }
523
+
524
+ // src/generators/rules/claudecode.ts
94
525
  async function generateClaudecodeConfig(rules, config, baseDir) {
95
526
  const outputs = [];
96
527
  const rootRules = rules.filter((r) => r.frontmatter.root === true);
97
528
  const detailRules = rules.filter((r) => r.frontmatter.root === false);
98
529
  const claudeMdContent = generateClaudeMarkdown(rootRules, detailRules);
99
- const claudeOutputDir = baseDir ? (0, import_node_path2.join)(baseDir, config.outputPaths.claudecode) : config.outputPaths.claudecode;
530
+ const claudeOutputDir = baseDir ? (0, import_node_path4.join)(baseDir, config.outputPaths.claudecode) : config.outputPaths.claudecode;
100
531
  outputs.push({
101
532
  tool: "claudecode",
102
- filepath: (0, import_node_path2.join)(claudeOutputDir, "CLAUDE.md"),
533
+ filepath: (0, import_node_path4.join)(claudeOutputDir, "CLAUDE.md"),
103
534
  content: claudeMdContent
104
535
  });
105
536
  for (const rule of detailRules) {
106
537
  const memoryContent = generateMemoryFile(rule);
107
538
  outputs.push({
108
539
  tool: "claudecode",
109
- filepath: (0, import_node_path2.join)(claudeOutputDir, ".claude", "memories", `${rule.filename}.md`),
540
+ filepath: (0, import_node_path4.join)(claudeOutputDir, ".claude", "memories", `${rule.filename}.md`),
110
541
  content: memoryContent
111
542
  });
112
543
  }
544
+ const ignorePatterns = await loadIgnorePatterns(baseDir);
545
+ if (ignorePatterns.patterns.length > 0) {
546
+ const settingsPath = baseDir ? (0, import_node_path4.join)(baseDir, ".claude", "settings.json") : (0, import_node_path4.join)(".claude", "settings.json");
547
+ await updateClaudeSettings(settingsPath, ignorePatterns.patterns);
548
+ }
113
549
  return outputs;
114
550
  }
115
551
  function generateClaudeMarkdown(rootRules, detailRules) {
@@ -138,42 +574,101 @@ function generateClaudeMarkdown(rootRules, detailRules) {
138
574
  function generateMemoryFile(rule) {
139
575
  return rule.content.trim();
140
576
  }
577
+ async function updateClaudeSettings(settingsPath, ignorePatterns) {
578
+ let settings = {};
579
+ if (await fileExists(settingsPath)) {
580
+ try {
581
+ const content = await readFileContent(settingsPath);
582
+ settings = JSON.parse(content);
583
+ } catch (_error) {
584
+ console.warn(`Failed to parse existing ${settingsPath}, creating new settings`);
585
+ settings = {};
586
+ }
587
+ }
588
+ if (!settings.permissions) {
589
+ settings.permissions = {};
590
+ }
591
+ if (!Array.isArray(settings.permissions.deny)) {
592
+ settings.permissions.deny = [];
593
+ }
594
+ const readDenyRules = ignorePatterns.map((pattern) => `Read(${pattern})`);
595
+ settings.permissions.deny = settings.permissions.deny.filter((rule) => {
596
+ if (!rule.startsWith("Read(")) return true;
597
+ const match = rule.match(/^Read\((.*)\)$/);
598
+ if (!match) return true;
599
+ return !ignorePatterns.includes(match[1] ?? "");
600
+ });
601
+ settings.permissions.deny.push(...readDenyRules);
602
+ settings.permissions.deny = [...new Set(settings.permissions.deny)];
603
+ const jsonContent = JSON.stringify(settings, null, 2);
604
+ await writeFileContent(settingsPath, jsonContent);
605
+ console.log(`\u2705 Updated Claude Code settings: ${settingsPath}`);
606
+ }
141
607
 
142
- // src/generators/cline.ts
143
- var import_node_path3 = require("path");
608
+ // src/generators/rules/cline.ts
609
+ var import_node_path5 = require("path");
144
610
  async function generateClineConfig(rules, config, baseDir) {
145
611
  const outputs = [];
146
612
  for (const rule of rules) {
147
613
  const content = generateClineMarkdown(rule);
148
- const outputDir = baseDir ? (0, import_node_path3.join)(baseDir, config.outputPaths.cline) : config.outputPaths.cline;
149
- const filepath = (0, import_node_path3.join)(outputDir, `${rule.filename}.md`);
614
+ const outputDir = baseDir ? (0, import_node_path5.join)(baseDir, config.outputPaths.cline) : config.outputPaths.cline;
615
+ const filepath = (0, import_node_path5.join)(outputDir, `${rule.filename}.md`);
150
616
  outputs.push({
151
617
  tool: "cline",
152
618
  filepath,
153
619
  content
154
620
  });
155
621
  }
622
+ const ignorePatterns = await loadIgnorePatterns(baseDir);
623
+ if (ignorePatterns.patterns.length > 0) {
624
+ const clineIgnorePath = baseDir ? (0, import_node_path5.join)(baseDir, ".clineignore") : ".clineignore";
625
+ const clineIgnoreContent = generateClineIgnore(ignorePatterns.patterns);
626
+ outputs.push({
627
+ tool: "cline",
628
+ filepath: clineIgnorePath,
629
+ content: clineIgnoreContent
630
+ });
631
+ }
156
632
  return outputs;
157
633
  }
158
634
  function generateClineMarkdown(rule) {
159
635
  return rule.content.trim();
160
636
  }
637
+ function generateClineIgnore(patterns) {
638
+ const lines = [
639
+ "# Generated by rulesync from .rulesyncignore",
640
+ "# This file is automatically generated. Do not edit manually.",
641
+ "",
642
+ ...patterns
643
+ ];
644
+ return lines.join("\n");
645
+ }
161
646
 
162
- // src/generators/copilot.ts
163
- var import_node_path4 = require("path");
647
+ // src/generators/rules/copilot.ts
648
+ var import_node_path6 = require("path");
164
649
  async function generateCopilotConfig(rules, config, baseDir) {
165
650
  const outputs = [];
166
651
  for (const rule of rules) {
167
652
  const content = generateCopilotMarkdown(rule);
168
653
  const baseFilename = rule.filename.replace(/\.md$/, "");
169
- const outputDir = baseDir ? (0, import_node_path4.join)(baseDir, config.outputPaths.copilot) : config.outputPaths.copilot;
170
- const filepath = (0, import_node_path4.join)(outputDir, `${baseFilename}.instructions.md`);
654
+ const outputDir = baseDir ? (0, import_node_path6.join)(baseDir, config.outputPaths.copilot) : config.outputPaths.copilot;
655
+ const filepath = (0, import_node_path6.join)(outputDir, `${baseFilename}.instructions.md`);
171
656
  outputs.push({
172
657
  tool: "copilot",
173
658
  filepath,
174
659
  content
175
660
  });
176
661
  }
662
+ const ignorePatterns = await loadIgnorePatterns(baseDir);
663
+ if (ignorePatterns.patterns.length > 0) {
664
+ const copilotIgnorePath = baseDir ? (0, import_node_path6.join)(baseDir, ".copilotignore") : ".copilotignore";
665
+ const copilotIgnoreContent = generateCopilotIgnore(ignorePatterns.patterns);
666
+ outputs.push({
667
+ tool: "copilot",
668
+ filepath: copilotIgnorePath,
669
+ content: copilotIgnoreContent
670
+ });
671
+ }
177
672
  return outputs;
178
673
  }
179
674
  function generateCopilotMarkdown(rule) {
@@ -189,21 +684,42 @@ function generateCopilotMarkdown(rule) {
189
684
  lines.push(rule.content);
190
685
  return lines.join("\n");
191
686
  }
687
+ function generateCopilotIgnore(patterns) {
688
+ const lines = [
689
+ "# Generated by rulesync from .rulesyncignore",
690
+ "# This file is automatically generated. Do not edit manually.",
691
+ "# Note: .copilotignore is not officially supported by GitHub Copilot.",
692
+ "# This file is for use with community tools like copilotignore-vscode extension.",
693
+ "",
694
+ ...patterns
695
+ ];
696
+ return lines.join("\n");
697
+ }
192
698
 
193
- // src/generators/cursor.ts
194
- var import_node_path5 = require("path");
699
+ // src/generators/rules/cursor.ts
700
+ var import_node_path7 = require("path");
195
701
  async function generateCursorConfig(rules, config, baseDir) {
196
702
  const outputs = [];
197
703
  for (const rule of rules) {
198
704
  const content = generateCursorMarkdown(rule);
199
- const outputDir = baseDir ? (0, import_node_path5.join)(baseDir, config.outputPaths.cursor) : config.outputPaths.cursor;
200
- const filepath = (0, import_node_path5.join)(outputDir, `${rule.filename}.mdc`);
705
+ const outputDir = baseDir ? (0, import_node_path7.join)(baseDir, config.outputPaths.cursor) : config.outputPaths.cursor;
706
+ const filepath = (0, import_node_path7.join)(outputDir, `${rule.filename}.mdc`);
201
707
  outputs.push({
202
708
  tool: "cursor",
203
709
  filepath,
204
710
  content
205
711
  });
206
712
  }
713
+ const ignorePatterns = await loadIgnorePatterns(baseDir);
714
+ if (ignorePatterns.patterns.length > 0) {
715
+ const cursorIgnorePath = baseDir ? (0, import_node_path7.join)(baseDir, ".cursorignore") : ".cursorignore";
716
+ const cursorIgnoreContent = generateCursorIgnore(ignorePatterns.patterns);
717
+ outputs.push({
718
+ tool: "cursor",
719
+ filepath: cursorIgnorePath,
720
+ content: cursorIgnoreContent
721
+ });
722
+ }
207
723
  return outputs;
208
724
  }
209
725
  function generateCursorMarkdown(rule) {
@@ -226,17 +742,26 @@ function generateCursorMarkdown(rule) {
226
742
  lines.push(rule.content);
227
743
  return lines.join("\n");
228
744
  }
745
+ function generateCursorIgnore(patterns) {
746
+ const lines = [
747
+ "# Generated by rulesync from .rulesyncignore",
748
+ "# This file is automatically generated. Do not edit manually.",
749
+ "",
750
+ ...patterns
751
+ ];
752
+ return lines.join("\n");
753
+ }
229
754
 
230
- // src/generators/geminicli.ts
231
- var import_node_path6 = require("path");
755
+ // src/generators/rules/geminicli.ts
756
+ var import_node_path8 = require("path");
232
757
  async function generateGeminiConfig(rules, config, baseDir) {
233
758
  const outputs = [];
234
759
  const rootRule = rules.find((rule) => rule.frontmatter.root === true);
235
760
  const memoryRules = rules.filter((rule) => rule.frontmatter.root === false);
236
761
  for (const rule of memoryRules) {
237
762
  const content = generateGeminiMemoryMarkdown(rule);
238
- const outputDir = baseDir ? (0, import_node_path6.join)(baseDir, config.outputPaths.geminicli) : config.outputPaths.geminicli;
239
- const filepath = (0, import_node_path6.join)(outputDir, `${rule.filename}.md`);
763
+ const outputDir = baseDir ? (0, import_node_path8.join)(baseDir, config.outputPaths.geminicli) : config.outputPaths.geminicli;
764
+ const filepath = (0, import_node_path8.join)(outputDir, `${rule.filename}.md`);
240
765
  outputs.push({
241
766
  tool: "geminicli",
242
767
  filepath,
@@ -244,12 +769,22 @@ async function generateGeminiConfig(rules, config, baseDir) {
244
769
  });
245
770
  }
246
771
  const rootContent = generateGeminiRootMarkdown(rootRule, memoryRules, baseDir);
247
- const rootFilepath = baseDir ? (0, import_node_path6.join)(baseDir, "GEMINI.md") : "GEMINI.md";
772
+ const rootFilepath = baseDir ? (0, import_node_path8.join)(baseDir, "GEMINI.md") : "GEMINI.md";
248
773
  outputs.push({
249
774
  tool: "geminicli",
250
775
  filepath: rootFilepath,
251
776
  content: rootContent
252
777
  });
778
+ const ignorePatterns = await loadIgnorePatterns(baseDir);
779
+ if (ignorePatterns.patterns.length > 0) {
780
+ const aiexcludePath = baseDir ? (0, import_node_path8.join)(baseDir, ".aiexclude") : ".aiexclude";
781
+ const aiexcludeContent = generateAiexclude(ignorePatterns.patterns);
782
+ outputs.push({
783
+ tool: "geminicli",
784
+ filepath: aiexcludePath,
785
+ content: aiexcludeContent
786
+ });
787
+ }
253
788
  return outputs;
254
789
  }
255
790
  function generateGeminiMemoryMarkdown(rule) {
@@ -279,92 +814,53 @@ function generateGeminiRootMarkdown(rootRule, memoryRules, _baseDir) {
279
814
  }
280
815
  return lines.join("\n");
281
816
  }
817
+ function generateAiexclude(patterns) {
818
+ const lines = [
819
+ "# Generated by rulesync from .rulesyncignore",
820
+ "# This file is automatically generated. Do not edit manually.",
821
+ "",
822
+ ...patterns
823
+ ];
824
+ return lines.join("\n");
825
+ }
282
826
 
283
- // src/generators/roo.ts
284
- var import_node_path7 = require("path");
827
+ // src/generators/rules/roo.ts
828
+ var import_node_path9 = require("path");
285
829
  async function generateRooConfig(rules, config, baseDir) {
286
830
  const outputs = [];
287
831
  for (const rule of rules) {
288
832
  const content = generateRooMarkdown(rule);
289
- const outputDir = baseDir ? (0, import_node_path7.join)(baseDir, config.outputPaths.roo) : config.outputPaths.roo;
290
- const filepath = (0, import_node_path7.join)(outputDir, `${rule.filename}.md`);
833
+ const outputDir = baseDir ? (0, import_node_path9.join)(baseDir, config.outputPaths.roo) : config.outputPaths.roo;
834
+ const filepath = (0, import_node_path9.join)(outputDir, `${rule.filename}.md`);
291
835
  outputs.push({
292
836
  tool: "roo",
293
837
  filepath,
294
838
  content
295
839
  });
296
840
  }
841
+ const ignorePatterns = await loadIgnorePatterns(baseDir);
842
+ if (ignorePatterns.patterns.length > 0) {
843
+ const rooIgnorePath = baseDir ? (0, import_node_path9.join)(baseDir, ".rooignore") : ".rooignore";
844
+ const rooIgnoreContent = generateRooIgnore(ignorePatterns.patterns);
845
+ outputs.push({
846
+ tool: "roo",
847
+ filepath: rooIgnorePath,
848
+ content: rooIgnoreContent
849
+ });
850
+ }
297
851
  return outputs;
298
852
  }
299
853
  function generateRooMarkdown(rule) {
300
854
  return rule.content.trim();
301
855
  }
302
-
303
- // src/utils/file.ts
304
- var import_promises2 = require("fs/promises");
305
- var import_node_path8 = require("path");
306
- async function ensureDir(dirPath) {
307
- try {
308
- await (0, import_promises2.stat)(dirPath);
309
- } catch {
310
- await (0, import_promises2.mkdir)(dirPath, { recursive: true });
311
- }
312
- }
313
- async function readFileContent(filepath) {
314
- return (0, import_promises2.readFile)(filepath, "utf-8");
315
- }
316
- async function writeFileContent(filepath, content) {
317
- await ensureDir((0, import_node_path8.dirname)(filepath));
318
- await (0, import_promises2.writeFile)(filepath, content, "utf-8");
319
- }
320
- async function findFiles(dir, extension = ".md") {
321
- try {
322
- const files = await (0, import_promises2.readdir)(dir);
323
- return files.filter((file) => file.endsWith(extension)).map((file) => (0, import_node_path8.join)(dir, file));
324
- } catch {
325
- return [];
326
- }
327
- }
328
- async function fileExists(filepath) {
329
- try {
330
- await (0, import_promises2.stat)(filepath);
331
- return true;
332
- } catch {
333
- return false;
334
- }
335
- }
336
- async function removeDirectory(dirPath) {
337
- const dangerousPaths = [".", "/", "~", "src", "node_modules"];
338
- if (dangerousPaths.includes(dirPath) || dirPath === "") {
339
- console.warn(`Skipping deletion of dangerous path: ${dirPath}`);
340
- return;
341
- }
342
- try {
343
- if (await fileExists(dirPath)) {
344
- await (0, import_promises2.rm)(dirPath, { recursive: true, force: true });
345
- }
346
- } catch (error) {
347
- console.warn(`Failed to remove directory ${dirPath}:`, error);
348
- }
349
- }
350
- async function removeFile(filepath) {
351
- try {
352
- if (await fileExists(filepath)) {
353
- await (0, import_promises2.rm)(filepath);
354
- }
355
- } catch (error) {
356
- console.warn(`Failed to remove file ${filepath}:`, error);
357
- }
358
- }
359
- async function removeClaudeGeneratedFiles() {
360
- const filesToRemove = ["CLAUDE.md", ".claude/memories"];
361
- for (const fileOrDir of filesToRemove) {
362
- if (fileOrDir.endsWith("/memories")) {
363
- await removeDirectory(fileOrDir);
364
- } else {
365
- await removeFile(fileOrDir);
366
- }
367
- }
856
+ function generateRooIgnore(patterns) {
857
+ const lines = [
858
+ "# Generated by rulesync from .rulesyncignore",
859
+ "# This file is automatically generated. Do not edit manually.",
860
+ "",
861
+ ...patterns
862
+ ];
863
+ return lines.join("\n");
368
864
  }
369
865
 
370
866
  // src/core/generator.ts
@@ -417,12 +913,16 @@ async function generateForTool(tool, rules, config, baseDir) {
417
913
  }
418
914
 
419
915
  // src/core/parser.ts
420
- var import_node_path9 = require("path");
916
+ var import_node_path10 = require("path");
421
917
  var import_gray_matter = __toESM(require("gray-matter"));
422
918
  async function parseRulesFromDirectory(aiRulesDir) {
423
- const ruleFiles = await findFiles(aiRulesDir);
919
+ const ignorePatterns = await loadIgnorePatterns();
920
+ const ruleFiles = await findFiles(aiRulesDir, ".md", ignorePatterns.patterns);
424
921
  const rules = [];
425
922
  const errors = [];
923
+ if (ignorePatterns.patterns.length > 0) {
924
+ console.log(`Loaded ${ignorePatterns.patterns.length} ignore patterns from .rulesyncignore`);
925
+ }
426
926
  for (const filepath of ruleFiles) {
427
927
  try {
428
928
  const rule = await parseRuleFile(filepath);
@@ -450,7 +950,7 @@ async function parseRuleFile(filepath) {
450
950
  const parsed = (0, import_gray_matter.default)(content);
451
951
  validateFrontmatter(parsed.data, filepath);
452
952
  const frontmatter = parsed.data;
453
- const filename = (0, import_node_path9.basename)(filepath, ".md");
953
+ const filename = (0, import_node_path10.basename)(filepath, ".md");
454
954
  return {
455
955
  frontmatter,
456
956
  content: parsed.content,
@@ -578,6 +1078,148 @@ async function validateRule(rule) {
578
1078
  };
579
1079
  }
580
1080
 
1081
+ // src/core/mcp-generator.ts
1082
+ var import_node_os = __toESM(require("os"));
1083
+ var import_node_path12 = __toESM(require("path"));
1084
+
1085
+ // src/generators/mcp/index.ts
1086
+ init_claude();
1087
+ init_cline();
1088
+ init_copilot();
1089
+ init_cursor();
1090
+ init_geminicli();
1091
+ init_roo();
1092
+
1093
+ // src/core/mcp-parser.ts
1094
+ var import_node_fs = __toESM(require("fs"));
1095
+ var import_node_path11 = __toESM(require("path"));
1096
+ function parseMcpConfig(projectRoot) {
1097
+ const mcpPath = import_node_path11.default.join(projectRoot, ".rulesync", ".mcp.json");
1098
+ if (!import_node_fs.default.existsSync(mcpPath)) {
1099
+ return null;
1100
+ }
1101
+ try {
1102
+ const content = import_node_fs.default.readFileSync(mcpPath, "utf-8");
1103
+ const rawConfig = JSON.parse(content);
1104
+ if (rawConfig.servers && !rawConfig.mcpServers) {
1105
+ rawConfig.mcpServers = rawConfig.servers;
1106
+ delete rawConfig.servers;
1107
+ }
1108
+ if (!rawConfig.mcpServers || typeof rawConfig.mcpServers !== "object") {
1109
+ throw new Error("Invalid mcp.json: 'mcpServers' field must be an object");
1110
+ }
1111
+ if (rawConfig.tools) {
1112
+ delete rawConfig.tools;
1113
+ }
1114
+ return { mcpServers: rawConfig.mcpServers };
1115
+ } catch (error) {
1116
+ throw new Error(
1117
+ `Failed to parse mcp.json: ${error instanceof Error ? error.message : String(error)}`
1118
+ );
1119
+ }
1120
+ }
1121
+
1122
+ // src/core/mcp-generator.ts
1123
+ async function generateMcpConfigs(projectRoot, baseDir) {
1124
+ const results = [];
1125
+ const targetRoot = baseDir || projectRoot;
1126
+ const config = parseMcpConfig(projectRoot);
1127
+ if (!config) {
1128
+ return results;
1129
+ }
1130
+ const generators = [
1131
+ {
1132
+ tool: "claude-project",
1133
+ path: import_node_path12.default.join(targetRoot, ".mcp.json"),
1134
+ generate: () => generateClaudeMcp(config, "project")
1135
+ },
1136
+ {
1137
+ tool: "copilot-editor",
1138
+ path: import_node_path12.default.join(targetRoot, ".vscode", "mcp.json"),
1139
+ generate: () => generateCopilotMcp(config, "editor")
1140
+ },
1141
+ {
1142
+ tool: "cursor-project",
1143
+ path: import_node_path12.default.join(targetRoot, ".cursor", "mcp.json"),
1144
+ generate: () => generateCursorMcp(config, "project")
1145
+ },
1146
+ {
1147
+ tool: "cline-project",
1148
+ path: import_node_path12.default.join(targetRoot, ".cline", "mcp.json"),
1149
+ generate: () => generateClineMcp(config, "project")
1150
+ },
1151
+ {
1152
+ tool: "gemini-project",
1153
+ path: import_node_path12.default.join(targetRoot, ".gemini", "settings.json"),
1154
+ generate: () => generateGeminiCliMcp(config, "project")
1155
+ },
1156
+ {
1157
+ tool: "roo-project",
1158
+ path: import_node_path12.default.join(targetRoot, ".roo", "mcp.json"),
1159
+ generate: () => generateRooMcp(config, "project")
1160
+ }
1161
+ ];
1162
+ if (!baseDir) {
1163
+ generators.push(
1164
+ {
1165
+ tool: "claude-global",
1166
+ path: import_node_path12.default.join(import_node_os.default.homedir(), ".claude", "settings.json"),
1167
+ generate: () => generateClaudeMcp(config, "global")
1168
+ },
1169
+ {
1170
+ tool: "cursor-global",
1171
+ path: import_node_path12.default.join(import_node_os.default.homedir(), ".cursor", "mcp.json"),
1172
+ generate: () => generateCursorMcp(config, "global")
1173
+ },
1174
+ {
1175
+ tool: "gemini-global",
1176
+ path: import_node_path12.default.join(import_node_os.default.homedir(), ".gemini", "settings.json"),
1177
+ generate: () => generateGeminiCliMcp(config, "global")
1178
+ }
1179
+ );
1180
+ }
1181
+ for (const generator of generators) {
1182
+ try {
1183
+ const content = generator.generate();
1184
+ const parsed = JSON.parse(content);
1185
+ if (generator.tool.includes("claude") || generator.tool.includes("cline") || generator.tool.includes("cursor") || generator.tool.includes("gemini") || generator.tool.includes("roo")) {
1186
+ if (!parsed.mcpServers || Object.keys(parsed.mcpServers).length === 0) {
1187
+ results.push({
1188
+ tool: generator.tool,
1189
+ path: generator.path,
1190
+ status: "skipped"
1191
+ });
1192
+ continue;
1193
+ }
1194
+ } else if (generator.tool.includes("copilot")) {
1195
+ const key = generator.tool.includes("codingAgent") ? "mcpServers" : "servers";
1196
+ if (!parsed[key] || Object.keys(parsed[key]).length === 0) {
1197
+ results.push({
1198
+ tool: generator.tool,
1199
+ path: generator.path,
1200
+ status: "skipped"
1201
+ });
1202
+ continue;
1203
+ }
1204
+ }
1205
+ await writeFileContent(generator.path, content);
1206
+ results.push({
1207
+ tool: generator.tool,
1208
+ path: generator.path,
1209
+ status: "success"
1210
+ });
1211
+ } catch (error) {
1212
+ results.push({
1213
+ tool: generator.tool,
1214
+ path: generator.path,
1215
+ status: "error",
1216
+ error: error instanceof Error ? error.message : String(error)
1217
+ });
1218
+ }
1219
+ }
1220
+ return results;
1221
+ }
1222
+
581
1223
  // src/cli/commands/generate.ts
582
1224
  async function generateCommand(options = {}) {
583
1225
  const config = getDefaultConfig();
@@ -658,6 +1300,30 @@ Generating configurations for base directory: ${baseDir}`);
658
1300
  }
659
1301
  console.log(`
660
1302
  \u{1F389} Successfully generated ${totalOutputs} configuration file(s)!`);
1303
+ if (options.verbose) {
1304
+ console.log("\nGenerating MCP configurations...");
1305
+ }
1306
+ for (const baseDir of baseDirs) {
1307
+ const mcpResults = await generateMcpConfigs(
1308
+ process.cwd(),
1309
+ baseDir === process.cwd() ? void 0 : baseDir
1310
+ );
1311
+ if (mcpResults.length === 0) {
1312
+ if (options.verbose) {
1313
+ console.log(`No MCP configuration found for ${baseDir}`);
1314
+ }
1315
+ continue;
1316
+ }
1317
+ for (const result of mcpResults) {
1318
+ if (result.status === "success") {
1319
+ console.log(`\u2705 Generated ${result.tool} MCP configuration: ${result.path}`);
1320
+ } else if (result.status === "error") {
1321
+ console.error(`\u274C Failed to generate ${result.tool} MCP configuration: ${result.error}`);
1322
+ } else if (options.verbose && result.status === "skipped") {
1323
+ console.log(`\u23ED\uFE0F Skipped ${result.tool} MCP configuration (no servers configured)`);
1324
+ }
1325
+ }
1326
+ }
661
1327
  } catch (error) {
662
1328
  console.error("\u274C Failed to generate configurations:", error);
663
1329
  process.exit(1);
@@ -665,25 +1331,36 @@ Generating configurations for base directory: ${baseDir}`);
665
1331
  }
666
1332
 
667
1333
  // src/cli/commands/gitignore.ts
668
- var import_node_fs = require("fs");
669
- var import_node_path10 = require("path");
1334
+ var import_node_fs2 = require("fs");
1335
+ var import_node_path13 = require("path");
670
1336
  var gitignoreCommand = async () => {
671
- const gitignorePath = (0, import_node_path10.join)(process.cwd(), ".gitignore");
1337
+ const gitignorePath = (0, import_node_path13.join)(process.cwd(), ".gitignore");
672
1338
  const rulesFilesToIgnore = [
673
1339
  "# Generated by rulesync - AI tool configuration files",
674
1340
  "**/.github/copilot-instructions.md",
675
1341
  "**/.github/instructions/",
676
1342
  "**/.cursor/rules/",
1343
+ "**/.cursorignore",
677
1344
  "**/.clinerules/",
1345
+ "**/.clineignore",
678
1346
  "**/CLAUDE.md",
679
1347
  "**/.claude/memories/",
680
1348
  "**/.roo/rules/",
1349
+ "**/.rooignore",
1350
+ "**/.copilotignore",
681
1351
  "**/GEMINI.md",
682
- "**/.gemini/memories/"
1352
+ "**/.gemini/memories/",
1353
+ "**/.aiexclude",
1354
+ "**/.mcp.json",
1355
+ "**/.cursor/mcp.json",
1356
+ "**/.cline/mcp.json",
1357
+ "**/.vscode/mcp.json",
1358
+ "**/.gemini/settings.json",
1359
+ "**/.roo/mcp.json"
683
1360
  ];
684
1361
  let gitignoreContent = "";
685
- if ((0, import_node_fs.existsSync)(gitignorePath)) {
686
- gitignoreContent = (0, import_node_fs.readFileSync)(gitignorePath, "utf-8");
1362
+ if ((0, import_node_fs2.existsSync)(gitignorePath)) {
1363
+ gitignoreContent = (0, import_node_fs2.readFileSync)(gitignorePath, "utf-8");
687
1364
  }
688
1365
  const linesToAdd = [];
689
1366
  for (const rule of rulesFilesToIgnore) {
@@ -700,7 +1377,7 @@ var gitignoreCommand = async () => {
700
1377
  ${linesToAdd.join("\n")}
701
1378
  ` : `${linesToAdd.join("\n")}
702
1379
  `;
703
- (0, import_node_fs.writeFileSync)(gitignorePath, newContent);
1380
+ (0, import_node_fs2.writeFileSync)(gitignorePath, newContent);
704
1381
  console.log(`\u2705 .gitignore\u306B${linesToAdd.length}\u500B\u306E\u30EB\u30FC\u30EB\u3092\u8FFD\u52A0\u3057\u307E\u3057\u305F:`);
705
1382
  for (const line of linesToAdd) {
706
1383
  if (!line.startsWith("#")) {
@@ -710,15 +1387,17 @@ ${linesToAdd.join("\n")}
710
1387
  };
711
1388
 
712
1389
  // src/core/importer.ts
713
- var import_node_path16 = require("path");
1390
+ var import_node_path20 = require("path");
714
1391
  var import_gray_matter4 = __toESM(require("gray-matter"));
715
1392
 
716
1393
  // src/parsers/claudecode.ts
717
- var import_node_path11 = require("path");
1394
+ var import_node_path14 = require("path");
718
1395
  async function parseClaudeConfiguration(baseDir = process.cwd()) {
719
1396
  const errors = [];
720
1397
  const rules = [];
721
- const claudeFilePath = (0, import_node_path11.join)(baseDir, "CLAUDE.md");
1398
+ let ignorePatterns;
1399
+ let mcpServers;
1400
+ const claudeFilePath = (0, import_node_path14.join)(baseDir, "CLAUDE.md");
722
1401
  if (!await fileExists(claudeFilePath)) {
723
1402
  errors.push("CLAUDE.md file not found");
724
1403
  return { rules, errors };
@@ -729,16 +1408,32 @@ async function parseClaudeConfiguration(baseDir = process.cwd()) {
729
1408
  if (mainRule) {
730
1409
  rules.push(mainRule);
731
1410
  }
732
- const memoryDir = (0, import_node_path11.join)(baseDir, ".claude", "memories");
1411
+ const memoryDir = (0, import_node_path14.join)(baseDir, ".claude", "memories");
733
1412
  if (await fileExists(memoryDir)) {
734
1413
  const memoryRules = await parseClaudeMemoryFiles(memoryDir);
735
1414
  rules.push(...memoryRules);
736
1415
  }
1416
+ const settingsPath = (0, import_node_path14.join)(baseDir, ".claude", "settings.json");
1417
+ if (await fileExists(settingsPath)) {
1418
+ const settingsResult = await parseClaudeSettings(settingsPath);
1419
+ if (settingsResult.ignorePatterns) {
1420
+ ignorePatterns = settingsResult.ignorePatterns;
1421
+ }
1422
+ if (settingsResult.mcpServers) {
1423
+ mcpServers = settingsResult.mcpServers;
1424
+ }
1425
+ errors.push(...settingsResult.errors);
1426
+ }
737
1427
  } catch (error) {
738
1428
  const errorMessage = error instanceof Error ? error.message : String(error);
739
1429
  errors.push(`Failed to parse Claude configuration: ${errorMessage}`);
740
1430
  }
741
- return { rules, errors };
1431
+ return {
1432
+ rules,
1433
+ errors,
1434
+ ...ignorePatterns && { ignorePatterns },
1435
+ ...mcpServers && { mcpServers }
1436
+ };
742
1437
  }
743
1438
  function parseClaudeMainFile(content, filepath) {
744
1439
  const lines = content.split("\n");
@@ -775,10 +1470,10 @@ async function parseClaudeMemoryFiles(memoryDir) {
775
1470
  const files = await readdir2(memoryDir);
776
1471
  for (const file of files) {
777
1472
  if (file.endsWith(".md")) {
778
- const filePath = (0, import_node_path11.join)(memoryDir, file);
1473
+ const filePath = (0, import_node_path14.join)(memoryDir, file);
779
1474
  const content = await readFileContent(filePath);
780
1475
  if (content.trim()) {
781
- const filename = (0, import_node_path11.basename)(file, ".md");
1476
+ const filename = (0, import_node_path14.basename)(file, ".md");
782
1477
  const frontmatter = {
783
1478
  root: false,
784
1479
  targets: ["claudecode"],
@@ -798,47 +1493,113 @@ async function parseClaudeMemoryFiles(memoryDir) {
798
1493
  }
799
1494
  return rules;
800
1495
  }
1496
+ async function parseClaudeSettings(settingsPath) {
1497
+ const errors = [];
1498
+ let ignorePatterns;
1499
+ let mcpServers;
1500
+ try {
1501
+ const content = await readFileContent(settingsPath);
1502
+ const settings = JSON.parse(content);
1503
+ if (settings.permissions?.deny) {
1504
+ const readPatterns = settings.permissions.deny.filter((rule) => rule.startsWith("Read(") && rule.endsWith(")")).map((rule) => {
1505
+ const match = rule.match(/^Read\((.+)\)$/);
1506
+ return match ? match[1] : null;
1507
+ }).filter((pattern) => pattern !== null);
1508
+ if (readPatterns.length > 0) {
1509
+ ignorePatterns = readPatterns;
1510
+ }
1511
+ }
1512
+ if (settings.mcpServers && Object.keys(settings.mcpServers).length > 0) {
1513
+ mcpServers = settings.mcpServers;
1514
+ }
1515
+ } catch (error) {
1516
+ const errorMessage = error instanceof Error ? error.message : String(error);
1517
+ errors.push(`Failed to parse settings.json: ${errorMessage}`);
1518
+ }
1519
+ return {
1520
+ errors,
1521
+ ...ignorePatterns && { ignorePatterns },
1522
+ ...mcpServers && { mcpServers }
1523
+ };
1524
+ }
801
1525
 
802
1526
  // src/parsers/cline.ts
803
- var import_node_path12 = require("path");
1527
+ var import_node_path15 = require("path");
804
1528
  async function parseClineConfiguration(baseDir = process.cwd()) {
805
1529
  const errors = [];
806
1530
  const rules = [];
807
- const clineFilePath = (0, import_node_path12.join)(baseDir, ".cline", "instructions.md");
808
- if (!await fileExists(clineFilePath)) {
809
- errors.push(".cline/instructions.md file not found");
810
- return { rules, errors };
1531
+ const clineFilePath = (0, import_node_path15.join)(baseDir, ".cline", "instructions.md");
1532
+ if (await fileExists(clineFilePath)) {
1533
+ try {
1534
+ const content = await readFileContent(clineFilePath);
1535
+ if (content.trim()) {
1536
+ const frontmatter = {
1537
+ root: false,
1538
+ targets: ["cline"],
1539
+ description: "Cline instructions",
1540
+ globs: ["**/*"]
1541
+ };
1542
+ rules.push({
1543
+ frontmatter,
1544
+ content: content.trim(),
1545
+ filename: "cline-instructions",
1546
+ filepath: clineFilePath
1547
+ });
1548
+ }
1549
+ } catch (error) {
1550
+ const errorMessage = error instanceof Error ? error.message : String(error);
1551
+ errors.push(`Failed to parse .cline/instructions.md: ${errorMessage}`);
1552
+ }
811
1553
  }
812
- try {
813
- const content = await readFileContent(clineFilePath);
814
- if (content.trim()) {
815
- const frontmatter = {
816
- root: false,
817
- targets: ["cline"],
818
- description: "Cline AI assistant instructions",
819
- globs: ["**/*"]
820
- };
821
- rules.push({
822
- frontmatter,
823
- content: content.trim(),
824
- filename: "cline-instructions",
825
- filepath: clineFilePath
826
- });
1554
+ const clinerulesDirPath = (0, import_node_path15.join)(baseDir, ".clinerules");
1555
+ if (await fileExists(clinerulesDirPath)) {
1556
+ try {
1557
+ const { readdir: readdir2 } = await import("fs/promises");
1558
+ const files = await readdir2(clinerulesDirPath);
1559
+ for (const file of files) {
1560
+ if (file.endsWith(".md")) {
1561
+ const filePath = (0, import_node_path15.join)(clinerulesDirPath, file);
1562
+ try {
1563
+ const content = await readFileContent(filePath);
1564
+ if (content.trim()) {
1565
+ const filename = file.replace(".md", "");
1566
+ const frontmatter = {
1567
+ root: false,
1568
+ targets: ["cline"],
1569
+ description: `Cline rule: ${filename}`,
1570
+ globs: ["**/*"]
1571
+ };
1572
+ rules.push({
1573
+ frontmatter,
1574
+ content: content.trim(),
1575
+ filename: `cline-${filename}`,
1576
+ filepath: filePath
1577
+ });
1578
+ }
1579
+ } catch (error) {
1580
+ const errorMessage = error instanceof Error ? error.message : String(error);
1581
+ errors.push(`Failed to parse ${filePath}: ${errorMessage}`);
1582
+ }
1583
+ }
1584
+ }
1585
+ } catch (error) {
1586
+ const errorMessage = error instanceof Error ? error.message : String(error);
1587
+ errors.push(`Failed to parse .clinerules files: ${errorMessage}`);
827
1588
  }
828
- } catch (error) {
829
- const errorMessage = error instanceof Error ? error.message : String(error);
830
- errors.push(`Failed to parse Cline configuration: ${errorMessage}`);
1589
+ }
1590
+ if (rules.length === 0) {
1591
+ errors.push("No Cline configuration files found (.cline/instructions.md or .clinerules/*.md)");
831
1592
  }
832
1593
  return { rules, errors };
833
1594
  }
834
1595
 
835
1596
  // src/parsers/copilot.ts
836
- var import_node_path13 = require("path");
1597
+ var import_node_path16 = require("path");
837
1598
  var import_gray_matter2 = __toESM(require("gray-matter"));
838
1599
  async function parseCopilotConfiguration(baseDir = process.cwd()) {
839
1600
  const errors = [];
840
1601
  const rules = [];
841
- const copilotFilePath = (0, import_node_path13.join)(baseDir, ".github", "copilot-instructions.md");
1602
+ const copilotFilePath = (0, import_node_path16.join)(baseDir, ".github", "copilot-instructions.md");
842
1603
  if (await fileExists(copilotFilePath)) {
843
1604
  try {
844
1605
  const rawContent = await readFileContent(copilotFilePath);
@@ -863,19 +1624,19 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
863
1624
  errors.push(`Failed to parse copilot-instructions.md: ${errorMessage}`);
864
1625
  }
865
1626
  }
866
- const instructionsDir = (0, import_node_path13.join)(baseDir, ".github", "instructions");
1627
+ const instructionsDir = (0, import_node_path16.join)(baseDir, ".github", "instructions");
867
1628
  if (await fileExists(instructionsDir)) {
868
1629
  try {
869
1630
  const { readdir: readdir2 } = await import("fs/promises");
870
1631
  const files = await readdir2(instructionsDir);
871
1632
  for (const file of files) {
872
1633
  if (file.endsWith(".instructions.md")) {
873
- const filePath = (0, import_node_path13.join)(instructionsDir, file);
1634
+ const filePath = (0, import_node_path16.join)(instructionsDir, file);
874
1635
  const rawContent = await readFileContent(filePath);
875
1636
  const parsed = (0, import_gray_matter2.default)(rawContent);
876
1637
  const content = parsed.content.trim();
877
1638
  if (content) {
878
- const filename = (0, import_node_path13.basename)(file, ".instructions.md");
1639
+ const filename = (0, import_node_path16.basename)(file, ".instructions.md");
879
1640
  const frontmatter = {
880
1641
  root: false,
881
1642
  targets: ["copilot"],
@@ -905,7 +1666,7 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
905
1666
  }
906
1667
 
907
1668
  // src/parsers/cursor.ts
908
- var import_node_path14 = require("path");
1669
+ var import_node_path17 = require("path");
909
1670
  var import_gray_matter3 = __toESM(require("gray-matter"));
910
1671
  var import_js_yaml = __toESM(require("js-yaml"));
911
1672
  var customMatterOptions = {
@@ -929,7 +1690,9 @@ var customMatterOptions = {
929
1690
  async function parseCursorConfiguration(baseDir = process.cwd()) {
930
1691
  const errors = [];
931
1692
  const rules = [];
932
- const cursorFilePath = (0, import_node_path14.join)(baseDir, ".cursorrules");
1693
+ let ignorePatterns;
1694
+ let mcpServers;
1695
+ const cursorFilePath = (0, import_node_path17.join)(baseDir, ".cursorrules");
933
1696
  if (await fileExists(cursorFilePath)) {
934
1697
  try {
935
1698
  const rawContent = await readFileContent(cursorFilePath);
@@ -954,20 +1717,20 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
954
1717
  errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
955
1718
  }
956
1719
  }
957
- const cursorRulesDir = (0, import_node_path14.join)(baseDir, ".cursor", "rules");
1720
+ const cursorRulesDir = (0, import_node_path17.join)(baseDir, ".cursor", "rules");
958
1721
  if (await fileExists(cursorRulesDir)) {
959
1722
  try {
960
1723
  const { readdir: readdir2 } = await import("fs/promises");
961
1724
  const files = await readdir2(cursorRulesDir);
962
1725
  for (const file of files) {
963
1726
  if (file.endsWith(".mdc")) {
964
- const filePath = (0, import_node_path14.join)(cursorRulesDir, file);
1727
+ const filePath = (0, import_node_path17.join)(cursorRulesDir, file);
965
1728
  try {
966
1729
  const rawContent = await readFileContent(filePath);
967
1730
  const parsed = (0, import_gray_matter3.default)(rawContent, customMatterOptions);
968
1731
  const content = parsed.content.trim();
969
1732
  if (content) {
970
- const filename = (0, import_node_path14.basename)(file, ".mdc");
1733
+ const filename = (0, import_node_path17.basename)(file, ".mdc");
971
1734
  const frontmatter = {
972
1735
  root: false,
973
1736
  targets: ["cursor"],
@@ -995,38 +1758,244 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
995
1758
  if (rules.length === 0) {
996
1759
  errors.push("No Cursor configuration files found (.cursorrules or .cursor/rules/*.mdc)");
997
1760
  }
998
- return { rules, errors };
1761
+ const cursorIgnorePath = (0, import_node_path17.join)(baseDir, ".cursorignore");
1762
+ if (await fileExists(cursorIgnorePath)) {
1763
+ try {
1764
+ const content = await readFileContent(cursorIgnorePath);
1765
+ const patterns = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
1766
+ if (patterns.length > 0) {
1767
+ ignorePatterns = patterns;
1768
+ }
1769
+ } catch (error) {
1770
+ const errorMessage = error instanceof Error ? error.message : String(error);
1771
+ errors.push(`Failed to parse .cursorignore: ${errorMessage}`);
1772
+ }
1773
+ }
1774
+ const cursorMcpPath = (0, import_node_path17.join)(baseDir, ".cursor", "mcp.json");
1775
+ if (await fileExists(cursorMcpPath)) {
1776
+ try {
1777
+ const content = await readFileContent(cursorMcpPath);
1778
+ const mcp = JSON.parse(content);
1779
+ if (mcp.mcpServers && Object.keys(mcp.mcpServers).length > 0) {
1780
+ mcpServers = mcp.mcpServers;
1781
+ }
1782
+ } catch (error) {
1783
+ const errorMessage = error instanceof Error ? error.message : String(error);
1784
+ errors.push(`Failed to parse .cursor/mcp.json: ${errorMessage}`);
1785
+ }
1786
+ }
1787
+ return {
1788
+ rules,
1789
+ errors,
1790
+ ...ignorePatterns && { ignorePatterns },
1791
+ ...mcpServers && { mcpServers }
1792
+ };
999
1793
  }
1000
1794
 
1001
- // src/parsers/roo.ts
1002
- var import_node_path15 = require("path");
1003
- async function parseRooConfiguration(baseDir = process.cwd()) {
1795
+ // src/parsers/geminicli.ts
1796
+ var import_node_path18 = require("path");
1797
+ async function parseGeminiConfiguration(baseDir = process.cwd()) {
1004
1798
  const errors = [];
1005
1799
  const rules = [];
1006
- const rooFilePath = (0, import_node_path15.join)(baseDir, ".roo", "instructions.md");
1007
- if (!await fileExists(rooFilePath)) {
1008
- errors.push(".roo/instructions.md file not found");
1800
+ let ignorePatterns;
1801
+ let mcpServers;
1802
+ const geminiFilePath = (0, import_node_path18.join)(baseDir, "GEMINI.md");
1803
+ if (!await fileExists(geminiFilePath)) {
1804
+ errors.push("GEMINI.md file not found");
1009
1805
  return { rules, errors };
1010
1806
  }
1011
1807
  try {
1012
- const content = await readFileContent(rooFilePath);
1013
- if (content.trim()) {
1014
- const frontmatter = {
1015
- root: false,
1016
- targets: ["roo"],
1017
- description: "Roo Code AI assistant instructions",
1018
- globs: ["**/*"]
1019
- };
1020
- rules.push({
1021
- frontmatter,
1022
- content: content.trim(),
1023
- filename: "roo-instructions",
1024
- filepath: rooFilePath
1025
- });
1808
+ const geminiContent = await readFileContent(geminiFilePath);
1809
+ const mainRule = parseGeminiMainFile(geminiContent, geminiFilePath);
1810
+ if (mainRule) {
1811
+ rules.push(mainRule);
1812
+ }
1813
+ const memoryDir = (0, import_node_path18.join)(baseDir, ".gemini", "memories");
1814
+ if (await fileExists(memoryDir)) {
1815
+ const memoryRules = await parseGeminiMemoryFiles(memoryDir);
1816
+ rules.push(...memoryRules);
1817
+ }
1818
+ const settingsPath = (0, import_node_path18.join)(baseDir, ".gemini", "settings.json");
1819
+ if (await fileExists(settingsPath)) {
1820
+ const settingsResult = await parseGeminiSettings(settingsPath);
1821
+ if (settingsResult.ignorePatterns) {
1822
+ ignorePatterns = settingsResult.ignorePatterns;
1823
+ }
1824
+ if (settingsResult.mcpServers) {
1825
+ mcpServers = settingsResult.mcpServers;
1826
+ }
1827
+ errors.push(...settingsResult.errors);
1828
+ }
1829
+ const aiexcludePath = (0, import_node_path18.join)(baseDir, ".aiexclude");
1830
+ if (await fileExists(aiexcludePath)) {
1831
+ const aiexcludePatterns = await parseAiexclude(aiexcludePath);
1832
+ if (aiexcludePatterns.length > 0) {
1833
+ ignorePatterns = ignorePatterns ? [...ignorePatterns, ...aiexcludePatterns] : aiexcludePatterns;
1834
+ }
1835
+ }
1836
+ } catch (error) {
1837
+ const errorMessage = error instanceof Error ? error.message : String(error);
1838
+ errors.push(`Failed to parse Gemini configuration: ${errorMessage}`);
1839
+ }
1840
+ return {
1841
+ rules,
1842
+ errors,
1843
+ ...ignorePatterns && { ignorePatterns },
1844
+ ...mcpServers && { mcpServers }
1845
+ };
1846
+ }
1847
+ function parseGeminiMainFile(content, filepath) {
1848
+ const lines = content.split("\n");
1849
+ let contentStartIndex = 0;
1850
+ if (lines.some((line) => line.includes("| Document | Description | File Patterns |"))) {
1851
+ const tableEndIndex = lines.findIndex(
1852
+ (line, index) => index > 0 && line.trim() === "" && lines[index - 1]?.includes("|") && !lines[index + 1]?.includes("|")
1853
+ );
1854
+ if (tableEndIndex !== -1) {
1855
+ contentStartIndex = tableEndIndex + 1;
1856
+ }
1857
+ }
1858
+ const mainContent = lines.slice(contentStartIndex).join("\n").trim();
1859
+ if (!mainContent) {
1860
+ return null;
1861
+ }
1862
+ const frontmatter = {
1863
+ root: false,
1864
+ targets: ["geminicli"],
1865
+ description: "Main Gemini CLI configuration",
1866
+ globs: ["**/*"]
1867
+ };
1868
+ return {
1869
+ frontmatter,
1870
+ content: mainContent,
1871
+ filename: "gemini-main",
1872
+ filepath
1873
+ };
1874
+ }
1875
+ async function parseGeminiMemoryFiles(memoryDir) {
1876
+ const rules = [];
1877
+ try {
1878
+ const { readdir: readdir2 } = await import("fs/promises");
1879
+ const files = await readdir2(memoryDir);
1880
+ for (const file of files) {
1881
+ if (file.endsWith(".md")) {
1882
+ const filePath = (0, import_node_path18.join)(memoryDir, file);
1883
+ const content = await readFileContent(filePath);
1884
+ if (content.trim()) {
1885
+ const filename = (0, import_node_path18.basename)(file, ".md");
1886
+ const frontmatter = {
1887
+ root: false,
1888
+ targets: ["geminicli"],
1889
+ description: `Memory file: ${filename}`,
1890
+ globs: ["**/*"]
1891
+ };
1892
+ rules.push({
1893
+ frontmatter,
1894
+ content: content.trim(),
1895
+ filename: `gemini-memory-${filename}`,
1896
+ filepath: filePath
1897
+ });
1898
+ }
1899
+ }
1900
+ }
1901
+ } catch (_error) {
1902
+ }
1903
+ return rules;
1904
+ }
1905
+ async function parseGeminiSettings(settingsPath) {
1906
+ const errors = [];
1907
+ let mcpServers;
1908
+ try {
1909
+ const content = await readFileContent(settingsPath);
1910
+ const settings = JSON.parse(content);
1911
+ if (settings.mcpServers && Object.keys(settings.mcpServers).length > 0) {
1912
+ mcpServers = settings.mcpServers;
1026
1913
  }
1027
1914
  } catch (error) {
1028
1915
  const errorMessage = error instanceof Error ? error.message : String(error);
1029
- errors.push(`Failed to parse Roo configuration: ${errorMessage}`);
1916
+ errors.push(`Failed to parse settings.json: ${errorMessage}`);
1917
+ }
1918
+ return {
1919
+ errors,
1920
+ ...mcpServers && { mcpServers }
1921
+ };
1922
+ }
1923
+ async function parseAiexclude(aiexcludePath) {
1924
+ try {
1925
+ const content = await readFileContent(aiexcludePath);
1926
+ const patterns = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
1927
+ return patterns;
1928
+ } catch (_error) {
1929
+ return [];
1930
+ }
1931
+ }
1932
+
1933
+ // src/parsers/roo.ts
1934
+ var import_node_path19 = require("path");
1935
+ async function parseRooConfiguration(baseDir = process.cwd()) {
1936
+ const errors = [];
1937
+ const rules = [];
1938
+ const rooFilePath = (0, import_node_path19.join)(baseDir, ".roo", "instructions.md");
1939
+ if (await fileExists(rooFilePath)) {
1940
+ try {
1941
+ const content = await readFileContent(rooFilePath);
1942
+ if (content.trim()) {
1943
+ const frontmatter = {
1944
+ root: false,
1945
+ targets: ["roo"],
1946
+ description: "Roo Code instructions",
1947
+ globs: ["**/*"]
1948
+ };
1949
+ rules.push({
1950
+ frontmatter,
1951
+ content: content.trim(),
1952
+ filename: "roo-instructions",
1953
+ filepath: rooFilePath
1954
+ });
1955
+ }
1956
+ } catch (error) {
1957
+ const errorMessage = error instanceof Error ? error.message : String(error);
1958
+ errors.push(`Failed to parse .roo/instructions.md: ${errorMessage}`);
1959
+ }
1960
+ }
1961
+ const rooRulesDir = (0, import_node_path19.join)(baseDir, ".roo", "rules");
1962
+ if (await fileExists(rooRulesDir)) {
1963
+ try {
1964
+ const { readdir: readdir2 } = await import("fs/promises");
1965
+ const files = await readdir2(rooRulesDir);
1966
+ for (const file of files) {
1967
+ if (file.endsWith(".md")) {
1968
+ const filePath = (0, import_node_path19.join)(rooRulesDir, file);
1969
+ try {
1970
+ const content = await readFileContent(filePath);
1971
+ if (content.trim()) {
1972
+ const filename = file.replace(".md", "");
1973
+ const frontmatter = {
1974
+ root: false,
1975
+ targets: ["roo"],
1976
+ description: `Roo rule: ${filename}`,
1977
+ globs: ["**/*"]
1978
+ };
1979
+ rules.push({
1980
+ frontmatter,
1981
+ content: content.trim(),
1982
+ filename: `roo-${filename}`,
1983
+ filepath: filePath
1984
+ });
1985
+ }
1986
+ } catch (error) {
1987
+ const errorMessage = error instanceof Error ? error.message : String(error);
1988
+ errors.push(`Failed to parse ${filePath}: ${errorMessage}`);
1989
+ }
1990
+ }
1991
+ }
1992
+ } catch (error) {
1993
+ const errorMessage = error instanceof Error ? error.message : String(error);
1994
+ errors.push(`Failed to parse .roo/rules files: ${errorMessage}`);
1995
+ }
1996
+ }
1997
+ if (rules.length === 0) {
1998
+ errors.push("No Roo Code configuration files found (.roo/instructions.md or .roo/rules/*.md)");
1030
1999
  }
1031
2000
  return { rules, errors };
1032
2001
  }
@@ -1036,6 +2005,8 @@ async function importConfiguration(options) {
1036
2005
  const { tool, baseDir = process.cwd(), rulesDir = ".rulesync", verbose = false } = options;
1037
2006
  const errors = [];
1038
2007
  let rules = [];
2008
+ let ignorePatterns;
2009
+ let mcpServers;
1039
2010
  if (verbose) {
1040
2011
  console.log(`Importing ${tool} configuration from ${baseDir}...`);
1041
2012
  }
@@ -1045,12 +2016,16 @@ async function importConfiguration(options) {
1045
2016
  const claudeResult = await parseClaudeConfiguration(baseDir);
1046
2017
  rules = claudeResult.rules;
1047
2018
  errors.push(...claudeResult.errors);
2019
+ ignorePatterns = claudeResult.ignorePatterns;
2020
+ mcpServers = claudeResult.mcpServers;
1048
2021
  break;
1049
2022
  }
1050
2023
  case "cursor": {
1051
2024
  const cursorResult = await parseCursorConfiguration(baseDir);
1052
2025
  rules = cursorResult.rules;
1053
2026
  errors.push(...cursorResult.errors);
2027
+ ignorePatterns = cursorResult.ignorePatterns;
2028
+ mcpServers = cursorResult.mcpServers;
1054
2029
  break;
1055
2030
  }
1056
2031
  case "copilot": {
@@ -1071,6 +2046,14 @@ async function importConfiguration(options) {
1071
2046
  errors.push(...rooResult.errors);
1072
2047
  break;
1073
2048
  }
2049
+ case "geminicli": {
2050
+ const geminiResult = await parseGeminiConfiguration(baseDir);
2051
+ rules = geminiResult.rules;
2052
+ errors.push(...geminiResult.errors);
2053
+ ignorePatterns = geminiResult.ignorePatterns;
2054
+ mcpServers = geminiResult.mcpServers;
2055
+ break;
2056
+ }
1074
2057
  default:
1075
2058
  errors.push(`Unsupported tool: ${tool}`);
1076
2059
  return { success: false, rulesCreated: 0, errors };
@@ -1080,10 +2063,10 @@ async function importConfiguration(options) {
1080
2063
  errors.push(`Failed to parse ${tool} configuration: ${errorMessage}`);
1081
2064
  return { success: false, rulesCreated: 0, errors };
1082
2065
  }
1083
- if (rules.length === 0) {
2066
+ if (rules.length === 0 && !ignorePatterns && !mcpServers) {
1084
2067
  return { success: false, rulesCreated: 0, errors };
1085
2068
  }
1086
- const rulesDirPath = (0, import_node_path16.join)(baseDir, rulesDir);
2069
+ const rulesDirPath = (0, import_node_path20.join)(baseDir, rulesDir);
1087
2070
  try {
1088
2071
  const { mkdir: mkdir3 } = await import("fs/promises");
1089
2072
  await mkdir3(rulesDirPath, { recursive: true });
@@ -1097,7 +2080,7 @@ async function importConfiguration(options) {
1097
2080
  try {
1098
2081
  const baseFilename = `${tool}__${rule.filename}`;
1099
2082
  const filename = await generateUniqueFilename(rulesDirPath, baseFilename);
1100
- const filePath = (0, import_node_path16.join)(rulesDirPath, `${filename}.md`);
2083
+ const filePath = (0, import_node_path20.join)(rulesDirPath, `${filename}.md`);
1101
2084
  const content = generateRuleFileContent(rule);
1102
2085
  await writeFileContent(filePath, content);
1103
2086
  rulesCreated++;
@@ -1109,10 +2092,44 @@ async function importConfiguration(options) {
1109
2092
  errors.push(`Failed to create rule file for ${rule.filename}: ${errorMessage}`);
1110
2093
  }
1111
2094
  }
2095
+ let ignoreFileCreated = false;
2096
+ if (ignorePatterns && ignorePatterns.length > 0) {
2097
+ try {
2098
+ const rulesyncignorePath = (0, import_node_path20.join)(baseDir, ".rulesyncignore");
2099
+ const ignoreContent = `${ignorePatterns.join("\n")}
2100
+ `;
2101
+ await writeFileContent(rulesyncignorePath, ignoreContent);
2102
+ ignoreFileCreated = true;
2103
+ if (verbose) {
2104
+ console.log(`\u2705 Created .rulesyncignore with ${ignorePatterns.length} patterns`);
2105
+ }
2106
+ } catch (error) {
2107
+ const errorMessage = error instanceof Error ? error.message : String(error);
2108
+ errors.push(`Failed to create .rulesyncignore: ${errorMessage}`);
2109
+ }
2110
+ }
2111
+ let mcpFileCreated = false;
2112
+ if (mcpServers && Object.keys(mcpServers).length > 0) {
2113
+ try {
2114
+ const mcpPath = (0, import_node_path20.join)(baseDir, rulesDir, ".mcp.json");
2115
+ const mcpContent = `${JSON.stringify({ mcpServers }, null, 2)}
2116
+ `;
2117
+ await writeFileContent(mcpPath, mcpContent);
2118
+ mcpFileCreated = true;
2119
+ if (verbose) {
2120
+ console.log(`\u2705 Created .mcp.json with ${Object.keys(mcpServers).length} servers`);
2121
+ }
2122
+ } catch (error) {
2123
+ const errorMessage = error instanceof Error ? error.message : String(error);
2124
+ errors.push(`Failed to create .mcp.json: ${errorMessage}`);
2125
+ }
2126
+ }
1112
2127
  return {
1113
- success: rulesCreated > 0,
2128
+ success: rulesCreated > 0 || ignoreFileCreated || mcpFileCreated,
1114
2129
  rulesCreated,
1115
- errors
2130
+ errors,
2131
+ ignoreFileCreated,
2132
+ mcpFileCreated
1116
2133
  };
1117
2134
  }
1118
2135
  function generateRuleFileContent(rule) {
@@ -1122,7 +2139,7 @@ function generateRuleFileContent(rule) {
1122
2139
  async function generateUniqueFilename(rulesDir, baseFilename) {
1123
2140
  let filename = baseFilename;
1124
2141
  let counter = 1;
1125
- while (await fileExists((0, import_node_path16.join)(rulesDir, `${filename}.md`))) {
2142
+ while (await fileExists((0, import_node_path20.join)(rulesDir, `${filename}.md`))) {
1126
2143
  filename = `${baseFilename}-${counter}`;
1127
2144
  counter++;
1128
2145
  }
@@ -1140,57 +2157,54 @@ async function importCommand(options = {}) {
1140
2157
  if (options.geminicli) tools.push("geminicli");
1141
2158
  if (tools.length === 0) {
1142
2159
  console.error(
1143
- "\u274C Please specify at least one tool to import from (--claudecode, --cursor, --copilot, --cline, --roo, --geminicli)"
2160
+ "\u274C Please specify one tool to import from (--claudecode, --cursor, --copilot, --cline, --roo, --geminicli)"
1144
2161
  );
1145
2162
  process.exit(1);
1146
2163
  }
1147
- console.log("Importing configuration files...");
1148
- let totalRulesCreated = 0;
1149
- const allErrors = [];
1150
- for (const tool of tools) {
1151
- if (options.verbose) {
1152
- console.log(`
1153
- Importing from ${tool}...`);
1154
- }
1155
- try {
1156
- const result = await importConfiguration({
1157
- tool,
1158
- verbose: options.verbose ?? false
1159
- });
1160
- if (result.success) {
1161
- console.log(`\u2705 Imported ${result.rulesCreated} rule(s) from ${tool}`);
1162
- totalRulesCreated += result.rulesCreated;
1163
- } else if (result.errors.length > 0) {
1164
- console.warn(`\u26A0\uFE0F Failed to import from ${tool}: ${result.errors[0]}`);
1165
- if (options.verbose) {
1166
- allErrors.push(...result.errors);
1167
- }
1168
- }
1169
- } catch (error) {
1170
- const errorMessage = error instanceof Error ? error.message : String(error);
1171
- console.error(`\u274C Error importing from ${tool}: ${errorMessage}`);
1172
- allErrors.push(`${tool}: ${errorMessage}`);
1173
- }
1174
- }
1175
- if (totalRulesCreated > 0) {
1176
- console.log(`
1177
- \u{1F389} Successfully imported ${totalRulesCreated} rule(s) total!`);
1178
- console.log("You can now run 'rulesync generate' to create tool-specific configurations.");
1179
- } else {
1180
- console.warn(
1181
- "\n\u26A0\uFE0F No rules were imported. Please check that configuration files exist for the selected tools."
2164
+ if (tools.length > 1) {
2165
+ console.error(
2166
+ "\u274C Only one tool can be specified at a time. Please run the import command separately for each tool."
1182
2167
  );
2168
+ process.exit(1);
1183
2169
  }
1184
- if (options.verbose && allErrors.length > 0) {
1185
- console.log("\nDetailed errors:");
1186
- for (const error of allErrors) {
1187
- console.log(` - ${error}`);
2170
+ const tool = tools[0];
2171
+ if (!tool) {
2172
+ console.error("Error: No tool specified");
2173
+ process.exit(1);
2174
+ }
2175
+ console.log(`Importing configuration files from ${tool}...`);
2176
+ try {
2177
+ const result = await importConfiguration({
2178
+ tool,
2179
+ verbose: options.verbose ?? false
2180
+ });
2181
+ if (result.success) {
2182
+ console.log(`\u2705 Imported ${result.rulesCreated} rule(s) from ${tool}`);
2183
+ if (result.ignoreFileCreated) {
2184
+ console.log("\u2705 Created .rulesyncignore file from ignore patterns");
2185
+ }
2186
+ if (result.mcpFileCreated) {
2187
+ console.log("\u2705 Created .rulesync/.mcp.json file from MCP configuration");
2188
+ }
2189
+ console.log("You can now run 'rulesync generate' to create tool-specific configurations.");
2190
+ } else if (result.errors.length > 0) {
2191
+ console.warn(`\u26A0\uFE0F Failed to import from ${tool}: ${result.errors[0]}`);
2192
+ if (options.verbose && result.errors.length > 1) {
2193
+ console.log("\nDetailed errors:");
2194
+ for (const error of result.errors) {
2195
+ console.log(` - ${error}`);
2196
+ }
2197
+ }
1188
2198
  }
2199
+ } catch (error) {
2200
+ const errorMessage = error instanceof Error ? error.message : String(error);
2201
+ console.error(`\u274C Error importing from ${tool}: ${errorMessage}`);
2202
+ process.exit(1);
1189
2203
  }
1190
2204
  }
1191
2205
 
1192
2206
  // src/cli/commands/init.ts
1193
- var import_node_path17 = require("path");
2207
+ var import_node_path21 = require("path");
1194
2208
  async function initCommand() {
1195
2209
  const aiRulesDir = ".rulesync";
1196
2210
  console.log("Initializing rulesync...");
@@ -1320,7 +2334,7 @@ globs: ["src/api/**/*.ts", "src/services/**/*.ts", "src/models/**/*.ts"]
1320
2334
  }
1321
2335
  ];
1322
2336
  for (const file of sampleFiles) {
1323
- const filepath = (0, import_node_path17.join)(aiRulesDir, file.filename);
2337
+ const filepath = (0, import_node_path21.join)(aiRulesDir, file.filename);
1324
2338
  if (!await fileExists(filepath)) {
1325
2339
  await writeFileContent(filepath, file.content);
1326
2340
  console.log(`Created ${filepath}`);
@@ -1433,11 +2447,11 @@ async function watchCommand() {
1433
2447
  persistent: true
1434
2448
  });
1435
2449
  let isGenerating = false;
1436
- const handleChange = async (path2) => {
2450
+ const handleChange = async (path4) => {
1437
2451
  if (isGenerating) return;
1438
2452
  isGenerating = true;
1439
2453
  console.log(`
1440
- \u{1F4DD} Detected change in ${path2}`);
2454
+ \u{1F4DD} Detected change in ${path4}`);
1441
2455
  try {
1442
2456
  await generateCommand({ verbose: false });
1443
2457
  console.log("\u2705 Regenerated configuration files");
@@ -1447,10 +2461,10 @@ async function watchCommand() {
1447
2461
  isGenerating = false;
1448
2462
  }
1449
2463
  };
1450
- watcher.on("change", handleChange).on("add", handleChange).on("unlink", (path2) => {
2464
+ watcher.on("change", handleChange).on("add", handleChange).on("unlink", (path4) => {
1451
2465
  console.log(`
1452
- \u{1F5D1}\uFE0F Removed ${path2}`);
1453
- handleChange(path2);
2466
+ \u{1F5D1}\uFE0F Removed ${path4}`);
2467
+ handleChange(path4);
1454
2468
  }).on("error", (error) => {
1455
2469
  console.error("\u274C Watcher error:", error);
1456
2470
  });
@@ -1463,7 +2477,7 @@ async function watchCommand() {
1463
2477
 
1464
2478
  // src/cli/index.ts
1465
2479
  var program = new import_commander.Command();
1466
- program.name("rulesync").description("Unified AI rules management CLI tool").version("0.34.0");
2480
+ program.name("rulesync").description("Unified AI rules management CLI tool").version("0.36.0");
1467
2481
  program.command("init").description("Initialize rulesync in current directory").action(initCommand);
1468
2482
  program.command("add <filename>").description("Add a new rule file").action(addCommand);
1469
2483
  program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);