rulesync 0.34.0 → 0.37.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,345 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
26
  mod
24
27
  ));
25
28
 
29
+ // src/schemas/mcp.ts
30
+ var import_zod, ToolTargetSchema, WildcardTargetSchema, SpecificTargetsSchema, RulesyncTargetsSchema, McpTransportTypeSchema, McpServerBaseSchema, RulesyncMcpServerSchema;
31
+ var init_mcp = __esm({
32
+ "src/schemas/mcp.ts"() {
33
+ "use strict";
34
+ import_zod = require("zod");
35
+ ToolTargetSchema = import_zod.z.enum([
36
+ "copilot",
37
+ "cursor",
38
+ "cline",
39
+ "claudecode",
40
+ "claude",
41
+ "roo",
42
+ "geminicli"
43
+ ]);
44
+ WildcardTargetSchema = import_zod.z.tuple([import_zod.z.literal("*")]);
45
+ SpecificTargetsSchema = import_zod.z.array(ToolTargetSchema);
46
+ RulesyncTargetsSchema = import_zod.z.union([SpecificTargetsSchema, WildcardTargetSchema]);
47
+ McpTransportTypeSchema = import_zod.z.enum(["stdio", "sse", "http"]);
48
+ McpServerBaseSchema = import_zod.z.object({
49
+ command: import_zod.z.string().optional(),
50
+ args: import_zod.z.array(import_zod.z.string()).optional(),
51
+ url: import_zod.z.string().optional(),
52
+ httpUrl: import_zod.z.string().optional(),
53
+ env: import_zod.z.record(import_zod.z.string()).optional(),
54
+ disabled: import_zod.z.boolean().optional(),
55
+ networkTimeout: import_zod.z.number().optional(),
56
+ timeout: import_zod.z.number().optional(),
57
+ trust: import_zod.z.boolean().optional(),
58
+ cwd: import_zod.z.string().optional(),
59
+ transport: McpTransportTypeSchema.optional(),
60
+ type: import_zod.z.enum(["sse", "streamable-http"]).optional(),
61
+ alwaysAllow: import_zod.z.array(import_zod.z.string()).optional(),
62
+ tools: import_zod.z.array(import_zod.z.string()).optional()
63
+ });
64
+ RulesyncMcpServerSchema = McpServerBaseSchema.extend({
65
+ rulesyncTargets: RulesyncTargetsSchema.optional()
66
+ });
67
+ }
68
+ });
69
+
70
+ // src/utils/mcp-helpers.ts
71
+ function shouldIncludeServer(server, targetTool) {
72
+ if (!server.rulesyncTargets || server.rulesyncTargets.length === 0) {
73
+ return true;
74
+ }
75
+ const parsedTargets = RulesyncTargetsSchema.parse(server.rulesyncTargets);
76
+ if (parsedTargets.length === 1 && parsedTargets[0] === "*") {
77
+ return true;
78
+ }
79
+ const validatedTool = ToolTargetSchema.parse(targetTool);
80
+ for (const target of parsedTargets) {
81
+ if (target === validatedTool) {
82
+ return true;
83
+ }
84
+ }
85
+ return false;
86
+ }
87
+ var init_mcp_helpers = __esm({
88
+ "src/utils/mcp-helpers.ts"() {
89
+ "use strict";
90
+ init_mcp();
91
+ }
92
+ });
93
+
94
+ // src/generators/mcp/claude.ts
95
+ function generateClaudeMcp(config, _target) {
96
+ const claudeSettings = {
97
+ mcpServers: {}
98
+ };
99
+ const shouldInclude = (server) => {
100
+ return shouldIncludeServer(server, "claude");
101
+ };
102
+ for (const [serverName, server] of Object.entries(config.mcpServers)) {
103
+ if (!shouldInclude(server)) continue;
104
+ const claudeServer = {};
105
+ if (server.command) {
106
+ claudeServer.command = server.command;
107
+ if (server.args) claudeServer.args = server.args;
108
+ } else if (server.url || server.httpUrl) {
109
+ const url = server.httpUrl || server.url;
110
+ if (url) {
111
+ claudeServer.url = url;
112
+ }
113
+ if (server.httpUrl) {
114
+ claudeServer.transport = "http";
115
+ } else if (server.transport === "sse") {
116
+ claudeServer.transport = "sse";
117
+ }
118
+ }
119
+ if (server.env) {
120
+ claudeServer.env = server.env;
121
+ }
122
+ claudeSettings.mcpServers[serverName] = claudeServer;
123
+ }
124
+ return JSON.stringify(claudeSettings, null, 2);
125
+ }
126
+ var init_claude = __esm({
127
+ "src/generators/mcp/claude.ts"() {
128
+ "use strict";
129
+ init_mcp_helpers();
130
+ }
131
+ });
132
+
133
+ // src/generators/mcp/cline.ts
134
+ function generateClineMcp(config, _target) {
135
+ const clineConfig = {
136
+ mcpServers: {}
137
+ };
138
+ const shouldInclude = (server) => {
139
+ return shouldIncludeServer(server, "cline");
140
+ };
141
+ for (const [serverName, server] of Object.entries(config.mcpServers)) {
142
+ if (!shouldInclude(server)) continue;
143
+ const clineServer = {};
144
+ if (server.command) {
145
+ clineServer.command = server.command;
146
+ if (server.args) clineServer.args = server.args;
147
+ } else if (server.url) {
148
+ clineServer.url = server.url;
149
+ }
150
+ if (server.env) {
151
+ clineServer.env = server.env;
152
+ }
153
+ if (server.disabled !== void 0) {
154
+ clineServer.disabled = server.disabled;
155
+ }
156
+ if (server.alwaysAllow) {
157
+ clineServer.alwaysAllow = server.alwaysAllow;
158
+ }
159
+ if (server.networkTimeout !== void 0) {
160
+ clineServer.networkTimeout = server.networkTimeout;
161
+ }
162
+ clineConfig.mcpServers[serverName] = clineServer;
163
+ }
164
+ return JSON.stringify(clineConfig, null, 2);
165
+ }
166
+ var init_cline = __esm({
167
+ "src/generators/mcp/cline.ts"() {
168
+ "use strict";
169
+ init_mcp_helpers();
170
+ }
171
+ });
172
+
173
+ // src/generators/mcp/copilot.ts
174
+ function generateCopilotMcp(config, target) {
175
+ const servers = {};
176
+ const inputs = [];
177
+ const inputMap = /* @__PURE__ */ new Map();
178
+ for (const [serverName, server] of Object.entries(config.mcpServers)) {
179
+ if (!shouldIncludeServer(server, "copilot")) continue;
180
+ const copilotServer = {};
181
+ if (server.command) {
182
+ copilotServer.command = server.command;
183
+ if (server.args) copilotServer.args = server.args;
184
+ } else if (server.url || server.httpUrl) {
185
+ const url = server.httpUrl || server.url;
186
+ if (url) {
187
+ copilotServer.url = url;
188
+ }
189
+ }
190
+ if (server.env) {
191
+ copilotServer.env = {};
192
+ for (const [key, value] of Object.entries(server.env)) {
193
+ if (target === "editor" && value.includes("SECRET")) {
194
+ const inputId = `${serverName}_${key}`;
195
+ inputMap.set(inputId, value);
196
+ copilotServer.env[key] = `\${input:${inputId}}`;
197
+ inputs.push({
198
+ id: inputId,
199
+ type: "password",
200
+ description: `${key} for ${serverName}`
201
+ });
202
+ } else {
203
+ copilotServer.env[key] = value;
204
+ }
205
+ }
206
+ }
207
+ if (server.tools) {
208
+ copilotServer.tools = server.tools;
209
+ } else if (server.alwaysAllow) {
210
+ copilotServer.tools = server.alwaysAllow;
211
+ }
212
+ servers[serverName] = copilotServer;
213
+ }
214
+ if (target === "codingAgent") {
215
+ const config2 = { mcpServers: servers };
216
+ return JSON.stringify(config2, null, 2);
217
+ } else {
218
+ const config2 = { servers };
219
+ if (inputs.length > 0) {
220
+ config2.inputs = inputs;
221
+ }
222
+ return JSON.stringify(config2, null, 2);
223
+ }
224
+ }
225
+ var init_copilot = __esm({
226
+ "src/generators/mcp/copilot.ts"() {
227
+ "use strict";
228
+ init_mcp_helpers();
229
+ }
230
+ });
231
+
232
+ // src/generators/mcp/cursor.ts
233
+ function generateCursorMcp(config, _target) {
234
+ const cursorConfig = {
235
+ mcpServers: {}
236
+ };
237
+ for (const [serverName, server] of Object.entries(config.mcpServers)) {
238
+ if (!shouldIncludeServer(server, "cursor")) continue;
239
+ const cursorServer = {};
240
+ if (server.command) {
241
+ cursorServer.command = server.command;
242
+ if (server.args) cursorServer.args = server.args;
243
+ } else if (server.url || server.httpUrl) {
244
+ const url = server.httpUrl || server.url;
245
+ if (url) {
246
+ cursorServer.url = url;
247
+ }
248
+ if (server.httpUrl || server.transport === "http") {
249
+ cursorServer.type = "streamable-http";
250
+ } else if (server.transport === "sse" || server.type === "sse") {
251
+ cursorServer.type = "sse";
252
+ }
253
+ }
254
+ if (server.env) {
255
+ cursorServer.env = server.env;
256
+ }
257
+ if (server.cwd) {
258
+ cursorServer.cwd = server.cwd;
259
+ }
260
+ cursorConfig.mcpServers[serverName] = cursorServer;
261
+ }
262
+ return JSON.stringify(cursorConfig, null, 2);
263
+ }
264
+ var init_cursor = __esm({
265
+ "src/generators/mcp/cursor.ts"() {
266
+ "use strict";
267
+ init_mcp_helpers();
268
+ }
269
+ });
270
+
271
+ // src/generators/mcp/geminicli.ts
272
+ function generateGeminiCliMcp(config, _target) {
273
+ const geminiSettings = {
274
+ mcpServers: {}
275
+ };
276
+ for (const [serverName, server] of Object.entries(config.mcpServers)) {
277
+ if (!shouldIncludeServer(server, "geminicli")) continue;
278
+ const geminiServer = {};
279
+ if (server.command) {
280
+ geminiServer.command = server.command;
281
+ if (server.args) geminiServer.args = server.args;
282
+ } else if (server.url || server.httpUrl) {
283
+ if (server.httpUrl) {
284
+ geminiServer.httpUrl = server.httpUrl;
285
+ } else if (server.url) {
286
+ geminiServer.url = server.url;
287
+ }
288
+ }
289
+ if (server.env) {
290
+ geminiServer.env = {};
291
+ for (const [key, value] of Object.entries(server.env)) {
292
+ if (value.startsWith("${") && value.endsWith("}")) {
293
+ geminiServer.env[key] = value;
294
+ } else {
295
+ geminiServer.env[key] = `\${${value}}`;
296
+ }
297
+ }
298
+ }
299
+ if (server.timeout !== void 0) {
300
+ geminiServer.timeout = server.timeout;
301
+ }
302
+ if (server.trust !== void 0) {
303
+ geminiServer.trust = server.trust;
304
+ }
305
+ geminiSettings.mcpServers[serverName] = geminiServer;
306
+ }
307
+ return JSON.stringify(geminiSettings, null, 2);
308
+ }
309
+ var init_geminicli = __esm({
310
+ "src/generators/mcp/geminicli.ts"() {
311
+ "use strict";
312
+ init_mcp_helpers();
313
+ }
314
+ });
315
+
316
+ // src/generators/mcp/roo.ts
317
+ function generateRooMcp(config, _target) {
318
+ const rooConfig = {
319
+ mcpServers: {}
320
+ };
321
+ for (const [serverName, server] of Object.entries(config.mcpServers)) {
322
+ if (!shouldIncludeServer(server, "roo")) continue;
323
+ const rooServer = {};
324
+ if (server.command) {
325
+ rooServer.command = server.command;
326
+ if (server.args) rooServer.args = server.args;
327
+ } else if (server.url || server.httpUrl) {
328
+ const url = server.httpUrl || server.url;
329
+ if (url) {
330
+ rooServer.url = url;
331
+ }
332
+ if (server.httpUrl || server.transport === "http") {
333
+ rooServer.type = "streamable-http";
334
+ } else if (server.transport === "sse" || server.type === "sse") {
335
+ rooServer.type = "sse";
336
+ }
337
+ }
338
+ if (server.env) {
339
+ rooServer.env = {};
340
+ for (const [key, value] of Object.entries(server.env)) {
341
+ if (value.startsWith("${env:") && value.endsWith("}")) {
342
+ rooServer.env[key] = value;
343
+ } else {
344
+ rooServer.env[key] = `\${env:${value}}`;
345
+ }
346
+ }
347
+ }
348
+ if (server.disabled !== void 0) {
349
+ rooServer.disabled = server.disabled;
350
+ }
351
+ if (server.alwaysAllow) {
352
+ rooServer.alwaysAllow = server.alwaysAllow;
353
+ }
354
+ if (server.networkTimeout !== void 0) {
355
+ rooServer.networkTimeout = Math.max(3e4, Math.min(3e5, server.networkTimeout));
356
+ }
357
+ rooConfig.mcpServers[serverName] = rooServer;
358
+ }
359
+ return JSON.stringify(rooConfig, null, 2);
360
+ }
361
+ var init_roo = __esm({
362
+ "src/generators/mcp/roo.ts"() {
363
+ "use strict";
364
+ init_mcp_helpers();
365
+ }
366
+ });
367
+
26
368
  // src/cli/index.ts
27
369
  var import_commander = require("commander");
28
370
 
@@ -39,11 +381,12 @@ function getDefaultConfig() {
39
381
  cursor: ".cursor/rules",
40
382
  cline: ".clinerules",
41
383
  claudecode: ".",
384
+ claude: ".",
42
385
  roo: ".roo/rules",
43
386
  geminicli: ".gemini/memories"
44
387
  },
45
388
  watchEnabled: false,
46
- defaultTargets: ["copilot", "cursor", "cline", "claudecode", "roo", "geminicli"]
389
+ defaultTargets: ["copilot", "cursor", "cline", "claudecode", "claude", "roo", "geminicli"]
47
390
  };
48
391
  }
49
392
  function resolveTargets(targets, config) {
@@ -89,27 +432,162 @@ async function addCommand(filename) {
89
432
  }
90
433
  }
91
434
 
92
- // src/generators/claudecode.ts
435
+ // src/generators/rules/claudecode.ts
436
+ var import_node_path5 = require("path");
437
+
438
+ // src/utils/file.ts
439
+ var import_promises3 = require("fs/promises");
440
+ var import_node_path4 = require("path");
441
+
442
+ // src/utils/file-ops.ts
443
+ var import_promises2 = require("fs/promises");
93
444
  var import_node_path2 = require("path");
445
+ async function ensureDir(dirPath) {
446
+ try {
447
+ await (0, import_promises2.stat)(dirPath);
448
+ } catch {
449
+ await (0, import_promises2.mkdir)(dirPath, { recursive: true });
450
+ }
451
+ }
452
+ async function readFileContent(filepath) {
453
+ return (0, import_promises2.readFile)(filepath, "utf-8");
454
+ }
455
+ async function writeFileContent(filepath, content) {
456
+ await ensureDir((0, import_node_path2.dirname)(filepath));
457
+ await (0, import_promises2.writeFile)(filepath, content, "utf-8");
458
+ }
459
+ async function fileExists(filepath) {
460
+ try {
461
+ await (0, import_promises2.stat)(filepath);
462
+ return true;
463
+ } catch {
464
+ return false;
465
+ }
466
+ }
467
+
468
+ // src/utils/ignore.ts
469
+ var import_node_path3 = require("path");
470
+ var import_micromatch = __toESM(require("micromatch"));
471
+ var cachedIgnorePatterns = null;
472
+ async function loadIgnorePatterns(baseDir = process.cwd()) {
473
+ if (cachedIgnorePatterns) {
474
+ return cachedIgnorePatterns;
475
+ }
476
+ const ignorePath = (0, import_node_path3.join)(baseDir, ".rulesyncignore");
477
+ if (!await fileExists(ignorePath)) {
478
+ cachedIgnorePatterns = { patterns: [] };
479
+ return cachedIgnorePatterns;
480
+ }
481
+ try {
482
+ const content = await readFileContent(ignorePath);
483
+ const patterns = parseIgnoreFile(content);
484
+ cachedIgnorePatterns = { patterns };
485
+ return cachedIgnorePatterns;
486
+ } catch (error) {
487
+ console.warn(`Failed to read .rulesyncignore: ${error}`);
488
+ cachedIgnorePatterns = { patterns: [] };
489
+ return cachedIgnorePatterns;
490
+ }
491
+ }
492
+ function parseIgnoreFile(content) {
493
+ return content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
494
+ }
495
+ function isFileIgnored(filepath, ignorePatterns) {
496
+ if (ignorePatterns.length === 0) {
497
+ return false;
498
+ }
499
+ const negationPatterns = ignorePatterns.filter((p) => p.startsWith("!"));
500
+ const positivePatterns = ignorePatterns.filter((p) => !p.startsWith("!"));
501
+ const isIgnored = positivePatterns.length > 0 && import_micromatch.default.isMatch(filepath, positivePatterns, {
502
+ dot: true
503
+ });
504
+ if (isIgnored && negationPatterns.length > 0) {
505
+ const negationPatternsWithoutPrefix = negationPatterns.map((p) => p.substring(1));
506
+ return !import_micromatch.default.isMatch(filepath, negationPatternsWithoutPrefix, {
507
+ dot: true
508
+ });
509
+ }
510
+ return isIgnored;
511
+ }
512
+ function filterIgnoredFiles(files, ignorePatterns) {
513
+ if (ignorePatterns.length === 0) {
514
+ return files;
515
+ }
516
+ return files.filter((file) => !isFileIgnored(file, ignorePatterns));
517
+ }
518
+
519
+ // src/utils/file.ts
520
+ async function findFiles(dir, extension = ".md", ignorePatterns) {
521
+ try {
522
+ const files = await (0, import_promises3.readdir)(dir);
523
+ const filtered = files.filter((file) => file.endsWith(extension)).map((file) => (0, import_node_path4.join)(dir, file));
524
+ if (ignorePatterns && ignorePatterns.length > 0) {
525
+ return filterIgnoredFiles(filtered, ignorePatterns);
526
+ }
527
+ return filtered;
528
+ } catch {
529
+ return [];
530
+ }
531
+ }
532
+ async function removeDirectory(dirPath) {
533
+ const dangerousPaths = [".", "/", "~", "src", "node_modules"];
534
+ if (dangerousPaths.includes(dirPath) || dirPath === "") {
535
+ console.warn(`Skipping deletion of dangerous path: ${dirPath}`);
536
+ return;
537
+ }
538
+ try {
539
+ if (await fileExists(dirPath)) {
540
+ await (0, import_promises3.rm)(dirPath, { recursive: true, force: true });
541
+ }
542
+ } catch (error) {
543
+ console.warn(`Failed to remove directory ${dirPath}:`, error);
544
+ }
545
+ }
546
+ async function removeFile(filepath) {
547
+ try {
548
+ if (await fileExists(filepath)) {
549
+ await (0, import_promises3.rm)(filepath);
550
+ }
551
+ } catch (error) {
552
+ console.warn(`Failed to remove file ${filepath}:`, error);
553
+ }
554
+ }
555
+ async function removeClaudeGeneratedFiles() {
556
+ const filesToRemove = ["CLAUDE.md", ".claude/memories"];
557
+ for (const fileOrDir of filesToRemove) {
558
+ if (fileOrDir.endsWith("/memories")) {
559
+ await removeDirectory(fileOrDir);
560
+ } else {
561
+ await removeFile(fileOrDir);
562
+ }
563
+ }
564
+ }
565
+
566
+ // src/generators/rules/claudecode.ts
94
567
  async function generateClaudecodeConfig(rules, config, baseDir) {
95
568
  const outputs = [];
96
569
  const rootRules = rules.filter((r) => r.frontmatter.root === true);
97
570
  const detailRules = rules.filter((r) => r.frontmatter.root === false);
98
571
  const claudeMdContent = generateClaudeMarkdown(rootRules, detailRules);
99
- const claudeOutputDir = baseDir ? (0, import_node_path2.join)(baseDir, config.outputPaths.claudecode) : config.outputPaths.claudecode;
572
+ const claudeOutputDir = baseDir ? (0, import_node_path5.join)(baseDir, config.outputPaths.claudecode) : config.outputPaths.claudecode;
100
573
  outputs.push({
101
574
  tool: "claudecode",
102
- filepath: (0, import_node_path2.join)(claudeOutputDir, "CLAUDE.md"),
575
+ filepath: (0, import_node_path5.join)(claudeOutputDir, "CLAUDE.md"),
103
576
  content: claudeMdContent
104
577
  });
105
578
  for (const rule of detailRules) {
106
579
  const memoryContent = generateMemoryFile(rule);
107
580
  outputs.push({
108
581
  tool: "claudecode",
109
- filepath: (0, import_node_path2.join)(claudeOutputDir, ".claude", "memories", `${rule.filename}.md`),
582
+ filepath: (0, import_node_path5.join)(claudeOutputDir, ".claude", "memories", `${rule.filename}.md`),
110
583
  content: memoryContent
111
584
  });
112
585
  }
586
+ const ignorePatterns = await loadIgnorePatterns(baseDir);
587
+ if (ignorePatterns.patterns.length > 0) {
588
+ const settingsPath = baseDir ? (0, import_node_path5.join)(baseDir, ".claude", "settings.json") : (0, import_node_path5.join)(".claude", "settings.json");
589
+ await updateClaudeSettings(settingsPath, ignorePatterns.patterns);
590
+ }
113
591
  return outputs;
114
592
  }
115
593
  function generateClaudeMarkdown(rootRules, detailRules) {
@@ -138,42 +616,108 @@ function generateClaudeMarkdown(rootRules, detailRules) {
138
616
  function generateMemoryFile(rule) {
139
617
  return rule.content.trim();
140
618
  }
619
+ async function updateClaudeSettings(settingsPath, ignorePatterns) {
620
+ let settings = {};
621
+ if (await fileExists(settingsPath)) {
622
+ try {
623
+ const content = await readFileContent(settingsPath);
624
+ settings = JSON.parse(content);
625
+ } catch {
626
+ console.warn(`Failed to parse existing ${settingsPath}, creating new settings`);
627
+ settings = {};
628
+ }
629
+ }
630
+ if (typeof settings !== "object" || settings === null) {
631
+ settings = {};
632
+ }
633
+ const settingsObj = settings;
634
+ if (!settingsObj.permissions || typeof settingsObj.permissions !== "object" || settingsObj.permissions === null) {
635
+ settingsObj.permissions = {};
636
+ }
637
+ const permissions = settingsObj.permissions;
638
+ if (!Array.isArray(permissions.deny)) {
639
+ permissions.deny = [];
640
+ }
641
+ const readDenyRules = ignorePatterns.map((pattern) => `Read(${pattern})`);
642
+ const denyArray = permissions.deny;
643
+ const filteredDeny = denyArray.filter((rule) => {
644
+ if (typeof rule !== "string") return false;
645
+ if (!rule.startsWith("Read(")) return true;
646
+ const match = rule.match(/^Read\((.*)\)$/);
647
+ if (!match) return true;
648
+ return !ignorePatterns.includes(match[1] ?? "");
649
+ });
650
+ filteredDeny.push(...readDenyRules);
651
+ permissions.deny = [...new Set(filteredDeny)];
652
+ const jsonContent = JSON.stringify(settingsObj, null, 2);
653
+ await writeFileContent(settingsPath, jsonContent);
654
+ console.log(`\u2705 Updated Claude Code settings: ${settingsPath}`);
655
+ }
141
656
 
142
- // src/generators/cline.ts
143
- var import_node_path3 = require("path");
657
+ // src/generators/rules/cline.ts
658
+ var import_node_path6 = require("path");
144
659
  async function generateClineConfig(rules, config, baseDir) {
145
660
  const outputs = [];
146
661
  for (const rule of rules) {
147
662
  const content = generateClineMarkdown(rule);
148
- const outputDir = baseDir ? (0, import_node_path3.join)(baseDir, config.outputPaths.cline) : config.outputPaths.cline;
149
- const filepath = (0, import_node_path3.join)(outputDir, `${rule.filename}.md`);
663
+ const outputDir = baseDir ? (0, import_node_path6.join)(baseDir, config.outputPaths.cline) : config.outputPaths.cline;
664
+ const filepath = (0, import_node_path6.join)(outputDir, `${rule.filename}.md`);
150
665
  outputs.push({
151
666
  tool: "cline",
152
667
  filepath,
153
668
  content
154
669
  });
155
670
  }
671
+ const ignorePatterns = await loadIgnorePatterns(baseDir);
672
+ if (ignorePatterns.patterns.length > 0) {
673
+ const clineIgnorePath = baseDir ? (0, import_node_path6.join)(baseDir, ".clineignore") : ".clineignore";
674
+ const clineIgnoreContent = generateClineIgnore(ignorePatterns.patterns);
675
+ outputs.push({
676
+ tool: "cline",
677
+ filepath: clineIgnorePath,
678
+ content: clineIgnoreContent
679
+ });
680
+ }
156
681
  return outputs;
157
682
  }
158
683
  function generateClineMarkdown(rule) {
159
684
  return rule.content.trim();
160
685
  }
686
+ function generateClineIgnore(patterns) {
687
+ const lines = [
688
+ "# Generated by rulesync from .rulesyncignore",
689
+ "# This file is automatically generated. Do not edit manually.",
690
+ "",
691
+ ...patterns
692
+ ];
693
+ return lines.join("\n");
694
+ }
161
695
 
162
- // src/generators/copilot.ts
163
- var import_node_path4 = require("path");
696
+ // src/generators/rules/copilot.ts
697
+ var import_node_path7 = require("path");
164
698
  async function generateCopilotConfig(rules, config, baseDir) {
165
699
  const outputs = [];
166
700
  for (const rule of rules) {
167
701
  const content = generateCopilotMarkdown(rule);
168
702
  const baseFilename = rule.filename.replace(/\.md$/, "");
169
- const outputDir = baseDir ? (0, import_node_path4.join)(baseDir, config.outputPaths.copilot) : config.outputPaths.copilot;
170
- const filepath = (0, import_node_path4.join)(outputDir, `${baseFilename}.instructions.md`);
703
+ const outputDir = baseDir ? (0, import_node_path7.join)(baseDir, config.outputPaths.copilot) : config.outputPaths.copilot;
704
+ const filepath = (0, import_node_path7.join)(outputDir, `${baseFilename}.instructions.md`);
171
705
  outputs.push({
172
706
  tool: "copilot",
173
707
  filepath,
174
708
  content
175
709
  });
176
710
  }
711
+ const ignorePatterns = await loadIgnorePatterns(baseDir);
712
+ if (ignorePatterns.patterns.length > 0) {
713
+ const copilotIgnorePath = baseDir ? (0, import_node_path7.join)(baseDir, ".copilotignore") : ".copilotignore";
714
+ const copilotIgnoreContent = generateCopilotIgnore(ignorePatterns.patterns);
715
+ outputs.push({
716
+ tool: "copilot",
717
+ filepath: copilotIgnorePath,
718
+ content: copilotIgnoreContent
719
+ });
720
+ }
177
721
  return outputs;
178
722
  }
179
723
  function generateCopilotMarkdown(rule) {
@@ -189,21 +733,42 @@ function generateCopilotMarkdown(rule) {
189
733
  lines.push(rule.content);
190
734
  return lines.join("\n");
191
735
  }
736
+ function generateCopilotIgnore(patterns) {
737
+ const lines = [
738
+ "# Generated by rulesync from .rulesyncignore",
739
+ "# This file is automatically generated. Do not edit manually.",
740
+ "# Note: .copilotignore is not officially supported by GitHub Copilot.",
741
+ "# This file is for use with community tools like copilotignore-vscode extension.",
742
+ "",
743
+ ...patterns
744
+ ];
745
+ return lines.join("\n");
746
+ }
192
747
 
193
- // src/generators/cursor.ts
194
- var import_node_path5 = require("path");
748
+ // src/generators/rules/cursor.ts
749
+ var import_node_path8 = require("path");
195
750
  async function generateCursorConfig(rules, config, baseDir) {
196
751
  const outputs = [];
197
752
  for (const rule of rules) {
198
753
  const content = generateCursorMarkdown(rule);
199
- const outputDir = baseDir ? (0, import_node_path5.join)(baseDir, config.outputPaths.cursor) : config.outputPaths.cursor;
200
- const filepath = (0, import_node_path5.join)(outputDir, `${rule.filename}.mdc`);
754
+ const outputDir = baseDir ? (0, import_node_path8.join)(baseDir, config.outputPaths.cursor) : config.outputPaths.cursor;
755
+ const filepath = (0, import_node_path8.join)(outputDir, `${rule.filename}.mdc`);
201
756
  outputs.push({
202
757
  tool: "cursor",
203
758
  filepath,
204
759
  content
205
760
  });
206
761
  }
762
+ const ignorePatterns = await loadIgnorePatterns(baseDir);
763
+ if (ignorePatterns.patterns.length > 0) {
764
+ const cursorIgnorePath = baseDir ? (0, import_node_path8.join)(baseDir, ".cursorignore") : ".cursorignore";
765
+ const cursorIgnoreContent = generateCursorIgnore(ignorePatterns.patterns);
766
+ outputs.push({
767
+ tool: "cursor",
768
+ filepath: cursorIgnorePath,
769
+ content: cursorIgnoreContent
770
+ });
771
+ }
207
772
  return outputs;
208
773
  }
209
774
  function generateCursorMarkdown(rule) {
@@ -226,17 +791,26 @@ function generateCursorMarkdown(rule) {
226
791
  lines.push(rule.content);
227
792
  return lines.join("\n");
228
793
  }
794
+ function generateCursorIgnore(patterns) {
795
+ const lines = [
796
+ "# Generated by rulesync from .rulesyncignore",
797
+ "# This file is automatically generated. Do not edit manually.",
798
+ "",
799
+ ...patterns
800
+ ];
801
+ return lines.join("\n");
802
+ }
229
803
 
230
- // src/generators/geminicli.ts
231
- var import_node_path6 = require("path");
804
+ // src/generators/rules/geminicli.ts
805
+ var import_node_path9 = require("path");
232
806
  async function generateGeminiConfig(rules, config, baseDir) {
233
807
  const outputs = [];
234
808
  const rootRule = rules.find((rule) => rule.frontmatter.root === true);
235
809
  const memoryRules = rules.filter((rule) => rule.frontmatter.root === false);
236
810
  for (const rule of memoryRules) {
237
811
  const content = generateGeminiMemoryMarkdown(rule);
238
- const outputDir = baseDir ? (0, import_node_path6.join)(baseDir, config.outputPaths.geminicli) : config.outputPaths.geminicli;
239
- const filepath = (0, import_node_path6.join)(outputDir, `${rule.filename}.md`);
812
+ const outputDir = baseDir ? (0, import_node_path9.join)(baseDir, config.outputPaths.geminicli) : config.outputPaths.geminicli;
813
+ const filepath = (0, import_node_path9.join)(outputDir, `${rule.filename}.md`);
240
814
  outputs.push({
241
815
  tool: "geminicli",
242
816
  filepath,
@@ -244,12 +818,22 @@ async function generateGeminiConfig(rules, config, baseDir) {
244
818
  });
245
819
  }
246
820
  const rootContent = generateGeminiRootMarkdown(rootRule, memoryRules, baseDir);
247
- const rootFilepath = baseDir ? (0, import_node_path6.join)(baseDir, "GEMINI.md") : "GEMINI.md";
821
+ const rootFilepath = baseDir ? (0, import_node_path9.join)(baseDir, "GEMINI.md") : "GEMINI.md";
248
822
  outputs.push({
249
823
  tool: "geminicli",
250
824
  filepath: rootFilepath,
251
825
  content: rootContent
252
826
  });
827
+ const ignorePatterns = await loadIgnorePatterns(baseDir);
828
+ if (ignorePatterns.patterns.length > 0) {
829
+ const aiexcludePath = baseDir ? (0, import_node_path9.join)(baseDir, ".aiexclude") : ".aiexclude";
830
+ const aiexcludeContent = generateAiexclude(ignorePatterns.patterns);
831
+ outputs.push({
832
+ tool: "geminicli",
833
+ filepath: aiexcludePath,
834
+ content: aiexcludeContent
835
+ });
836
+ }
253
837
  return outputs;
254
838
  }
255
839
  function generateGeminiMemoryMarkdown(rule) {
@@ -279,92 +863,53 @@ function generateGeminiRootMarkdown(rootRule, memoryRules, _baseDir) {
279
863
  }
280
864
  return lines.join("\n");
281
865
  }
866
+ function generateAiexclude(patterns) {
867
+ const lines = [
868
+ "# Generated by rulesync from .rulesyncignore",
869
+ "# This file is automatically generated. Do not edit manually.",
870
+ "",
871
+ ...patterns
872
+ ];
873
+ return lines.join("\n");
874
+ }
282
875
 
283
- // src/generators/roo.ts
284
- var import_node_path7 = require("path");
876
+ // src/generators/rules/roo.ts
877
+ var import_node_path10 = require("path");
285
878
  async function generateRooConfig(rules, config, baseDir) {
286
879
  const outputs = [];
287
880
  for (const rule of rules) {
288
881
  const content = generateRooMarkdown(rule);
289
- const outputDir = baseDir ? (0, import_node_path7.join)(baseDir, config.outputPaths.roo) : config.outputPaths.roo;
290
- const filepath = (0, import_node_path7.join)(outputDir, `${rule.filename}.md`);
882
+ const outputDir = baseDir ? (0, import_node_path10.join)(baseDir, config.outputPaths.roo) : config.outputPaths.roo;
883
+ const filepath = (0, import_node_path10.join)(outputDir, `${rule.filename}.md`);
291
884
  outputs.push({
292
885
  tool: "roo",
293
886
  filepath,
294
887
  content
295
888
  });
296
889
  }
890
+ const ignorePatterns = await loadIgnorePatterns(baseDir);
891
+ if (ignorePatterns.patterns.length > 0) {
892
+ const rooIgnorePath = baseDir ? (0, import_node_path10.join)(baseDir, ".rooignore") : ".rooignore";
893
+ const rooIgnoreContent = generateRooIgnore(ignorePatterns.patterns);
894
+ outputs.push({
895
+ tool: "roo",
896
+ filepath: rooIgnorePath,
897
+ content: rooIgnoreContent
898
+ });
899
+ }
297
900
  return outputs;
298
901
  }
299
902
  function generateRooMarkdown(rule) {
300
903
  return rule.content.trim();
301
904
  }
302
-
303
- // src/utils/file.ts
304
- var import_promises2 = require("fs/promises");
305
- var import_node_path8 = require("path");
306
- async function ensureDir(dirPath) {
307
- try {
308
- await (0, import_promises2.stat)(dirPath);
309
- } catch {
310
- await (0, import_promises2.mkdir)(dirPath, { recursive: true });
311
- }
312
- }
313
- async function readFileContent(filepath) {
314
- return (0, import_promises2.readFile)(filepath, "utf-8");
315
- }
316
- async function writeFileContent(filepath, content) {
317
- await ensureDir((0, import_node_path8.dirname)(filepath));
318
- await (0, import_promises2.writeFile)(filepath, content, "utf-8");
319
- }
320
- async function findFiles(dir, extension = ".md") {
321
- try {
322
- const files = await (0, import_promises2.readdir)(dir);
323
- return files.filter((file) => file.endsWith(extension)).map((file) => (0, import_node_path8.join)(dir, file));
324
- } catch {
325
- return [];
326
- }
327
- }
328
- async function fileExists(filepath) {
329
- try {
330
- await (0, import_promises2.stat)(filepath);
331
- return true;
332
- } catch {
333
- return false;
334
- }
335
- }
336
- async function removeDirectory(dirPath) {
337
- const dangerousPaths = [".", "/", "~", "src", "node_modules"];
338
- if (dangerousPaths.includes(dirPath) || dirPath === "") {
339
- console.warn(`Skipping deletion of dangerous path: ${dirPath}`);
340
- return;
341
- }
342
- try {
343
- if (await fileExists(dirPath)) {
344
- await (0, import_promises2.rm)(dirPath, { recursive: true, force: true });
345
- }
346
- } catch (error) {
347
- console.warn(`Failed to remove directory ${dirPath}:`, error);
348
- }
349
- }
350
- async function removeFile(filepath) {
351
- try {
352
- if (await fileExists(filepath)) {
353
- await (0, import_promises2.rm)(filepath);
354
- }
355
- } catch (error) {
356
- console.warn(`Failed to remove file ${filepath}:`, error);
357
- }
358
- }
359
- async function removeClaudeGeneratedFiles() {
360
- const filesToRemove = ["CLAUDE.md", ".claude/memories"];
361
- for (const fileOrDir of filesToRemove) {
362
- if (fileOrDir.endsWith("/memories")) {
363
- await removeDirectory(fileOrDir);
364
- } else {
365
- await removeFile(fileOrDir);
366
- }
367
- }
905
+ function generateRooIgnore(patterns) {
906
+ const lines = [
907
+ "# Generated by rulesync from .rulesyncignore",
908
+ "# This file is automatically generated. Do not edit manually.",
909
+ "",
910
+ ...patterns
911
+ ];
912
+ return lines.join("\n");
368
913
  }
369
914
 
370
915
  // src/core/generator.ts
@@ -417,12 +962,16 @@ async function generateForTool(tool, rules, config, baseDir) {
417
962
  }
418
963
 
419
964
  // src/core/parser.ts
420
- var import_node_path9 = require("path");
965
+ var import_node_path11 = require("path");
421
966
  var import_gray_matter = __toESM(require("gray-matter"));
422
967
  async function parseRulesFromDirectory(aiRulesDir) {
423
- const ruleFiles = await findFiles(aiRulesDir);
968
+ const ignorePatterns = await loadIgnorePatterns();
969
+ const ruleFiles = await findFiles(aiRulesDir, ".md", ignorePatterns.patterns);
424
970
  const rules = [];
425
971
  const errors = [];
972
+ if (ignorePatterns.patterns.length > 0) {
973
+ console.log(`Loaded ${ignorePatterns.patterns.length} ignore patterns from .rulesyncignore`);
974
+ }
426
975
  for (const filepath of ruleFiles) {
427
976
  try {
428
977
  const rule = await parseRuleFile(filepath);
@@ -450,7 +999,7 @@ async function parseRuleFile(filepath) {
450
999
  const parsed = (0, import_gray_matter.default)(content);
451
1000
  validateFrontmatter(parsed.data, filepath);
452
1001
  const frontmatter = parsed.data;
453
- const filename = (0, import_node_path9.basename)(filepath, ".md");
1002
+ const filename = (0, import_node_path11.basename)(filepath, ".md");
454
1003
  return {
455
1004
  frontmatter,
456
1005
  content: parsed.content,
@@ -578,6 +1127,148 @@ async function validateRule(rule) {
578
1127
  };
579
1128
  }
580
1129
 
1130
+ // src/core/mcp-generator.ts
1131
+ var import_node_os = __toESM(require("os"));
1132
+ var import_node_path13 = __toESM(require("path"));
1133
+
1134
+ // src/generators/mcp/index.ts
1135
+ init_claude();
1136
+ init_cline();
1137
+ init_copilot();
1138
+ init_cursor();
1139
+ init_geminicli();
1140
+ init_roo();
1141
+
1142
+ // src/core/mcp-parser.ts
1143
+ var import_node_fs = __toESM(require("fs"));
1144
+ var import_node_path12 = __toESM(require("path"));
1145
+ function parseMcpConfig(projectRoot) {
1146
+ const mcpPath = import_node_path12.default.join(projectRoot, ".rulesync", ".mcp.json");
1147
+ if (!import_node_fs.default.existsSync(mcpPath)) {
1148
+ return null;
1149
+ }
1150
+ try {
1151
+ const content = import_node_fs.default.readFileSync(mcpPath, "utf-8");
1152
+ const rawConfig = JSON.parse(content);
1153
+ if (rawConfig.servers && !rawConfig.mcpServers) {
1154
+ rawConfig.mcpServers = rawConfig.servers;
1155
+ delete rawConfig.servers;
1156
+ }
1157
+ if (!rawConfig.mcpServers || typeof rawConfig.mcpServers !== "object") {
1158
+ throw new Error("Invalid mcp.json: 'mcpServers' field must be an object");
1159
+ }
1160
+ if (rawConfig.tools) {
1161
+ delete rawConfig.tools;
1162
+ }
1163
+ return { mcpServers: rawConfig.mcpServers };
1164
+ } catch (error) {
1165
+ throw new Error(
1166
+ `Failed to parse mcp.json: ${error instanceof Error ? error.message : String(error)}`
1167
+ );
1168
+ }
1169
+ }
1170
+
1171
+ // src/core/mcp-generator.ts
1172
+ async function generateMcpConfigs(projectRoot, baseDir) {
1173
+ const results = [];
1174
+ const targetRoot = baseDir || projectRoot;
1175
+ const config = parseMcpConfig(projectRoot);
1176
+ if (!config) {
1177
+ return results;
1178
+ }
1179
+ const generators = [
1180
+ {
1181
+ tool: "claude-project",
1182
+ path: import_node_path13.default.join(targetRoot, ".mcp.json"),
1183
+ generate: () => generateClaudeMcp(config, "project")
1184
+ },
1185
+ {
1186
+ tool: "copilot-editor",
1187
+ path: import_node_path13.default.join(targetRoot, ".vscode", "mcp.json"),
1188
+ generate: () => generateCopilotMcp(config, "editor")
1189
+ },
1190
+ {
1191
+ tool: "cursor-project",
1192
+ path: import_node_path13.default.join(targetRoot, ".cursor", "mcp.json"),
1193
+ generate: () => generateCursorMcp(config, "project")
1194
+ },
1195
+ {
1196
+ tool: "cline-project",
1197
+ path: import_node_path13.default.join(targetRoot, ".cline", "mcp.json"),
1198
+ generate: () => generateClineMcp(config, "project")
1199
+ },
1200
+ {
1201
+ tool: "gemini-project",
1202
+ path: import_node_path13.default.join(targetRoot, ".gemini", "settings.json"),
1203
+ generate: () => generateGeminiCliMcp(config, "project")
1204
+ },
1205
+ {
1206
+ tool: "roo-project",
1207
+ path: import_node_path13.default.join(targetRoot, ".roo", "mcp.json"),
1208
+ generate: () => generateRooMcp(config, "project")
1209
+ }
1210
+ ];
1211
+ if (!baseDir) {
1212
+ generators.push(
1213
+ {
1214
+ tool: "claude-global",
1215
+ path: import_node_path13.default.join(import_node_os.default.homedir(), ".claude", "settings.json"),
1216
+ generate: () => generateClaudeMcp(config, "global")
1217
+ },
1218
+ {
1219
+ tool: "cursor-global",
1220
+ path: import_node_path13.default.join(import_node_os.default.homedir(), ".cursor", "mcp.json"),
1221
+ generate: () => generateCursorMcp(config, "global")
1222
+ },
1223
+ {
1224
+ tool: "gemini-global",
1225
+ path: import_node_path13.default.join(import_node_os.default.homedir(), ".gemini", "settings.json"),
1226
+ generate: () => generateGeminiCliMcp(config, "global")
1227
+ }
1228
+ );
1229
+ }
1230
+ for (const generator of generators) {
1231
+ try {
1232
+ const content = generator.generate();
1233
+ const parsed = JSON.parse(content);
1234
+ if (generator.tool.includes("claude") || generator.tool.includes("cline") || generator.tool.includes("cursor") || generator.tool.includes("gemini") || generator.tool.includes("roo")) {
1235
+ if (!parsed.mcpServers || Object.keys(parsed.mcpServers).length === 0) {
1236
+ results.push({
1237
+ tool: generator.tool,
1238
+ path: generator.path,
1239
+ status: "skipped"
1240
+ });
1241
+ continue;
1242
+ }
1243
+ } else if (generator.tool.includes("copilot")) {
1244
+ const key = generator.tool.includes("codingAgent") ? "mcpServers" : "servers";
1245
+ if (!parsed[key] || Object.keys(parsed[key]).length === 0) {
1246
+ results.push({
1247
+ tool: generator.tool,
1248
+ path: generator.path,
1249
+ status: "skipped"
1250
+ });
1251
+ continue;
1252
+ }
1253
+ }
1254
+ await writeFileContent(generator.path, content);
1255
+ results.push({
1256
+ tool: generator.tool,
1257
+ path: generator.path,
1258
+ status: "success"
1259
+ });
1260
+ } catch (error) {
1261
+ results.push({
1262
+ tool: generator.tool,
1263
+ path: generator.path,
1264
+ status: "error",
1265
+ error: error instanceof Error ? error.message : String(error)
1266
+ });
1267
+ }
1268
+ }
1269
+ return results;
1270
+ }
1271
+
581
1272
  // src/cli/commands/generate.ts
582
1273
  async function generateCommand(options = {}) {
583
1274
  const config = getDefaultConfig();
@@ -658,6 +1349,30 @@ Generating configurations for base directory: ${baseDir}`);
658
1349
  }
659
1350
  console.log(`
660
1351
  \u{1F389} Successfully generated ${totalOutputs} configuration file(s)!`);
1352
+ if (options.verbose) {
1353
+ console.log("\nGenerating MCP configurations...");
1354
+ }
1355
+ for (const baseDir of baseDirs) {
1356
+ const mcpResults = await generateMcpConfigs(
1357
+ process.cwd(),
1358
+ baseDir === process.cwd() ? void 0 : baseDir
1359
+ );
1360
+ if (mcpResults.length === 0) {
1361
+ if (options.verbose) {
1362
+ console.log(`No MCP configuration found for ${baseDir}`);
1363
+ }
1364
+ continue;
1365
+ }
1366
+ for (const result of mcpResults) {
1367
+ if (result.status === "success") {
1368
+ console.log(`\u2705 Generated ${result.tool} MCP configuration: ${result.path}`);
1369
+ } else if (result.status === "error") {
1370
+ console.error(`\u274C Failed to generate ${result.tool} MCP configuration: ${result.error}`);
1371
+ } else if (options.verbose && result.status === "skipped") {
1372
+ console.log(`\u23ED\uFE0F Skipped ${result.tool} MCP configuration (no servers configured)`);
1373
+ }
1374
+ }
1375
+ }
661
1376
  } catch (error) {
662
1377
  console.error("\u274C Failed to generate configurations:", error);
663
1378
  process.exit(1);
@@ -665,25 +1380,36 @@ Generating configurations for base directory: ${baseDir}`);
665
1380
  }
666
1381
 
667
1382
  // src/cli/commands/gitignore.ts
668
- var import_node_fs = require("fs");
669
- var import_node_path10 = require("path");
1383
+ var import_node_fs2 = require("fs");
1384
+ var import_node_path14 = require("path");
670
1385
  var gitignoreCommand = async () => {
671
- const gitignorePath = (0, import_node_path10.join)(process.cwd(), ".gitignore");
1386
+ const gitignorePath = (0, import_node_path14.join)(process.cwd(), ".gitignore");
672
1387
  const rulesFilesToIgnore = [
673
1388
  "# Generated by rulesync - AI tool configuration files",
674
1389
  "**/.github/copilot-instructions.md",
675
1390
  "**/.github/instructions/",
676
1391
  "**/.cursor/rules/",
1392
+ "**/.cursorignore",
677
1393
  "**/.clinerules/",
1394
+ "**/.clineignore",
678
1395
  "**/CLAUDE.md",
679
1396
  "**/.claude/memories/",
680
1397
  "**/.roo/rules/",
1398
+ "**/.rooignore",
1399
+ "**/.copilotignore",
681
1400
  "**/GEMINI.md",
682
- "**/.gemini/memories/"
1401
+ "**/.gemini/memories/",
1402
+ "**/.aiexclude",
1403
+ "**/.mcp.json",
1404
+ "**/.cursor/mcp.json",
1405
+ "**/.cline/mcp.json",
1406
+ "**/.vscode/mcp.json",
1407
+ "**/.gemini/settings.json",
1408
+ "**/.roo/mcp.json"
683
1409
  ];
684
1410
  let gitignoreContent = "";
685
- if ((0, import_node_fs.existsSync)(gitignorePath)) {
686
- gitignoreContent = (0, import_node_fs.readFileSync)(gitignorePath, "utf-8");
1411
+ if ((0, import_node_fs2.existsSync)(gitignorePath)) {
1412
+ gitignoreContent = (0, import_node_fs2.readFileSync)(gitignorePath, "utf-8");
687
1413
  }
688
1414
  const linesToAdd = [];
689
1415
  for (const rule of rulesFilesToIgnore) {
@@ -700,7 +1426,7 @@ var gitignoreCommand = async () => {
700
1426
  ${linesToAdd.join("\n")}
701
1427
  ` : `${linesToAdd.join("\n")}
702
1428
  `;
703
- (0, import_node_fs.writeFileSync)(gitignorePath, newContent);
1429
+ (0, import_node_fs2.writeFileSync)(gitignorePath, newContent);
704
1430
  console.log(`\u2705 .gitignore\u306B${linesToAdd.length}\u500B\u306E\u30EB\u30FC\u30EB\u3092\u8FFD\u52A0\u3057\u307E\u3057\u305F:`);
705
1431
  for (const line of linesToAdd) {
706
1432
  if (!line.startsWith("#")) {
@@ -710,15 +1436,17 @@ ${linesToAdd.join("\n")}
710
1436
  };
711
1437
 
712
1438
  // src/core/importer.ts
713
- var import_node_path16 = require("path");
1439
+ var import_node_path21 = require("path");
714
1440
  var import_gray_matter4 = __toESM(require("gray-matter"));
715
1441
 
716
1442
  // src/parsers/claudecode.ts
717
- var import_node_path11 = require("path");
1443
+ var import_node_path15 = require("path");
718
1444
  async function parseClaudeConfiguration(baseDir = process.cwd()) {
719
1445
  const errors = [];
720
1446
  const rules = [];
721
- const claudeFilePath = (0, import_node_path11.join)(baseDir, "CLAUDE.md");
1447
+ let ignorePatterns;
1448
+ let mcpServers;
1449
+ const claudeFilePath = (0, import_node_path15.join)(baseDir, "CLAUDE.md");
722
1450
  if (!await fileExists(claudeFilePath)) {
723
1451
  errors.push("CLAUDE.md file not found");
724
1452
  return { rules, errors };
@@ -729,16 +1457,32 @@ async function parseClaudeConfiguration(baseDir = process.cwd()) {
729
1457
  if (mainRule) {
730
1458
  rules.push(mainRule);
731
1459
  }
732
- const memoryDir = (0, import_node_path11.join)(baseDir, ".claude", "memories");
1460
+ const memoryDir = (0, import_node_path15.join)(baseDir, ".claude", "memories");
733
1461
  if (await fileExists(memoryDir)) {
734
1462
  const memoryRules = await parseClaudeMemoryFiles(memoryDir);
735
1463
  rules.push(...memoryRules);
736
1464
  }
1465
+ const settingsPath = (0, import_node_path15.join)(baseDir, ".claude", "settings.json");
1466
+ if (await fileExists(settingsPath)) {
1467
+ const settingsResult = await parseClaudeSettings(settingsPath);
1468
+ if (settingsResult.ignorePatterns) {
1469
+ ignorePatterns = settingsResult.ignorePatterns;
1470
+ }
1471
+ if (settingsResult.mcpServers) {
1472
+ mcpServers = settingsResult.mcpServers;
1473
+ }
1474
+ errors.push(...settingsResult.errors);
1475
+ }
737
1476
  } catch (error) {
738
1477
  const errorMessage = error instanceof Error ? error.message : String(error);
739
1478
  errors.push(`Failed to parse Claude configuration: ${errorMessage}`);
740
1479
  }
741
- return { rules, errors };
1480
+ return {
1481
+ rules,
1482
+ errors,
1483
+ ...ignorePatterns && { ignorePatterns },
1484
+ ...mcpServers && { mcpServers }
1485
+ };
742
1486
  }
743
1487
  function parseClaudeMainFile(content, filepath) {
744
1488
  const lines = content.split("\n");
@@ -775,10 +1519,10 @@ async function parseClaudeMemoryFiles(memoryDir) {
775
1519
  const files = await readdir2(memoryDir);
776
1520
  for (const file of files) {
777
1521
  if (file.endsWith(".md")) {
778
- const filePath = (0, import_node_path11.join)(memoryDir, file);
1522
+ const filePath = (0, import_node_path15.join)(memoryDir, file);
779
1523
  const content = await readFileContent(filePath);
780
1524
  if (content.trim()) {
781
- const filename = (0, import_node_path11.basename)(file, ".md");
1525
+ const filename = (0, import_node_path15.basename)(file, ".md");
782
1526
  const frontmatter = {
783
1527
  root: false,
784
1528
  targets: ["claudecode"],
@@ -794,51 +1538,125 @@ async function parseClaudeMemoryFiles(memoryDir) {
794
1538
  }
795
1539
  }
796
1540
  }
797
- } catch (_error) {
1541
+ } catch {
798
1542
  }
799
1543
  return rules;
800
1544
  }
1545
+ async function parseClaudeSettings(settingsPath) {
1546
+ const errors = [];
1547
+ let ignorePatterns;
1548
+ let mcpServers;
1549
+ try {
1550
+ const content = await readFileContent(settingsPath);
1551
+ const settings = JSON.parse(content);
1552
+ if (typeof settings === "object" && settings !== null && "permissions" in settings) {
1553
+ const permissions = settings.permissions;
1554
+ if (permissions && "deny" in permissions && Array.isArray(permissions.deny)) {
1555
+ const readPatterns = permissions.deny.filter(
1556
+ (rule) => typeof rule === "string" && rule.startsWith("Read(") && rule.endsWith(")")
1557
+ ).map((rule) => {
1558
+ const match = rule.match(/^Read\((.+)\)$/);
1559
+ return match ? match[1] : null;
1560
+ }).filter((pattern) => pattern !== null);
1561
+ if (readPatterns.length > 0) {
1562
+ ignorePatterns = readPatterns;
1563
+ }
1564
+ }
1565
+ }
1566
+ if (typeof settings === "object" && settings !== null && "mcpServers" in settings) {
1567
+ const servers = settings.mcpServers;
1568
+ if (servers && typeof servers === "object" && Object.keys(servers).length > 0) {
1569
+ mcpServers = servers;
1570
+ }
1571
+ }
1572
+ } catch (error) {
1573
+ const errorMessage = error instanceof Error ? error.message : String(error);
1574
+ errors.push(`Failed to parse settings.json: ${errorMessage}`);
1575
+ }
1576
+ return {
1577
+ errors,
1578
+ ...ignorePatterns && { ignorePatterns },
1579
+ ...mcpServers && { mcpServers }
1580
+ };
1581
+ }
801
1582
 
802
1583
  // src/parsers/cline.ts
803
- var import_node_path12 = require("path");
1584
+ var import_node_path16 = require("path");
804
1585
  async function parseClineConfiguration(baseDir = process.cwd()) {
805
1586
  const errors = [];
806
1587
  const rules = [];
807
- const clineFilePath = (0, import_node_path12.join)(baseDir, ".cline", "instructions.md");
808
- if (!await fileExists(clineFilePath)) {
809
- errors.push(".cline/instructions.md file not found");
810
- return { rules, errors };
1588
+ const clineFilePath = (0, import_node_path16.join)(baseDir, ".cline", "instructions.md");
1589
+ if (await fileExists(clineFilePath)) {
1590
+ try {
1591
+ const content = await readFileContent(clineFilePath);
1592
+ if (content.trim()) {
1593
+ const frontmatter = {
1594
+ root: false,
1595
+ targets: ["cline"],
1596
+ description: "Cline instructions",
1597
+ globs: ["**/*"]
1598
+ };
1599
+ rules.push({
1600
+ frontmatter,
1601
+ content: content.trim(),
1602
+ filename: "cline-instructions",
1603
+ filepath: clineFilePath
1604
+ });
1605
+ }
1606
+ } catch (error) {
1607
+ const errorMessage = error instanceof Error ? error.message : String(error);
1608
+ errors.push(`Failed to parse .cline/instructions.md: ${errorMessage}`);
1609
+ }
811
1610
  }
812
- try {
813
- const content = await readFileContent(clineFilePath);
814
- if (content.trim()) {
815
- const frontmatter = {
816
- root: false,
817
- targets: ["cline"],
818
- description: "Cline AI assistant instructions",
819
- globs: ["**/*"]
820
- };
821
- rules.push({
822
- frontmatter,
823
- content: content.trim(),
824
- filename: "cline-instructions",
825
- filepath: clineFilePath
826
- });
1611
+ const clinerulesDirPath = (0, import_node_path16.join)(baseDir, ".clinerules");
1612
+ if (await fileExists(clinerulesDirPath)) {
1613
+ try {
1614
+ const { readdir: readdir2 } = await import("fs/promises");
1615
+ const files = await readdir2(clinerulesDirPath);
1616
+ for (const file of files) {
1617
+ if (file.endsWith(".md")) {
1618
+ const filePath = (0, import_node_path16.join)(clinerulesDirPath, file);
1619
+ try {
1620
+ const content = await readFileContent(filePath);
1621
+ if (content.trim()) {
1622
+ const filename = file.replace(".md", "");
1623
+ const frontmatter = {
1624
+ root: false,
1625
+ targets: ["cline"],
1626
+ description: `Cline rule: ${filename}`,
1627
+ globs: ["**/*"]
1628
+ };
1629
+ rules.push({
1630
+ frontmatter,
1631
+ content: content.trim(),
1632
+ filename: `cline-${filename}`,
1633
+ filepath: filePath
1634
+ });
1635
+ }
1636
+ } catch (error) {
1637
+ const errorMessage = error instanceof Error ? error.message : String(error);
1638
+ errors.push(`Failed to parse ${filePath}: ${errorMessage}`);
1639
+ }
1640
+ }
1641
+ }
1642
+ } catch (error) {
1643
+ const errorMessage = error instanceof Error ? error.message : String(error);
1644
+ errors.push(`Failed to parse .clinerules files: ${errorMessage}`);
827
1645
  }
828
- } catch (error) {
829
- const errorMessage = error instanceof Error ? error.message : String(error);
830
- errors.push(`Failed to parse Cline configuration: ${errorMessage}`);
1646
+ }
1647
+ if (rules.length === 0) {
1648
+ errors.push("No Cline configuration files found (.cline/instructions.md or .clinerules/*.md)");
831
1649
  }
832
1650
  return { rules, errors };
833
1651
  }
834
1652
 
835
1653
  // src/parsers/copilot.ts
836
- var import_node_path13 = require("path");
1654
+ var import_node_path17 = require("path");
837
1655
  var import_gray_matter2 = __toESM(require("gray-matter"));
838
1656
  async function parseCopilotConfiguration(baseDir = process.cwd()) {
839
1657
  const errors = [];
840
1658
  const rules = [];
841
- const copilotFilePath = (0, import_node_path13.join)(baseDir, ".github", "copilot-instructions.md");
1659
+ const copilotFilePath = (0, import_node_path17.join)(baseDir, ".github", "copilot-instructions.md");
842
1660
  if (await fileExists(copilotFilePath)) {
843
1661
  try {
844
1662
  const rawContent = await readFileContent(copilotFilePath);
@@ -863,19 +1681,19 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
863
1681
  errors.push(`Failed to parse copilot-instructions.md: ${errorMessage}`);
864
1682
  }
865
1683
  }
866
- const instructionsDir = (0, import_node_path13.join)(baseDir, ".github", "instructions");
1684
+ const instructionsDir = (0, import_node_path17.join)(baseDir, ".github", "instructions");
867
1685
  if (await fileExists(instructionsDir)) {
868
1686
  try {
869
1687
  const { readdir: readdir2 } = await import("fs/promises");
870
1688
  const files = await readdir2(instructionsDir);
871
1689
  for (const file of files) {
872
1690
  if (file.endsWith(".instructions.md")) {
873
- const filePath = (0, import_node_path13.join)(instructionsDir, file);
1691
+ const filePath = (0, import_node_path17.join)(instructionsDir, file);
874
1692
  const rawContent = await readFileContent(filePath);
875
1693
  const parsed = (0, import_gray_matter2.default)(rawContent);
876
1694
  const content = parsed.content.trim();
877
1695
  if (content) {
878
- const filename = (0, import_node_path13.basename)(file, ".instructions.md");
1696
+ const filename = (0, import_node_path17.basename)(file, ".instructions.md");
879
1697
  const frontmatter = {
880
1698
  root: false,
881
1699
  targets: ["copilot"],
@@ -905,19 +1723,19 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
905
1723
  }
906
1724
 
907
1725
  // src/parsers/cursor.ts
908
- var import_node_path14 = require("path");
1726
+ var import_node_path18 = require("path");
909
1727
  var import_gray_matter3 = __toESM(require("gray-matter"));
910
- var import_js_yaml = __toESM(require("js-yaml"));
1728
+ var import_js_yaml = require("js-yaml");
911
1729
  var customMatterOptions = {
912
1730
  engines: {
913
1731
  yaml: {
914
1732
  parse: (str) => {
915
1733
  try {
916
1734
  const preprocessed = str.replace(/^(\s*globs:\s*)\*\s*$/gm, '$1"*"');
917
- return import_js_yaml.default.load(preprocessed, { schema: import_js_yaml.default.DEFAULT_SCHEMA });
1735
+ return (0, import_js_yaml.load)(preprocessed, { schema: import_js_yaml.DEFAULT_SCHEMA });
918
1736
  } catch (error) {
919
1737
  try {
920
- return import_js_yaml.default.load(str, { schema: import_js_yaml.default.FAILSAFE_SCHEMA });
1738
+ return (0, import_js_yaml.load)(str, { schema: import_js_yaml.FAILSAFE_SCHEMA });
921
1739
  } catch {
922
1740
  throw error;
923
1741
  }
@@ -929,7 +1747,9 @@ var customMatterOptions = {
929
1747
  async function parseCursorConfiguration(baseDir = process.cwd()) {
930
1748
  const errors = [];
931
1749
  const rules = [];
932
- const cursorFilePath = (0, import_node_path14.join)(baseDir, ".cursorrules");
1750
+ let ignorePatterns;
1751
+ let mcpServers;
1752
+ const cursorFilePath = (0, import_node_path18.join)(baseDir, ".cursorrules");
933
1753
  if (await fileExists(cursorFilePath)) {
934
1754
  try {
935
1755
  const rawContent = await readFileContent(cursorFilePath);
@@ -954,20 +1774,20 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
954
1774
  errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
955
1775
  }
956
1776
  }
957
- const cursorRulesDir = (0, import_node_path14.join)(baseDir, ".cursor", "rules");
1777
+ const cursorRulesDir = (0, import_node_path18.join)(baseDir, ".cursor", "rules");
958
1778
  if (await fileExists(cursorRulesDir)) {
959
1779
  try {
960
1780
  const { readdir: readdir2 } = await import("fs/promises");
961
1781
  const files = await readdir2(cursorRulesDir);
962
1782
  for (const file of files) {
963
1783
  if (file.endsWith(".mdc")) {
964
- const filePath = (0, import_node_path14.join)(cursorRulesDir, file);
1784
+ const filePath = (0, import_node_path18.join)(cursorRulesDir, file);
965
1785
  try {
966
1786
  const rawContent = await readFileContent(filePath);
967
1787
  const parsed = (0, import_gray_matter3.default)(rawContent, customMatterOptions);
968
1788
  const content = parsed.content.trim();
969
1789
  if (content) {
970
- const filename = (0, import_node_path14.basename)(file, ".mdc");
1790
+ const filename = (0, import_node_path18.basename)(file, ".mdc");
971
1791
  const frontmatter = {
972
1792
  root: false,
973
1793
  targets: ["cursor"],
@@ -995,38 +1815,244 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
995
1815
  if (rules.length === 0) {
996
1816
  errors.push("No Cursor configuration files found (.cursorrules or .cursor/rules/*.mdc)");
997
1817
  }
998
- return { rules, errors };
1818
+ const cursorIgnorePath = (0, import_node_path18.join)(baseDir, ".cursorignore");
1819
+ if (await fileExists(cursorIgnorePath)) {
1820
+ try {
1821
+ const content = await readFileContent(cursorIgnorePath);
1822
+ const patterns = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
1823
+ if (patterns.length > 0) {
1824
+ ignorePatterns = patterns;
1825
+ }
1826
+ } catch (error) {
1827
+ const errorMessage = error instanceof Error ? error.message : String(error);
1828
+ errors.push(`Failed to parse .cursorignore: ${errorMessage}`);
1829
+ }
1830
+ }
1831
+ const cursorMcpPath = (0, import_node_path18.join)(baseDir, ".cursor", "mcp.json");
1832
+ if (await fileExists(cursorMcpPath)) {
1833
+ try {
1834
+ const content = await readFileContent(cursorMcpPath);
1835
+ const mcp = JSON.parse(content);
1836
+ if (mcp.mcpServers && Object.keys(mcp.mcpServers).length > 0) {
1837
+ mcpServers = mcp.mcpServers;
1838
+ }
1839
+ } catch (error) {
1840
+ const errorMessage = error instanceof Error ? error.message : String(error);
1841
+ errors.push(`Failed to parse .cursor/mcp.json: ${errorMessage}`);
1842
+ }
1843
+ }
1844
+ return {
1845
+ rules,
1846
+ errors,
1847
+ ...ignorePatterns && { ignorePatterns },
1848
+ ...mcpServers && { mcpServers }
1849
+ };
999
1850
  }
1000
1851
 
1001
- // src/parsers/roo.ts
1002
- var import_node_path15 = require("path");
1003
- async function parseRooConfiguration(baseDir = process.cwd()) {
1852
+ // src/parsers/geminicli.ts
1853
+ var import_node_path19 = require("path");
1854
+ async function parseGeminiConfiguration(baseDir = process.cwd()) {
1004
1855
  const errors = [];
1005
1856
  const rules = [];
1006
- const rooFilePath = (0, import_node_path15.join)(baseDir, ".roo", "instructions.md");
1007
- if (!await fileExists(rooFilePath)) {
1008
- errors.push(".roo/instructions.md file not found");
1857
+ let ignorePatterns;
1858
+ let mcpServers;
1859
+ const geminiFilePath = (0, import_node_path19.join)(baseDir, "GEMINI.md");
1860
+ if (!await fileExists(geminiFilePath)) {
1861
+ errors.push("GEMINI.md file not found");
1009
1862
  return { rules, errors };
1010
1863
  }
1011
1864
  try {
1012
- const content = await readFileContent(rooFilePath);
1013
- if (content.trim()) {
1014
- const frontmatter = {
1015
- root: false,
1016
- targets: ["roo"],
1017
- description: "Roo Code AI assistant instructions",
1018
- globs: ["**/*"]
1019
- };
1020
- rules.push({
1021
- frontmatter,
1022
- content: content.trim(),
1023
- filename: "roo-instructions",
1024
- filepath: rooFilePath
1025
- });
1865
+ const geminiContent = await readFileContent(geminiFilePath);
1866
+ const mainRule = parseGeminiMainFile(geminiContent, geminiFilePath);
1867
+ if (mainRule) {
1868
+ rules.push(mainRule);
1869
+ }
1870
+ const memoryDir = (0, import_node_path19.join)(baseDir, ".gemini", "memories");
1871
+ if (await fileExists(memoryDir)) {
1872
+ const memoryRules = await parseGeminiMemoryFiles(memoryDir);
1873
+ rules.push(...memoryRules);
1874
+ }
1875
+ const settingsPath = (0, import_node_path19.join)(baseDir, ".gemini", "settings.json");
1876
+ if (await fileExists(settingsPath)) {
1877
+ const settingsResult = await parseGeminiSettings(settingsPath);
1878
+ if (settingsResult.ignorePatterns) {
1879
+ ignorePatterns = settingsResult.ignorePatterns;
1880
+ }
1881
+ if (settingsResult.mcpServers) {
1882
+ mcpServers = settingsResult.mcpServers;
1883
+ }
1884
+ errors.push(...settingsResult.errors);
1885
+ }
1886
+ const aiexcludePath = (0, import_node_path19.join)(baseDir, ".aiexclude");
1887
+ if (await fileExists(aiexcludePath)) {
1888
+ const aiexcludePatterns = await parseAiexclude(aiexcludePath);
1889
+ if (aiexcludePatterns.length > 0) {
1890
+ ignorePatterns = ignorePatterns ? [...ignorePatterns, ...aiexcludePatterns] : aiexcludePatterns;
1891
+ }
1892
+ }
1893
+ } catch (error) {
1894
+ const errorMessage = error instanceof Error ? error.message : String(error);
1895
+ errors.push(`Failed to parse Gemini configuration: ${errorMessage}`);
1896
+ }
1897
+ return {
1898
+ rules,
1899
+ errors,
1900
+ ...ignorePatterns && { ignorePatterns },
1901
+ ...mcpServers && { mcpServers }
1902
+ };
1903
+ }
1904
+ function parseGeminiMainFile(content, filepath) {
1905
+ const lines = content.split("\n");
1906
+ let contentStartIndex = 0;
1907
+ if (lines.some((line) => line.includes("| Document | Description | File Patterns |"))) {
1908
+ const tableEndIndex = lines.findIndex(
1909
+ (line, index) => index > 0 && line.trim() === "" && lines[index - 1]?.includes("|") && !lines[index + 1]?.includes("|")
1910
+ );
1911
+ if (tableEndIndex !== -1) {
1912
+ contentStartIndex = tableEndIndex + 1;
1913
+ }
1914
+ }
1915
+ const mainContent = lines.slice(contentStartIndex).join("\n").trim();
1916
+ if (!mainContent) {
1917
+ return null;
1918
+ }
1919
+ const frontmatter = {
1920
+ root: false,
1921
+ targets: ["geminicli"],
1922
+ description: "Main Gemini CLI configuration",
1923
+ globs: ["**/*"]
1924
+ };
1925
+ return {
1926
+ frontmatter,
1927
+ content: mainContent,
1928
+ filename: "gemini-main",
1929
+ filepath
1930
+ };
1931
+ }
1932
+ async function parseGeminiMemoryFiles(memoryDir) {
1933
+ const rules = [];
1934
+ try {
1935
+ const { readdir: readdir2 } = await import("fs/promises");
1936
+ const files = await readdir2(memoryDir);
1937
+ for (const file of files) {
1938
+ if (file.endsWith(".md")) {
1939
+ const filePath = (0, import_node_path19.join)(memoryDir, file);
1940
+ const content = await readFileContent(filePath);
1941
+ if (content.trim()) {
1942
+ const filename = (0, import_node_path19.basename)(file, ".md");
1943
+ const frontmatter = {
1944
+ root: false,
1945
+ targets: ["geminicli"],
1946
+ description: `Memory file: ${filename}`,
1947
+ globs: ["**/*"]
1948
+ };
1949
+ rules.push({
1950
+ frontmatter,
1951
+ content: content.trim(),
1952
+ filename: `gemini-memory-${filename}`,
1953
+ filepath: filePath
1954
+ });
1955
+ }
1956
+ }
1957
+ }
1958
+ } catch {
1959
+ }
1960
+ return rules;
1961
+ }
1962
+ async function parseGeminiSettings(settingsPath) {
1963
+ const errors = [];
1964
+ let mcpServers;
1965
+ try {
1966
+ const content = await readFileContent(settingsPath);
1967
+ const settings = JSON.parse(content);
1968
+ if (settings.mcpServers && Object.keys(settings.mcpServers).length > 0) {
1969
+ mcpServers = settings.mcpServers;
1026
1970
  }
1027
1971
  } catch (error) {
1028
1972
  const errorMessage = error instanceof Error ? error.message : String(error);
1029
- errors.push(`Failed to parse Roo configuration: ${errorMessage}`);
1973
+ errors.push(`Failed to parse settings.json: ${errorMessage}`);
1974
+ }
1975
+ return {
1976
+ errors,
1977
+ ...mcpServers && { mcpServers }
1978
+ };
1979
+ }
1980
+ async function parseAiexclude(aiexcludePath) {
1981
+ try {
1982
+ const content = await readFileContent(aiexcludePath);
1983
+ const patterns = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
1984
+ return patterns;
1985
+ } catch {
1986
+ return [];
1987
+ }
1988
+ }
1989
+
1990
+ // src/parsers/roo.ts
1991
+ var import_node_path20 = require("path");
1992
+ async function parseRooConfiguration(baseDir = process.cwd()) {
1993
+ const errors = [];
1994
+ const rules = [];
1995
+ const rooFilePath = (0, import_node_path20.join)(baseDir, ".roo", "instructions.md");
1996
+ if (await fileExists(rooFilePath)) {
1997
+ try {
1998
+ const content = await readFileContent(rooFilePath);
1999
+ if (content.trim()) {
2000
+ const frontmatter = {
2001
+ root: false,
2002
+ targets: ["roo"],
2003
+ description: "Roo Code instructions",
2004
+ globs: ["**/*"]
2005
+ };
2006
+ rules.push({
2007
+ frontmatter,
2008
+ content: content.trim(),
2009
+ filename: "roo-instructions",
2010
+ filepath: rooFilePath
2011
+ });
2012
+ }
2013
+ } catch (error) {
2014
+ const errorMessage = error instanceof Error ? error.message : String(error);
2015
+ errors.push(`Failed to parse .roo/instructions.md: ${errorMessage}`);
2016
+ }
2017
+ }
2018
+ const rooRulesDir = (0, import_node_path20.join)(baseDir, ".roo", "rules");
2019
+ if (await fileExists(rooRulesDir)) {
2020
+ try {
2021
+ const { readdir: readdir2 } = await import("fs/promises");
2022
+ const files = await readdir2(rooRulesDir);
2023
+ for (const file of files) {
2024
+ if (file.endsWith(".md")) {
2025
+ const filePath = (0, import_node_path20.join)(rooRulesDir, file);
2026
+ try {
2027
+ const content = await readFileContent(filePath);
2028
+ if (content.trim()) {
2029
+ const filename = file.replace(".md", "");
2030
+ const frontmatter = {
2031
+ root: false,
2032
+ targets: ["roo"],
2033
+ description: `Roo rule: ${filename}`,
2034
+ globs: ["**/*"]
2035
+ };
2036
+ rules.push({
2037
+ frontmatter,
2038
+ content: content.trim(),
2039
+ filename: `roo-${filename}`,
2040
+ filepath: filePath
2041
+ });
2042
+ }
2043
+ } catch (error) {
2044
+ const errorMessage = error instanceof Error ? error.message : String(error);
2045
+ errors.push(`Failed to parse ${filePath}: ${errorMessage}`);
2046
+ }
2047
+ }
2048
+ }
2049
+ } catch (error) {
2050
+ const errorMessage = error instanceof Error ? error.message : String(error);
2051
+ errors.push(`Failed to parse .roo/rules files: ${errorMessage}`);
2052
+ }
2053
+ }
2054
+ if (rules.length === 0) {
2055
+ errors.push("No Roo Code configuration files found (.roo/instructions.md or .roo/rules/*.md)");
1030
2056
  }
1031
2057
  return { rules, errors };
1032
2058
  }
@@ -1036,6 +2062,8 @@ async function importConfiguration(options) {
1036
2062
  const { tool, baseDir = process.cwd(), rulesDir = ".rulesync", verbose = false } = options;
1037
2063
  const errors = [];
1038
2064
  let rules = [];
2065
+ let ignorePatterns;
2066
+ let mcpServers;
1039
2067
  if (verbose) {
1040
2068
  console.log(`Importing ${tool} configuration from ${baseDir}...`);
1041
2069
  }
@@ -1045,12 +2073,16 @@ async function importConfiguration(options) {
1045
2073
  const claudeResult = await parseClaudeConfiguration(baseDir);
1046
2074
  rules = claudeResult.rules;
1047
2075
  errors.push(...claudeResult.errors);
2076
+ ignorePatterns = claudeResult.ignorePatterns;
2077
+ mcpServers = claudeResult.mcpServers;
1048
2078
  break;
1049
2079
  }
1050
2080
  case "cursor": {
1051
2081
  const cursorResult = await parseCursorConfiguration(baseDir);
1052
2082
  rules = cursorResult.rules;
1053
2083
  errors.push(...cursorResult.errors);
2084
+ ignorePatterns = cursorResult.ignorePatterns;
2085
+ mcpServers = cursorResult.mcpServers;
1054
2086
  break;
1055
2087
  }
1056
2088
  case "copilot": {
@@ -1071,6 +2103,14 @@ async function importConfiguration(options) {
1071
2103
  errors.push(...rooResult.errors);
1072
2104
  break;
1073
2105
  }
2106
+ case "geminicli": {
2107
+ const geminiResult = await parseGeminiConfiguration(baseDir);
2108
+ rules = geminiResult.rules;
2109
+ errors.push(...geminiResult.errors);
2110
+ ignorePatterns = geminiResult.ignorePatterns;
2111
+ mcpServers = geminiResult.mcpServers;
2112
+ break;
2113
+ }
1074
2114
  default:
1075
2115
  errors.push(`Unsupported tool: ${tool}`);
1076
2116
  return { success: false, rulesCreated: 0, errors };
@@ -1080,10 +2120,10 @@ async function importConfiguration(options) {
1080
2120
  errors.push(`Failed to parse ${tool} configuration: ${errorMessage}`);
1081
2121
  return { success: false, rulesCreated: 0, errors };
1082
2122
  }
1083
- if (rules.length === 0) {
2123
+ if (rules.length === 0 && !ignorePatterns && !mcpServers) {
1084
2124
  return { success: false, rulesCreated: 0, errors };
1085
2125
  }
1086
- const rulesDirPath = (0, import_node_path16.join)(baseDir, rulesDir);
2126
+ const rulesDirPath = (0, import_node_path21.join)(baseDir, rulesDir);
1087
2127
  try {
1088
2128
  const { mkdir: mkdir3 } = await import("fs/promises");
1089
2129
  await mkdir3(rulesDirPath, { recursive: true });
@@ -1097,7 +2137,7 @@ async function importConfiguration(options) {
1097
2137
  try {
1098
2138
  const baseFilename = `${tool}__${rule.filename}`;
1099
2139
  const filename = await generateUniqueFilename(rulesDirPath, baseFilename);
1100
- const filePath = (0, import_node_path16.join)(rulesDirPath, `${filename}.md`);
2140
+ const filePath = (0, import_node_path21.join)(rulesDirPath, `${filename}.md`);
1101
2141
  const content = generateRuleFileContent(rule);
1102
2142
  await writeFileContent(filePath, content);
1103
2143
  rulesCreated++;
@@ -1109,10 +2149,44 @@ async function importConfiguration(options) {
1109
2149
  errors.push(`Failed to create rule file for ${rule.filename}: ${errorMessage}`);
1110
2150
  }
1111
2151
  }
2152
+ let ignoreFileCreated = false;
2153
+ if (ignorePatterns && ignorePatterns.length > 0) {
2154
+ try {
2155
+ const rulesyncignorePath = (0, import_node_path21.join)(baseDir, ".rulesyncignore");
2156
+ const ignoreContent = `${ignorePatterns.join("\n")}
2157
+ `;
2158
+ await writeFileContent(rulesyncignorePath, ignoreContent);
2159
+ ignoreFileCreated = true;
2160
+ if (verbose) {
2161
+ console.log(`\u2705 Created .rulesyncignore with ${ignorePatterns.length} patterns`);
2162
+ }
2163
+ } catch (error) {
2164
+ const errorMessage = error instanceof Error ? error.message : String(error);
2165
+ errors.push(`Failed to create .rulesyncignore: ${errorMessage}`);
2166
+ }
2167
+ }
2168
+ let mcpFileCreated = false;
2169
+ if (mcpServers && Object.keys(mcpServers).length > 0) {
2170
+ try {
2171
+ const mcpPath = (0, import_node_path21.join)(baseDir, rulesDir, ".mcp.json");
2172
+ const mcpContent = `${JSON.stringify({ mcpServers }, null, 2)}
2173
+ `;
2174
+ await writeFileContent(mcpPath, mcpContent);
2175
+ mcpFileCreated = true;
2176
+ if (verbose) {
2177
+ console.log(`\u2705 Created .mcp.json with ${Object.keys(mcpServers).length} servers`);
2178
+ }
2179
+ } catch (error) {
2180
+ const errorMessage = error instanceof Error ? error.message : String(error);
2181
+ errors.push(`Failed to create .mcp.json: ${errorMessage}`);
2182
+ }
2183
+ }
1112
2184
  return {
1113
- success: rulesCreated > 0,
2185
+ success: rulesCreated > 0 || ignoreFileCreated || mcpFileCreated,
1114
2186
  rulesCreated,
1115
- errors
2187
+ errors,
2188
+ ignoreFileCreated,
2189
+ mcpFileCreated
1116
2190
  };
1117
2191
  }
1118
2192
  function generateRuleFileContent(rule) {
@@ -1122,7 +2196,7 @@ function generateRuleFileContent(rule) {
1122
2196
  async function generateUniqueFilename(rulesDir, baseFilename) {
1123
2197
  let filename = baseFilename;
1124
2198
  let counter = 1;
1125
- while (await fileExists((0, import_node_path16.join)(rulesDir, `${filename}.md`))) {
2199
+ while (await fileExists((0, import_node_path21.join)(rulesDir, `${filename}.md`))) {
1126
2200
  filename = `${baseFilename}-${counter}`;
1127
2201
  counter++;
1128
2202
  }
@@ -1140,57 +2214,54 @@ async function importCommand(options = {}) {
1140
2214
  if (options.geminicli) tools.push("geminicli");
1141
2215
  if (tools.length === 0) {
1142
2216
  console.error(
1143
- "\u274C Please specify at least one tool to import from (--claudecode, --cursor, --copilot, --cline, --roo, --geminicli)"
2217
+ "\u274C Please specify one tool to import from (--claudecode, --cursor, --copilot, --cline, --roo, --geminicli)"
1144
2218
  );
1145
2219
  process.exit(1);
1146
2220
  }
1147
- console.log("Importing configuration files...");
1148
- let totalRulesCreated = 0;
1149
- const allErrors = [];
1150
- for (const tool of tools) {
1151
- if (options.verbose) {
1152
- console.log(`
1153
- Importing from ${tool}...`);
1154
- }
1155
- try {
1156
- const result = await importConfiguration({
1157
- tool,
1158
- verbose: options.verbose ?? false
1159
- });
1160
- if (result.success) {
1161
- console.log(`\u2705 Imported ${result.rulesCreated} rule(s) from ${tool}`);
1162
- totalRulesCreated += result.rulesCreated;
1163
- } else if (result.errors.length > 0) {
1164
- console.warn(`\u26A0\uFE0F Failed to import from ${tool}: ${result.errors[0]}`);
1165
- if (options.verbose) {
1166
- allErrors.push(...result.errors);
1167
- }
1168
- }
1169
- } catch (error) {
1170
- const errorMessage = error instanceof Error ? error.message : String(error);
1171
- console.error(`\u274C Error importing from ${tool}: ${errorMessage}`);
1172
- allErrors.push(`${tool}: ${errorMessage}`);
1173
- }
1174
- }
1175
- if (totalRulesCreated > 0) {
1176
- console.log(`
1177
- \u{1F389} Successfully imported ${totalRulesCreated} rule(s) total!`);
1178
- console.log("You can now run 'rulesync generate' to create tool-specific configurations.");
1179
- } else {
1180
- console.warn(
1181
- "\n\u26A0\uFE0F No rules were imported. Please check that configuration files exist for the selected tools."
2221
+ if (tools.length > 1) {
2222
+ console.error(
2223
+ "\u274C Only one tool can be specified at a time. Please run the import command separately for each tool."
1182
2224
  );
2225
+ process.exit(1);
1183
2226
  }
1184
- if (options.verbose && allErrors.length > 0) {
1185
- console.log("\nDetailed errors:");
1186
- for (const error of allErrors) {
1187
- console.log(` - ${error}`);
2227
+ const tool = tools[0];
2228
+ if (!tool) {
2229
+ console.error("Error: No tool specified");
2230
+ process.exit(1);
2231
+ }
2232
+ console.log(`Importing configuration files from ${tool}...`);
2233
+ try {
2234
+ const result = await importConfiguration({
2235
+ tool,
2236
+ verbose: options.verbose ?? false
2237
+ });
2238
+ if (result.success) {
2239
+ console.log(`\u2705 Imported ${result.rulesCreated} rule(s) from ${tool}`);
2240
+ if (result.ignoreFileCreated) {
2241
+ console.log("\u2705 Created .rulesyncignore file from ignore patterns");
2242
+ }
2243
+ if (result.mcpFileCreated) {
2244
+ console.log("\u2705 Created .rulesync/.mcp.json file from MCP configuration");
2245
+ }
2246
+ console.log("You can now run 'rulesync generate' to create tool-specific configurations.");
2247
+ } else if (result.errors.length > 0) {
2248
+ console.warn(`\u26A0\uFE0F Failed to import from ${tool}: ${result.errors[0]}`);
2249
+ if (options.verbose && result.errors.length > 1) {
2250
+ console.log("\nDetailed errors:");
2251
+ for (const error of result.errors) {
2252
+ console.log(` - ${error}`);
2253
+ }
2254
+ }
1188
2255
  }
2256
+ } catch (error) {
2257
+ const errorMessage = error instanceof Error ? error.message : String(error);
2258
+ console.error(`\u274C Error importing from ${tool}: ${errorMessage}`);
2259
+ process.exit(1);
1189
2260
  }
1190
2261
  }
1191
2262
 
1192
2263
  // src/cli/commands/init.ts
1193
- var import_node_path17 = require("path");
2264
+ var import_node_path22 = require("path");
1194
2265
  async function initCommand() {
1195
2266
  const aiRulesDir = ".rulesync";
1196
2267
  console.log("Initializing rulesync...");
@@ -1320,7 +2391,7 @@ globs: ["src/api/**/*.ts", "src/services/**/*.ts", "src/models/**/*.ts"]
1320
2391
  }
1321
2392
  ];
1322
2393
  for (const file of sampleFiles) {
1323
- const filepath = (0, import_node_path17.join)(aiRulesDir, file.filename);
2394
+ const filepath = (0, import_node_path22.join)(aiRulesDir, file.filename);
1324
2395
  if (!await fileExists(filepath)) {
1325
2396
  await writeFileContent(filepath, file.content);
1326
2397
  console.log(`Created ${filepath}`);
@@ -1422,22 +2493,22 @@ async function validateCommand() {
1422
2493
  }
1423
2494
 
1424
2495
  // src/cli/commands/watch.ts
1425
- var import_chokidar = __toESM(require("chokidar"));
2496
+ var import_chokidar = require("chokidar");
1426
2497
  async function watchCommand() {
1427
2498
  const config = getDefaultConfig();
1428
2499
  console.log("\u{1F440} Watching for changes in .rulesync directory...");
1429
2500
  console.log("Press Ctrl+C to stop watching");
1430
2501
  await generateCommand({ verbose: false });
1431
- const watcher = import_chokidar.default.watch(`${config.aiRulesDir}/**/*.md`, {
2502
+ const watcher = (0, import_chokidar.watch)(`${config.aiRulesDir}/**/*.md`, {
1432
2503
  ignoreInitial: true,
1433
2504
  persistent: true
1434
2505
  });
1435
2506
  let isGenerating = false;
1436
- const handleChange = async (path2) => {
2507
+ const handleChange = async (path4) => {
1437
2508
  if (isGenerating) return;
1438
2509
  isGenerating = true;
1439
2510
  console.log(`
1440
- \u{1F4DD} Detected change in ${path2}`);
2511
+ \u{1F4DD} Detected change in ${path4}`);
1441
2512
  try {
1442
2513
  await generateCommand({ verbose: false });
1443
2514
  console.log("\u2705 Regenerated configuration files");
@@ -1447,10 +2518,10 @@ async function watchCommand() {
1447
2518
  isGenerating = false;
1448
2519
  }
1449
2520
  };
1450
- watcher.on("change", handleChange).on("add", handleChange).on("unlink", (path2) => {
2521
+ watcher.on("change", handleChange).on("add", handleChange).on("unlink", (path4) => {
1451
2522
  console.log(`
1452
- \u{1F5D1}\uFE0F Removed ${path2}`);
1453
- handleChange(path2);
2523
+ \u{1F5D1}\uFE0F Removed ${path4}`);
2524
+ handleChange(path4);
1454
2525
  }).on("error", (error) => {
1455
2526
  console.error("\u274C Watcher error:", error);
1456
2527
  });
@@ -1463,7 +2534,7 @@ async function watchCommand() {
1463
2534
 
1464
2535
  // src/cli/index.ts
1465
2536
  var program = new import_commander.Command();
1466
- program.name("rulesync").description("Unified AI rules management CLI tool").version("0.34.0");
2537
+ program.name("rulesync").description("Unified AI rules management CLI tool").version("0.37.0");
1467
2538
  program.command("init").description("Initialize rulesync in current directory").action(initCommand);
1468
2539
  program.command("add <filename>").description("Add a new rule file").action(addCommand);
1469
2540
  program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);