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/index.js CHANGED
@@ -1,30 +1,34 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  generateClaudeMcp
4
- } from "./chunk-HMMPZV7X.js";
4
+ } from "./chunk-22GWBUIP.js";
5
5
  import {
6
6
  generateClineMcp
7
- } from "./chunk-X3FEMISQ.js";
7
+ } from "./chunk-BD37M3ZH.js";
8
8
  import {
9
9
  generateCopilotMcp
10
- } from "./chunk-SXEFNT27.js";
10
+ } from "./chunk-ZORSPGDD.js";
11
11
  import {
12
12
  generateCursorMcp
13
- } from "./chunk-D3YGI36J.js";
13
+ } from "./chunk-FAZT3ILF.js";
14
14
  import {
15
15
  generateGeminiCliMcp
16
- } from "./chunk-2SPL7QTK.js";
16
+ } from "./chunk-DCSO5MY7.js";
17
17
  import {
18
18
  generateRooMcp
19
- } from "./chunk-3NRSCDLQ.js";
20
- import "./chunk-6YNGMPAL.js";
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", "claude", "roo", "geminicli"]
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
- return targets;
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 join3 } from "path";
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 join2 } from "path";
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 = join(baseDir, ".rulesyncignore");
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) => join2(dir, 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 ? join3(baseDir, config.outputPaths.claudecode) : config.outputPaths.claudecode;
241
+ const claudeOutputDir = baseDir ? join4(baseDir, config.outputPaths.claudecode) : config.outputPaths.claudecode;
227
242
  outputs.push({
228
243
  tool: "claudecode",
229
- filepath: join3(claudeOutputDir, "CLAUDE.md"),
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: join3(claudeOutputDir, ".claude", "memories", `${rule.filename}.md`),
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 ? join3(baseDir, ".claude", "settings.json") : join3(".claude", "settings.json");
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
- `| @.claude/memories/${rule.filename}.md | ${rule.frontmatter.description} | ${globsText} |`
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 settings = {};
287
+ let rawSettings = {};
275
288
  if (await fileExists(settingsPath)) {
276
289
  try {
277
290
  const content = await readFileContent(settingsPath);
278
- settings = JSON.parse(content);
291
+ rawSettings = JSON.parse(content);
279
292
  } catch {
280
293
  console.warn(`Failed to parse existing ${settingsPath}, creating new settings`);
281
- settings = {};
294
+ rawSettings = {};
282
295
  }
283
296
  }
284
- if (typeof settings !== "object" || settings === null) {
285
- settings = {};
286
- }
287
- const settingsObj = settings;
288
- if (!settingsObj.permissions || typeof settingsObj.permissions !== "object" || settingsObj.permissions === null) {
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
- const permissions = settingsObj.permissions;
292
- if (!Array.isArray(permissions.deny)) {
293
- permissions.deny = [];
303
+ if (!Array.isArray(settings.permissions.deny)) {
304
+ settings.permissions.deny = [];
294
305
  }
295
- const readDenyRules = ignorePatterns.map((pattern) => `Read(${pattern})`);
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 = [...new Set(filteredDeny)];
306
- const jsonContent = JSON.stringify(settingsObj, null, 2);
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 join4 } from "path";
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 ? join4(baseDir, config.outputPaths.cline) : config.outputPaths.cline;
318
- const filepath = join4(outputDir, `${rule.filename}.md`);
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 ? join4(baseDir, ".clineignore") : ".clineignore";
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 join5 } from "path";
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 ? join5(baseDir, config.outputPaths.copilot) : config.outputPaths.copilot;
358
- const filepath = join5(outputDir, `${baseFilename}.instructions.md`);
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 ? join5(baseDir, ".copilotignore") : ".copilotignore";
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 join6 } from "path";
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 ? join6(baseDir, config.outputPaths.cursor) : config.outputPaths.cursor;
409
- const filepath = join6(outputDir, `${rule.filename}.mdc`);
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 ? join6(baseDir, ".cursorignore") : ".cursorignore";
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 join7 } from "path";
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 ? join7(baseDir, config.outputPaths.geminicli) : config.outputPaths.geminicli;
499
- const filepath = join7(outputDir, `${rule.filename}.md`);
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 ? join7(baseDir, "GEMINI.md") : "GEMINI.md";
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 ? join7(baseDir, ".aiexclude") : ".aiexclude";
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 join8 } from "path";
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 ? join8(baseDir, config.outputPaths.roo) : config.outputPaths.roo;
569
- const filepath = join8(outputDir, `${rule.filename}.md`);
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 ? join8(baseDir, ".rooignore") : ".rooignore";
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
- validateFrontmatter(parsed.data, filepath);
687
- const frontmatter = parsed.data;
688
- const filename = basename(filepath, ".md");
689
- return {
690
- frontmatter,
691
- content: parsed.content,
692
- filename,
693
- filepath
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 "globs" field in ${filepath}: must be an array, got ${typeof obj.globs}`
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
- return { mcpServers: rawConfig.mcpServers };
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 join9 } from "path";
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 = join9(process.cwd(), ".gitignore");
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 (existsSync(gitignorePath)) {
1071
- gitignoreContent = readFileSync(gitignorePath, "utf-8");
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 join16 } from "path";
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 join10 } from "path";
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 = join10(baseDir, "CLAUDE.md");
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 = join10(baseDir, ".claude", "memories");
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 = join10(baseDir, ".claude", "settings.json");
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 = join10(memoryDir, file);
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
- if (typeof settings === "object" && settings !== null && "mcpServers" in settings) {
1226
- const servers = settings.mcpServers;
1227
- if (servers && typeof servers === "object" && Object.keys(servers).length > 0) {
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 join11 } from "path";
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 = join11(baseDir, ".cline", "instructions.md");
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 = join11(baseDir, ".clinerules");
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 = join11(clinerulesDirPath, file);
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 join12 } from "path";
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 = join12(baseDir, ".github", "copilot-instructions.md");
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 = join12(baseDir, ".github", "instructions");
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 = join12(instructionsDir, file);
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 join13 } from "path";
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
- let preprocessed = str.replace(/^(\s*globs:\s*)\*\s*$/gm, '$1"*"').replace(/^(\s*globs:\s*)([^\s"'[\n][^"'[\n]*?)(\s*)$/gm, '$1"$2"$3');
1394
- return load(preprocessed, { schema: DEFAULT_SCHEMA });
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
- return load(str, { schema: FAILSAFE_SCHEMA });
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 frontmatter = cursorFrontmatter;
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 = join13(baseDir, ".cursorrules");
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 = join13(baseDir, ".cursor", "rules");
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 = join13(cursorRulesDir, file);
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 = join13(baseDir, ".cursorignore");
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 = join13(baseDir, ".cursor", "mcp.json");
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
- if (mcp.mcpServers && Object.keys(mcp.mcpServers).length > 0) {
1566
- mcpServers = mcp.mcpServers;
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 join14 } from "path";
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 = join14(baseDir, "GEMINI.md");
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 = join14(baseDir, ".gemini", "memories");
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 = join14(baseDir, ".gemini", "settings.json");
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 = join14(baseDir, ".aiexclude");
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 = join14(memoryDir, file);
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
- if (settings.mcpServers && Object.keys(settings.mcpServers).length > 0) {
1698
- mcpServers = settings.mcpServers;
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 join15 } from "path";
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 = join15(baseDir, ".roo", "instructions.md");
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 = join15(baseDir, ".roo", "rules");
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 = join15(rooRulesDir, file);
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 = join16(baseDir, rulesDir);
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 = join16(rulesDirPath, `${filename}.md`);
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 = join16(baseDir, ".rulesyncignore");
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 = join16(baseDir, rulesDir, ".mcp.json");
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(join16(rulesDir, `${filename}.md`))) {
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 join17 } from "path";
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 = join17(aiRulesDir, file.filename);
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 in targetCounts) {
2159
- targetCounts[target]++;
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.42.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);