rulesync 0.42.0 → 0.44.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/{chunk-HMMPZV7X.js → chunk-22GWBUIP.js} +15 -9
- package/dist/{chunk-X3FEMISQ.js → chunk-BD37M3ZH.js} +2 -2
- package/dist/{chunk-2SPL7QTK.js → chunk-DCSO5MY7.js} +2 -2
- package/dist/{chunk-D3YGI36J.js → chunk-FAZT3ILF.js} +8 -7
- package/dist/chunk-I5XVU7C6.js +38 -0
- package/dist/{chunk-3NRSCDLQ.js → chunk-PJUNIIF4.js} +7 -11
- package/dist/{chunk-SXEFNT27.js → chunk-ZORSPGDD.js} +1 -1
- package/dist/{claude-3YGZIO5F.js → claudecode-KSK2BEI7.js} +2 -2
- package/dist/{cline-ERYW7TOO.js → cline-T5YVGYBF.js} +2 -2
- package/dist/{copilot-NLSI3ID7.js → copilot-UDCWNUAH.js} +2 -2
- package/dist/{cursor-CX55HMO4.js → cursor-KPV6OVST.js} +2 -2
- package/dist/{geminicli-XHQR4RCQ.js → geminicli-2DC5F34J.js} +2 -2
- package/dist/index.cjs +271 -254
- package/dist/index.js +239 -206
- package/dist/{roo-OKE7XF3B.js → roo-DRA2SU4L.js} +2 -2
- package/package.json +12 -5
- package/dist/chunk-6YNGMPAL.js +0 -56
package/dist/index.js
CHANGED
|
@@ -1,30 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
generateClaudeMcp
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-22GWBUIP.js";
|
|
5
5
|
import {
|
|
6
6
|
generateClineMcp
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-BD37M3ZH.js";
|
|
8
8
|
import {
|
|
9
9
|
generateCopilotMcp
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-ZORSPGDD.js";
|
|
11
11
|
import {
|
|
12
12
|
generateCursorMcp
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-FAZT3ILF.js";
|
|
14
14
|
import {
|
|
15
15
|
generateGeminiCliMcp
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-DCSO5MY7.js";
|
|
17
17
|
import {
|
|
18
18
|
generateRooMcp
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
import
|
|
19
|
+
} from "./chunk-PJUNIIF4.js";
|
|
20
|
+
import {
|
|
21
|
+
RulesyncTargetsSchema,
|
|
22
|
+
ToolTargetSchema,
|
|
23
|
+
ToolTargetsSchema
|
|
24
|
+
} from "./chunk-I5XVU7C6.js";
|
|
21
25
|
|
|
22
26
|
// src/cli/index.ts
|
|
23
27
|
import { Command } from "commander";
|
|
24
28
|
|
|
25
29
|
// src/cli/commands/add.ts
|
|
26
30
|
import { mkdir, writeFile } from "fs/promises";
|
|
27
|
-
import path from "path";
|
|
31
|
+
import * as path from "path";
|
|
28
32
|
|
|
29
33
|
// src/utils/config.ts
|
|
30
34
|
function getDefaultConfig() {
|
|
@@ -35,19 +39,19 @@ function getDefaultConfig() {
|
|
|
35
39
|
cursor: ".cursor/rules",
|
|
36
40
|
cline: ".clinerules",
|
|
37
41
|
claudecode: ".",
|
|
38
|
-
claude: ".",
|
|
39
42
|
roo: ".roo/rules",
|
|
40
43
|
geminicli: ".gemini/memories"
|
|
41
44
|
},
|
|
42
45
|
watchEnabled: false,
|
|
43
|
-
defaultTargets: ["copilot", "cursor", "cline", "claudecode", "
|
|
46
|
+
defaultTargets: ["copilot", "cursor", "cline", "claudecode", "roo", "geminicli"]
|
|
44
47
|
};
|
|
45
48
|
}
|
|
46
49
|
function resolveTargets(targets, config) {
|
|
47
|
-
if (targets[0] === "*") {
|
|
50
|
+
if (targets.length === 1 && targets[0] === "*") {
|
|
48
51
|
return config.defaultTargets;
|
|
49
52
|
}
|
|
50
|
-
|
|
53
|
+
const validatedTargets = ToolTargetsSchema.parse(targets);
|
|
54
|
+
return validatedTargets;
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
// src/cli/commands/add.ts
|
|
@@ -87,11 +91,22 @@ async function addCommand(filename) {
|
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
// src/generators/rules/claudecode.ts
|
|
90
|
-
import { join as
|
|
94
|
+
import { join as join4 } from "path";
|
|
95
|
+
|
|
96
|
+
// src/types/claudecode.ts
|
|
97
|
+
import { z } from "zod/v4-mini";
|
|
98
|
+
var ClaudeSettingsSchema = z.looseObject({
|
|
99
|
+
permissions: z._default(
|
|
100
|
+
z.looseObject({
|
|
101
|
+
deny: z._default(z.array(z.string()), [])
|
|
102
|
+
}),
|
|
103
|
+
{ deny: [] }
|
|
104
|
+
)
|
|
105
|
+
});
|
|
91
106
|
|
|
92
107
|
// src/utils/file.ts
|
|
93
108
|
import { readdir, rm } from "fs/promises";
|
|
94
|
-
import { join as
|
|
109
|
+
import { join as join3 } from "path";
|
|
95
110
|
|
|
96
111
|
// src/utils/file-ops.ts
|
|
97
112
|
import { mkdir as mkdir2, readFile, stat, writeFile as writeFile2 } from "fs/promises";
|
|
@@ -120,14 +135,14 @@ async function fileExists(filepath) {
|
|
|
120
135
|
}
|
|
121
136
|
|
|
122
137
|
// src/utils/ignore.ts
|
|
123
|
-
import { join } from "path";
|
|
138
|
+
import { join as join2 } from "path";
|
|
124
139
|
import micromatch from "micromatch";
|
|
125
140
|
var cachedIgnorePatterns = null;
|
|
126
141
|
async function loadIgnorePatterns(baseDir = process.cwd()) {
|
|
127
142
|
if (cachedIgnorePatterns) {
|
|
128
143
|
return cachedIgnorePatterns;
|
|
129
144
|
}
|
|
130
|
-
const ignorePath =
|
|
145
|
+
const ignorePath = join2(baseDir, ".rulesyncignore");
|
|
131
146
|
if (!await fileExists(ignorePath)) {
|
|
132
147
|
cachedIgnorePatterns = { patterns: [] };
|
|
133
148
|
return cachedIgnorePatterns;
|
|
@@ -174,7 +189,7 @@ function filterIgnoredFiles(files, ignorePatterns) {
|
|
|
174
189
|
async function findFiles(dir, extension = ".md", ignorePatterns) {
|
|
175
190
|
try {
|
|
176
191
|
const files = await readdir(dir);
|
|
177
|
-
const filtered = files.filter((file) => file.endsWith(extension)).map((file) =>
|
|
192
|
+
const filtered = files.filter((file) => file.endsWith(extension)).map((file) => join3(dir, file));
|
|
178
193
|
if (ignorePatterns && ignorePatterns.length > 0) {
|
|
179
194
|
return filterIgnoredFiles(filtered, ignorePatterns);
|
|
180
195
|
}
|
|
@@ -223,23 +238,23 @@ async function generateClaudecodeConfig(rules, config, baseDir) {
|
|
|
223
238
|
const rootRules = rules.filter((r) => r.frontmatter.root === true);
|
|
224
239
|
const detailRules = rules.filter((r) => r.frontmatter.root === false);
|
|
225
240
|
const claudeMdContent = generateClaudeMarkdown(rootRules, detailRules);
|
|
226
|
-
const claudeOutputDir = baseDir ?
|
|
241
|
+
const claudeOutputDir = baseDir ? join4(baseDir, config.outputPaths.claudecode) : config.outputPaths.claudecode;
|
|
227
242
|
outputs.push({
|
|
228
243
|
tool: "claudecode",
|
|
229
|
-
filepath:
|
|
244
|
+
filepath: join4(claudeOutputDir, "CLAUDE.md"),
|
|
230
245
|
content: claudeMdContent
|
|
231
246
|
});
|
|
232
247
|
for (const rule of detailRules) {
|
|
233
248
|
const memoryContent = generateMemoryFile(rule);
|
|
234
249
|
outputs.push({
|
|
235
250
|
tool: "claudecode",
|
|
236
|
-
filepath:
|
|
251
|
+
filepath: join4(claudeOutputDir, ".claude", "memories", `${rule.filename}.md`),
|
|
237
252
|
content: memoryContent
|
|
238
253
|
});
|
|
239
254
|
}
|
|
240
255
|
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
241
256
|
if (ignorePatterns.patterns.length > 0) {
|
|
242
|
-
const settingsPath = baseDir ?
|
|
257
|
+
const settingsPath = baseDir ? join4(baseDir, ".claude", "settings.json") : join4(".claude", "settings.json");
|
|
243
258
|
await updateClaudeSettings(settingsPath, ignorePatterns.patterns);
|
|
244
259
|
}
|
|
245
260
|
return outputs;
|
|
@@ -249,12 +264,10 @@ function generateClaudeMarkdown(rootRules, detailRules) {
|
|
|
249
264
|
if (detailRules.length > 0) {
|
|
250
265
|
lines.push("Please also reference the following documents as needed:");
|
|
251
266
|
lines.push("");
|
|
252
|
-
lines.push("| Document | Description | File Patterns |");
|
|
253
|
-
lines.push("|----------|-------------|---------------|");
|
|
254
267
|
for (const rule of detailRules) {
|
|
255
|
-
const globsText = rule.frontmatter.globs.length > 0 ? rule.frontmatter.globs.join(", ") : "
|
|
268
|
+
const globsText = rule.frontmatter.globs.length > 0 ? rule.frontmatter.globs.join(", ") : "";
|
|
256
269
|
lines.push(
|
|
257
|
-
|
|
270
|
+
`@.claude/memories/${rule.filename}.md ${rule.frontmatter.description} ${globsText}`.trim()
|
|
258
271
|
);
|
|
259
272
|
}
|
|
260
273
|
lines.push("");
|
|
@@ -271,51 +284,46 @@ function generateMemoryFile(rule) {
|
|
|
271
284
|
return rule.content.trim();
|
|
272
285
|
}
|
|
273
286
|
async function updateClaudeSettings(settingsPath, ignorePatterns) {
|
|
274
|
-
let
|
|
287
|
+
let rawSettings = {};
|
|
275
288
|
if (await fileExists(settingsPath)) {
|
|
276
289
|
try {
|
|
277
290
|
const content = await readFileContent(settingsPath);
|
|
278
|
-
|
|
291
|
+
rawSettings = JSON.parse(content);
|
|
279
292
|
} catch {
|
|
280
293
|
console.warn(`Failed to parse existing ${settingsPath}, creating new settings`);
|
|
281
|
-
|
|
294
|
+
rawSettings = {};
|
|
282
295
|
}
|
|
283
296
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
settingsObj.permissions = {};
|
|
297
|
+
const parseResult = ClaudeSettingsSchema.safeParse(rawSettings);
|
|
298
|
+
const settings = parseResult.success ? parseResult.data : ClaudeSettingsSchema.parse({});
|
|
299
|
+
const readDenyRules = ignorePatterns.map((pattern) => `Read(${pattern})`);
|
|
300
|
+
if (!settings.permissions) {
|
|
301
|
+
settings.permissions = { deny: [] };
|
|
290
302
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
permissions.deny = [];
|
|
303
|
+
if (!Array.isArray(settings.permissions.deny)) {
|
|
304
|
+
settings.permissions.deny = [];
|
|
294
305
|
}
|
|
295
|
-
const
|
|
296
|
-
const denyArray = permissions.deny;
|
|
297
|
-
const filteredDeny = denyArray.filter((rule) => {
|
|
298
|
-
if (typeof rule !== "string") return false;
|
|
306
|
+
const filteredDeny = settings.permissions.deny.filter((rule) => {
|
|
299
307
|
if (!rule.startsWith("Read(")) return true;
|
|
300
308
|
const match = rule.match(/^Read\((.*)\)$/);
|
|
301
309
|
if (!match) return true;
|
|
302
310
|
return !ignorePatterns.includes(match[1] ?? "");
|
|
303
311
|
});
|
|
304
312
|
filteredDeny.push(...readDenyRules);
|
|
305
|
-
permissions.deny =
|
|
306
|
-
const jsonContent = JSON.stringify(
|
|
313
|
+
settings.permissions.deny = Array.from(new Set(filteredDeny));
|
|
314
|
+
const jsonContent = JSON.stringify(settings, null, 2);
|
|
307
315
|
await writeFileContent(settingsPath, jsonContent);
|
|
308
316
|
console.log(`\u2705 Updated Claude Code settings: ${settingsPath}`);
|
|
309
317
|
}
|
|
310
318
|
|
|
311
319
|
// src/generators/rules/cline.ts
|
|
312
|
-
import { join as
|
|
320
|
+
import { join as join5 } from "path";
|
|
313
321
|
async function generateClineConfig(rules, config, baseDir) {
|
|
314
322
|
const outputs = [];
|
|
315
323
|
for (const rule of rules) {
|
|
316
324
|
const content = generateClineMarkdown(rule);
|
|
317
|
-
const outputDir = baseDir ?
|
|
318
|
-
const filepath =
|
|
325
|
+
const outputDir = baseDir ? join5(baseDir, config.outputPaths.cline) : config.outputPaths.cline;
|
|
326
|
+
const filepath = join5(outputDir, `${rule.filename}.md`);
|
|
319
327
|
outputs.push({
|
|
320
328
|
tool: "cline",
|
|
321
329
|
filepath,
|
|
@@ -324,7 +332,7 @@ async function generateClineConfig(rules, config, baseDir) {
|
|
|
324
332
|
}
|
|
325
333
|
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
326
334
|
if (ignorePatterns.patterns.length > 0) {
|
|
327
|
-
const clineIgnorePath = baseDir ?
|
|
335
|
+
const clineIgnorePath = baseDir ? join5(baseDir, ".clineignore") : ".clineignore";
|
|
328
336
|
const clineIgnoreContent = generateClineIgnore(ignorePatterns.patterns);
|
|
329
337
|
outputs.push({
|
|
330
338
|
tool: "cline",
|
|
@@ -348,14 +356,14 @@ function generateClineIgnore(patterns) {
|
|
|
348
356
|
}
|
|
349
357
|
|
|
350
358
|
// src/generators/rules/copilot.ts
|
|
351
|
-
import { join as
|
|
359
|
+
import { join as join6 } from "path";
|
|
352
360
|
async function generateCopilotConfig(rules, config, baseDir) {
|
|
353
361
|
const outputs = [];
|
|
354
362
|
for (const rule of rules) {
|
|
355
363
|
const content = generateCopilotMarkdown(rule);
|
|
356
364
|
const baseFilename = rule.filename.replace(/\.md$/, "");
|
|
357
|
-
const outputDir = baseDir ?
|
|
358
|
-
const filepath =
|
|
365
|
+
const outputDir = baseDir ? join6(baseDir, config.outputPaths.copilot) : config.outputPaths.copilot;
|
|
366
|
+
const filepath = join6(outputDir, `${baseFilename}.instructions.md`);
|
|
359
367
|
outputs.push({
|
|
360
368
|
tool: "copilot",
|
|
361
369
|
filepath,
|
|
@@ -364,7 +372,7 @@ async function generateCopilotConfig(rules, config, baseDir) {
|
|
|
364
372
|
}
|
|
365
373
|
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
366
374
|
if (ignorePatterns.patterns.length > 0) {
|
|
367
|
-
const copilotIgnorePath = baseDir ?
|
|
375
|
+
const copilotIgnorePath = baseDir ? join6(baseDir, ".copilotignore") : ".copilotignore";
|
|
368
376
|
const copilotIgnoreContent = generateCopilotIgnore(ignorePatterns.patterns);
|
|
369
377
|
outputs.push({
|
|
370
378
|
tool: "copilot",
|
|
@@ -400,13 +408,13 @@ function generateCopilotIgnore(patterns) {
|
|
|
400
408
|
}
|
|
401
409
|
|
|
402
410
|
// src/generators/rules/cursor.ts
|
|
403
|
-
import { join as
|
|
411
|
+
import { join as join7 } from "path";
|
|
404
412
|
async function generateCursorConfig(rules, config, baseDir) {
|
|
405
413
|
const outputs = [];
|
|
406
414
|
for (const rule of rules) {
|
|
407
415
|
const content = generateCursorMarkdown(rule);
|
|
408
|
-
const outputDir = baseDir ?
|
|
409
|
-
const filepath =
|
|
416
|
+
const outputDir = baseDir ? join7(baseDir, config.outputPaths.cursor) : config.outputPaths.cursor;
|
|
417
|
+
const filepath = join7(outputDir, `${rule.filename}.mdc`);
|
|
410
418
|
outputs.push({
|
|
411
419
|
tool: "cursor",
|
|
412
420
|
filepath,
|
|
@@ -415,7 +423,7 @@ async function generateCursorConfig(rules, config, baseDir) {
|
|
|
415
423
|
}
|
|
416
424
|
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
417
425
|
if (ignorePatterns.patterns.length > 0) {
|
|
418
|
-
const cursorIgnorePath = baseDir ?
|
|
426
|
+
const cursorIgnorePath = baseDir ? join7(baseDir, ".cursorignore") : ".cursorignore";
|
|
419
427
|
const cursorIgnoreContent = generateCursorIgnore(ignorePatterns.patterns);
|
|
420
428
|
outputs.push({
|
|
421
429
|
tool: "cursor",
|
|
@@ -488,15 +496,15 @@ function generateCursorIgnore(patterns) {
|
|
|
488
496
|
}
|
|
489
497
|
|
|
490
498
|
// src/generators/rules/geminicli.ts
|
|
491
|
-
import { join as
|
|
499
|
+
import { join as join8 } from "path";
|
|
492
500
|
async function generateGeminiConfig(rules, config, baseDir) {
|
|
493
501
|
const outputs = [];
|
|
494
502
|
const rootRule = rules.find((rule) => rule.frontmatter.root === true);
|
|
495
503
|
const memoryRules = rules.filter((rule) => rule.frontmatter.root === false);
|
|
496
504
|
for (const rule of memoryRules) {
|
|
497
505
|
const content = generateGeminiMemoryMarkdown(rule);
|
|
498
|
-
const outputDir = baseDir ?
|
|
499
|
-
const filepath =
|
|
506
|
+
const outputDir = baseDir ? join8(baseDir, config.outputPaths.geminicli) : config.outputPaths.geminicli;
|
|
507
|
+
const filepath = join8(outputDir, `${rule.filename}.md`);
|
|
500
508
|
outputs.push({
|
|
501
509
|
tool: "geminicli",
|
|
502
510
|
filepath,
|
|
@@ -504,7 +512,7 @@ async function generateGeminiConfig(rules, config, baseDir) {
|
|
|
504
512
|
});
|
|
505
513
|
}
|
|
506
514
|
const rootContent = generateGeminiRootMarkdown(rootRule, memoryRules, baseDir);
|
|
507
|
-
const rootFilepath = baseDir ?
|
|
515
|
+
const rootFilepath = baseDir ? join8(baseDir, "GEMINI.md") : "GEMINI.md";
|
|
508
516
|
outputs.push({
|
|
509
517
|
tool: "geminicli",
|
|
510
518
|
filepath: rootFilepath,
|
|
@@ -512,7 +520,7 @@ async function generateGeminiConfig(rules, config, baseDir) {
|
|
|
512
520
|
});
|
|
513
521
|
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
514
522
|
if (ignorePatterns.patterns.length > 0) {
|
|
515
|
-
const aiexcludePath = baseDir ?
|
|
523
|
+
const aiexcludePath = baseDir ? join8(baseDir, ".aiexclude") : ".aiexclude";
|
|
516
524
|
const aiexcludeContent = generateAiexclude(ignorePatterns.patterns);
|
|
517
525
|
outputs.push({
|
|
518
526
|
tool: "geminicli",
|
|
@@ -560,13 +568,13 @@ function generateAiexclude(patterns) {
|
|
|
560
568
|
}
|
|
561
569
|
|
|
562
570
|
// src/generators/rules/roo.ts
|
|
563
|
-
import { join as
|
|
571
|
+
import { join as join9 } from "path";
|
|
564
572
|
async function generateRooConfig(rules, config, baseDir) {
|
|
565
573
|
const outputs = [];
|
|
566
574
|
for (const rule of rules) {
|
|
567
575
|
const content = generateRooMarkdown(rule);
|
|
568
|
-
const outputDir = baseDir ?
|
|
569
|
-
const filepath =
|
|
576
|
+
const outputDir = baseDir ? join9(baseDir, config.outputPaths.roo) : config.outputPaths.roo;
|
|
577
|
+
const filepath = join9(outputDir, `${rule.filename}.md`);
|
|
570
578
|
outputs.push({
|
|
571
579
|
tool: "roo",
|
|
572
580
|
filepath,
|
|
@@ -575,7 +583,7 @@ async function generateRooConfig(rules, config, baseDir) {
|
|
|
575
583
|
}
|
|
576
584
|
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
577
585
|
if (ignorePatterns.patterns.length > 0) {
|
|
578
|
-
const rooIgnorePath = baseDir ?
|
|
586
|
+
const rooIgnorePath = baseDir ? join9(baseDir, ".rooignore") : ".rooignore";
|
|
579
587
|
const rooIgnoreContent = generateRooIgnore(ignorePatterns.patterns);
|
|
580
588
|
outputs.push({
|
|
581
589
|
tool: "roo",
|
|
@@ -650,6 +658,72 @@ async function generateForTool(tool, rules, config, baseDir) {
|
|
|
650
658
|
// src/core/parser.ts
|
|
651
659
|
import { basename } from "path";
|
|
652
660
|
import matter from "gray-matter";
|
|
661
|
+
|
|
662
|
+
// src/types/config.ts
|
|
663
|
+
import { z as z2 } from "zod/v4-mini";
|
|
664
|
+
var ConfigSchema = z2.object({
|
|
665
|
+
aiRulesDir: z2.string(),
|
|
666
|
+
outputPaths: z2.record(ToolTargetSchema, z2.string()),
|
|
667
|
+
watchEnabled: z2.boolean(),
|
|
668
|
+
defaultTargets: ToolTargetsSchema
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
// src/types/mcp.ts
|
|
672
|
+
import { z as z3 } from "zod/v4-mini";
|
|
673
|
+
var McpTransportTypeSchema = z3.enum(["stdio", "sse", "http"]);
|
|
674
|
+
var McpServerBaseSchema = z3.object({
|
|
675
|
+
command: z3.optional(z3.string()),
|
|
676
|
+
args: z3.optional(z3.array(z3.string())),
|
|
677
|
+
url: z3.optional(z3.string()),
|
|
678
|
+
httpUrl: z3.optional(z3.string()),
|
|
679
|
+
env: z3.optional(z3.record(z3.string(), z3.string())),
|
|
680
|
+
disabled: z3.optional(z3.boolean()),
|
|
681
|
+
networkTimeout: z3.optional(z3.number()),
|
|
682
|
+
timeout: z3.optional(z3.number()),
|
|
683
|
+
trust: z3.optional(z3.boolean()),
|
|
684
|
+
cwd: z3.optional(z3.string()),
|
|
685
|
+
transport: z3.optional(McpTransportTypeSchema),
|
|
686
|
+
type: z3.optional(z3.enum(["sse", "streamable-http"])),
|
|
687
|
+
alwaysAllow: z3.optional(z3.array(z3.string())),
|
|
688
|
+
tools: z3.optional(z3.array(z3.string()))
|
|
689
|
+
});
|
|
690
|
+
var RulesyncMcpServerSchema = z3.extend(McpServerBaseSchema, {
|
|
691
|
+
targets: z3.optional(RulesyncTargetsSchema)
|
|
692
|
+
});
|
|
693
|
+
var McpConfigSchema = z3.object({
|
|
694
|
+
mcpServers: z3.record(z3.string(), McpServerBaseSchema)
|
|
695
|
+
});
|
|
696
|
+
var RulesyncMcpConfigSchema = z3.object({
|
|
697
|
+
mcpServers: z3.record(z3.string(), RulesyncMcpServerSchema)
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
// src/types/rules.ts
|
|
701
|
+
import { z as z4 } from "zod/v4-mini";
|
|
702
|
+
var RuleFrontmatterSchema = z4.object({
|
|
703
|
+
root: z4.boolean(),
|
|
704
|
+
targets: RulesyncTargetsSchema,
|
|
705
|
+
description: z4.string(),
|
|
706
|
+
globs: z4.array(z4.string()),
|
|
707
|
+
cursorRuleType: z4.optional(z4.enum(["always", "manual", "specificFiles", "intelligently"]))
|
|
708
|
+
});
|
|
709
|
+
var ParsedRuleSchema = z4.object({
|
|
710
|
+
frontmatter: RuleFrontmatterSchema,
|
|
711
|
+
content: z4.string(),
|
|
712
|
+
filename: z4.string(),
|
|
713
|
+
filepath: z4.string()
|
|
714
|
+
});
|
|
715
|
+
var GeneratedOutputSchema = z4.object({
|
|
716
|
+
tool: ToolTargetSchema,
|
|
717
|
+
filepath: z4.string(),
|
|
718
|
+
content: z4.string()
|
|
719
|
+
});
|
|
720
|
+
var GenerateOptionsSchema = z4.object({
|
|
721
|
+
targetTools: z4.optional(ToolTargetsSchema),
|
|
722
|
+
outputDir: z4.optional(z4.string()),
|
|
723
|
+
watch: z4.optional(z4.boolean())
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
// src/core/parser.ts
|
|
653
727
|
async function parseRulesFromDirectory(aiRulesDir) {
|
|
654
728
|
const ignorePatterns = await loadIgnorePatterns();
|
|
655
729
|
const ruleFiles = await findFiles(aiRulesDir, ".md", ignorePatterns.patterns);
|
|
@@ -683,84 +757,20 @@ ${errors.join("\n")}`);
|
|
|
683
757
|
async function parseRuleFile(filepath) {
|
|
684
758
|
const content = await readFileContent(filepath);
|
|
685
759
|
const parsed = matter(content);
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
}
|
|
696
|
-
function validateFrontmatter(data, filepath) {
|
|
697
|
-
if (!data || typeof data !== "object") {
|
|
698
|
-
if (!data) {
|
|
699
|
-
throw new Error(
|
|
700
|
-
`Missing frontmatter in ${filepath}: file must contain YAML frontmatter with required fields (root, targets, description, globs)`
|
|
701
|
-
);
|
|
702
|
-
}
|
|
703
|
-
throw new Error(`Invalid frontmatter in ${filepath}: frontmatter must be a valid YAML object`);
|
|
704
|
-
}
|
|
705
|
-
const obj = data;
|
|
706
|
-
if (Object.keys(obj).length === 0) {
|
|
707
|
-
throw new Error(
|
|
708
|
-
`Missing frontmatter in ${filepath}: file must contain YAML frontmatter with required fields (root, targets, description, globs)`
|
|
709
|
-
);
|
|
710
|
-
}
|
|
711
|
-
if (obj.root === void 0) {
|
|
712
|
-
throw new Error(`Missing required field "root" in ${filepath}: must be true or false`);
|
|
713
|
-
}
|
|
714
|
-
if (typeof obj.root !== "boolean") {
|
|
715
|
-
throw new Error(
|
|
716
|
-
`Invalid "root" field in ${filepath}: must be a boolean (true or false), got ${typeof obj.root}`
|
|
717
|
-
);
|
|
718
|
-
}
|
|
719
|
-
if (obj.targets === void 0) {
|
|
720
|
-
throw new Error(
|
|
721
|
-
`Missing required field "targets" in ${filepath}: must be an array like ["*"] or ["copilot", "cursor"]`
|
|
722
|
-
);
|
|
723
|
-
}
|
|
724
|
-
if (!Array.isArray(obj.targets)) {
|
|
725
|
-
throw new Error(
|
|
726
|
-
`Invalid "targets" field in ${filepath}: must be an array, got ${typeof obj.targets}`
|
|
727
|
-
);
|
|
728
|
-
}
|
|
729
|
-
const validTargets = ["copilot", "cursor", "cline", "claudecode", "roo", "geminicli", "*"];
|
|
730
|
-
for (const target of obj.targets) {
|
|
731
|
-
if (typeof target !== "string" || !validTargets.includes(target)) {
|
|
732
|
-
throw new Error(
|
|
733
|
-
`Invalid target "${target}" in ${filepath}: must be one of ${validTargets.join(", ")}`
|
|
734
|
-
);
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
if (obj.description === void 0) {
|
|
738
|
-
throw new Error(
|
|
739
|
-
`Missing required field "description" in ${filepath}: must be a descriptive string`
|
|
740
|
-
);
|
|
741
|
-
}
|
|
742
|
-
if (typeof obj.description !== "string") {
|
|
743
|
-
throw new Error(
|
|
744
|
-
`Invalid "description" field in ${filepath}: must be a string, got ${typeof obj.description}`
|
|
745
|
-
);
|
|
746
|
-
}
|
|
747
|
-
if (obj.globs === void 0) {
|
|
748
|
-
throw new Error(
|
|
749
|
-
`Missing required field "globs" in ${filepath}: must be an array of file patterns like ["**/*.ts"]`
|
|
750
|
-
);
|
|
751
|
-
}
|
|
752
|
-
if (!Array.isArray(obj.globs)) {
|
|
760
|
+
try {
|
|
761
|
+
const frontmatter = RuleFrontmatterSchema.parse(parsed.data);
|
|
762
|
+
const filename = basename(filepath, ".md");
|
|
763
|
+
return {
|
|
764
|
+
frontmatter,
|
|
765
|
+
content: parsed.content,
|
|
766
|
+
filename,
|
|
767
|
+
filepath
|
|
768
|
+
};
|
|
769
|
+
} catch (error) {
|
|
753
770
|
throw new Error(
|
|
754
|
-
`Invalid
|
|
771
|
+
`Invalid frontmatter in ${filepath}: ${error instanceof Error ? error.message : String(error)}`
|
|
755
772
|
);
|
|
756
773
|
}
|
|
757
|
-
for (const glob of obj.globs) {
|
|
758
|
-
if (typeof glob !== "string") {
|
|
759
|
-
throw new Error(
|
|
760
|
-
`Invalid glob pattern in ${filepath}: all globs must be strings, got ${typeof glob}`
|
|
761
|
-
);
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
774
|
}
|
|
765
775
|
|
|
766
776
|
// src/core/validator.ts
|
|
@@ -814,11 +824,11 @@ async function validateRule(rule) {
|
|
|
814
824
|
}
|
|
815
825
|
|
|
816
826
|
// src/core/mcp-generator.ts
|
|
817
|
-
import path3 from "path";
|
|
827
|
+
import * as path3 from "path";
|
|
818
828
|
|
|
819
829
|
// src/core/mcp-parser.ts
|
|
820
|
-
import fs from "fs";
|
|
821
|
-
import path2 from "path";
|
|
830
|
+
import * as fs from "fs";
|
|
831
|
+
import * as path2 from "path";
|
|
822
832
|
function parseMcpConfig(projectRoot) {
|
|
823
833
|
const mcpPath = path2.join(projectRoot, ".rulesync", ".mcp.json");
|
|
824
834
|
if (!fs.existsSync(mcpPath)) {
|
|
@@ -831,13 +841,11 @@ function parseMcpConfig(projectRoot) {
|
|
|
831
841
|
rawConfig.mcpServers = rawConfig.servers;
|
|
832
842
|
delete rawConfig.servers;
|
|
833
843
|
}
|
|
834
|
-
if (!rawConfig.mcpServers || typeof rawConfig.mcpServers !== "object") {
|
|
835
|
-
throw new Error("Invalid mcp.json: 'mcpServers' field must be an object");
|
|
836
|
-
}
|
|
837
844
|
if (rawConfig.tools) {
|
|
838
845
|
delete rawConfig.tools;
|
|
839
846
|
}
|
|
840
|
-
|
|
847
|
+
const validatedConfig = RulesyncMcpConfigSchema.parse(rawConfig);
|
|
848
|
+
return validatedConfig;
|
|
841
849
|
} catch (error) {
|
|
842
850
|
throw new Error(
|
|
843
851
|
`Failed to parse mcp.json: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -1038,10 +1046,10 @@ Generating configurations for base directory: ${baseDir}`);
|
|
|
1038
1046
|
}
|
|
1039
1047
|
|
|
1040
1048
|
// src/cli/commands/gitignore.ts
|
|
1041
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
1042
|
-
import { join as
|
|
1049
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
1050
|
+
import { join as join12 } from "path";
|
|
1043
1051
|
var gitignoreCommand = async () => {
|
|
1044
|
-
const gitignorePath =
|
|
1052
|
+
const gitignorePath = join12(process.cwd(), ".gitignore");
|
|
1045
1053
|
const rulesFilesToIgnore = [
|
|
1046
1054
|
"# Generated by rulesync - AI tool configuration files",
|
|
1047
1055
|
"**/.github/copilot-instructions.md",
|
|
@@ -1067,8 +1075,8 @@ var gitignoreCommand = async () => {
|
|
|
1067
1075
|
"**/.roo/mcp.json"
|
|
1068
1076
|
];
|
|
1069
1077
|
let gitignoreContent = "";
|
|
1070
|
-
if (
|
|
1071
|
-
gitignoreContent =
|
|
1078
|
+
if (existsSync2(gitignorePath)) {
|
|
1079
|
+
gitignoreContent = readFileSync2(gitignorePath, "utf-8");
|
|
1072
1080
|
}
|
|
1073
1081
|
const linesToAdd = [];
|
|
1074
1082
|
for (const rule of rulesFilesToIgnore) {
|
|
@@ -1095,17 +1103,17 @@ ${linesToAdd.join("\n")}
|
|
|
1095
1103
|
};
|
|
1096
1104
|
|
|
1097
1105
|
// src/core/importer.ts
|
|
1098
|
-
import { join as
|
|
1106
|
+
import { join as join19 } from "path";
|
|
1099
1107
|
import matter4 from "gray-matter";
|
|
1100
1108
|
|
|
1101
1109
|
// src/parsers/claudecode.ts
|
|
1102
|
-
import { basename as basename2, join as
|
|
1110
|
+
import { basename as basename2, join as join13 } from "path";
|
|
1103
1111
|
async function parseClaudeConfiguration(baseDir = process.cwd()) {
|
|
1104
1112
|
const errors = [];
|
|
1105
1113
|
const rules = [];
|
|
1106
1114
|
let ignorePatterns;
|
|
1107
1115
|
let mcpServers;
|
|
1108
|
-
const claudeFilePath =
|
|
1116
|
+
const claudeFilePath = join13(baseDir, "CLAUDE.md");
|
|
1109
1117
|
if (!await fileExists(claudeFilePath)) {
|
|
1110
1118
|
errors.push("CLAUDE.md file not found");
|
|
1111
1119
|
return { rules, errors };
|
|
@@ -1116,12 +1124,12 @@ async function parseClaudeConfiguration(baseDir = process.cwd()) {
|
|
|
1116
1124
|
if (mainRule) {
|
|
1117
1125
|
rules.push(mainRule);
|
|
1118
1126
|
}
|
|
1119
|
-
const memoryDir =
|
|
1127
|
+
const memoryDir = join13(baseDir, ".claude", "memories");
|
|
1120
1128
|
if (await fileExists(memoryDir)) {
|
|
1121
1129
|
const memoryRules = await parseClaudeMemoryFiles(memoryDir);
|
|
1122
1130
|
rules.push(...memoryRules);
|
|
1123
1131
|
}
|
|
1124
|
-
const settingsPath =
|
|
1132
|
+
const settingsPath = join13(baseDir, ".claude", "settings.json");
|
|
1125
1133
|
if (await fileExists(settingsPath)) {
|
|
1126
1134
|
const settingsResult = await parseClaudeSettings(settingsPath);
|
|
1127
1135
|
if (settingsResult.ignorePatterns) {
|
|
@@ -1178,7 +1186,7 @@ async function parseClaudeMemoryFiles(memoryDir) {
|
|
|
1178
1186
|
const files = await readdir2(memoryDir);
|
|
1179
1187
|
for (const file of files) {
|
|
1180
1188
|
if (file.endsWith(".md")) {
|
|
1181
|
-
const filePath =
|
|
1189
|
+
const filePath = join13(memoryDir, file);
|
|
1182
1190
|
const content = await readFileContent(filePath);
|
|
1183
1191
|
if (content.trim()) {
|
|
1184
1192
|
const filename = basename2(file, ".md");
|
|
@@ -1210,6 +1218,9 @@ async function parseClaudeSettings(settingsPath) {
|
|
|
1210
1218
|
const settings = JSON.parse(content);
|
|
1211
1219
|
if (typeof settings === "object" && settings !== null && "permissions" in settings) {
|
|
1212
1220
|
const permissions = settings.permissions;
|
|
1221
|
+
if (typeof permissions !== "object" || permissions === null) {
|
|
1222
|
+
return { ignorePatterns: [], errors: [] };
|
|
1223
|
+
}
|
|
1213
1224
|
if (permissions && "deny" in permissions && Array.isArray(permissions.deny)) {
|
|
1214
1225
|
const readPatterns = permissions.deny.filter(
|
|
1215
1226
|
(rule) => typeof rule === "string" && rule.startsWith("Read(") && rule.endsWith(")")
|
|
@@ -1222,11 +1233,9 @@ async function parseClaudeSettings(settingsPath) {
|
|
|
1222
1233
|
}
|
|
1223
1234
|
}
|
|
1224
1235
|
}
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
mcpServers = servers;
|
|
1229
|
-
}
|
|
1236
|
+
const parseResult = RulesyncMcpConfigSchema.safeParse(settings);
|
|
1237
|
+
if (parseResult.success && Object.keys(parseResult.data.mcpServers).length > 0) {
|
|
1238
|
+
mcpServers = parseResult.data.mcpServers;
|
|
1230
1239
|
}
|
|
1231
1240
|
} catch (error) {
|
|
1232
1241
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -1240,11 +1249,11 @@ async function parseClaudeSettings(settingsPath) {
|
|
|
1240
1249
|
}
|
|
1241
1250
|
|
|
1242
1251
|
// src/parsers/cline.ts
|
|
1243
|
-
import { join as
|
|
1252
|
+
import { join as join14 } from "path";
|
|
1244
1253
|
async function parseClineConfiguration(baseDir = process.cwd()) {
|
|
1245
1254
|
const errors = [];
|
|
1246
1255
|
const rules = [];
|
|
1247
|
-
const clineFilePath =
|
|
1256
|
+
const clineFilePath = join14(baseDir, ".cline", "instructions.md");
|
|
1248
1257
|
if (await fileExists(clineFilePath)) {
|
|
1249
1258
|
try {
|
|
1250
1259
|
const content = await readFileContent(clineFilePath);
|
|
@@ -1267,14 +1276,14 @@ async function parseClineConfiguration(baseDir = process.cwd()) {
|
|
|
1267
1276
|
errors.push(`Failed to parse .cline/instructions.md: ${errorMessage}`);
|
|
1268
1277
|
}
|
|
1269
1278
|
}
|
|
1270
|
-
const clinerulesDirPath =
|
|
1279
|
+
const clinerulesDirPath = join14(baseDir, ".clinerules");
|
|
1271
1280
|
if (await fileExists(clinerulesDirPath)) {
|
|
1272
1281
|
try {
|
|
1273
1282
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
1274
1283
|
const files = await readdir2(clinerulesDirPath);
|
|
1275
1284
|
for (const file of files) {
|
|
1276
1285
|
if (file.endsWith(".md")) {
|
|
1277
|
-
const filePath =
|
|
1286
|
+
const filePath = join14(clinerulesDirPath, file);
|
|
1278
1287
|
try {
|
|
1279
1288
|
const content = await readFileContent(filePath);
|
|
1280
1289
|
if (content.trim()) {
|
|
@@ -1310,12 +1319,12 @@ async function parseClineConfiguration(baseDir = process.cwd()) {
|
|
|
1310
1319
|
}
|
|
1311
1320
|
|
|
1312
1321
|
// src/parsers/copilot.ts
|
|
1313
|
-
import { basename as basename3, join as
|
|
1322
|
+
import { basename as basename3, join as join15 } from "path";
|
|
1314
1323
|
import matter2 from "gray-matter";
|
|
1315
1324
|
async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
1316
1325
|
const errors = [];
|
|
1317
1326
|
const rules = [];
|
|
1318
|
-
const copilotFilePath =
|
|
1327
|
+
const copilotFilePath = join15(baseDir, ".github", "copilot-instructions.md");
|
|
1319
1328
|
if (await fileExists(copilotFilePath)) {
|
|
1320
1329
|
try {
|
|
1321
1330
|
const rawContent = await readFileContent(copilotFilePath);
|
|
@@ -1340,14 +1349,14 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
|
1340
1349
|
errors.push(`Failed to parse copilot-instructions.md: ${errorMessage}`);
|
|
1341
1350
|
}
|
|
1342
1351
|
}
|
|
1343
|
-
const instructionsDir =
|
|
1352
|
+
const instructionsDir = join15(baseDir, ".github", "instructions");
|
|
1344
1353
|
if (await fileExists(instructionsDir)) {
|
|
1345
1354
|
try {
|
|
1346
1355
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
1347
1356
|
const files = await readdir2(instructionsDir);
|
|
1348
1357
|
for (const file of files) {
|
|
1349
1358
|
if (file.endsWith(".instructions.md")) {
|
|
1350
|
-
const filePath =
|
|
1359
|
+
const filePath = join15(instructionsDir, file);
|
|
1351
1360
|
const rawContent = await readFileContent(filePath);
|
|
1352
1361
|
const parsed = matter2(rawContent);
|
|
1353
1362
|
const content = parsed.content.trim();
|
|
@@ -1382,19 +1391,28 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
|
1382
1391
|
}
|
|
1383
1392
|
|
|
1384
1393
|
// src/parsers/cursor.ts
|
|
1385
|
-
import { basename as basename4, join as
|
|
1394
|
+
import { basename as basename4, join as join16 } from "path";
|
|
1386
1395
|
import matter3 from "gray-matter";
|
|
1387
1396
|
import { DEFAULT_SCHEMA, FAILSAFE_SCHEMA, load } from "js-yaml";
|
|
1397
|
+
import { z as z5 } from "zod/v4-mini";
|
|
1388
1398
|
var customMatterOptions = {
|
|
1389
1399
|
engines: {
|
|
1390
1400
|
yaml: {
|
|
1391
1401
|
parse: (str) => {
|
|
1392
1402
|
try {
|
|
1393
|
-
|
|
1394
|
-
|
|
1403
|
+
const preprocessed = str.replace(/^(\s*globs:\s*)\*\s*$/gm, '$1"*"').replace(/^(\s*globs:\s*)([^\s"'[\n][^"'[\n]*?)(\s*)$/gm, '$1"$2"$3');
|
|
1404
|
+
const result = load(preprocessed, { schema: DEFAULT_SCHEMA });
|
|
1405
|
+
if (typeof result === "object" && result !== null) {
|
|
1406
|
+
return result;
|
|
1407
|
+
}
|
|
1408
|
+
throw new Error("Failed to parse YAML: result is not an object");
|
|
1395
1409
|
} catch (error) {
|
|
1396
1410
|
try {
|
|
1397
|
-
|
|
1411
|
+
const result = load(str, { schema: FAILSAFE_SCHEMA });
|
|
1412
|
+
if (typeof result === "object" && result !== null) {
|
|
1413
|
+
return result;
|
|
1414
|
+
}
|
|
1415
|
+
throw new Error("Failed to parse YAML: result is not an object");
|
|
1398
1416
|
} catch {
|
|
1399
1417
|
throw error;
|
|
1400
1418
|
}
|
|
@@ -1404,7 +1422,18 @@ var customMatterOptions = {
|
|
|
1404
1422
|
}
|
|
1405
1423
|
};
|
|
1406
1424
|
function convertCursorMdcFrontmatter(cursorFrontmatter, _filename) {
|
|
1407
|
-
const
|
|
1425
|
+
const FrontmatterSchema = z5.record(z5.string(), z5.unknown());
|
|
1426
|
+
const parseResult = FrontmatterSchema.safeParse(cursorFrontmatter);
|
|
1427
|
+
if (!parseResult.success) {
|
|
1428
|
+
return {
|
|
1429
|
+
root: false,
|
|
1430
|
+
targets: ["*"],
|
|
1431
|
+
description: "",
|
|
1432
|
+
globs: [],
|
|
1433
|
+
cursorRuleType: "manual"
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
const frontmatter = parseResult.data;
|
|
1408
1437
|
const description = normalizeValue(frontmatter?.description);
|
|
1409
1438
|
const globs = normalizeGlobsValue(frontmatter?.globs);
|
|
1410
1439
|
const alwaysApply = frontmatter?.alwaysApply === true || frontmatter?.alwaysApply === "true";
|
|
@@ -1439,7 +1468,7 @@ function convertCursorMdcFrontmatter(cursorFrontmatter, _filename) {
|
|
|
1439
1468
|
return {
|
|
1440
1469
|
root: false,
|
|
1441
1470
|
targets: ["*"],
|
|
1442
|
-
description,
|
|
1471
|
+
description: description || "",
|
|
1443
1472
|
globs: [],
|
|
1444
1473
|
cursorRuleType: "intelligently"
|
|
1445
1474
|
};
|
|
@@ -1487,7 +1516,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
1487
1516
|
const rules = [];
|
|
1488
1517
|
let ignorePatterns;
|
|
1489
1518
|
let mcpServers;
|
|
1490
|
-
const cursorFilePath =
|
|
1519
|
+
const cursorFilePath = join16(baseDir, ".cursorrules");
|
|
1491
1520
|
if (await fileExists(cursorFilePath)) {
|
|
1492
1521
|
try {
|
|
1493
1522
|
const rawContent = await readFileContent(cursorFilePath);
|
|
@@ -1508,14 +1537,14 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
1508
1537
|
errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
|
|
1509
1538
|
}
|
|
1510
1539
|
}
|
|
1511
|
-
const cursorRulesDir =
|
|
1540
|
+
const cursorRulesDir = join16(baseDir, ".cursor", "rules");
|
|
1512
1541
|
if (await fileExists(cursorRulesDir)) {
|
|
1513
1542
|
try {
|
|
1514
1543
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
1515
1544
|
const files = await readdir2(cursorRulesDir);
|
|
1516
1545
|
for (const file of files) {
|
|
1517
1546
|
if (file.endsWith(".mdc")) {
|
|
1518
|
-
const filePath =
|
|
1547
|
+
const filePath = join16(cursorRulesDir, file);
|
|
1519
1548
|
try {
|
|
1520
1549
|
const rawContent = await readFileContent(filePath);
|
|
1521
1550
|
const parsed = matter3(rawContent, customMatterOptions);
|
|
@@ -1544,7 +1573,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
1544
1573
|
if (rules.length === 0) {
|
|
1545
1574
|
errors.push("No Cursor configuration files found (.cursorrules or .cursor/rules/*.mdc)");
|
|
1546
1575
|
}
|
|
1547
|
-
const cursorIgnorePath =
|
|
1576
|
+
const cursorIgnorePath = join16(baseDir, ".cursorignore");
|
|
1548
1577
|
if (await fileExists(cursorIgnorePath)) {
|
|
1549
1578
|
try {
|
|
1550
1579
|
const content = await readFileContent(cursorIgnorePath);
|
|
@@ -1557,13 +1586,14 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
1557
1586
|
errors.push(`Failed to parse .cursorignore: ${errorMessage}`);
|
|
1558
1587
|
}
|
|
1559
1588
|
}
|
|
1560
|
-
const cursorMcpPath =
|
|
1589
|
+
const cursorMcpPath = join16(baseDir, ".cursor", "mcp.json");
|
|
1561
1590
|
if (await fileExists(cursorMcpPath)) {
|
|
1562
1591
|
try {
|
|
1563
1592
|
const content = await readFileContent(cursorMcpPath);
|
|
1564
1593
|
const mcp = JSON.parse(content);
|
|
1565
|
-
|
|
1566
|
-
|
|
1594
|
+
const parseResult = RulesyncMcpConfigSchema.safeParse(mcp);
|
|
1595
|
+
if (parseResult.success && Object.keys(parseResult.data.mcpServers).length > 0) {
|
|
1596
|
+
mcpServers = parseResult.data.mcpServers;
|
|
1567
1597
|
}
|
|
1568
1598
|
} catch (error) {
|
|
1569
1599
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -1579,13 +1609,13 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
1579
1609
|
}
|
|
1580
1610
|
|
|
1581
1611
|
// src/parsers/geminicli.ts
|
|
1582
|
-
import { basename as basename5, join as
|
|
1612
|
+
import { basename as basename5, join as join17 } from "path";
|
|
1583
1613
|
async function parseGeminiConfiguration(baseDir = process.cwd()) {
|
|
1584
1614
|
const errors = [];
|
|
1585
1615
|
const rules = [];
|
|
1586
1616
|
let ignorePatterns;
|
|
1587
1617
|
let mcpServers;
|
|
1588
|
-
const geminiFilePath =
|
|
1618
|
+
const geminiFilePath = join17(baseDir, "GEMINI.md");
|
|
1589
1619
|
if (!await fileExists(geminiFilePath)) {
|
|
1590
1620
|
errors.push("GEMINI.md file not found");
|
|
1591
1621
|
return { rules, errors };
|
|
@@ -1596,12 +1626,12 @@ async function parseGeminiConfiguration(baseDir = process.cwd()) {
|
|
|
1596
1626
|
if (mainRule) {
|
|
1597
1627
|
rules.push(mainRule);
|
|
1598
1628
|
}
|
|
1599
|
-
const memoryDir =
|
|
1629
|
+
const memoryDir = join17(baseDir, ".gemini", "memories");
|
|
1600
1630
|
if (await fileExists(memoryDir)) {
|
|
1601
1631
|
const memoryRules = await parseGeminiMemoryFiles(memoryDir);
|
|
1602
1632
|
rules.push(...memoryRules);
|
|
1603
1633
|
}
|
|
1604
|
-
const settingsPath =
|
|
1634
|
+
const settingsPath = join17(baseDir, ".gemini", "settings.json");
|
|
1605
1635
|
if (await fileExists(settingsPath)) {
|
|
1606
1636
|
const settingsResult = await parseGeminiSettings(settingsPath);
|
|
1607
1637
|
if (settingsResult.ignorePatterns) {
|
|
@@ -1612,7 +1642,7 @@ async function parseGeminiConfiguration(baseDir = process.cwd()) {
|
|
|
1612
1642
|
}
|
|
1613
1643
|
errors.push(...settingsResult.errors);
|
|
1614
1644
|
}
|
|
1615
|
-
const aiexcludePath =
|
|
1645
|
+
const aiexcludePath = join17(baseDir, ".aiexclude");
|
|
1616
1646
|
if (await fileExists(aiexcludePath)) {
|
|
1617
1647
|
const aiexcludePatterns = await parseAiexclude(aiexcludePath);
|
|
1618
1648
|
if (aiexcludePatterns.length > 0) {
|
|
@@ -1665,7 +1695,7 @@ async function parseGeminiMemoryFiles(memoryDir) {
|
|
|
1665
1695
|
const files = await readdir2(memoryDir);
|
|
1666
1696
|
for (const file of files) {
|
|
1667
1697
|
if (file.endsWith(".md")) {
|
|
1668
|
-
const filePath =
|
|
1698
|
+
const filePath = join17(memoryDir, file);
|
|
1669
1699
|
const content = await readFileContent(filePath);
|
|
1670
1700
|
if (content.trim()) {
|
|
1671
1701
|
const filename = basename5(file, ".md");
|
|
@@ -1694,8 +1724,9 @@ async function parseGeminiSettings(settingsPath) {
|
|
|
1694
1724
|
try {
|
|
1695
1725
|
const content = await readFileContent(settingsPath);
|
|
1696
1726
|
const settings = JSON.parse(content);
|
|
1697
|
-
|
|
1698
|
-
|
|
1727
|
+
const parseResult = RulesyncMcpConfigSchema.safeParse(settings);
|
|
1728
|
+
if (parseResult.success && Object.keys(parseResult.data.mcpServers).length > 0) {
|
|
1729
|
+
mcpServers = parseResult.data.mcpServers;
|
|
1699
1730
|
}
|
|
1700
1731
|
} catch (error) {
|
|
1701
1732
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -1717,11 +1748,11 @@ async function parseAiexclude(aiexcludePath) {
|
|
|
1717
1748
|
}
|
|
1718
1749
|
|
|
1719
1750
|
// src/parsers/roo.ts
|
|
1720
|
-
import { join as
|
|
1751
|
+
import { join as join18 } from "path";
|
|
1721
1752
|
async function parseRooConfiguration(baseDir = process.cwd()) {
|
|
1722
1753
|
const errors = [];
|
|
1723
1754
|
const rules = [];
|
|
1724
|
-
const rooFilePath =
|
|
1755
|
+
const rooFilePath = join18(baseDir, ".roo", "instructions.md");
|
|
1725
1756
|
if (await fileExists(rooFilePath)) {
|
|
1726
1757
|
try {
|
|
1727
1758
|
const content = await readFileContent(rooFilePath);
|
|
@@ -1744,14 +1775,14 @@ async function parseRooConfiguration(baseDir = process.cwd()) {
|
|
|
1744
1775
|
errors.push(`Failed to parse .roo/instructions.md: ${errorMessage}`);
|
|
1745
1776
|
}
|
|
1746
1777
|
}
|
|
1747
|
-
const rooRulesDir =
|
|
1778
|
+
const rooRulesDir = join18(baseDir, ".roo", "rules");
|
|
1748
1779
|
if (await fileExists(rooRulesDir)) {
|
|
1749
1780
|
try {
|
|
1750
1781
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
1751
1782
|
const files = await readdir2(rooRulesDir);
|
|
1752
1783
|
for (const file of files) {
|
|
1753
1784
|
if (file.endsWith(".md")) {
|
|
1754
|
-
const filePath =
|
|
1785
|
+
const filePath = join18(rooRulesDir, file);
|
|
1755
1786
|
try {
|
|
1756
1787
|
const content = await readFileContent(filePath);
|
|
1757
1788
|
if (content.trim()) {
|
|
@@ -1852,7 +1883,7 @@ async function importConfiguration(options) {
|
|
|
1852
1883
|
if (rules.length === 0 && !ignorePatterns && !mcpServers) {
|
|
1853
1884
|
return { success: false, rulesCreated: 0, errors };
|
|
1854
1885
|
}
|
|
1855
|
-
const rulesDirPath =
|
|
1886
|
+
const rulesDirPath = join19(baseDir, rulesDir);
|
|
1856
1887
|
try {
|
|
1857
1888
|
const { mkdir: mkdir3 } = await import("fs/promises");
|
|
1858
1889
|
await mkdir3(rulesDirPath, { recursive: true });
|
|
@@ -1866,7 +1897,7 @@ async function importConfiguration(options) {
|
|
|
1866
1897
|
try {
|
|
1867
1898
|
const baseFilename = `${tool}__${rule.filename}`;
|
|
1868
1899
|
const filename = await generateUniqueFilename(rulesDirPath, baseFilename);
|
|
1869
|
-
const filePath =
|
|
1900
|
+
const filePath = join19(rulesDirPath, `${filename}.md`);
|
|
1870
1901
|
const content = generateRuleFileContent(rule);
|
|
1871
1902
|
await writeFileContent(filePath, content);
|
|
1872
1903
|
rulesCreated++;
|
|
@@ -1881,7 +1912,7 @@ async function importConfiguration(options) {
|
|
|
1881
1912
|
let ignoreFileCreated = false;
|
|
1882
1913
|
if (ignorePatterns && ignorePatterns.length > 0) {
|
|
1883
1914
|
try {
|
|
1884
|
-
const rulesyncignorePath =
|
|
1915
|
+
const rulesyncignorePath = join19(baseDir, ".rulesyncignore");
|
|
1885
1916
|
const ignoreContent = `${ignorePatterns.join("\n")}
|
|
1886
1917
|
`;
|
|
1887
1918
|
await writeFileContent(rulesyncignorePath, ignoreContent);
|
|
@@ -1897,7 +1928,7 @@ async function importConfiguration(options) {
|
|
|
1897
1928
|
let mcpFileCreated = false;
|
|
1898
1929
|
if (mcpServers && Object.keys(mcpServers).length > 0) {
|
|
1899
1930
|
try {
|
|
1900
|
-
const mcpPath =
|
|
1931
|
+
const mcpPath = join19(baseDir, rulesDir, ".mcp.json");
|
|
1901
1932
|
const mcpContent = `${JSON.stringify({ mcpServers }, null, 2)}
|
|
1902
1933
|
`;
|
|
1903
1934
|
await writeFileContent(mcpPath, mcpContent);
|
|
@@ -1925,7 +1956,7 @@ function generateRuleFileContent(rule) {
|
|
|
1925
1956
|
async function generateUniqueFilename(rulesDir, baseFilename) {
|
|
1926
1957
|
let filename = baseFilename;
|
|
1927
1958
|
let counter = 1;
|
|
1928
|
-
while (await fileExists(
|
|
1959
|
+
while (await fileExists(join19(rulesDir, `${filename}.md`))) {
|
|
1929
1960
|
filename = `${baseFilename}-${counter}`;
|
|
1930
1961
|
counter++;
|
|
1931
1962
|
}
|
|
@@ -1990,7 +2021,7 @@ async function importCommand(options = {}) {
|
|
|
1990
2021
|
}
|
|
1991
2022
|
|
|
1992
2023
|
// src/cli/commands/init.ts
|
|
1993
|
-
import { join as
|
|
2024
|
+
import { join as join20 } from "path";
|
|
1994
2025
|
async function initCommand() {
|
|
1995
2026
|
const aiRulesDir = ".rulesync";
|
|
1996
2027
|
console.log("Initializing rulesync...");
|
|
@@ -2120,7 +2151,7 @@ globs: ["src/api/**/*.ts", "src/services/**/*.ts", "src/models/**/*.ts"]
|
|
|
2120
2151
|
}
|
|
2121
2152
|
];
|
|
2122
2153
|
for (const file of sampleFiles) {
|
|
2123
|
-
const filepath =
|
|
2154
|
+
const filepath = join20(aiRulesDir, file.filename);
|
|
2124
2155
|
if (!await fileExists(filepath)) {
|
|
2125
2156
|
await writeFileContent(filepath, file.content);
|
|
2126
2157
|
console.log(`Created ${filepath}`);
|
|
@@ -2155,9 +2186,11 @@ async function statusCommand() {
|
|
|
2155
2186
|
for (const rule of rules) {
|
|
2156
2187
|
const targets = rule.frontmatter.targets[0] === "*" ? config.defaultTargets : rule.frontmatter.targets;
|
|
2157
2188
|
for (const target of targets) {
|
|
2158
|
-
if (target
|
|
2159
|
-
|
|
2160
|
-
|
|
2189
|
+
if (target === "copilot") targetCounts.copilot++;
|
|
2190
|
+
else if (target === "cursor") targetCounts.cursor++;
|
|
2191
|
+
else if (target === "cline") targetCounts.cline++;
|
|
2192
|
+
else if (target === "claudecode") targetCounts.claudecode++;
|
|
2193
|
+
else if (target === "roo") targetCounts.roo++;
|
|
2161
2194
|
}
|
|
2162
2195
|
}
|
|
2163
2196
|
console.log("\n\u{1F3AF} Target tool coverage:");
|
|
@@ -2263,7 +2296,7 @@ async function watchCommand() {
|
|
|
2263
2296
|
|
|
2264
2297
|
// src/cli/index.ts
|
|
2265
2298
|
var program = new Command();
|
|
2266
|
-
program.name("rulesync").description("Unified AI rules management CLI tool").version("0.
|
|
2299
|
+
program.name("rulesync").description("Unified AI rules management CLI tool").version("0.44.0");
|
|
2267
2300
|
program.command("init").description("Initialize rulesync in current directory").action(initCommand);
|
|
2268
2301
|
program.command("add <filename>").description("Add a new rule file").action(addCommand);
|
|
2269
2302
|
program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
|