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/README.ja.md +95 -6
- package/README.md +95 -6
- package/dist/chunk-6PQ4APY4.mjs +70 -0
- package/dist/chunk-QHXMJZTJ.mjs +62 -0
- package/dist/chunk-QVPQ2X4L.mjs +77 -0
- package/dist/chunk-SBYRCTWS.mjs +64 -0
- package/dist/chunk-UNTCJDMQ.mjs +73 -0
- package/dist/chunk-YGXGGUBG.mjs +80 -0
- package/dist/claude-O4SRX6VC.mjs +8 -0
- package/dist/cline-H5JF2OPT.mjs +8 -0
- package/dist/copilot-GCIYHK4Q.mjs +8 -0
- package/dist/cursor-N75OH6WS.mjs +8 -0
- package/dist/geminicli-AGOQ32ZE.mjs +8 -0
- package/dist/index.js +1232 -218
- package/dist/index.mjs +929 -209
- package/dist/roo-V5YVC222.mjs +8 -0
- package/package.json +14 -6
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 ?
|
|
221
|
+
const claudeOutputDir = baseDir ? join3(baseDir, config.outputPaths.claudecode) : config.outputPaths.claudecode;
|
|
77
222
|
outputs.push({
|
|
78
223
|
tool: "claudecode",
|
|
79
|
-
filepath:
|
|
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:
|
|
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
|
|
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 ?
|
|
126
|
-
const filepath =
|
|
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
|
|
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 ?
|
|
147
|
-
const filepath =
|
|
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
|
|
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 ?
|
|
177
|
-
const filepath =
|
|
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
|
|
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 ?
|
|
216
|
-
const filepath =
|
|
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 ?
|
|
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
|
|
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 ?
|
|
267
|
-
const filepath =
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
|
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
|
|
1018
|
+
import { join as join9 } from "path";
|
|
647
1019
|
var gitignoreCommand = async () => {
|
|
648
|
-
const gitignorePath =
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 {
|
|
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 =
|
|
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
|
|
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 =
|
|
785
|
-
if (
|
|
786
|
-
|
|
787
|
-
|
|
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
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
const
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
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
|
-
}
|
|
806
|
-
|
|
807
|
-
errors.push(
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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/
|
|
979
|
-
import { join as
|
|
980
|
-
async function
|
|
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
|
-
|
|
984
|
-
|
|
985
|
-
|
|
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
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
rules.push(
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
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
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
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
|
|
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 =
|
|
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 (
|
|
2133
|
+
const handleChange = async (path4) => {
|
|
1414
2134
|
if (isGenerating) return;
|
|
1415
2135
|
isGenerating = true;
|
|
1416
2136
|
console.log(`
|
|
1417
|
-
\u{1F4DD} Detected change in ${
|
|
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", (
|
|
2147
|
+
watcher.on("change", handleChange).on("add", handleChange).on("unlink", (path4) => {
|
|
1428
2148
|
console.log(`
|
|
1429
|
-
\u{1F5D1}\uFE0F Removed ${
|
|
1430
|
-
handleChange(
|
|
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.
|
|
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);
|