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.mjs CHANGED
@@ -1,4 +1,22 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ generateClaudeMcp
4
+ } from "./chunk-UNTCJDMQ.mjs";
5
+ import {
6
+ generateClineMcp
7
+ } from "./chunk-QHXMJZTJ.mjs";
8
+ import {
9
+ generateCopilotMcp
10
+ } from "./chunk-YGXGGUBG.mjs";
11
+ import {
12
+ generateCursorMcp
13
+ } from "./chunk-SBYRCTWS.mjs";
14
+ import {
15
+ generateGeminiCliMcp
16
+ } from "./chunk-6PQ4APY4.mjs";
17
+ import {
18
+ generateRooMcp
19
+ } from "./chunk-QVPQ2X4L.mjs";
2
20
 
3
21
  // src/cli/index.ts
4
22
  import { Command } from "commander";
@@ -16,11 +34,12 @@ function getDefaultConfig() {
16
34
  cursor: ".cursor/rules",
17
35
  cline: ".clinerules",
18
36
  claudecode: ".",
37
+ claude: ".",
19
38
  roo: ".roo/rules",
20
39
  geminicli: ".gemini/memories"
21
40
  },
22
41
  watchEnabled: false,
23
- defaultTargets: ["copilot", "cursor", "cline", "claudecode", "roo", "geminicli"]
42
+ defaultTargets: ["copilot", "cursor", "cline", "claudecode", "claude", "roo", "geminicli"]
24
43
  };
25
44
  }
26
45
  function resolveTargets(targets, config) {
@@ -66,27 +85,158 @@ async function addCommand(filename) {
66
85
  }
67
86
  }
68
87
 
69
- // src/generators/claudecode.ts
88
+ // src/generators/rules/claudecode.ts
89
+ import { join as join3 } from "path";
90
+
91
+ // src/utils/file.ts
92
+ import { mkdir as mkdir2, readdir, readFile, rm, stat, writeFile as writeFile2 } from "fs/promises";
93
+ import { dirname, join as join2 } from "path";
94
+
95
+ // src/utils/ignore.ts
70
96
  import { join } from "path";
97
+ import micromatch from "micromatch";
98
+ var cachedIgnorePatterns = null;
99
+ async function loadIgnorePatterns(baseDir = process.cwd()) {
100
+ if (cachedIgnorePatterns) {
101
+ return cachedIgnorePatterns;
102
+ }
103
+ const ignorePath = join(baseDir, ".rulesyncignore");
104
+ if (!await fileExists(ignorePath)) {
105
+ cachedIgnorePatterns = { patterns: [] };
106
+ return cachedIgnorePatterns;
107
+ }
108
+ try {
109
+ const content = await readFileContent(ignorePath);
110
+ const patterns = parseIgnoreFile(content);
111
+ cachedIgnorePatterns = { patterns };
112
+ return cachedIgnorePatterns;
113
+ } catch (error) {
114
+ console.warn(`Failed to read .rulesyncignore: ${error}`);
115
+ cachedIgnorePatterns = { patterns: [] };
116
+ return cachedIgnorePatterns;
117
+ }
118
+ }
119
+ function parseIgnoreFile(content) {
120
+ return content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
121
+ }
122
+ function isFileIgnored(filepath, ignorePatterns) {
123
+ if (ignorePatterns.length === 0) {
124
+ return false;
125
+ }
126
+ const negationPatterns = ignorePatterns.filter((p) => p.startsWith("!"));
127
+ const positivePatterns = ignorePatterns.filter((p) => !p.startsWith("!"));
128
+ const isIgnored = positivePatterns.length > 0 && micromatch.isMatch(filepath, positivePatterns, {
129
+ dot: true
130
+ });
131
+ if (isIgnored && negationPatterns.length > 0) {
132
+ const negationPatternsWithoutPrefix = negationPatterns.map((p) => p.substring(1));
133
+ return !micromatch.isMatch(filepath, negationPatternsWithoutPrefix, {
134
+ dot: true
135
+ });
136
+ }
137
+ return isIgnored;
138
+ }
139
+ function filterIgnoredFiles(files, ignorePatterns) {
140
+ if (ignorePatterns.length === 0) {
141
+ return files;
142
+ }
143
+ return files.filter((file) => !isFileIgnored(file, ignorePatterns));
144
+ }
145
+
146
+ // src/utils/file.ts
147
+ async function ensureDir(dirPath) {
148
+ try {
149
+ await stat(dirPath);
150
+ } catch {
151
+ await mkdir2(dirPath, { recursive: true });
152
+ }
153
+ }
154
+ async function readFileContent(filepath) {
155
+ return readFile(filepath, "utf-8");
156
+ }
157
+ async function writeFileContent(filepath, content) {
158
+ await ensureDir(dirname(filepath));
159
+ await writeFile2(filepath, content, "utf-8");
160
+ }
161
+ async function findFiles(dir, extension = ".md", ignorePatterns) {
162
+ try {
163
+ const files = await readdir(dir);
164
+ const filtered = files.filter((file) => file.endsWith(extension)).map((file) => join2(dir, file));
165
+ if (ignorePatterns && ignorePatterns.length > 0) {
166
+ return filterIgnoredFiles(filtered, ignorePatterns);
167
+ }
168
+ return filtered;
169
+ } catch {
170
+ return [];
171
+ }
172
+ }
173
+ async function fileExists(filepath) {
174
+ try {
175
+ await stat(filepath);
176
+ return true;
177
+ } catch {
178
+ return false;
179
+ }
180
+ }
181
+ async function removeDirectory(dirPath) {
182
+ const dangerousPaths = [".", "/", "~", "src", "node_modules"];
183
+ if (dangerousPaths.includes(dirPath) || dirPath === "") {
184
+ console.warn(`Skipping deletion of dangerous path: ${dirPath}`);
185
+ return;
186
+ }
187
+ try {
188
+ if (await fileExists(dirPath)) {
189
+ await rm(dirPath, { recursive: true, force: true });
190
+ }
191
+ } catch (error) {
192
+ console.warn(`Failed to remove directory ${dirPath}:`, error);
193
+ }
194
+ }
195
+ async function removeFile(filepath) {
196
+ try {
197
+ if (await fileExists(filepath)) {
198
+ await rm(filepath);
199
+ }
200
+ } catch (error) {
201
+ console.warn(`Failed to remove file ${filepath}:`, error);
202
+ }
203
+ }
204
+ async function removeClaudeGeneratedFiles() {
205
+ const filesToRemove = ["CLAUDE.md", ".claude/memories"];
206
+ for (const fileOrDir of filesToRemove) {
207
+ if (fileOrDir.endsWith("/memories")) {
208
+ await removeDirectory(fileOrDir);
209
+ } else {
210
+ await removeFile(fileOrDir);
211
+ }
212
+ }
213
+ }
214
+
215
+ // src/generators/rules/claudecode.ts
71
216
  async function generateClaudecodeConfig(rules, config, baseDir) {
72
217
  const outputs = [];
73
218
  const rootRules = rules.filter((r) => r.frontmatter.root === true);
74
219
  const detailRules = rules.filter((r) => r.frontmatter.root === false);
75
220
  const claudeMdContent = generateClaudeMarkdown(rootRules, detailRules);
76
- const claudeOutputDir = baseDir ? join(baseDir, config.outputPaths.claudecode) : config.outputPaths.claudecode;
221
+ const claudeOutputDir = baseDir ? join3(baseDir, config.outputPaths.claudecode) : config.outputPaths.claudecode;
77
222
  outputs.push({
78
223
  tool: "claudecode",
79
- filepath: join(claudeOutputDir, "CLAUDE.md"),
224
+ filepath: join3(claudeOutputDir, "CLAUDE.md"),
80
225
  content: claudeMdContent
81
226
  });
82
227
  for (const rule of detailRules) {
83
228
  const memoryContent = generateMemoryFile(rule);
84
229
  outputs.push({
85
230
  tool: "claudecode",
86
- filepath: join(claudeOutputDir, ".claude", "memories", `${rule.filename}.md`),
231
+ filepath: join3(claudeOutputDir, ".claude", "memories", `${rule.filename}.md`),
87
232
  content: memoryContent
88
233
  });
89
234
  }
235
+ const ignorePatterns = await loadIgnorePatterns(baseDir);
236
+ if (ignorePatterns.patterns.length > 0) {
237
+ const settingsPath = baseDir ? join3(baseDir, ".claude", "settings.json") : join3(".claude", "settings.json");
238
+ await updateClaudeSettings(settingsPath, ignorePatterns.patterns);
239
+ }
90
240
  return outputs;
91
241
  }
92
242
  function generateClaudeMarkdown(rootRules, detailRules) {
@@ -115,42 +265,101 @@ function generateClaudeMarkdown(rootRules, detailRules) {
115
265
  function generateMemoryFile(rule) {
116
266
  return rule.content.trim();
117
267
  }
268
+ async function updateClaudeSettings(settingsPath, ignorePatterns) {
269
+ let settings = {};
270
+ if (await fileExists(settingsPath)) {
271
+ try {
272
+ const content = await readFileContent(settingsPath);
273
+ settings = JSON.parse(content);
274
+ } catch (_error) {
275
+ console.warn(`Failed to parse existing ${settingsPath}, creating new settings`);
276
+ settings = {};
277
+ }
278
+ }
279
+ if (!settings.permissions) {
280
+ settings.permissions = {};
281
+ }
282
+ if (!Array.isArray(settings.permissions.deny)) {
283
+ settings.permissions.deny = [];
284
+ }
285
+ const readDenyRules = ignorePatterns.map((pattern) => `Read(${pattern})`);
286
+ settings.permissions.deny = settings.permissions.deny.filter((rule) => {
287
+ if (!rule.startsWith("Read(")) return true;
288
+ const match = rule.match(/^Read\((.*)\)$/);
289
+ if (!match) return true;
290
+ return !ignorePatterns.includes(match[1] ?? "");
291
+ });
292
+ settings.permissions.deny.push(...readDenyRules);
293
+ settings.permissions.deny = [...new Set(settings.permissions.deny)];
294
+ const jsonContent = JSON.stringify(settings, null, 2);
295
+ await writeFileContent(settingsPath, jsonContent);
296
+ console.log(`\u2705 Updated Claude Code settings: ${settingsPath}`);
297
+ }
118
298
 
119
- // src/generators/cline.ts
120
- import { join as join2 } from "path";
299
+ // src/generators/rules/cline.ts
300
+ import { join as join4 } from "path";
121
301
  async function generateClineConfig(rules, config, baseDir) {
122
302
  const outputs = [];
123
303
  for (const rule of rules) {
124
304
  const content = generateClineMarkdown(rule);
125
- const outputDir = baseDir ? join2(baseDir, config.outputPaths.cline) : config.outputPaths.cline;
126
- const filepath = join2(outputDir, `${rule.filename}.md`);
305
+ const outputDir = baseDir ? join4(baseDir, config.outputPaths.cline) : config.outputPaths.cline;
306
+ const filepath = join4(outputDir, `${rule.filename}.md`);
127
307
  outputs.push({
128
308
  tool: "cline",
129
309
  filepath,
130
310
  content
131
311
  });
132
312
  }
313
+ const ignorePatterns = await loadIgnorePatterns(baseDir);
314
+ if (ignorePatterns.patterns.length > 0) {
315
+ const clineIgnorePath = baseDir ? join4(baseDir, ".clineignore") : ".clineignore";
316
+ const clineIgnoreContent = generateClineIgnore(ignorePatterns.patterns);
317
+ outputs.push({
318
+ tool: "cline",
319
+ filepath: clineIgnorePath,
320
+ content: clineIgnoreContent
321
+ });
322
+ }
133
323
  return outputs;
134
324
  }
135
325
  function generateClineMarkdown(rule) {
136
326
  return rule.content.trim();
137
327
  }
328
+ function generateClineIgnore(patterns) {
329
+ const lines = [
330
+ "# Generated by rulesync from .rulesyncignore",
331
+ "# This file is automatically generated. Do not edit manually.",
332
+ "",
333
+ ...patterns
334
+ ];
335
+ return lines.join("\n");
336
+ }
138
337
 
139
- // src/generators/copilot.ts
140
- import { join as join3 } from "path";
338
+ // src/generators/rules/copilot.ts
339
+ import { join as join5 } from "path";
141
340
  async function generateCopilotConfig(rules, config, baseDir) {
142
341
  const outputs = [];
143
342
  for (const rule of rules) {
144
343
  const content = generateCopilotMarkdown(rule);
145
344
  const baseFilename = rule.filename.replace(/\.md$/, "");
146
- const outputDir = baseDir ? join3(baseDir, config.outputPaths.copilot) : config.outputPaths.copilot;
147
- const filepath = join3(outputDir, `${baseFilename}.instructions.md`);
345
+ const outputDir = baseDir ? join5(baseDir, config.outputPaths.copilot) : config.outputPaths.copilot;
346
+ const filepath = join5(outputDir, `${baseFilename}.instructions.md`);
148
347
  outputs.push({
149
348
  tool: "copilot",
150
349
  filepath,
151
350
  content
152
351
  });
153
352
  }
353
+ const ignorePatterns = await loadIgnorePatterns(baseDir);
354
+ if (ignorePatterns.patterns.length > 0) {
355
+ const copilotIgnorePath = baseDir ? join5(baseDir, ".copilotignore") : ".copilotignore";
356
+ const copilotIgnoreContent = generateCopilotIgnore(ignorePatterns.patterns);
357
+ outputs.push({
358
+ tool: "copilot",
359
+ filepath: copilotIgnorePath,
360
+ content: copilotIgnoreContent
361
+ });
362
+ }
154
363
  return outputs;
155
364
  }
156
365
  function generateCopilotMarkdown(rule) {
@@ -166,21 +375,42 @@ function generateCopilotMarkdown(rule) {
166
375
  lines.push(rule.content);
167
376
  return lines.join("\n");
168
377
  }
378
+ function generateCopilotIgnore(patterns) {
379
+ const lines = [
380
+ "# Generated by rulesync from .rulesyncignore",
381
+ "# This file is automatically generated. Do not edit manually.",
382
+ "# Note: .copilotignore is not officially supported by GitHub Copilot.",
383
+ "# This file is for use with community tools like copilotignore-vscode extension.",
384
+ "",
385
+ ...patterns
386
+ ];
387
+ return lines.join("\n");
388
+ }
169
389
 
170
- // src/generators/cursor.ts
171
- import { join as join4 } from "path";
390
+ // src/generators/rules/cursor.ts
391
+ import { join as join6 } from "path";
172
392
  async function generateCursorConfig(rules, config, baseDir) {
173
393
  const outputs = [];
174
394
  for (const rule of rules) {
175
395
  const content = generateCursorMarkdown(rule);
176
- const outputDir = baseDir ? join4(baseDir, config.outputPaths.cursor) : config.outputPaths.cursor;
177
- const filepath = join4(outputDir, `${rule.filename}.mdc`);
396
+ const outputDir = baseDir ? join6(baseDir, config.outputPaths.cursor) : config.outputPaths.cursor;
397
+ const filepath = join6(outputDir, `${rule.filename}.mdc`);
178
398
  outputs.push({
179
399
  tool: "cursor",
180
400
  filepath,
181
401
  content
182
402
  });
183
403
  }
404
+ const ignorePatterns = await loadIgnorePatterns(baseDir);
405
+ if (ignorePatterns.patterns.length > 0) {
406
+ const cursorIgnorePath = baseDir ? join6(baseDir, ".cursorignore") : ".cursorignore";
407
+ const cursorIgnoreContent = generateCursorIgnore(ignorePatterns.patterns);
408
+ outputs.push({
409
+ tool: "cursor",
410
+ filepath: cursorIgnorePath,
411
+ content: cursorIgnoreContent
412
+ });
413
+ }
184
414
  return outputs;
185
415
  }
186
416
  function generateCursorMarkdown(rule) {
@@ -203,17 +433,26 @@ function generateCursorMarkdown(rule) {
203
433
  lines.push(rule.content);
204
434
  return lines.join("\n");
205
435
  }
436
+ function generateCursorIgnore(patterns) {
437
+ const lines = [
438
+ "# Generated by rulesync from .rulesyncignore",
439
+ "# This file is automatically generated. Do not edit manually.",
440
+ "",
441
+ ...patterns
442
+ ];
443
+ return lines.join("\n");
444
+ }
206
445
 
207
- // src/generators/geminicli.ts
208
- import { join as join5 } from "path";
446
+ // src/generators/rules/geminicli.ts
447
+ import { join as join7 } from "path";
209
448
  async function generateGeminiConfig(rules, config, baseDir) {
210
449
  const outputs = [];
211
450
  const rootRule = rules.find((rule) => rule.frontmatter.root === true);
212
451
  const memoryRules = rules.filter((rule) => rule.frontmatter.root === false);
213
452
  for (const rule of memoryRules) {
214
453
  const content = generateGeminiMemoryMarkdown(rule);
215
- const outputDir = baseDir ? join5(baseDir, config.outputPaths.geminicli) : config.outputPaths.geminicli;
216
- const filepath = join5(outputDir, `${rule.filename}.md`);
454
+ const outputDir = baseDir ? join7(baseDir, config.outputPaths.geminicli) : config.outputPaths.geminicli;
455
+ const filepath = join7(outputDir, `${rule.filename}.md`);
217
456
  outputs.push({
218
457
  tool: "geminicli",
219
458
  filepath,
@@ -221,12 +460,22 @@ async function generateGeminiConfig(rules, config, baseDir) {
221
460
  });
222
461
  }
223
462
  const rootContent = generateGeminiRootMarkdown(rootRule, memoryRules, baseDir);
224
- const rootFilepath = baseDir ? join5(baseDir, "GEMINI.md") : "GEMINI.md";
463
+ const rootFilepath = baseDir ? join7(baseDir, "GEMINI.md") : "GEMINI.md";
225
464
  outputs.push({
226
465
  tool: "geminicli",
227
466
  filepath: rootFilepath,
228
467
  content: rootContent
229
468
  });
469
+ const ignorePatterns = await loadIgnorePatterns(baseDir);
470
+ if (ignorePatterns.patterns.length > 0) {
471
+ const aiexcludePath = baseDir ? join7(baseDir, ".aiexclude") : ".aiexclude";
472
+ const aiexcludeContent = generateAiexclude(ignorePatterns.patterns);
473
+ outputs.push({
474
+ tool: "geminicli",
475
+ filepath: aiexcludePath,
476
+ content: aiexcludeContent
477
+ });
478
+ }
230
479
  return outputs;
231
480
  }
232
481
  function generateGeminiMemoryMarkdown(rule) {
@@ -256,92 +505,53 @@ function generateGeminiRootMarkdown(rootRule, memoryRules, _baseDir) {
256
505
  }
257
506
  return lines.join("\n");
258
507
  }
508
+ function generateAiexclude(patterns) {
509
+ const lines = [
510
+ "# Generated by rulesync from .rulesyncignore",
511
+ "# This file is automatically generated. Do not edit manually.",
512
+ "",
513
+ ...patterns
514
+ ];
515
+ return lines.join("\n");
516
+ }
259
517
 
260
- // src/generators/roo.ts
261
- import { join as join6 } from "path";
518
+ // src/generators/rules/roo.ts
519
+ import { join as join8 } from "path";
262
520
  async function generateRooConfig(rules, config, baseDir) {
263
521
  const outputs = [];
264
522
  for (const rule of rules) {
265
523
  const content = generateRooMarkdown(rule);
266
- const outputDir = baseDir ? join6(baseDir, config.outputPaths.roo) : config.outputPaths.roo;
267
- const filepath = join6(outputDir, `${rule.filename}.md`);
524
+ const outputDir = baseDir ? join8(baseDir, config.outputPaths.roo) : config.outputPaths.roo;
525
+ const filepath = join8(outputDir, `${rule.filename}.md`);
268
526
  outputs.push({
269
527
  tool: "roo",
270
528
  filepath,
271
529
  content
272
530
  });
273
531
  }
532
+ const ignorePatterns = await loadIgnorePatterns(baseDir);
533
+ if (ignorePatterns.patterns.length > 0) {
534
+ const rooIgnorePath = baseDir ? join8(baseDir, ".rooignore") : ".rooignore";
535
+ const rooIgnoreContent = generateRooIgnore(ignorePatterns.patterns);
536
+ outputs.push({
537
+ tool: "roo",
538
+ filepath: rooIgnorePath,
539
+ content: rooIgnoreContent
540
+ });
541
+ }
274
542
  return outputs;
275
543
  }
276
544
  function generateRooMarkdown(rule) {
277
545
  return rule.content.trim();
278
546
  }
279
-
280
- // src/utils/file.ts
281
- import { mkdir as mkdir2, readdir, readFile, rm, stat, writeFile as writeFile2 } from "fs/promises";
282
- import { dirname, join as join7 } from "path";
283
- async function ensureDir(dirPath) {
284
- try {
285
- await stat(dirPath);
286
- } catch {
287
- await mkdir2(dirPath, { recursive: true });
288
- }
289
- }
290
- async function readFileContent(filepath) {
291
- return readFile(filepath, "utf-8");
292
- }
293
- async function writeFileContent(filepath, content) {
294
- await ensureDir(dirname(filepath));
295
- await writeFile2(filepath, content, "utf-8");
296
- }
297
- async function findFiles(dir, extension = ".md") {
298
- try {
299
- const files = await readdir(dir);
300
- return files.filter((file) => file.endsWith(extension)).map((file) => join7(dir, file));
301
- } catch {
302
- return [];
303
- }
304
- }
305
- async function fileExists(filepath) {
306
- try {
307
- await stat(filepath);
308
- return true;
309
- } catch {
310
- return false;
311
- }
312
- }
313
- async function removeDirectory(dirPath) {
314
- const dangerousPaths = [".", "/", "~", "src", "node_modules"];
315
- if (dangerousPaths.includes(dirPath) || dirPath === "") {
316
- console.warn(`Skipping deletion of dangerous path: ${dirPath}`);
317
- return;
318
- }
319
- try {
320
- if (await fileExists(dirPath)) {
321
- await rm(dirPath, { recursive: true, force: true });
322
- }
323
- } catch (error) {
324
- console.warn(`Failed to remove directory ${dirPath}:`, error);
325
- }
326
- }
327
- async function removeFile(filepath) {
328
- try {
329
- if (await fileExists(filepath)) {
330
- await rm(filepath);
331
- }
332
- } catch (error) {
333
- console.warn(`Failed to remove file ${filepath}:`, error);
334
- }
335
- }
336
- async function removeClaudeGeneratedFiles() {
337
- const filesToRemove = ["CLAUDE.md", ".claude/memories"];
338
- for (const fileOrDir of filesToRemove) {
339
- if (fileOrDir.endsWith("/memories")) {
340
- await removeDirectory(fileOrDir);
341
- } else {
342
- await removeFile(fileOrDir);
343
- }
344
- }
547
+ function generateRooIgnore(patterns) {
548
+ const lines = [
549
+ "# Generated by rulesync from .rulesyncignore",
550
+ "# This file is automatically generated. Do not edit manually.",
551
+ "",
552
+ ...patterns
553
+ ];
554
+ return lines.join("\n");
345
555
  }
346
556
 
347
557
  // src/core/generator.ts
@@ -397,9 +607,13 @@ async function generateForTool(tool, rules, config, baseDir) {
397
607
  import { basename } from "path";
398
608
  import matter from "gray-matter";
399
609
  async function parseRulesFromDirectory(aiRulesDir) {
400
- const ruleFiles = await findFiles(aiRulesDir);
610
+ const ignorePatterns = await loadIgnorePatterns();
611
+ const ruleFiles = await findFiles(aiRulesDir, ".md", ignorePatterns.patterns);
401
612
  const rules = [];
402
613
  const errors = [];
614
+ if (ignorePatterns.patterns.length > 0) {
615
+ console.log(`Loaded ${ignorePatterns.patterns.length} ignore patterns from .rulesyncignore`);
616
+ }
403
617
  for (const filepath of ruleFiles) {
404
618
  try {
405
619
  const rule = await parseRuleFile(filepath);
@@ -555,6 +769,140 @@ async function validateRule(rule) {
555
769
  };
556
770
  }
557
771
 
772
+ // src/core/mcp-generator.ts
773
+ import os from "os";
774
+ import path3 from "path";
775
+
776
+ // src/core/mcp-parser.ts
777
+ import fs from "fs";
778
+ import path2 from "path";
779
+ function parseMcpConfig(projectRoot) {
780
+ const mcpPath = path2.join(projectRoot, ".rulesync", ".mcp.json");
781
+ if (!fs.existsSync(mcpPath)) {
782
+ return null;
783
+ }
784
+ try {
785
+ const content = fs.readFileSync(mcpPath, "utf-8");
786
+ const rawConfig = JSON.parse(content);
787
+ if (rawConfig.servers && !rawConfig.mcpServers) {
788
+ rawConfig.mcpServers = rawConfig.servers;
789
+ delete rawConfig.servers;
790
+ }
791
+ if (!rawConfig.mcpServers || typeof rawConfig.mcpServers !== "object") {
792
+ throw new Error("Invalid mcp.json: 'mcpServers' field must be an object");
793
+ }
794
+ if (rawConfig.tools) {
795
+ delete rawConfig.tools;
796
+ }
797
+ return { mcpServers: rawConfig.mcpServers };
798
+ } catch (error) {
799
+ throw new Error(
800
+ `Failed to parse mcp.json: ${error instanceof Error ? error.message : String(error)}`
801
+ );
802
+ }
803
+ }
804
+
805
+ // src/core/mcp-generator.ts
806
+ async function generateMcpConfigs(projectRoot, baseDir) {
807
+ const results = [];
808
+ const targetRoot = baseDir || projectRoot;
809
+ const config = parseMcpConfig(projectRoot);
810
+ if (!config) {
811
+ return results;
812
+ }
813
+ const generators = [
814
+ {
815
+ tool: "claude-project",
816
+ path: path3.join(targetRoot, ".mcp.json"),
817
+ generate: () => generateClaudeMcp(config, "project")
818
+ },
819
+ {
820
+ tool: "copilot-editor",
821
+ path: path3.join(targetRoot, ".vscode", "mcp.json"),
822
+ generate: () => generateCopilotMcp(config, "editor")
823
+ },
824
+ {
825
+ tool: "cursor-project",
826
+ path: path3.join(targetRoot, ".cursor", "mcp.json"),
827
+ generate: () => generateCursorMcp(config, "project")
828
+ },
829
+ {
830
+ tool: "cline-project",
831
+ path: path3.join(targetRoot, ".cline", "mcp.json"),
832
+ generate: () => generateClineMcp(config, "project")
833
+ },
834
+ {
835
+ tool: "gemini-project",
836
+ path: path3.join(targetRoot, ".gemini", "settings.json"),
837
+ generate: () => generateGeminiCliMcp(config, "project")
838
+ },
839
+ {
840
+ tool: "roo-project",
841
+ path: path3.join(targetRoot, ".roo", "mcp.json"),
842
+ generate: () => generateRooMcp(config, "project")
843
+ }
844
+ ];
845
+ if (!baseDir) {
846
+ generators.push(
847
+ {
848
+ tool: "claude-global",
849
+ path: path3.join(os.homedir(), ".claude", "settings.json"),
850
+ generate: () => generateClaudeMcp(config, "global")
851
+ },
852
+ {
853
+ tool: "cursor-global",
854
+ path: path3.join(os.homedir(), ".cursor", "mcp.json"),
855
+ generate: () => generateCursorMcp(config, "global")
856
+ },
857
+ {
858
+ tool: "gemini-global",
859
+ path: path3.join(os.homedir(), ".gemini", "settings.json"),
860
+ generate: () => generateGeminiCliMcp(config, "global")
861
+ }
862
+ );
863
+ }
864
+ for (const generator of generators) {
865
+ try {
866
+ const content = generator.generate();
867
+ const parsed = JSON.parse(content);
868
+ if (generator.tool.includes("claude") || generator.tool.includes("cline") || generator.tool.includes("cursor") || generator.tool.includes("gemini") || generator.tool.includes("roo")) {
869
+ if (!parsed.mcpServers || Object.keys(parsed.mcpServers).length === 0) {
870
+ results.push({
871
+ tool: generator.tool,
872
+ path: generator.path,
873
+ status: "skipped"
874
+ });
875
+ continue;
876
+ }
877
+ } else if (generator.tool.includes("copilot")) {
878
+ const key = generator.tool.includes("codingAgent") ? "mcpServers" : "servers";
879
+ if (!parsed[key] || Object.keys(parsed[key]).length === 0) {
880
+ results.push({
881
+ tool: generator.tool,
882
+ path: generator.path,
883
+ status: "skipped"
884
+ });
885
+ continue;
886
+ }
887
+ }
888
+ await writeFileContent(generator.path, content);
889
+ results.push({
890
+ tool: generator.tool,
891
+ path: generator.path,
892
+ status: "success"
893
+ });
894
+ } catch (error) {
895
+ results.push({
896
+ tool: generator.tool,
897
+ path: generator.path,
898
+ status: "error",
899
+ error: error instanceof Error ? error.message : String(error)
900
+ });
901
+ }
902
+ }
903
+ return results;
904
+ }
905
+
558
906
  // src/cli/commands/generate.ts
559
907
  async function generateCommand(options = {}) {
560
908
  const config = getDefaultConfig();
@@ -635,6 +983,30 @@ Generating configurations for base directory: ${baseDir}`);
635
983
  }
636
984
  console.log(`
637
985
  \u{1F389} Successfully generated ${totalOutputs} configuration file(s)!`);
986
+ if (options.verbose) {
987
+ console.log("\nGenerating MCP configurations...");
988
+ }
989
+ for (const baseDir of baseDirs) {
990
+ const mcpResults = await generateMcpConfigs(
991
+ process.cwd(),
992
+ baseDir === process.cwd() ? void 0 : baseDir
993
+ );
994
+ if (mcpResults.length === 0) {
995
+ if (options.verbose) {
996
+ console.log(`No MCP configuration found for ${baseDir}`);
997
+ }
998
+ continue;
999
+ }
1000
+ for (const result of mcpResults) {
1001
+ if (result.status === "success") {
1002
+ console.log(`\u2705 Generated ${result.tool} MCP configuration: ${result.path}`);
1003
+ } else if (result.status === "error") {
1004
+ console.error(`\u274C Failed to generate ${result.tool} MCP configuration: ${result.error}`);
1005
+ } else if (options.verbose && result.status === "skipped") {
1006
+ console.log(`\u23ED\uFE0F Skipped ${result.tool} MCP configuration (no servers configured)`);
1007
+ }
1008
+ }
1009
+ }
638
1010
  } catch (error) {
639
1011
  console.error("\u274C Failed to generate configurations:", error);
640
1012
  process.exit(1);
@@ -643,20 +1015,31 @@ Generating configurations for base directory: ${baseDir}`);
643
1015
 
644
1016
  // src/cli/commands/gitignore.ts
645
1017
  import { existsSync, readFileSync, writeFileSync } from "fs";
646
- import { join as join8 } from "path";
1018
+ import { join as join9 } from "path";
647
1019
  var gitignoreCommand = async () => {
648
- const gitignorePath = join8(process.cwd(), ".gitignore");
1020
+ const gitignorePath = join9(process.cwd(), ".gitignore");
649
1021
  const rulesFilesToIgnore = [
650
1022
  "# Generated by rulesync - AI tool configuration files",
651
1023
  "**/.github/copilot-instructions.md",
652
1024
  "**/.github/instructions/",
653
1025
  "**/.cursor/rules/",
1026
+ "**/.cursorignore",
654
1027
  "**/.clinerules/",
1028
+ "**/.clineignore",
655
1029
  "**/CLAUDE.md",
656
1030
  "**/.claude/memories/",
657
1031
  "**/.roo/rules/",
1032
+ "**/.rooignore",
1033
+ "**/.copilotignore",
658
1034
  "**/GEMINI.md",
659
- "**/.gemini/memories/"
1035
+ "**/.gemini/memories/",
1036
+ "**/.aiexclude",
1037
+ "**/.mcp.json",
1038
+ "**/.cursor/mcp.json",
1039
+ "**/.cline/mcp.json",
1040
+ "**/.vscode/mcp.json",
1041
+ "**/.gemini/settings.json",
1042
+ "**/.roo/mcp.json"
660
1043
  ];
661
1044
  let gitignoreContent = "";
662
1045
  if (existsSync(gitignorePath)) {
@@ -687,15 +1070,17 @@ ${linesToAdd.join("\n")}
687
1070
  };
688
1071
 
689
1072
  // src/core/importer.ts
690
- import { join as join14 } from "path";
1073
+ import { join as join16 } from "path";
691
1074
  import matter4 from "gray-matter";
692
1075
 
693
1076
  // src/parsers/claudecode.ts
694
- import { basename as basename2, join as join9 } from "path";
1077
+ import { basename as basename2, join as join10 } from "path";
695
1078
  async function parseClaudeConfiguration(baseDir = process.cwd()) {
696
1079
  const errors = [];
697
1080
  const rules = [];
698
- const claudeFilePath = join9(baseDir, "CLAUDE.md");
1081
+ let ignorePatterns;
1082
+ let mcpServers;
1083
+ const claudeFilePath = join10(baseDir, "CLAUDE.md");
699
1084
  if (!await fileExists(claudeFilePath)) {
700
1085
  errors.push("CLAUDE.md file not found");
701
1086
  return { rules, errors };
@@ -706,16 +1091,32 @@ async function parseClaudeConfiguration(baseDir = process.cwd()) {
706
1091
  if (mainRule) {
707
1092
  rules.push(mainRule);
708
1093
  }
709
- const memoryDir = join9(baseDir, ".claude", "memories");
1094
+ const memoryDir = join10(baseDir, ".claude", "memories");
710
1095
  if (await fileExists(memoryDir)) {
711
1096
  const memoryRules = await parseClaudeMemoryFiles(memoryDir);
712
1097
  rules.push(...memoryRules);
713
1098
  }
1099
+ const settingsPath = join10(baseDir, ".claude", "settings.json");
1100
+ if (await fileExists(settingsPath)) {
1101
+ const settingsResult = await parseClaudeSettings(settingsPath);
1102
+ if (settingsResult.ignorePatterns) {
1103
+ ignorePatterns = settingsResult.ignorePatterns;
1104
+ }
1105
+ if (settingsResult.mcpServers) {
1106
+ mcpServers = settingsResult.mcpServers;
1107
+ }
1108
+ errors.push(...settingsResult.errors);
1109
+ }
714
1110
  } catch (error) {
715
1111
  const errorMessage = error instanceof Error ? error.message : String(error);
716
1112
  errors.push(`Failed to parse Claude configuration: ${errorMessage}`);
717
1113
  }
718
- return { rules, errors };
1114
+ return {
1115
+ rules,
1116
+ errors,
1117
+ ...ignorePatterns && { ignorePatterns },
1118
+ ...mcpServers && { mcpServers }
1119
+ };
719
1120
  }
720
1121
  function parseClaudeMainFile(content, filepath) {
721
1122
  const lines = content.split("\n");
@@ -752,7 +1153,7 @@ async function parseClaudeMemoryFiles(memoryDir) {
752
1153
  const files = await readdir2(memoryDir);
753
1154
  for (const file of files) {
754
1155
  if (file.endsWith(".md")) {
755
- const filePath = join9(memoryDir, file);
1156
+ const filePath = join10(memoryDir, file);
756
1157
  const content = await readFileContent(filePath);
757
1158
  if (content.trim()) {
758
1159
  const filename = basename2(file, ".md");
@@ -775,47 +1176,113 @@ async function parseClaudeMemoryFiles(memoryDir) {
775
1176
  }
776
1177
  return rules;
777
1178
  }
1179
+ async function parseClaudeSettings(settingsPath) {
1180
+ const errors = [];
1181
+ let ignorePatterns;
1182
+ let mcpServers;
1183
+ try {
1184
+ const content = await readFileContent(settingsPath);
1185
+ const settings = JSON.parse(content);
1186
+ if (settings.permissions?.deny) {
1187
+ const readPatterns = settings.permissions.deny.filter((rule) => rule.startsWith("Read(") && rule.endsWith(")")).map((rule) => {
1188
+ const match = rule.match(/^Read\((.+)\)$/);
1189
+ return match ? match[1] : null;
1190
+ }).filter((pattern) => pattern !== null);
1191
+ if (readPatterns.length > 0) {
1192
+ ignorePatterns = readPatterns;
1193
+ }
1194
+ }
1195
+ if (settings.mcpServers && Object.keys(settings.mcpServers).length > 0) {
1196
+ mcpServers = settings.mcpServers;
1197
+ }
1198
+ } catch (error) {
1199
+ const errorMessage = error instanceof Error ? error.message : String(error);
1200
+ errors.push(`Failed to parse settings.json: ${errorMessage}`);
1201
+ }
1202
+ return {
1203
+ errors,
1204
+ ...ignorePatterns && { ignorePatterns },
1205
+ ...mcpServers && { mcpServers }
1206
+ };
1207
+ }
778
1208
 
779
1209
  // src/parsers/cline.ts
780
- import { join as join10 } from "path";
1210
+ import { join as join11 } from "path";
781
1211
  async function parseClineConfiguration(baseDir = process.cwd()) {
782
1212
  const errors = [];
783
1213
  const rules = [];
784
- const clineFilePath = join10(baseDir, ".cline", "instructions.md");
785
- if (!await fileExists(clineFilePath)) {
786
- errors.push(".cline/instructions.md file not found");
787
- return { rules, errors };
1214
+ const clineFilePath = join11(baseDir, ".cline", "instructions.md");
1215
+ if (await fileExists(clineFilePath)) {
1216
+ try {
1217
+ const content = await readFileContent(clineFilePath);
1218
+ if (content.trim()) {
1219
+ const frontmatter = {
1220
+ root: false,
1221
+ targets: ["cline"],
1222
+ description: "Cline instructions",
1223
+ globs: ["**/*"]
1224
+ };
1225
+ rules.push({
1226
+ frontmatter,
1227
+ content: content.trim(),
1228
+ filename: "cline-instructions",
1229
+ filepath: clineFilePath
1230
+ });
1231
+ }
1232
+ } catch (error) {
1233
+ const errorMessage = error instanceof Error ? error.message : String(error);
1234
+ errors.push(`Failed to parse .cline/instructions.md: ${errorMessage}`);
1235
+ }
788
1236
  }
789
- try {
790
- const content = await readFileContent(clineFilePath);
791
- if (content.trim()) {
792
- const frontmatter = {
793
- root: false,
794
- targets: ["cline"],
795
- description: "Cline AI assistant instructions",
796
- globs: ["**/*"]
797
- };
798
- rules.push({
799
- frontmatter,
800
- content: content.trim(),
801
- filename: "cline-instructions",
802
- filepath: clineFilePath
803
- });
1237
+ const clinerulesDirPath = join11(baseDir, ".clinerules");
1238
+ if (await fileExists(clinerulesDirPath)) {
1239
+ try {
1240
+ const { readdir: readdir2 } = await import("fs/promises");
1241
+ const files = await readdir2(clinerulesDirPath);
1242
+ for (const file of files) {
1243
+ if (file.endsWith(".md")) {
1244
+ const filePath = join11(clinerulesDirPath, file);
1245
+ try {
1246
+ const content = await readFileContent(filePath);
1247
+ if (content.trim()) {
1248
+ const filename = file.replace(".md", "");
1249
+ const frontmatter = {
1250
+ root: false,
1251
+ targets: ["cline"],
1252
+ description: `Cline rule: ${filename}`,
1253
+ globs: ["**/*"]
1254
+ };
1255
+ rules.push({
1256
+ frontmatter,
1257
+ content: content.trim(),
1258
+ filename: `cline-${filename}`,
1259
+ filepath: filePath
1260
+ });
1261
+ }
1262
+ } catch (error) {
1263
+ const errorMessage = error instanceof Error ? error.message : String(error);
1264
+ errors.push(`Failed to parse ${filePath}: ${errorMessage}`);
1265
+ }
1266
+ }
1267
+ }
1268
+ } catch (error) {
1269
+ const errorMessage = error instanceof Error ? error.message : String(error);
1270
+ errors.push(`Failed to parse .clinerules files: ${errorMessage}`);
804
1271
  }
805
- } catch (error) {
806
- const errorMessage = error instanceof Error ? error.message : String(error);
807
- errors.push(`Failed to parse Cline configuration: ${errorMessage}`);
1272
+ }
1273
+ if (rules.length === 0) {
1274
+ errors.push("No Cline configuration files found (.cline/instructions.md or .clinerules/*.md)");
808
1275
  }
809
1276
  return { rules, errors };
810
1277
  }
811
1278
 
812
1279
  // src/parsers/copilot.ts
813
- import { basename as basename3, join as join11 } from "path";
1280
+ import { basename as basename3, join as join12 } from "path";
814
1281
  import matter2 from "gray-matter";
815
1282
  async function parseCopilotConfiguration(baseDir = process.cwd()) {
816
1283
  const errors = [];
817
1284
  const rules = [];
818
- const copilotFilePath = join11(baseDir, ".github", "copilot-instructions.md");
1285
+ const copilotFilePath = join12(baseDir, ".github", "copilot-instructions.md");
819
1286
  if (await fileExists(copilotFilePath)) {
820
1287
  try {
821
1288
  const rawContent = await readFileContent(copilotFilePath);
@@ -840,14 +1307,14 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
840
1307
  errors.push(`Failed to parse copilot-instructions.md: ${errorMessage}`);
841
1308
  }
842
1309
  }
843
- const instructionsDir = join11(baseDir, ".github", "instructions");
1310
+ const instructionsDir = join12(baseDir, ".github", "instructions");
844
1311
  if (await fileExists(instructionsDir)) {
845
1312
  try {
846
1313
  const { readdir: readdir2 } = await import("fs/promises");
847
1314
  const files = await readdir2(instructionsDir);
848
1315
  for (const file of files) {
849
1316
  if (file.endsWith(".instructions.md")) {
850
- const filePath = join11(instructionsDir, file);
1317
+ const filePath = join12(instructionsDir, file);
851
1318
  const rawContent = await readFileContent(filePath);
852
1319
  const parsed = matter2(rawContent);
853
1320
  const content = parsed.content.trim();
@@ -882,7 +1349,7 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
882
1349
  }
883
1350
 
884
1351
  // src/parsers/cursor.ts
885
- import { basename as basename4, join as join12 } from "path";
1352
+ import { basename as basename4, join as join13 } from "path";
886
1353
  import matter3 from "gray-matter";
887
1354
  import yaml from "js-yaml";
888
1355
  var customMatterOptions = {
@@ -906,7 +1373,9 @@ var customMatterOptions = {
906
1373
  async function parseCursorConfiguration(baseDir = process.cwd()) {
907
1374
  const errors = [];
908
1375
  const rules = [];
909
- const cursorFilePath = join12(baseDir, ".cursorrules");
1376
+ let ignorePatterns;
1377
+ let mcpServers;
1378
+ const cursorFilePath = join13(baseDir, ".cursorrules");
910
1379
  if (await fileExists(cursorFilePath)) {
911
1380
  try {
912
1381
  const rawContent = await readFileContent(cursorFilePath);
@@ -931,14 +1400,14 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
931
1400
  errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
932
1401
  }
933
1402
  }
934
- const cursorRulesDir = join12(baseDir, ".cursor", "rules");
1403
+ const cursorRulesDir = join13(baseDir, ".cursor", "rules");
935
1404
  if (await fileExists(cursorRulesDir)) {
936
1405
  try {
937
1406
  const { readdir: readdir2 } = await import("fs/promises");
938
1407
  const files = await readdir2(cursorRulesDir);
939
1408
  for (const file of files) {
940
1409
  if (file.endsWith(".mdc")) {
941
- const filePath = join12(cursorRulesDir, file);
1410
+ const filePath = join13(cursorRulesDir, file);
942
1411
  try {
943
1412
  const rawContent = await readFileContent(filePath);
944
1413
  const parsed = matter3(rawContent, customMatterOptions);
@@ -972,38 +1441,244 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
972
1441
  if (rules.length === 0) {
973
1442
  errors.push("No Cursor configuration files found (.cursorrules or .cursor/rules/*.mdc)");
974
1443
  }
975
- return { rules, errors };
1444
+ const cursorIgnorePath = join13(baseDir, ".cursorignore");
1445
+ if (await fileExists(cursorIgnorePath)) {
1446
+ try {
1447
+ const content = await readFileContent(cursorIgnorePath);
1448
+ const patterns = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
1449
+ if (patterns.length > 0) {
1450
+ ignorePatterns = patterns;
1451
+ }
1452
+ } catch (error) {
1453
+ const errorMessage = error instanceof Error ? error.message : String(error);
1454
+ errors.push(`Failed to parse .cursorignore: ${errorMessage}`);
1455
+ }
1456
+ }
1457
+ const cursorMcpPath = join13(baseDir, ".cursor", "mcp.json");
1458
+ if (await fileExists(cursorMcpPath)) {
1459
+ try {
1460
+ const content = await readFileContent(cursorMcpPath);
1461
+ const mcp = JSON.parse(content);
1462
+ if (mcp.mcpServers && Object.keys(mcp.mcpServers).length > 0) {
1463
+ mcpServers = mcp.mcpServers;
1464
+ }
1465
+ } catch (error) {
1466
+ const errorMessage = error instanceof Error ? error.message : String(error);
1467
+ errors.push(`Failed to parse .cursor/mcp.json: ${errorMessage}`);
1468
+ }
1469
+ }
1470
+ return {
1471
+ rules,
1472
+ errors,
1473
+ ...ignorePatterns && { ignorePatterns },
1474
+ ...mcpServers && { mcpServers }
1475
+ };
976
1476
  }
977
1477
 
978
- // src/parsers/roo.ts
979
- import { join as join13 } from "path";
980
- async function parseRooConfiguration(baseDir = process.cwd()) {
1478
+ // src/parsers/geminicli.ts
1479
+ import { basename as basename5, join as join14 } from "path";
1480
+ async function parseGeminiConfiguration(baseDir = process.cwd()) {
981
1481
  const errors = [];
982
1482
  const rules = [];
983
- const rooFilePath = join13(baseDir, ".roo", "instructions.md");
984
- if (!await fileExists(rooFilePath)) {
985
- errors.push(".roo/instructions.md file not found");
1483
+ let ignorePatterns;
1484
+ let mcpServers;
1485
+ const geminiFilePath = join14(baseDir, "GEMINI.md");
1486
+ if (!await fileExists(geminiFilePath)) {
1487
+ errors.push("GEMINI.md file not found");
986
1488
  return { rules, errors };
987
1489
  }
988
1490
  try {
989
- const content = await readFileContent(rooFilePath);
990
- if (content.trim()) {
991
- const frontmatter = {
992
- root: false,
993
- targets: ["roo"],
994
- description: "Roo Code AI assistant instructions",
995
- globs: ["**/*"]
996
- };
997
- rules.push({
998
- frontmatter,
999
- content: content.trim(),
1000
- filename: "roo-instructions",
1001
- filepath: rooFilePath
1002
- });
1491
+ const geminiContent = await readFileContent(geminiFilePath);
1492
+ const mainRule = parseGeminiMainFile(geminiContent, geminiFilePath);
1493
+ if (mainRule) {
1494
+ rules.push(mainRule);
1495
+ }
1496
+ const memoryDir = join14(baseDir, ".gemini", "memories");
1497
+ if (await fileExists(memoryDir)) {
1498
+ const memoryRules = await parseGeminiMemoryFiles(memoryDir);
1499
+ rules.push(...memoryRules);
1500
+ }
1501
+ const settingsPath = join14(baseDir, ".gemini", "settings.json");
1502
+ if (await fileExists(settingsPath)) {
1503
+ const settingsResult = await parseGeminiSettings(settingsPath);
1504
+ if (settingsResult.ignorePatterns) {
1505
+ ignorePatterns = settingsResult.ignorePatterns;
1506
+ }
1507
+ if (settingsResult.mcpServers) {
1508
+ mcpServers = settingsResult.mcpServers;
1509
+ }
1510
+ errors.push(...settingsResult.errors);
1511
+ }
1512
+ const aiexcludePath = join14(baseDir, ".aiexclude");
1513
+ if (await fileExists(aiexcludePath)) {
1514
+ const aiexcludePatterns = await parseAiexclude(aiexcludePath);
1515
+ if (aiexcludePatterns.length > 0) {
1516
+ ignorePatterns = ignorePatterns ? [...ignorePatterns, ...aiexcludePatterns] : aiexcludePatterns;
1517
+ }
1003
1518
  }
1004
1519
  } catch (error) {
1005
1520
  const errorMessage = error instanceof Error ? error.message : String(error);
1006
- errors.push(`Failed to parse Roo configuration: ${errorMessage}`);
1521
+ errors.push(`Failed to parse Gemini configuration: ${errorMessage}`);
1522
+ }
1523
+ return {
1524
+ rules,
1525
+ errors,
1526
+ ...ignorePatterns && { ignorePatterns },
1527
+ ...mcpServers && { mcpServers }
1528
+ };
1529
+ }
1530
+ function parseGeminiMainFile(content, filepath) {
1531
+ const lines = content.split("\n");
1532
+ let contentStartIndex = 0;
1533
+ if (lines.some((line) => line.includes("| Document | Description | File Patterns |"))) {
1534
+ const tableEndIndex = lines.findIndex(
1535
+ (line, index) => index > 0 && line.trim() === "" && lines[index - 1]?.includes("|") && !lines[index + 1]?.includes("|")
1536
+ );
1537
+ if (tableEndIndex !== -1) {
1538
+ contentStartIndex = tableEndIndex + 1;
1539
+ }
1540
+ }
1541
+ const mainContent = lines.slice(contentStartIndex).join("\n").trim();
1542
+ if (!mainContent) {
1543
+ return null;
1544
+ }
1545
+ const frontmatter = {
1546
+ root: false,
1547
+ targets: ["geminicli"],
1548
+ description: "Main Gemini CLI configuration",
1549
+ globs: ["**/*"]
1550
+ };
1551
+ return {
1552
+ frontmatter,
1553
+ content: mainContent,
1554
+ filename: "gemini-main",
1555
+ filepath
1556
+ };
1557
+ }
1558
+ async function parseGeminiMemoryFiles(memoryDir) {
1559
+ const rules = [];
1560
+ try {
1561
+ const { readdir: readdir2 } = await import("fs/promises");
1562
+ const files = await readdir2(memoryDir);
1563
+ for (const file of files) {
1564
+ if (file.endsWith(".md")) {
1565
+ const filePath = join14(memoryDir, file);
1566
+ const content = await readFileContent(filePath);
1567
+ if (content.trim()) {
1568
+ const filename = basename5(file, ".md");
1569
+ const frontmatter = {
1570
+ root: false,
1571
+ targets: ["geminicli"],
1572
+ description: `Memory file: ${filename}`,
1573
+ globs: ["**/*"]
1574
+ };
1575
+ rules.push({
1576
+ frontmatter,
1577
+ content: content.trim(),
1578
+ filename: `gemini-memory-${filename}`,
1579
+ filepath: filePath
1580
+ });
1581
+ }
1582
+ }
1583
+ }
1584
+ } catch (_error) {
1585
+ }
1586
+ return rules;
1587
+ }
1588
+ async function parseGeminiSettings(settingsPath) {
1589
+ const errors = [];
1590
+ let mcpServers;
1591
+ try {
1592
+ const content = await readFileContent(settingsPath);
1593
+ const settings = JSON.parse(content);
1594
+ if (settings.mcpServers && Object.keys(settings.mcpServers).length > 0) {
1595
+ mcpServers = settings.mcpServers;
1596
+ }
1597
+ } catch (error) {
1598
+ const errorMessage = error instanceof Error ? error.message : String(error);
1599
+ errors.push(`Failed to parse settings.json: ${errorMessage}`);
1600
+ }
1601
+ return {
1602
+ errors,
1603
+ ...mcpServers && { mcpServers }
1604
+ };
1605
+ }
1606
+ async function parseAiexclude(aiexcludePath) {
1607
+ try {
1608
+ const content = await readFileContent(aiexcludePath);
1609
+ const patterns = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
1610
+ return patterns;
1611
+ } catch (_error) {
1612
+ return [];
1613
+ }
1614
+ }
1615
+
1616
+ // src/parsers/roo.ts
1617
+ import { join as join15 } from "path";
1618
+ async function parseRooConfiguration(baseDir = process.cwd()) {
1619
+ const errors = [];
1620
+ const rules = [];
1621
+ const rooFilePath = join15(baseDir, ".roo", "instructions.md");
1622
+ if (await fileExists(rooFilePath)) {
1623
+ try {
1624
+ const content = await readFileContent(rooFilePath);
1625
+ if (content.trim()) {
1626
+ const frontmatter = {
1627
+ root: false,
1628
+ targets: ["roo"],
1629
+ description: "Roo Code instructions",
1630
+ globs: ["**/*"]
1631
+ };
1632
+ rules.push({
1633
+ frontmatter,
1634
+ content: content.trim(),
1635
+ filename: "roo-instructions",
1636
+ filepath: rooFilePath
1637
+ });
1638
+ }
1639
+ } catch (error) {
1640
+ const errorMessage = error instanceof Error ? error.message : String(error);
1641
+ errors.push(`Failed to parse .roo/instructions.md: ${errorMessage}`);
1642
+ }
1643
+ }
1644
+ const rooRulesDir = join15(baseDir, ".roo", "rules");
1645
+ if (await fileExists(rooRulesDir)) {
1646
+ try {
1647
+ const { readdir: readdir2 } = await import("fs/promises");
1648
+ const files = await readdir2(rooRulesDir);
1649
+ for (const file of files) {
1650
+ if (file.endsWith(".md")) {
1651
+ const filePath = join15(rooRulesDir, file);
1652
+ try {
1653
+ const content = await readFileContent(filePath);
1654
+ if (content.trim()) {
1655
+ const filename = file.replace(".md", "");
1656
+ const frontmatter = {
1657
+ root: false,
1658
+ targets: ["roo"],
1659
+ description: `Roo rule: ${filename}`,
1660
+ globs: ["**/*"]
1661
+ };
1662
+ rules.push({
1663
+ frontmatter,
1664
+ content: content.trim(),
1665
+ filename: `roo-${filename}`,
1666
+ filepath: filePath
1667
+ });
1668
+ }
1669
+ } catch (error) {
1670
+ const errorMessage = error instanceof Error ? error.message : String(error);
1671
+ errors.push(`Failed to parse ${filePath}: ${errorMessage}`);
1672
+ }
1673
+ }
1674
+ }
1675
+ } catch (error) {
1676
+ const errorMessage = error instanceof Error ? error.message : String(error);
1677
+ errors.push(`Failed to parse .roo/rules files: ${errorMessage}`);
1678
+ }
1679
+ }
1680
+ if (rules.length === 0) {
1681
+ errors.push("No Roo Code configuration files found (.roo/instructions.md or .roo/rules/*.md)");
1007
1682
  }
1008
1683
  return { rules, errors };
1009
1684
  }
@@ -1013,6 +1688,8 @@ async function importConfiguration(options) {
1013
1688
  const { tool, baseDir = process.cwd(), rulesDir = ".rulesync", verbose = false } = options;
1014
1689
  const errors = [];
1015
1690
  let rules = [];
1691
+ let ignorePatterns;
1692
+ let mcpServers;
1016
1693
  if (verbose) {
1017
1694
  console.log(`Importing ${tool} configuration from ${baseDir}...`);
1018
1695
  }
@@ -1022,12 +1699,16 @@ async function importConfiguration(options) {
1022
1699
  const claudeResult = await parseClaudeConfiguration(baseDir);
1023
1700
  rules = claudeResult.rules;
1024
1701
  errors.push(...claudeResult.errors);
1702
+ ignorePatterns = claudeResult.ignorePatterns;
1703
+ mcpServers = claudeResult.mcpServers;
1025
1704
  break;
1026
1705
  }
1027
1706
  case "cursor": {
1028
1707
  const cursorResult = await parseCursorConfiguration(baseDir);
1029
1708
  rules = cursorResult.rules;
1030
1709
  errors.push(...cursorResult.errors);
1710
+ ignorePatterns = cursorResult.ignorePatterns;
1711
+ mcpServers = cursorResult.mcpServers;
1031
1712
  break;
1032
1713
  }
1033
1714
  case "copilot": {
@@ -1048,6 +1729,14 @@ async function importConfiguration(options) {
1048
1729
  errors.push(...rooResult.errors);
1049
1730
  break;
1050
1731
  }
1732
+ case "geminicli": {
1733
+ const geminiResult = await parseGeminiConfiguration(baseDir);
1734
+ rules = geminiResult.rules;
1735
+ errors.push(...geminiResult.errors);
1736
+ ignorePatterns = geminiResult.ignorePatterns;
1737
+ mcpServers = geminiResult.mcpServers;
1738
+ break;
1739
+ }
1051
1740
  default:
1052
1741
  errors.push(`Unsupported tool: ${tool}`);
1053
1742
  return { success: false, rulesCreated: 0, errors };
@@ -1057,10 +1746,10 @@ async function importConfiguration(options) {
1057
1746
  errors.push(`Failed to parse ${tool} configuration: ${errorMessage}`);
1058
1747
  return { success: false, rulesCreated: 0, errors };
1059
1748
  }
1060
- if (rules.length === 0) {
1749
+ if (rules.length === 0 && !ignorePatterns && !mcpServers) {
1061
1750
  return { success: false, rulesCreated: 0, errors };
1062
1751
  }
1063
- const rulesDirPath = join14(baseDir, rulesDir);
1752
+ const rulesDirPath = join16(baseDir, rulesDir);
1064
1753
  try {
1065
1754
  const { mkdir: mkdir3 } = await import("fs/promises");
1066
1755
  await mkdir3(rulesDirPath, { recursive: true });
@@ -1074,7 +1763,7 @@ async function importConfiguration(options) {
1074
1763
  try {
1075
1764
  const baseFilename = `${tool}__${rule.filename}`;
1076
1765
  const filename = await generateUniqueFilename(rulesDirPath, baseFilename);
1077
- const filePath = join14(rulesDirPath, `${filename}.md`);
1766
+ const filePath = join16(rulesDirPath, `${filename}.md`);
1078
1767
  const content = generateRuleFileContent(rule);
1079
1768
  await writeFileContent(filePath, content);
1080
1769
  rulesCreated++;
@@ -1086,10 +1775,44 @@ async function importConfiguration(options) {
1086
1775
  errors.push(`Failed to create rule file for ${rule.filename}: ${errorMessage}`);
1087
1776
  }
1088
1777
  }
1778
+ let ignoreFileCreated = false;
1779
+ if (ignorePatterns && ignorePatterns.length > 0) {
1780
+ try {
1781
+ const rulesyncignorePath = join16(baseDir, ".rulesyncignore");
1782
+ const ignoreContent = `${ignorePatterns.join("\n")}
1783
+ `;
1784
+ await writeFileContent(rulesyncignorePath, ignoreContent);
1785
+ ignoreFileCreated = true;
1786
+ if (verbose) {
1787
+ console.log(`\u2705 Created .rulesyncignore with ${ignorePatterns.length} patterns`);
1788
+ }
1789
+ } catch (error) {
1790
+ const errorMessage = error instanceof Error ? error.message : String(error);
1791
+ errors.push(`Failed to create .rulesyncignore: ${errorMessage}`);
1792
+ }
1793
+ }
1794
+ let mcpFileCreated = false;
1795
+ if (mcpServers && Object.keys(mcpServers).length > 0) {
1796
+ try {
1797
+ const mcpPath = join16(baseDir, rulesDir, ".mcp.json");
1798
+ const mcpContent = `${JSON.stringify({ mcpServers }, null, 2)}
1799
+ `;
1800
+ await writeFileContent(mcpPath, mcpContent);
1801
+ mcpFileCreated = true;
1802
+ if (verbose) {
1803
+ console.log(`\u2705 Created .mcp.json with ${Object.keys(mcpServers).length} servers`);
1804
+ }
1805
+ } catch (error) {
1806
+ const errorMessage = error instanceof Error ? error.message : String(error);
1807
+ errors.push(`Failed to create .mcp.json: ${errorMessage}`);
1808
+ }
1809
+ }
1089
1810
  return {
1090
- success: rulesCreated > 0,
1811
+ success: rulesCreated > 0 || ignoreFileCreated || mcpFileCreated,
1091
1812
  rulesCreated,
1092
- errors
1813
+ errors,
1814
+ ignoreFileCreated,
1815
+ mcpFileCreated
1093
1816
  };
1094
1817
  }
1095
1818
  function generateRuleFileContent(rule) {
@@ -1099,7 +1822,7 @@ function generateRuleFileContent(rule) {
1099
1822
  async function generateUniqueFilename(rulesDir, baseFilename) {
1100
1823
  let filename = baseFilename;
1101
1824
  let counter = 1;
1102
- while (await fileExists(join14(rulesDir, `${filename}.md`))) {
1825
+ while (await fileExists(join16(rulesDir, `${filename}.md`))) {
1103
1826
  filename = `${baseFilename}-${counter}`;
1104
1827
  counter++;
1105
1828
  }
@@ -1117,57 +1840,54 @@ async function importCommand(options = {}) {
1117
1840
  if (options.geminicli) tools.push("geminicli");
1118
1841
  if (tools.length === 0) {
1119
1842
  console.error(
1120
- "\u274C Please specify at least one tool to import from (--claudecode, --cursor, --copilot, --cline, --roo, --geminicli)"
1843
+ "\u274C Please specify one tool to import from (--claudecode, --cursor, --copilot, --cline, --roo, --geminicli)"
1121
1844
  );
1122
1845
  process.exit(1);
1123
1846
  }
1124
- console.log("Importing configuration files...");
1125
- let totalRulesCreated = 0;
1126
- const allErrors = [];
1127
- for (const tool of tools) {
1128
- if (options.verbose) {
1129
- console.log(`
1130
- Importing from ${tool}...`);
1131
- }
1132
- try {
1133
- const result = await importConfiguration({
1134
- tool,
1135
- verbose: options.verbose ?? false
1136
- });
1137
- if (result.success) {
1138
- console.log(`\u2705 Imported ${result.rulesCreated} rule(s) from ${tool}`);
1139
- totalRulesCreated += result.rulesCreated;
1140
- } else if (result.errors.length > 0) {
1141
- console.warn(`\u26A0\uFE0F Failed to import from ${tool}: ${result.errors[0]}`);
1142
- if (options.verbose) {
1143
- allErrors.push(...result.errors);
1144
- }
1145
- }
1146
- } catch (error) {
1147
- const errorMessage = error instanceof Error ? error.message : String(error);
1148
- console.error(`\u274C Error importing from ${tool}: ${errorMessage}`);
1149
- allErrors.push(`${tool}: ${errorMessage}`);
1150
- }
1151
- }
1152
- if (totalRulesCreated > 0) {
1153
- console.log(`
1154
- \u{1F389} Successfully imported ${totalRulesCreated} rule(s) total!`);
1155
- console.log("You can now run 'rulesync generate' to create tool-specific configurations.");
1156
- } else {
1157
- console.warn(
1158
- "\n\u26A0\uFE0F No rules were imported. Please check that configuration files exist for the selected tools."
1847
+ if (tools.length > 1) {
1848
+ console.error(
1849
+ "\u274C Only one tool can be specified at a time. Please run the import command separately for each tool."
1159
1850
  );
1851
+ process.exit(1);
1852
+ }
1853
+ const tool = tools[0];
1854
+ if (!tool) {
1855
+ console.error("Error: No tool specified");
1856
+ process.exit(1);
1160
1857
  }
1161
- if (options.verbose && allErrors.length > 0) {
1162
- console.log("\nDetailed errors:");
1163
- for (const error of allErrors) {
1164
- console.log(` - ${error}`);
1858
+ console.log(`Importing configuration files from ${tool}...`);
1859
+ try {
1860
+ const result = await importConfiguration({
1861
+ tool,
1862
+ verbose: options.verbose ?? false
1863
+ });
1864
+ if (result.success) {
1865
+ console.log(`\u2705 Imported ${result.rulesCreated} rule(s) from ${tool}`);
1866
+ if (result.ignoreFileCreated) {
1867
+ console.log("\u2705 Created .rulesyncignore file from ignore patterns");
1868
+ }
1869
+ if (result.mcpFileCreated) {
1870
+ console.log("\u2705 Created .rulesync/.mcp.json file from MCP configuration");
1871
+ }
1872
+ console.log("You can now run 'rulesync generate' to create tool-specific configurations.");
1873
+ } else if (result.errors.length > 0) {
1874
+ console.warn(`\u26A0\uFE0F Failed to import from ${tool}: ${result.errors[0]}`);
1875
+ if (options.verbose && result.errors.length > 1) {
1876
+ console.log("\nDetailed errors:");
1877
+ for (const error of result.errors) {
1878
+ console.log(` - ${error}`);
1879
+ }
1880
+ }
1165
1881
  }
1882
+ } catch (error) {
1883
+ const errorMessage = error instanceof Error ? error.message : String(error);
1884
+ console.error(`\u274C Error importing from ${tool}: ${errorMessage}`);
1885
+ process.exit(1);
1166
1886
  }
1167
1887
  }
1168
1888
 
1169
1889
  // src/cli/commands/init.ts
1170
- import { join as join15 } from "path";
1890
+ import { join as join17 } from "path";
1171
1891
  async function initCommand() {
1172
1892
  const aiRulesDir = ".rulesync";
1173
1893
  console.log("Initializing rulesync...");
@@ -1297,7 +2017,7 @@ globs: ["src/api/**/*.ts", "src/services/**/*.ts", "src/models/**/*.ts"]
1297
2017
  }
1298
2018
  ];
1299
2019
  for (const file of sampleFiles) {
1300
- const filepath = join15(aiRulesDir, file.filename);
2020
+ const filepath = join17(aiRulesDir, file.filename);
1301
2021
  if (!await fileExists(filepath)) {
1302
2022
  await writeFileContent(filepath, file.content);
1303
2023
  console.log(`Created ${filepath}`);
@@ -1410,11 +2130,11 @@ async function watchCommand() {
1410
2130
  persistent: true
1411
2131
  });
1412
2132
  let isGenerating = false;
1413
- const handleChange = async (path2) => {
2133
+ const handleChange = async (path4) => {
1414
2134
  if (isGenerating) return;
1415
2135
  isGenerating = true;
1416
2136
  console.log(`
1417
- \u{1F4DD} Detected change in ${path2}`);
2137
+ \u{1F4DD} Detected change in ${path4}`);
1418
2138
  try {
1419
2139
  await generateCommand({ verbose: false });
1420
2140
  console.log("\u2705 Regenerated configuration files");
@@ -1424,10 +2144,10 @@ async function watchCommand() {
1424
2144
  isGenerating = false;
1425
2145
  }
1426
2146
  };
1427
- watcher.on("change", handleChange).on("add", handleChange).on("unlink", (path2) => {
2147
+ watcher.on("change", handleChange).on("add", handleChange).on("unlink", (path4) => {
1428
2148
  console.log(`
1429
- \u{1F5D1}\uFE0F Removed ${path2}`);
1430
- handleChange(path2);
2149
+ \u{1F5D1}\uFE0F Removed ${path4}`);
2150
+ handleChange(path4);
1431
2151
  }).on("error", (error) => {
1432
2152
  console.error("\u274C Watcher error:", error);
1433
2153
  });
@@ -1440,7 +2160,7 @@ async function watchCommand() {
1440
2160
 
1441
2161
  // src/cli/index.ts
1442
2162
  var program = new Command();
1443
- program.name("rulesync").description("Unified AI rules management CLI tool").version("0.34.0");
2163
+ program.name("rulesync").description("Unified AI rules management CLI tool").version("0.36.0");
1444
2164
  program.command("init").description("Initialize rulesync in current directory").action(initCommand);
1445
2165
  program.command("add <filename>").description("Add a new rule file").action(addCommand);
1446
2166
  program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);