rulesync 0.33.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,10 +343,12 @@ function getDefaultConfig() {
39
343
  cursor: ".cursor/rules",
40
344
  cline: ".clinerules",
41
345
  claudecode: ".",
42
- roo: ".roo/rules"
346
+ claude: ".",
347
+ roo: ".roo/rules",
348
+ geminicli: ".gemini/memories"
43
349
  },
44
350
  watchEnabled: false,
45
- defaultTargets: ["copilot", "cursor", "cline", "claudecode", "roo"]
351
+ defaultTargets: ["copilot", "cursor", "cline", "claudecode", "claude", "roo", "geminicli"]
46
352
  };
47
353
  }
48
354
  function resolveTargets(targets, config) {
@@ -88,27 +394,158 @@ async function addCommand(filename) {
88
394
  }
89
395
  }
90
396
 
91
- // 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
92
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
93
525
  async function generateClaudecodeConfig(rules, config, baseDir) {
94
526
  const outputs = [];
95
527
  const rootRules = rules.filter((r) => r.frontmatter.root === true);
96
528
  const detailRules = rules.filter((r) => r.frontmatter.root === false);
97
529
  const claudeMdContent = generateClaudeMarkdown(rootRules, detailRules);
98
- 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;
99
531
  outputs.push({
100
532
  tool: "claudecode",
101
- filepath: (0, import_node_path2.join)(claudeOutputDir, "CLAUDE.md"),
533
+ filepath: (0, import_node_path4.join)(claudeOutputDir, "CLAUDE.md"),
102
534
  content: claudeMdContent
103
535
  });
104
536
  for (const rule of detailRules) {
105
537
  const memoryContent = generateMemoryFile(rule);
106
538
  outputs.push({
107
539
  tool: "claudecode",
108
- 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`),
109
541
  content: memoryContent
110
542
  });
111
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
+ }
112
549
  return outputs;
113
550
  }
114
551
  function generateClaudeMarkdown(rootRules, detailRules) {
@@ -137,42 +574,101 @@ function generateClaudeMarkdown(rootRules, detailRules) {
137
574
  function generateMemoryFile(rule) {
138
575
  return rule.content.trim();
139
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
+ }
140
607
 
141
- // src/generators/cline.ts
142
- var import_node_path3 = require("path");
608
+ // src/generators/rules/cline.ts
609
+ var import_node_path5 = require("path");
143
610
  async function generateClineConfig(rules, config, baseDir) {
144
611
  const outputs = [];
145
612
  for (const rule of rules) {
146
613
  const content = generateClineMarkdown(rule);
147
- const outputDir = baseDir ? (0, import_node_path3.join)(baseDir, config.outputPaths.cline) : config.outputPaths.cline;
148
- 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`);
149
616
  outputs.push({
150
617
  tool: "cline",
151
618
  filepath,
152
619
  content
153
620
  });
154
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
+ }
155
632
  return outputs;
156
633
  }
157
634
  function generateClineMarkdown(rule) {
158
635
  return rule.content.trim();
159
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
+ }
160
646
 
161
- // src/generators/copilot.ts
162
- var import_node_path4 = require("path");
647
+ // src/generators/rules/copilot.ts
648
+ var import_node_path6 = require("path");
163
649
  async function generateCopilotConfig(rules, config, baseDir) {
164
650
  const outputs = [];
165
651
  for (const rule of rules) {
166
652
  const content = generateCopilotMarkdown(rule);
167
653
  const baseFilename = rule.filename.replace(/\.md$/, "");
168
- const outputDir = baseDir ? (0, import_node_path4.join)(baseDir, config.outputPaths.copilot) : config.outputPaths.copilot;
169
- 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`);
170
656
  outputs.push({
171
657
  tool: "copilot",
172
658
  filepath,
173
659
  content
174
660
  });
175
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
+ }
176
672
  return outputs;
177
673
  }
178
674
  function generateCopilotMarkdown(rule) {
@@ -188,21 +684,42 @@ function generateCopilotMarkdown(rule) {
188
684
  lines.push(rule.content);
189
685
  return lines.join("\n");
190
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
+ }
191
698
 
192
- // src/generators/cursor.ts
193
- var import_node_path5 = require("path");
699
+ // src/generators/rules/cursor.ts
700
+ var import_node_path7 = require("path");
194
701
  async function generateCursorConfig(rules, config, baseDir) {
195
702
  const outputs = [];
196
703
  for (const rule of rules) {
197
704
  const content = generateCursorMarkdown(rule);
198
- const outputDir = baseDir ? (0, import_node_path5.join)(baseDir, config.outputPaths.cursor) : config.outputPaths.cursor;
199
- 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`);
200
707
  outputs.push({
201
708
  tool: "cursor",
202
709
  filepath,
203
710
  content
204
711
  });
205
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
+ }
206
723
  return outputs;
207
724
  }
208
725
  function generateCursorMarkdown(rule) {
@@ -225,92 +742,125 @@ function generateCursorMarkdown(rule) {
225
742
  lines.push(rule.content);
226
743
  return lines.join("\n");
227
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
+ }
228
754
 
229
- // src/generators/roo.ts
230
- var import_node_path6 = require("path");
231
- async function generateRooConfig(rules, config, baseDir) {
755
+ // src/generators/rules/geminicli.ts
756
+ var import_node_path8 = require("path");
757
+ async function generateGeminiConfig(rules, config, baseDir) {
232
758
  const outputs = [];
233
- for (const rule of rules) {
234
- const content = generateRooMarkdown(rule);
235
- const outputDir = baseDir ? (0, import_node_path6.join)(baseDir, config.outputPaths.roo) : config.outputPaths.roo;
236
- const filepath = (0, import_node_path6.join)(outputDir, `${rule.filename}.md`);
759
+ const rootRule = rules.find((rule) => rule.frontmatter.root === true);
760
+ const memoryRules = rules.filter((rule) => rule.frontmatter.root === false);
761
+ for (const rule of memoryRules) {
762
+ const content = generateGeminiMemoryMarkdown(rule);
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`);
237
765
  outputs.push({
238
- tool: "roo",
766
+ tool: "geminicli",
239
767
  filepath,
240
768
  content
241
769
  });
242
770
  }
771
+ const rootContent = generateGeminiRootMarkdown(rootRule, memoryRules, baseDir);
772
+ const rootFilepath = baseDir ? (0, import_node_path8.join)(baseDir, "GEMINI.md") : "GEMINI.md";
773
+ outputs.push({
774
+ tool: "geminicli",
775
+ filepath: rootFilepath,
776
+ content: rootContent
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
+ }
243
788
  return outputs;
244
789
  }
245
- function generateRooMarkdown(rule) {
790
+ function generateGeminiMemoryMarkdown(rule) {
246
791
  return rule.content.trim();
247
792
  }
248
-
249
- // src/utils/file.ts
250
- var import_promises2 = require("fs/promises");
251
- var import_node_path7 = require("path");
252
- async function ensureDir(dirPath) {
253
- try {
254
- await (0, import_promises2.stat)(dirPath);
255
- } catch {
256
- await (0, import_promises2.mkdir)(dirPath, { recursive: true });
793
+ function generateGeminiRootMarkdown(rootRule, memoryRules, _baseDir) {
794
+ const lines = [];
795
+ if (memoryRules.length > 0) {
796
+ lines.push("Please also reference the following documents as needed:");
797
+ lines.push("");
798
+ lines.push("| Document | Description | File Patterns |");
799
+ lines.push("|----------|-------------|---------------|");
800
+ for (const rule of memoryRules) {
801
+ const relativePath = `@.gemini/memories/${rule.filename}.md`;
802
+ const filePatterns = rule.frontmatter.globs.length > 0 ? rule.frontmatter.globs.join(", ") : "-";
803
+ lines.push(`| ${relativePath} | ${rule.frontmatter.description} | ${filePatterns} |`);
804
+ }
805
+ lines.push("");
806
+ lines.push("");
257
807
  }
258
- }
259
- async function readFileContent(filepath) {
260
- return (0, import_promises2.readFile)(filepath, "utf-8");
261
- }
262
- async function writeFileContent(filepath, content) {
263
- await ensureDir((0, import_node_path7.dirname)(filepath));
264
- await (0, import_promises2.writeFile)(filepath, content, "utf-8");
265
- }
266
- async function findFiles(dir, extension = ".md") {
267
- try {
268
- const files = await (0, import_promises2.readdir)(dir);
269
- return files.filter((file) => file.endsWith(extension)).map((file) => (0, import_node_path7.join)(dir, file));
270
- } catch {
271
- return [];
808
+ if (rootRule) {
809
+ lines.push(rootRule.content.trim());
810
+ } else if (memoryRules.length === 0) {
811
+ lines.push("# Gemini CLI Configuration");
812
+ lines.push("");
813
+ lines.push("No configuration rules have been defined yet.");
272
814
  }
815
+ return lines.join("\n");
273
816
  }
274
- async function fileExists(filepath) {
275
- try {
276
- await (0, import_promises2.stat)(filepath);
277
- return true;
278
- } catch {
279
- return false;
280
- }
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");
281
825
  }
282
- async function removeDirectory(dirPath) {
283
- const dangerousPaths = [".", "/", "~", "src", "node_modules"];
284
- if (dangerousPaths.includes(dirPath) || dirPath === "") {
285
- console.warn(`Skipping deletion of dangerous path: ${dirPath}`);
286
- return;
826
+
827
+ // src/generators/rules/roo.ts
828
+ var import_node_path9 = require("path");
829
+ async function generateRooConfig(rules, config, baseDir) {
830
+ const outputs = [];
831
+ for (const rule of rules) {
832
+ const content = generateRooMarkdown(rule);
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`);
835
+ outputs.push({
836
+ tool: "roo",
837
+ filepath,
838
+ content
839
+ });
287
840
  }
288
- try {
289
- if (await fileExists(dirPath)) {
290
- await (0, import_promises2.rm)(dirPath, { recursive: true, force: true });
291
- }
292
- } catch (error) {
293
- console.warn(`Failed to remove directory ${dirPath}:`, error);
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
+ });
294
850
  }
851
+ return outputs;
295
852
  }
296
- async function removeFile(filepath) {
297
- try {
298
- if (await fileExists(filepath)) {
299
- await (0, import_promises2.rm)(filepath);
300
- }
301
- } catch (error) {
302
- console.warn(`Failed to remove file ${filepath}:`, error);
303
- }
853
+ function generateRooMarkdown(rule) {
854
+ return rule.content.trim();
304
855
  }
305
- async function removeClaudeGeneratedFiles() {
306
- const filesToRemove = ["CLAUDE.md", ".claude/memories"];
307
- for (const fileOrDir of filesToRemove) {
308
- if (fileOrDir.endsWith("/memories")) {
309
- await removeDirectory(fileOrDir);
310
- } else {
311
- await removeFile(fileOrDir);
312
- }
313
- }
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");
314
864
  }
315
865
 
316
866
  // src/core/generator.ts
@@ -354,6 +904,8 @@ async function generateForTool(tool, rules, config, baseDir) {
354
904
  return await generateClaudecodeConfig(rules, config, baseDir);
355
905
  case "roo":
356
906
  return generateRooConfig(rules, config, baseDir);
907
+ case "geminicli":
908
+ return generateGeminiConfig(rules, config, baseDir);
357
909
  default:
358
910
  console.warn(`Unknown tool: ${tool}`);
359
911
  return null;
@@ -361,12 +913,16 @@ async function generateForTool(tool, rules, config, baseDir) {
361
913
  }
362
914
 
363
915
  // src/core/parser.ts
364
- var import_node_path8 = require("path");
916
+ var import_node_path10 = require("path");
365
917
  var import_gray_matter = __toESM(require("gray-matter"));
366
918
  async function parseRulesFromDirectory(aiRulesDir) {
367
- const ruleFiles = await findFiles(aiRulesDir);
919
+ const ignorePatterns = await loadIgnorePatterns();
920
+ const ruleFiles = await findFiles(aiRulesDir, ".md", ignorePatterns.patterns);
368
921
  const rules = [];
369
922
  const errors = [];
923
+ if (ignorePatterns.patterns.length > 0) {
924
+ console.log(`Loaded ${ignorePatterns.patterns.length} ignore patterns from .rulesyncignore`);
925
+ }
370
926
  for (const filepath of ruleFiles) {
371
927
  try {
372
928
  const rule = await parseRuleFile(filepath);
@@ -394,7 +950,7 @@ async function parseRuleFile(filepath) {
394
950
  const parsed = (0, import_gray_matter.default)(content);
395
951
  validateFrontmatter(parsed.data, filepath);
396
952
  const frontmatter = parsed.data;
397
- const filename = (0, import_node_path8.basename)(filepath, ".md");
953
+ const filename = (0, import_node_path10.basename)(filepath, ".md");
398
954
  return {
399
955
  frontmatter,
400
956
  content: parsed.content,
@@ -435,7 +991,7 @@ function validateFrontmatter(data, filepath) {
435
991
  `Invalid "targets" field in ${filepath}: must be an array, got ${typeof obj.targets}`
436
992
  );
437
993
  }
438
- const validTargets = ["copilot", "cursor", "cline", "claudecode", "roo", "*"];
994
+ const validTargets = ["copilot", "cursor", "cline", "claudecode", "roo", "geminicli", "*"];
439
995
  for (const target of obj.targets) {
440
996
  if (typeof target !== "string" || !validTargets.includes(target)) {
441
997
  throw new Error(
@@ -522,6 +1078,148 @@ async function validateRule(rule) {
522
1078
  };
523
1079
  }
524
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
+
525
1223
  // src/cli/commands/generate.ts
526
1224
  async function generateCommand(options = {}) {
527
1225
  const config = getDefaultConfig();
@@ -567,6 +1265,9 @@ async function generateCommand(options = {}) {
567
1265
  case "roo":
568
1266
  deleteTasks.push(removeDirectory(config.outputPaths.roo));
569
1267
  break;
1268
+ case "geminicli":
1269
+ deleteTasks.push(removeDirectory(config.outputPaths.geminicli));
1270
+ break;
570
1271
  }
571
1272
  }
572
1273
  await Promise.all(deleteTasks);
@@ -599,6 +1300,30 @@ Generating configurations for base directory: ${baseDir}`);
599
1300
  }
600
1301
  console.log(`
601
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
+ }
602
1327
  } catch (error) {
603
1328
  console.error("\u274C Failed to generate configurations:", error);
604
1329
  process.exit(1);
@@ -606,23 +1331,36 @@ Generating configurations for base directory: ${baseDir}`);
606
1331
  }
607
1332
 
608
1333
  // src/cli/commands/gitignore.ts
609
- var import_node_fs = require("fs");
610
- var import_node_path9 = require("path");
1334
+ var import_node_fs2 = require("fs");
1335
+ var import_node_path13 = require("path");
611
1336
  var gitignoreCommand = async () => {
612
- const gitignorePath = (0, import_node_path9.join)(process.cwd(), ".gitignore");
1337
+ const gitignorePath = (0, import_node_path13.join)(process.cwd(), ".gitignore");
613
1338
  const rulesFilesToIgnore = [
614
1339
  "# Generated by rulesync - AI tool configuration files",
615
1340
  "**/.github/copilot-instructions.md",
616
1341
  "**/.github/instructions/",
617
1342
  "**/.cursor/rules/",
1343
+ "**/.cursorignore",
618
1344
  "**/.clinerules/",
1345
+ "**/.clineignore",
619
1346
  "**/CLAUDE.md",
620
1347
  "**/.claude/memories/",
621
- "**/.roo/rules/"
1348
+ "**/.roo/rules/",
1349
+ "**/.rooignore",
1350
+ "**/.copilotignore",
1351
+ "**/GEMINI.md",
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"
622
1360
  ];
623
1361
  let gitignoreContent = "";
624
- if ((0, import_node_fs.existsSync)(gitignorePath)) {
625
- 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");
626
1364
  }
627
1365
  const linesToAdd = [];
628
1366
  for (const rule of rulesFilesToIgnore) {
@@ -639,7 +1377,7 @@ var gitignoreCommand = async () => {
639
1377
  ${linesToAdd.join("\n")}
640
1378
  ` : `${linesToAdd.join("\n")}
641
1379
  `;
642
- (0, import_node_fs.writeFileSync)(gitignorePath, newContent);
1380
+ (0, import_node_fs2.writeFileSync)(gitignorePath, newContent);
643
1381
  console.log(`\u2705 .gitignore\u306B${linesToAdd.length}\u500B\u306E\u30EB\u30FC\u30EB\u3092\u8FFD\u52A0\u3057\u307E\u3057\u305F:`);
644
1382
  for (const line of linesToAdd) {
645
1383
  if (!line.startsWith("#")) {
@@ -649,15 +1387,17 @@ ${linesToAdd.join("\n")}
649
1387
  };
650
1388
 
651
1389
  // src/core/importer.ts
652
- var import_node_path15 = require("path");
1390
+ var import_node_path20 = require("path");
653
1391
  var import_gray_matter4 = __toESM(require("gray-matter"));
654
1392
 
655
1393
  // src/parsers/claudecode.ts
656
- var import_node_path10 = require("path");
1394
+ var import_node_path14 = require("path");
657
1395
  async function parseClaudeConfiguration(baseDir = process.cwd()) {
658
1396
  const errors = [];
659
1397
  const rules = [];
660
- const claudeFilePath = (0, import_node_path10.join)(baseDir, "CLAUDE.md");
1398
+ let ignorePatterns;
1399
+ let mcpServers;
1400
+ const claudeFilePath = (0, import_node_path14.join)(baseDir, "CLAUDE.md");
661
1401
  if (!await fileExists(claudeFilePath)) {
662
1402
  errors.push("CLAUDE.md file not found");
663
1403
  return { rules, errors };
@@ -668,16 +1408,32 @@ async function parseClaudeConfiguration(baseDir = process.cwd()) {
668
1408
  if (mainRule) {
669
1409
  rules.push(mainRule);
670
1410
  }
671
- const memoryDir = (0, import_node_path10.join)(baseDir, ".claude", "memories");
1411
+ const memoryDir = (0, import_node_path14.join)(baseDir, ".claude", "memories");
672
1412
  if (await fileExists(memoryDir)) {
673
1413
  const memoryRules = await parseClaudeMemoryFiles(memoryDir);
674
1414
  rules.push(...memoryRules);
675
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
+ }
676
1427
  } catch (error) {
677
1428
  const errorMessage = error instanceof Error ? error.message : String(error);
678
1429
  errors.push(`Failed to parse Claude configuration: ${errorMessage}`);
679
1430
  }
680
- return { rules, errors };
1431
+ return {
1432
+ rules,
1433
+ errors,
1434
+ ...ignorePatterns && { ignorePatterns },
1435
+ ...mcpServers && { mcpServers }
1436
+ };
681
1437
  }
682
1438
  function parseClaudeMainFile(content, filepath) {
683
1439
  const lines = content.split("\n");
@@ -714,10 +1470,10 @@ async function parseClaudeMemoryFiles(memoryDir) {
714
1470
  const files = await readdir2(memoryDir);
715
1471
  for (const file of files) {
716
1472
  if (file.endsWith(".md")) {
717
- const filePath = (0, import_node_path10.join)(memoryDir, file);
1473
+ const filePath = (0, import_node_path14.join)(memoryDir, file);
718
1474
  const content = await readFileContent(filePath);
719
1475
  if (content.trim()) {
720
- const filename = (0, import_node_path10.basename)(file, ".md");
1476
+ const filename = (0, import_node_path14.basename)(file, ".md");
721
1477
  const frontmatter = {
722
1478
  root: false,
723
1479
  targets: ["claudecode"],
@@ -737,47 +1493,113 @@ async function parseClaudeMemoryFiles(memoryDir) {
737
1493
  }
738
1494
  return rules;
739
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
+ }
740
1525
 
741
1526
  // src/parsers/cline.ts
742
- var import_node_path11 = require("path");
1527
+ var import_node_path15 = require("path");
743
1528
  async function parseClineConfiguration(baseDir = process.cwd()) {
744
1529
  const errors = [];
745
1530
  const rules = [];
746
- const clineFilePath = (0, import_node_path11.join)(baseDir, ".cline", "instructions.md");
747
- if (!await fileExists(clineFilePath)) {
748
- errors.push(".cline/instructions.md file not found");
749
- 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
+ }
750
1553
  }
751
- try {
752
- const content = await readFileContent(clineFilePath);
753
- if (content.trim()) {
754
- const frontmatter = {
755
- root: false,
756
- targets: ["cline"],
757
- description: "Cline AI assistant instructions",
758
- globs: ["**/*"]
759
- };
760
- rules.push({
761
- frontmatter,
762
- content: content.trim(),
763
- filename: "cline-instructions",
764
- filepath: clineFilePath
765
- });
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}`);
766
1588
  }
767
- } catch (error) {
768
- const errorMessage = error instanceof Error ? error.message : String(error);
769
- 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)");
770
1592
  }
771
1593
  return { rules, errors };
772
1594
  }
773
1595
 
774
1596
  // src/parsers/copilot.ts
775
- var import_node_path12 = require("path");
1597
+ var import_node_path16 = require("path");
776
1598
  var import_gray_matter2 = __toESM(require("gray-matter"));
777
1599
  async function parseCopilotConfiguration(baseDir = process.cwd()) {
778
1600
  const errors = [];
779
1601
  const rules = [];
780
- const copilotFilePath = (0, import_node_path12.join)(baseDir, ".github", "copilot-instructions.md");
1602
+ const copilotFilePath = (0, import_node_path16.join)(baseDir, ".github", "copilot-instructions.md");
781
1603
  if (await fileExists(copilotFilePath)) {
782
1604
  try {
783
1605
  const rawContent = await readFileContent(copilotFilePath);
@@ -802,19 +1624,19 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
802
1624
  errors.push(`Failed to parse copilot-instructions.md: ${errorMessage}`);
803
1625
  }
804
1626
  }
805
- const instructionsDir = (0, import_node_path12.join)(baseDir, ".github", "instructions");
1627
+ const instructionsDir = (0, import_node_path16.join)(baseDir, ".github", "instructions");
806
1628
  if (await fileExists(instructionsDir)) {
807
1629
  try {
808
1630
  const { readdir: readdir2 } = await import("fs/promises");
809
1631
  const files = await readdir2(instructionsDir);
810
1632
  for (const file of files) {
811
1633
  if (file.endsWith(".instructions.md")) {
812
- const filePath = (0, import_node_path12.join)(instructionsDir, file);
1634
+ const filePath = (0, import_node_path16.join)(instructionsDir, file);
813
1635
  const rawContent = await readFileContent(filePath);
814
1636
  const parsed = (0, import_gray_matter2.default)(rawContent);
815
1637
  const content = parsed.content.trim();
816
1638
  if (content) {
817
- const filename = (0, import_node_path12.basename)(file, ".instructions.md");
1639
+ const filename = (0, import_node_path16.basename)(file, ".instructions.md");
818
1640
  const frontmatter = {
819
1641
  root: false,
820
1642
  targets: ["copilot"],
@@ -844,7 +1666,7 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
844
1666
  }
845
1667
 
846
1668
  // src/parsers/cursor.ts
847
- var import_node_path13 = require("path");
1669
+ var import_node_path17 = require("path");
848
1670
  var import_gray_matter3 = __toESM(require("gray-matter"));
849
1671
  var import_js_yaml = __toESM(require("js-yaml"));
850
1672
  var customMatterOptions = {
@@ -868,7 +1690,9 @@ var customMatterOptions = {
868
1690
  async function parseCursorConfiguration(baseDir = process.cwd()) {
869
1691
  const errors = [];
870
1692
  const rules = [];
871
- const cursorFilePath = (0, import_node_path13.join)(baseDir, ".cursorrules");
1693
+ let ignorePatterns;
1694
+ let mcpServers;
1695
+ const cursorFilePath = (0, import_node_path17.join)(baseDir, ".cursorrules");
872
1696
  if (await fileExists(cursorFilePath)) {
873
1697
  try {
874
1698
  const rawContent = await readFileContent(cursorFilePath);
@@ -893,20 +1717,20 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
893
1717
  errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
894
1718
  }
895
1719
  }
896
- const cursorRulesDir = (0, import_node_path13.join)(baseDir, ".cursor", "rules");
1720
+ const cursorRulesDir = (0, import_node_path17.join)(baseDir, ".cursor", "rules");
897
1721
  if (await fileExists(cursorRulesDir)) {
898
1722
  try {
899
1723
  const { readdir: readdir2 } = await import("fs/promises");
900
1724
  const files = await readdir2(cursorRulesDir);
901
1725
  for (const file of files) {
902
1726
  if (file.endsWith(".mdc")) {
903
- const filePath = (0, import_node_path13.join)(cursorRulesDir, file);
1727
+ const filePath = (0, import_node_path17.join)(cursorRulesDir, file);
904
1728
  try {
905
1729
  const rawContent = await readFileContent(filePath);
906
1730
  const parsed = (0, import_gray_matter3.default)(rawContent, customMatterOptions);
907
1731
  const content = parsed.content.trim();
908
1732
  if (content) {
909
- const filename = (0, import_node_path13.basename)(file, ".mdc");
1733
+ const filename = (0, import_node_path17.basename)(file, ".mdc");
910
1734
  const frontmatter = {
911
1735
  root: false,
912
1736
  targets: ["cursor"],
@@ -934,38 +1758,244 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
934
1758
  if (rules.length === 0) {
935
1759
  errors.push("No Cursor configuration files found (.cursorrules or .cursor/rules/*.mdc)");
936
1760
  }
937
- 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
+ };
938
1793
  }
939
1794
 
940
- // src/parsers/roo.ts
941
- var import_node_path14 = require("path");
942
- 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()) {
943
1798
  const errors = [];
944
1799
  const rules = [];
945
- const rooFilePath = (0, import_node_path14.join)(baseDir, ".roo", "instructions.md");
946
- if (!await fileExists(rooFilePath)) {
947
- 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");
948
1805
  return { rules, errors };
949
1806
  }
950
1807
  try {
951
- const content = await readFileContent(rooFilePath);
952
- if (content.trim()) {
953
- const frontmatter = {
954
- root: false,
955
- targets: ["roo"],
956
- description: "Roo Code AI assistant instructions",
957
- globs: ["**/*"]
958
- };
959
- rules.push({
960
- frontmatter,
961
- content: content.trim(),
962
- filename: "roo-instructions",
963
- filepath: rooFilePath
964
- });
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;
965
1913
  }
966
1914
  } catch (error) {
967
1915
  const errorMessage = error instanceof Error ? error.message : String(error);
968
- 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)");
969
1999
  }
970
2000
  return { rules, errors };
971
2001
  }
@@ -975,6 +2005,8 @@ async function importConfiguration(options) {
975
2005
  const { tool, baseDir = process.cwd(), rulesDir = ".rulesync", verbose = false } = options;
976
2006
  const errors = [];
977
2007
  let rules = [];
2008
+ let ignorePatterns;
2009
+ let mcpServers;
978
2010
  if (verbose) {
979
2011
  console.log(`Importing ${tool} configuration from ${baseDir}...`);
980
2012
  }
@@ -984,12 +2016,16 @@ async function importConfiguration(options) {
984
2016
  const claudeResult = await parseClaudeConfiguration(baseDir);
985
2017
  rules = claudeResult.rules;
986
2018
  errors.push(...claudeResult.errors);
2019
+ ignorePatterns = claudeResult.ignorePatterns;
2020
+ mcpServers = claudeResult.mcpServers;
987
2021
  break;
988
2022
  }
989
2023
  case "cursor": {
990
2024
  const cursorResult = await parseCursorConfiguration(baseDir);
991
2025
  rules = cursorResult.rules;
992
2026
  errors.push(...cursorResult.errors);
2027
+ ignorePatterns = cursorResult.ignorePatterns;
2028
+ mcpServers = cursorResult.mcpServers;
993
2029
  break;
994
2030
  }
995
2031
  case "copilot": {
@@ -1010,6 +2046,14 @@ async function importConfiguration(options) {
1010
2046
  errors.push(...rooResult.errors);
1011
2047
  break;
1012
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
+ }
1013
2057
  default:
1014
2058
  errors.push(`Unsupported tool: ${tool}`);
1015
2059
  return { success: false, rulesCreated: 0, errors };
@@ -1019,10 +2063,10 @@ async function importConfiguration(options) {
1019
2063
  errors.push(`Failed to parse ${tool} configuration: ${errorMessage}`);
1020
2064
  return { success: false, rulesCreated: 0, errors };
1021
2065
  }
1022
- if (rules.length === 0) {
2066
+ if (rules.length === 0 && !ignorePatterns && !mcpServers) {
1023
2067
  return { success: false, rulesCreated: 0, errors };
1024
2068
  }
1025
- const rulesDirPath = (0, import_node_path15.join)(baseDir, rulesDir);
2069
+ const rulesDirPath = (0, import_node_path20.join)(baseDir, rulesDir);
1026
2070
  try {
1027
2071
  const { mkdir: mkdir3 } = await import("fs/promises");
1028
2072
  await mkdir3(rulesDirPath, { recursive: true });
@@ -1036,7 +2080,7 @@ async function importConfiguration(options) {
1036
2080
  try {
1037
2081
  const baseFilename = `${tool}__${rule.filename}`;
1038
2082
  const filename = await generateUniqueFilename(rulesDirPath, baseFilename);
1039
- const filePath = (0, import_node_path15.join)(rulesDirPath, `${filename}.md`);
2083
+ const filePath = (0, import_node_path20.join)(rulesDirPath, `${filename}.md`);
1040
2084
  const content = generateRuleFileContent(rule);
1041
2085
  await writeFileContent(filePath, content);
1042
2086
  rulesCreated++;
@@ -1048,10 +2092,44 @@ async function importConfiguration(options) {
1048
2092
  errors.push(`Failed to create rule file for ${rule.filename}: ${errorMessage}`);
1049
2093
  }
1050
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
+ }
1051
2127
  return {
1052
- success: rulesCreated > 0,
2128
+ success: rulesCreated > 0 || ignoreFileCreated || mcpFileCreated,
1053
2129
  rulesCreated,
1054
- errors
2130
+ errors,
2131
+ ignoreFileCreated,
2132
+ mcpFileCreated
1055
2133
  };
1056
2134
  }
1057
2135
  function generateRuleFileContent(rule) {
@@ -1061,7 +2139,7 @@ function generateRuleFileContent(rule) {
1061
2139
  async function generateUniqueFilename(rulesDir, baseFilename) {
1062
2140
  let filename = baseFilename;
1063
2141
  let counter = 1;
1064
- while (await fileExists((0, import_node_path15.join)(rulesDir, `${filename}.md`))) {
2142
+ while (await fileExists((0, import_node_path20.join)(rulesDir, `${filename}.md`))) {
1065
2143
  filename = `${baseFilename}-${counter}`;
1066
2144
  counter++;
1067
2145
  }
@@ -1076,59 +2154,57 @@ async function importCommand(options = {}) {
1076
2154
  if (options.copilot) tools.push("copilot");
1077
2155
  if (options.cline) tools.push("cline");
1078
2156
  if (options.roo) tools.push("roo");
2157
+ if (options.geminicli) tools.push("geminicli");
1079
2158
  if (tools.length === 0) {
1080
2159
  console.error(
1081
- "\u274C Please specify at least one tool to import from (--claudecode, --cursor, --copilot, --cline, --roo)"
2160
+ "\u274C Please specify one tool to import from (--claudecode, --cursor, --copilot, --cline, --roo, --geminicli)"
1082
2161
  );
1083
2162
  process.exit(1);
1084
2163
  }
1085
- console.log("Importing configuration files...");
1086
- let totalRulesCreated = 0;
1087
- const allErrors = [];
1088
- for (const tool of tools) {
1089
- if (options.verbose) {
1090
- console.log(`
1091
- Importing from ${tool}...`);
1092
- }
1093
- try {
1094
- const result = await importConfiguration({
1095
- tool,
1096
- verbose: options.verbose ?? false
1097
- });
1098
- if (result.success) {
1099
- console.log(`\u2705 Imported ${result.rulesCreated} rule(s) from ${tool}`);
1100
- totalRulesCreated += result.rulesCreated;
1101
- } else if (result.errors.length > 0) {
1102
- console.warn(`\u26A0\uFE0F Failed to import from ${tool}: ${result.errors[0]}`);
1103
- if (options.verbose) {
1104
- allErrors.push(...result.errors);
1105
- }
1106
- }
1107
- } catch (error) {
1108
- const errorMessage = error instanceof Error ? error.message : String(error);
1109
- console.error(`\u274C Error importing from ${tool}: ${errorMessage}`);
1110
- allErrors.push(`${tool}: ${errorMessage}`);
1111
- }
1112
- }
1113
- if (totalRulesCreated > 0) {
1114
- console.log(`
1115
- \u{1F389} Successfully imported ${totalRulesCreated} rule(s) total!`);
1116
- console.log("You can now run 'rulesync generate' to create tool-specific configurations.");
1117
- } else {
1118
- console.warn(
1119
- "\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."
1120
2167
  );
2168
+ process.exit(1);
1121
2169
  }
1122
- if (options.verbose && allErrors.length > 0) {
1123
- console.log("\nDetailed errors:");
1124
- for (const error of allErrors) {
1125
- 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
+ }
1126
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);
1127
2203
  }
1128
2204
  }
1129
2205
 
1130
2206
  // src/cli/commands/init.ts
1131
- var import_node_path16 = require("path");
2207
+ var import_node_path21 = require("path");
1132
2208
  async function initCommand() {
1133
2209
  const aiRulesDir = ".rulesync";
1134
2210
  console.log("Initializing rulesync...");
@@ -1258,7 +2334,7 @@ globs: ["src/api/**/*.ts", "src/services/**/*.ts", "src/models/**/*.ts"]
1258
2334
  }
1259
2335
  ];
1260
2336
  for (const file of sampleFiles) {
1261
- const filepath = (0, import_node_path16.join)(aiRulesDir, file.filename);
2337
+ const filepath = (0, import_node_path21.join)(aiRulesDir, file.filename);
1262
2338
  if (!await fileExists(filepath)) {
1263
2339
  await writeFileContent(filepath, file.content);
1264
2340
  console.log(`Created ${filepath}`);
@@ -1371,11 +2447,11 @@ async function watchCommand() {
1371
2447
  persistent: true
1372
2448
  });
1373
2449
  let isGenerating = false;
1374
- const handleChange = async (path2) => {
2450
+ const handleChange = async (path4) => {
1375
2451
  if (isGenerating) return;
1376
2452
  isGenerating = true;
1377
2453
  console.log(`
1378
- \u{1F4DD} Detected change in ${path2}`);
2454
+ \u{1F4DD} Detected change in ${path4}`);
1379
2455
  try {
1380
2456
  await generateCommand({ verbose: false });
1381
2457
  console.log("\u2705 Regenerated configuration files");
@@ -1385,10 +2461,10 @@ async function watchCommand() {
1385
2461
  isGenerating = false;
1386
2462
  }
1387
2463
  };
1388
- watcher.on("change", handleChange).on("add", handleChange).on("unlink", (path2) => {
2464
+ watcher.on("change", handleChange).on("add", handleChange).on("unlink", (path4) => {
1389
2465
  console.log(`
1390
- \u{1F5D1}\uFE0F Removed ${path2}`);
1391
- handleChange(path2);
2466
+ \u{1F5D1}\uFE0F Removed ${path4}`);
2467
+ handleChange(path4);
1392
2468
  }).on("error", (error) => {
1393
2469
  console.error("\u274C Watcher error:", error);
1394
2470
  });
@@ -1401,12 +2477,12 @@ async function watchCommand() {
1401
2477
 
1402
2478
  // src/cli/index.ts
1403
2479
  var program = new import_commander.Command();
1404
- program.name("rulesync").description("Unified AI rules management CLI tool").version("0.33.0");
2480
+ program.name("rulesync").description("Unified AI rules management CLI tool").version("0.36.0");
1405
2481
  program.command("init").description("Initialize rulesync in current directory").action(initCommand);
1406
2482
  program.command("add <filename>").description("Add a new rule file").action(addCommand);
1407
2483
  program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
1408
- program.command("import").description("Import configurations from AI tools to rulesync format").option("--claudecode", "Import from Claude Code (CLAUDE.md)").option("--cursor", "Import from Cursor (.cursorrules)").option("--copilot", "Import from GitHub Copilot (.github/copilot-instructions.md)").option("--cline", "Import from Cline (.cline/instructions.md)").option("--roo", "Import from Roo Code (.roo/instructions.md)").option("-v, --verbose", "Verbose output").action(importCommand);
1409
- program.command("generate").description("Generate configuration files for AI tools").option("--copilot", "Generate only for GitHub Copilot").option("--cursor", "Generate only for Cursor").option("--cline", "Generate only for Cline").option("--claudecode", "Generate only for Claude Code").option("--roo", "Generate only for Roo Code").option("--delete", "Delete all existing files in output directories before generating").option(
2484
+ program.command("import").description("Import configurations from AI tools to rulesync format").option("--claudecode", "Import from Claude Code (CLAUDE.md)").option("--cursor", "Import from Cursor (.cursorrules)").option("--copilot", "Import from GitHub Copilot (.github/copilot-instructions.md)").option("--cline", "Import from Cline (.cline/instructions.md)").option("--roo", "Import from Roo Code (.roo/instructions.md)").option("--geminicli", "Import from Gemini CLI (GEMINI.md)").option("-v, --verbose", "Verbose output").action(importCommand);
2485
+ program.command("generate").description("Generate configuration files for AI tools").option("--copilot", "Generate only for GitHub Copilot").option("--cursor", "Generate only for Cursor").option("--cline", "Generate only for Cline").option("--claudecode", "Generate only for Claude Code").option("--roo", "Generate only for Roo Code").option("--geminicli", "Generate only for Gemini CLI").option("--delete", "Delete all existing files in output directories before generating").option(
1410
2486
  "-b, --base-dir <paths>",
1411
2487
  "Base directories to generate files (comma-separated for multiple paths)"
1412
2488
  ).option("-v, --verbose", "Verbose output").action(async (options) => {
@@ -1416,6 +2492,7 @@ program.command("generate").description("Generate configuration files for AI too
1416
2492
  if (options.cline) tools.push("cline");
1417
2493
  if (options.claudecode) tools.push("claudecode");
1418
2494
  if (options.roo) tools.push("roo");
2495
+ if (options.geminicli) tools.push("geminicli");
1419
2496
  const generateOptions = {
1420
2497
  verbose: options.verbose,
1421
2498
  delete: options.delete