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.cjs CHANGED
@@ -26,44 +26,23 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  mod
27
27
  ));
28
28
 
29
- // src/schemas/mcp.ts
30
- var import_zod, ToolTargetSchema, WildcardTargetSchema, SpecificTargetsSchema, RulesyncTargetsSchema, McpTransportTypeSchema, McpServerBaseSchema, RulesyncMcpServerSchema;
31
- var init_mcp = __esm({
32
- "src/schemas/mcp.ts"() {
29
+ // src/types/tool-targets.ts
30
+ var import_v4_mini, ToolTargetSchema, ToolTargetsSchema, WildcardTargetSchema, RulesyncTargetsSchema;
31
+ var init_tool_targets = __esm({
32
+ "src/types/tool-targets.ts"() {
33
33
  "use strict";
34
- import_zod = require("zod");
35
- ToolTargetSchema = import_zod.z.enum([
34
+ import_v4_mini = require("zod/v4-mini");
35
+ ToolTargetSchema = import_v4_mini.z.enum([
36
36
  "copilot",
37
37
  "cursor",
38
38
  "cline",
39
39
  "claudecode",
40
- "claude",
41
40
  "roo",
42
41
  "geminicli"
43
42
  ]);
44
- WildcardTargetSchema = import_zod.z.tuple([import_zod.z.literal("*")]);
45
- SpecificTargetsSchema = import_zod.z.array(ToolTargetSchema);
46
- RulesyncTargetsSchema = import_zod.z.union([SpecificTargetsSchema, WildcardTargetSchema]);
47
- McpTransportTypeSchema = import_zod.z.enum(["stdio", "sse", "http"]);
48
- McpServerBaseSchema = import_zod.z.object({
49
- command: import_zod.z.string().optional(),
50
- args: import_zod.z.array(import_zod.z.string()).optional(),
51
- url: import_zod.z.string().optional(),
52
- httpUrl: import_zod.z.string().optional(),
53
- env: import_zod.z.record(import_zod.z.string()).optional(),
54
- disabled: import_zod.z.boolean().optional(),
55
- networkTimeout: import_zod.z.number().optional(),
56
- timeout: import_zod.z.number().optional(),
57
- trust: import_zod.z.boolean().optional(),
58
- cwd: import_zod.z.string().optional(),
59
- transport: McpTransportTypeSchema.optional(),
60
- type: import_zod.z.enum(["sse", "streamable-http"]).optional(),
61
- alwaysAllow: import_zod.z.array(import_zod.z.string()).optional(),
62
- tools: import_zod.z.array(import_zod.z.string()).optional()
63
- });
64
- RulesyncMcpServerSchema = McpServerBaseSchema.extend({
65
- targets: RulesyncTargetsSchema.optional()
66
- });
43
+ ToolTargetsSchema = import_v4_mini.z.array(ToolTargetSchema);
44
+ WildcardTargetSchema = import_v4_mini.z.tuple([import_v4_mini.z.literal("*")]);
45
+ RulesyncTargetsSchema = import_v4_mini.z.union([ToolTargetsSchema, WildcardTargetSchema]);
67
46
  }
68
47
  });
69
48
 
@@ -87,17 +66,17 @@ function shouldIncludeServer(server, targetTool) {
87
66
  var init_mcp_helpers = __esm({
88
67
  "src/utils/mcp-helpers.ts"() {
89
68
  "use strict";
90
- init_mcp();
69
+ init_tool_targets();
91
70
  }
92
71
  });
93
72
 
94
- // src/generators/mcp/claude.ts
73
+ // src/generators/mcp/claudecode.ts
95
74
  function generateClaudeMcp(config) {
96
75
  const claudeSettings = {
97
76
  mcpServers: {}
98
77
  };
99
78
  const shouldInclude = (server) => {
100
- return shouldIncludeServer(server, "claude");
79
+ return shouldIncludeServer(server, "claudecode");
101
80
  };
102
81
  for (const [serverName, server] of Object.entries(config.mcpServers)) {
103
82
  if (!shouldInclude(server)) continue;
@@ -119,12 +98,14 @@ function generateClaudeMcp(config) {
119
98
  if (server.env) {
120
99
  claudeServer.env = server.env;
121
100
  }
122
- claudeSettings.mcpServers[serverName] = claudeServer;
101
+ if (claudeSettings.mcpServers) {
102
+ claudeSettings.mcpServers[serverName] = claudeServer;
103
+ }
123
104
  }
124
105
  return JSON.stringify(claudeSettings, null, 2);
125
106
  }
126
- var init_claude = __esm({
127
- "src/generators/mcp/claude.ts"() {
107
+ var init_claudecode = __esm({
108
+ "src/generators/mcp/claudecode.ts"() {
128
109
  "use strict";
129
110
  init_mcp_helpers();
130
111
  }
@@ -370,9 +351,10 @@ var import_commander = require("commander");
370
351
 
371
352
  // src/cli/commands/add.ts
372
353
  var import_promises = require("fs/promises");
373
- var import_node_path = __toESM(require("path"), 1);
354
+ var path = __toESM(require("path"), 1);
374
355
 
375
356
  // src/utils/config.ts
357
+ init_tool_targets();
376
358
  function getDefaultConfig() {
377
359
  return {
378
360
  aiRulesDir: ".rulesync",
@@ -381,19 +363,19 @@ function getDefaultConfig() {
381
363
  cursor: ".cursor/rules",
382
364
  cline: ".clinerules",
383
365
  claudecode: ".",
384
- claude: ".",
385
366
  roo: ".roo/rules",
386
367
  geminicli: ".gemini/memories"
387
368
  },
388
369
  watchEnabled: false,
389
- defaultTargets: ["copilot", "cursor", "cline", "claudecode", "claude", "roo", "geminicli"]
370
+ defaultTargets: ["copilot", "cursor", "cline", "claudecode", "roo", "geminicli"]
390
371
  };
391
372
  }
392
373
  function resolveTargets(targets, config) {
393
- if (targets[0] === "*") {
374
+ if (targets.length === 1 && targets[0] === "*") {
394
375
  return config.defaultTargets;
395
376
  }
396
- return targets;
377
+ const validatedTargets = ToolTargetsSchema.parse(targets);
378
+ return validatedTargets;
397
379
  }
398
380
 
399
381
  // src/cli/commands/add.ts
@@ -418,7 +400,7 @@ async function addCommand(filename) {
418
400
  const config = getDefaultConfig();
419
401
  const sanitizedFilename = sanitizeFilename(filename);
420
402
  const rulesDir = config.aiRulesDir;
421
- const filePath = import_node_path.default.join(rulesDir, `${sanitizedFilename}.md`);
403
+ const filePath = path.join(rulesDir, `${sanitizedFilename}.md`);
422
404
  await (0, import_promises.mkdir)(rulesDir, { recursive: true });
423
405
  const template = generateRuleTemplate(sanitizedFilename);
424
406
  await (0, import_promises.writeFile)(filePath, template, "utf8");
@@ -433,15 +415,26 @@ async function addCommand(filename) {
433
415
  }
434
416
 
435
417
  // src/generators/rules/claudecode.ts
436
- var import_node_path5 = require("path");
418
+ var import_node_path4 = require("path");
419
+
420
+ // src/types/claudecode.ts
421
+ var import_v4_mini2 = require("zod/v4-mini");
422
+ var ClaudeSettingsSchema = import_v4_mini2.z.looseObject({
423
+ permissions: import_v4_mini2.z._default(
424
+ import_v4_mini2.z.looseObject({
425
+ deny: import_v4_mini2.z._default(import_v4_mini2.z.array(import_v4_mini2.z.string()), [])
426
+ }),
427
+ { deny: [] }
428
+ )
429
+ });
437
430
 
438
431
  // src/utils/file.ts
439
432
  var import_promises3 = require("fs/promises");
440
- var import_node_path4 = require("path");
433
+ var import_node_path3 = require("path");
441
434
 
442
435
  // src/utils/file-ops.ts
443
436
  var import_promises2 = require("fs/promises");
444
- var import_node_path2 = require("path");
437
+ var import_node_path = require("path");
445
438
  async function ensureDir(dirPath) {
446
439
  try {
447
440
  await (0, import_promises2.stat)(dirPath);
@@ -453,7 +446,7 @@ async function readFileContent(filepath) {
453
446
  return (0, import_promises2.readFile)(filepath, "utf-8");
454
447
  }
455
448
  async function writeFileContent(filepath, content) {
456
- await ensureDir((0, import_node_path2.dirname)(filepath));
449
+ await ensureDir((0, import_node_path.dirname)(filepath));
457
450
  await (0, import_promises2.writeFile)(filepath, content, "utf-8");
458
451
  }
459
452
  async function fileExists(filepath) {
@@ -466,14 +459,14 @@ async function fileExists(filepath) {
466
459
  }
467
460
 
468
461
  // src/utils/ignore.ts
469
- var import_node_path3 = require("path");
462
+ var import_node_path2 = require("path");
470
463
  var import_micromatch = __toESM(require("micromatch"), 1);
471
464
  var cachedIgnorePatterns = null;
472
465
  async function loadIgnorePatterns(baseDir = process.cwd()) {
473
466
  if (cachedIgnorePatterns) {
474
467
  return cachedIgnorePatterns;
475
468
  }
476
- const ignorePath = (0, import_node_path3.join)(baseDir, ".rulesyncignore");
469
+ const ignorePath = (0, import_node_path2.join)(baseDir, ".rulesyncignore");
477
470
  if (!await fileExists(ignorePath)) {
478
471
  cachedIgnorePatterns = { patterns: [] };
479
472
  return cachedIgnorePatterns;
@@ -520,7 +513,7 @@ function filterIgnoredFiles(files, ignorePatterns) {
520
513
  async function findFiles(dir, extension = ".md", ignorePatterns) {
521
514
  try {
522
515
  const files = await (0, import_promises3.readdir)(dir);
523
- const filtered = files.filter((file) => file.endsWith(extension)).map((file) => (0, import_node_path4.join)(dir, file));
516
+ const filtered = files.filter((file) => file.endsWith(extension)).map((file) => (0, import_node_path3.join)(dir, file));
524
517
  if (ignorePatterns && ignorePatterns.length > 0) {
525
518
  return filterIgnoredFiles(filtered, ignorePatterns);
526
519
  }
@@ -569,23 +562,23 @@ async function generateClaudecodeConfig(rules, config, baseDir) {
569
562
  const rootRules = rules.filter((r) => r.frontmatter.root === true);
570
563
  const detailRules = rules.filter((r) => r.frontmatter.root === false);
571
564
  const claudeMdContent = generateClaudeMarkdown(rootRules, detailRules);
572
- const claudeOutputDir = baseDir ? (0, import_node_path5.join)(baseDir, config.outputPaths.claudecode) : config.outputPaths.claudecode;
565
+ const claudeOutputDir = baseDir ? (0, import_node_path4.join)(baseDir, config.outputPaths.claudecode) : config.outputPaths.claudecode;
573
566
  outputs.push({
574
567
  tool: "claudecode",
575
- filepath: (0, import_node_path5.join)(claudeOutputDir, "CLAUDE.md"),
568
+ filepath: (0, import_node_path4.join)(claudeOutputDir, "CLAUDE.md"),
576
569
  content: claudeMdContent
577
570
  });
578
571
  for (const rule of detailRules) {
579
572
  const memoryContent = generateMemoryFile(rule);
580
573
  outputs.push({
581
574
  tool: "claudecode",
582
- filepath: (0, import_node_path5.join)(claudeOutputDir, ".claude", "memories", `${rule.filename}.md`),
575
+ filepath: (0, import_node_path4.join)(claudeOutputDir, ".claude", "memories", `${rule.filename}.md`),
583
576
  content: memoryContent
584
577
  });
585
578
  }
586
579
  const ignorePatterns = await loadIgnorePatterns(baseDir);
587
580
  if (ignorePatterns.patterns.length > 0) {
588
- const settingsPath = baseDir ? (0, import_node_path5.join)(baseDir, ".claude", "settings.json") : (0, import_node_path5.join)(".claude", "settings.json");
581
+ const settingsPath = baseDir ? (0, import_node_path4.join)(baseDir, ".claude", "settings.json") : (0, import_node_path4.join)(".claude", "settings.json");
589
582
  await updateClaudeSettings(settingsPath, ignorePatterns.patterns);
590
583
  }
591
584
  return outputs;
@@ -595,12 +588,10 @@ function generateClaudeMarkdown(rootRules, detailRules) {
595
588
  if (detailRules.length > 0) {
596
589
  lines.push("Please also reference the following documents as needed:");
597
590
  lines.push("");
598
- lines.push("| Document | Description | File Patterns |");
599
- lines.push("|----------|-------------|---------------|");
600
591
  for (const rule of detailRules) {
601
- const globsText = rule.frontmatter.globs.length > 0 ? rule.frontmatter.globs.join(", ") : "-";
592
+ const globsText = rule.frontmatter.globs.length > 0 ? rule.frontmatter.globs.join(", ") : "";
602
593
  lines.push(
603
- `| @.claude/memories/${rule.filename}.md | ${rule.frontmatter.description} | ${globsText} |`
594
+ `@.claude/memories/${rule.filename}.md ${rule.frontmatter.description} ${globsText}`.trim()
604
595
  );
605
596
  }
606
597
  lines.push("");
@@ -617,51 +608,46 @@ function generateMemoryFile(rule) {
617
608
  return rule.content.trim();
618
609
  }
619
610
  async function updateClaudeSettings(settingsPath, ignorePatterns) {
620
- let settings = {};
611
+ let rawSettings = {};
621
612
  if (await fileExists(settingsPath)) {
622
613
  try {
623
614
  const content = await readFileContent(settingsPath);
624
- settings = JSON.parse(content);
615
+ rawSettings = JSON.parse(content);
625
616
  } catch {
626
617
  console.warn(`Failed to parse existing ${settingsPath}, creating new settings`);
627
- settings = {};
618
+ rawSettings = {};
628
619
  }
629
620
  }
630
- if (typeof settings !== "object" || settings === null) {
631
- settings = {};
632
- }
633
- const settingsObj = settings;
634
- if (!settingsObj.permissions || typeof settingsObj.permissions !== "object" || settingsObj.permissions === null) {
635
- settingsObj.permissions = {};
621
+ const parseResult = ClaudeSettingsSchema.safeParse(rawSettings);
622
+ const settings = parseResult.success ? parseResult.data : ClaudeSettingsSchema.parse({});
623
+ const readDenyRules = ignorePatterns.map((pattern) => `Read(${pattern})`);
624
+ if (!settings.permissions) {
625
+ settings.permissions = { deny: [] };
636
626
  }
637
- const permissions = settingsObj.permissions;
638
- if (!Array.isArray(permissions.deny)) {
639
- permissions.deny = [];
627
+ if (!Array.isArray(settings.permissions.deny)) {
628
+ settings.permissions.deny = [];
640
629
  }
641
- const readDenyRules = ignorePatterns.map((pattern) => `Read(${pattern})`);
642
- const denyArray = permissions.deny;
643
- const filteredDeny = denyArray.filter((rule) => {
644
- if (typeof rule !== "string") return false;
630
+ const filteredDeny = settings.permissions.deny.filter((rule) => {
645
631
  if (!rule.startsWith("Read(")) return true;
646
632
  const match = rule.match(/^Read\((.*)\)$/);
647
633
  if (!match) return true;
648
634
  return !ignorePatterns.includes(match[1] ?? "");
649
635
  });
650
636
  filteredDeny.push(...readDenyRules);
651
- permissions.deny = [...new Set(filteredDeny)];
652
- const jsonContent = JSON.stringify(settingsObj, null, 2);
637
+ settings.permissions.deny = Array.from(new Set(filteredDeny));
638
+ const jsonContent = JSON.stringify(settings, null, 2);
653
639
  await writeFileContent(settingsPath, jsonContent);
654
640
  console.log(`\u2705 Updated Claude Code settings: ${settingsPath}`);
655
641
  }
656
642
 
657
643
  // src/generators/rules/cline.ts
658
- var import_node_path6 = require("path");
644
+ var import_node_path5 = require("path");
659
645
  async function generateClineConfig(rules, config, baseDir) {
660
646
  const outputs = [];
661
647
  for (const rule of rules) {
662
648
  const content = generateClineMarkdown(rule);
663
- const outputDir = baseDir ? (0, import_node_path6.join)(baseDir, config.outputPaths.cline) : config.outputPaths.cline;
664
- const filepath = (0, import_node_path6.join)(outputDir, `${rule.filename}.md`);
649
+ const outputDir = baseDir ? (0, import_node_path5.join)(baseDir, config.outputPaths.cline) : config.outputPaths.cline;
650
+ const filepath = (0, import_node_path5.join)(outputDir, `${rule.filename}.md`);
665
651
  outputs.push({
666
652
  tool: "cline",
667
653
  filepath,
@@ -670,7 +656,7 @@ async function generateClineConfig(rules, config, baseDir) {
670
656
  }
671
657
  const ignorePatterns = await loadIgnorePatterns(baseDir);
672
658
  if (ignorePatterns.patterns.length > 0) {
673
- const clineIgnorePath = baseDir ? (0, import_node_path6.join)(baseDir, ".clineignore") : ".clineignore";
659
+ const clineIgnorePath = baseDir ? (0, import_node_path5.join)(baseDir, ".clineignore") : ".clineignore";
674
660
  const clineIgnoreContent = generateClineIgnore(ignorePatterns.patterns);
675
661
  outputs.push({
676
662
  tool: "cline",
@@ -694,14 +680,14 @@ function generateClineIgnore(patterns) {
694
680
  }
695
681
 
696
682
  // src/generators/rules/copilot.ts
697
- var import_node_path7 = require("path");
683
+ var import_node_path6 = require("path");
698
684
  async function generateCopilotConfig(rules, config, baseDir) {
699
685
  const outputs = [];
700
686
  for (const rule of rules) {
701
687
  const content = generateCopilotMarkdown(rule);
702
688
  const baseFilename = rule.filename.replace(/\.md$/, "");
703
- const outputDir = baseDir ? (0, import_node_path7.join)(baseDir, config.outputPaths.copilot) : config.outputPaths.copilot;
704
- const filepath = (0, import_node_path7.join)(outputDir, `${baseFilename}.instructions.md`);
689
+ const outputDir = baseDir ? (0, import_node_path6.join)(baseDir, config.outputPaths.copilot) : config.outputPaths.copilot;
690
+ const filepath = (0, import_node_path6.join)(outputDir, `${baseFilename}.instructions.md`);
705
691
  outputs.push({
706
692
  tool: "copilot",
707
693
  filepath,
@@ -710,7 +696,7 @@ async function generateCopilotConfig(rules, config, baseDir) {
710
696
  }
711
697
  const ignorePatterns = await loadIgnorePatterns(baseDir);
712
698
  if (ignorePatterns.patterns.length > 0) {
713
- const copilotIgnorePath = baseDir ? (0, import_node_path7.join)(baseDir, ".copilotignore") : ".copilotignore";
699
+ const copilotIgnorePath = baseDir ? (0, import_node_path6.join)(baseDir, ".copilotignore") : ".copilotignore";
714
700
  const copilotIgnoreContent = generateCopilotIgnore(ignorePatterns.patterns);
715
701
  outputs.push({
716
702
  tool: "copilot",
@@ -746,13 +732,13 @@ function generateCopilotIgnore(patterns) {
746
732
  }
747
733
 
748
734
  // src/generators/rules/cursor.ts
749
- var import_node_path8 = require("path");
735
+ var import_node_path7 = require("path");
750
736
  async function generateCursorConfig(rules, config, baseDir) {
751
737
  const outputs = [];
752
738
  for (const rule of rules) {
753
739
  const content = generateCursorMarkdown(rule);
754
- const outputDir = baseDir ? (0, import_node_path8.join)(baseDir, config.outputPaths.cursor) : config.outputPaths.cursor;
755
- const filepath = (0, import_node_path8.join)(outputDir, `${rule.filename}.mdc`);
740
+ const outputDir = baseDir ? (0, import_node_path7.join)(baseDir, config.outputPaths.cursor) : config.outputPaths.cursor;
741
+ const filepath = (0, import_node_path7.join)(outputDir, `${rule.filename}.mdc`);
756
742
  outputs.push({
757
743
  tool: "cursor",
758
744
  filepath,
@@ -761,7 +747,7 @@ async function generateCursorConfig(rules, config, baseDir) {
761
747
  }
762
748
  const ignorePatterns = await loadIgnorePatterns(baseDir);
763
749
  if (ignorePatterns.patterns.length > 0) {
764
- const cursorIgnorePath = baseDir ? (0, import_node_path8.join)(baseDir, ".cursorignore") : ".cursorignore";
750
+ const cursorIgnorePath = baseDir ? (0, import_node_path7.join)(baseDir, ".cursorignore") : ".cursorignore";
765
751
  const cursorIgnoreContent = generateCursorIgnore(ignorePatterns.patterns);
766
752
  outputs.push({
767
753
  tool: "cursor",
@@ -834,15 +820,15 @@ function generateCursorIgnore(patterns) {
834
820
  }
835
821
 
836
822
  // src/generators/rules/geminicli.ts
837
- var import_node_path9 = require("path");
823
+ var import_node_path8 = require("path");
838
824
  async function generateGeminiConfig(rules, config, baseDir) {
839
825
  const outputs = [];
840
826
  const rootRule = rules.find((rule) => rule.frontmatter.root === true);
841
827
  const memoryRules = rules.filter((rule) => rule.frontmatter.root === false);
842
828
  for (const rule of memoryRules) {
843
829
  const content = generateGeminiMemoryMarkdown(rule);
844
- const outputDir = baseDir ? (0, import_node_path9.join)(baseDir, config.outputPaths.geminicli) : config.outputPaths.geminicli;
845
- const filepath = (0, import_node_path9.join)(outputDir, `${rule.filename}.md`);
830
+ const outputDir = baseDir ? (0, import_node_path8.join)(baseDir, config.outputPaths.geminicli) : config.outputPaths.geminicli;
831
+ const filepath = (0, import_node_path8.join)(outputDir, `${rule.filename}.md`);
846
832
  outputs.push({
847
833
  tool: "geminicli",
848
834
  filepath,
@@ -850,7 +836,7 @@ async function generateGeminiConfig(rules, config, baseDir) {
850
836
  });
851
837
  }
852
838
  const rootContent = generateGeminiRootMarkdown(rootRule, memoryRules, baseDir);
853
- const rootFilepath = baseDir ? (0, import_node_path9.join)(baseDir, "GEMINI.md") : "GEMINI.md";
839
+ const rootFilepath = baseDir ? (0, import_node_path8.join)(baseDir, "GEMINI.md") : "GEMINI.md";
854
840
  outputs.push({
855
841
  tool: "geminicli",
856
842
  filepath: rootFilepath,
@@ -858,7 +844,7 @@ async function generateGeminiConfig(rules, config, baseDir) {
858
844
  });
859
845
  const ignorePatterns = await loadIgnorePatterns(baseDir);
860
846
  if (ignorePatterns.patterns.length > 0) {
861
- const aiexcludePath = baseDir ? (0, import_node_path9.join)(baseDir, ".aiexclude") : ".aiexclude";
847
+ const aiexcludePath = baseDir ? (0, import_node_path8.join)(baseDir, ".aiexclude") : ".aiexclude";
862
848
  const aiexcludeContent = generateAiexclude(ignorePatterns.patterns);
863
849
  outputs.push({
864
850
  tool: "geminicli",
@@ -906,13 +892,13 @@ function generateAiexclude(patterns) {
906
892
  }
907
893
 
908
894
  // src/generators/rules/roo.ts
909
- var import_node_path10 = require("path");
895
+ var import_node_path9 = require("path");
910
896
  async function generateRooConfig(rules, config, baseDir) {
911
897
  const outputs = [];
912
898
  for (const rule of rules) {
913
899
  const content = generateRooMarkdown(rule);
914
- const outputDir = baseDir ? (0, import_node_path10.join)(baseDir, config.outputPaths.roo) : config.outputPaths.roo;
915
- const filepath = (0, import_node_path10.join)(outputDir, `${rule.filename}.md`);
900
+ const outputDir = baseDir ? (0, import_node_path9.join)(baseDir, config.outputPaths.roo) : config.outputPaths.roo;
901
+ const filepath = (0, import_node_path9.join)(outputDir, `${rule.filename}.md`);
916
902
  outputs.push({
917
903
  tool: "roo",
918
904
  filepath,
@@ -921,7 +907,7 @@ async function generateRooConfig(rules, config, baseDir) {
921
907
  }
922
908
  const ignorePatterns = await loadIgnorePatterns(baseDir);
923
909
  if (ignorePatterns.patterns.length > 0) {
924
- const rooIgnorePath = baseDir ? (0, import_node_path10.join)(baseDir, ".rooignore") : ".rooignore";
910
+ const rooIgnorePath = baseDir ? (0, import_node_path9.join)(baseDir, ".rooignore") : ".rooignore";
925
911
  const rooIgnoreContent = generateRooIgnore(ignorePatterns.patterns);
926
912
  outputs.push({
927
913
  tool: "roo",
@@ -994,8 +980,80 @@ async function generateForTool(tool, rules, config, baseDir) {
994
980
  }
995
981
 
996
982
  // src/core/parser.ts
997
- var import_node_path11 = require("path");
983
+ var import_node_path10 = require("path");
998
984
  var import_gray_matter = __toESM(require("gray-matter"), 1);
985
+
986
+ // src/types/config.ts
987
+ var import_v4_mini3 = require("zod/v4-mini");
988
+ init_tool_targets();
989
+ var ConfigSchema = import_v4_mini3.z.object({
990
+ aiRulesDir: import_v4_mini3.z.string(),
991
+ outputPaths: import_v4_mini3.z.record(ToolTargetSchema, import_v4_mini3.z.string()),
992
+ watchEnabled: import_v4_mini3.z.boolean(),
993
+ defaultTargets: ToolTargetsSchema
994
+ });
995
+
996
+ // src/types/mcp.ts
997
+ var import_v4_mini4 = require("zod/v4-mini");
998
+ init_tool_targets();
999
+ var McpTransportTypeSchema = import_v4_mini4.z.enum(["stdio", "sse", "http"]);
1000
+ var McpServerBaseSchema = import_v4_mini4.z.object({
1001
+ command: import_v4_mini4.z.optional(import_v4_mini4.z.string()),
1002
+ args: import_v4_mini4.z.optional(import_v4_mini4.z.array(import_v4_mini4.z.string())),
1003
+ url: import_v4_mini4.z.optional(import_v4_mini4.z.string()),
1004
+ httpUrl: import_v4_mini4.z.optional(import_v4_mini4.z.string()),
1005
+ env: import_v4_mini4.z.optional(import_v4_mini4.z.record(import_v4_mini4.z.string(), import_v4_mini4.z.string())),
1006
+ disabled: import_v4_mini4.z.optional(import_v4_mini4.z.boolean()),
1007
+ networkTimeout: import_v4_mini4.z.optional(import_v4_mini4.z.number()),
1008
+ timeout: import_v4_mini4.z.optional(import_v4_mini4.z.number()),
1009
+ trust: import_v4_mini4.z.optional(import_v4_mini4.z.boolean()),
1010
+ cwd: import_v4_mini4.z.optional(import_v4_mini4.z.string()),
1011
+ transport: import_v4_mini4.z.optional(McpTransportTypeSchema),
1012
+ type: import_v4_mini4.z.optional(import_v4_mini4.z.enum(["sse", "streamable-http"])),
1013
+ alwaysAllow: import_v4_mini4.z.optional(import_v4_mini4.z.array(import_v4_mini4.z.string())),
1014
+ tools: import_v4_mini4.z.optional(import_v4_mini4.z.array(import_v4_mini4.z.string()))
1015
+ });
1016
+ var RulesyncMcpServerSchema = import_v4_mini4.z.extend(McpServerBaseSchema, {
1017
+ targets: import_v4_mini4.z.optional(RulesyncTargetsSchema)
1018
+ });
1019
+ var McpConfigSchema = import_v4_mini4.z.object({
1020
+ mcpServers: import_v4_mini4.z.record(import_v4_mini4.z.string(), McpServerBaseSchema)
1021
+ });
1022
+ var RulesyncMcpConfigSchema = import_v4_mini4.z.object({
1023
+ mcpServers: import_v4_mini4.z.record(import_v4_mini4.z.string(), RulesyncMcpServerSchema)
1024
+ });
1025
+
1026
+ // src/types/rules.ts
1027
+ var import_v4_mini5 = require("zod/v4-mini");
1028
+ init_tool_targets();
1029
+ var RuleFrontmatterSchema = import_v4_mini5.z.object({
1030
+ root: import_v4_mini5.z.boolean(),
1031
+ targets: RulesyncTargetsSchema,
1032
+ description: import_v4_mini5.z.string(),
1033
+ globs: import_v4_mini5.z.array(import_v4_mini5.z.string()),
1034
+ cursorRuleType: import_v4_mini5.z.optional(import_v4_mini5.z.enum(["always", "manual", "specificFiles", "intelligently"]))
1035
+ });
1036
+ var ParsedRuleSchema = import_v4_mini5.z.object({
1037
+ frontmatter: RuleFrontmatterSchema,
1038
+ content: import_v4_mini5.z.string(),
1039
+ filename: import_v4_mini5.z.string(),
1040
+ filepath: import_v4_mini5.z.string()
1041
+ });
1042
+ var GeneratedOutputSchema = import_v4_mini5.z.object({
1043
+ tool: ToolTargetSchema,
1044
+ filepath: import_v4_mini5.z.string(),
1045
+ content: import_v4_mini5.z.string()
1046
+ });
1047
+ var GenerateOptionsSchema = import_v4_mini5.z.object({
1048
+ targetTools: import_v4_mini5.z.optional(ToolTargetsSchema),
1049
+ outputDir: import_v4_mini5.z.optional(import_v4_mini5.z.string()),
1050
+ watch: import_v4_mini5.z.optional(import_v4_mini5.z.boolean())
1051
+ });
1052
+
1053
+ // src/types/index.ts
1054
+ init_tool_targets();
1055
+
1056
+ // src/core/parser.ts
999
1057
  async function parseRulesFromDirectory(aiRulesDir) {
1000
1058
  const ignorePatterns = await loadIgnorePatterns();
1001
1059
  const ruleFiles = await findFiles(aiRulesDir, ".md", ignorePatterns.patterns);
@@ -1029,84 +1087,20 @@ ${errors.join("\n")}`);
1029
1087
  async function parseRuleFile(filepath) {
1030
1088
  const content = await readFileContent(filepath);
1031
1089
  const parsed = (0, import_gray_matter.default)(content);
1032
- validateFrontmatter(parsed.data, filepath);
1033
- const frontmatter = parsed.data;
1034
- const filename = (0, import_node_path11.basename)(filepath, ".md");
1035
- return {
1036
- frontmatter,
1037
- content: parsed.content,
1038
- filename,
1039
- filepath
1040
- };
1041
- }
1042
- function validateFrontmatter(data, filepath) {
1043
- if (!data || typeof data !== "object") {
1044
- if (!data) {
1045
- throw new Error(
1046
- `Missing frontmatter in ${filepath}: file must contain YAML frontmatter with required fields (root, targets, description, globs)`
1047
- );
1048
- }
1049
- throw new Error(`Invalid frontmatter in ${filepath}: frontmatter must be a valid YAML object`);
1050
- }
1051
- const obj = data;
1052
- if (Object.keys(obj).length === 0) {
1053
- throw new Error(
1054
- `Missing frontmatter in ${filepath}: file must contain YAML frontmatter with required fields (root, targets, description, globs)`
1055
- );
1056
- }
1057
- if (obj.root === void 0) {
1058
- throw new Error(`Missing required field "root" in ${filepath}: must be true or false`);
1059
- }
1060
- if (typeof obj.root !== "boolean") {
1061
- throw new Error(
1062
- `Invalid "root" field in ${filepath}: must be a boolean (true or false), got ${typeof obj.root}`
1063
- );
1064
- }
1065
- if (obj.targets === void 0) {
1066
- throw new Error(
1067
- `Missing required field "targets" in ${filepath}: must be an array like ["*"] or ["copilot", "cursor"]`
1068
- );
1069
- }
1070
- if (!Array.isArray(obj.targets)) {
1071
- throw new Error(
1072
- `Invalid "targets" field in ${filepath}: must be an array, got ${typeof obj.targets}`
1073
- );
1074
- }
1075
- const validTargets = ["copilot", "cursor", "cline", "claudecode", "roo", "geminicli", "*"];
1076
- for (const target of obj.targets) {
1077
- if (typeof target !== "string" || !validTargets.includes(target)) {
1078
- throw new Error(
1079
- `Invalid target "${target}" in ${filepath}: must be one of ${validTargets.join(", ")}`
1080
- );
1081
- }
1082
- }
1083
- if (obj.description === void 0) {
1084
- throw new Error(
1085
- `Missing required field "description" in ${filepath}: must be a descriptive string`
1086
- );
1087
- }
1088
- if (typeof obj.description !== "string") {
1089
- throw new Error(
1090
- `Invalid "description" field in ${filepath}: must be a string, got ${typeof obj.description}`
1091
- );
1092
- }
1093
- if (obj.globs === void 0) {
1094
- throw new Error(
1095
- `Missing required field "globs" in ${filepath}: must be an array of file patterns like ["**/*.ts"]`
1096
- );
1097
- }
1098
- if (!Array.isArray(obj.globs)) {
1090
+ try {
1091
+ const frontmatter = RuleFrontmatterSchema.parse(parsed.data);
1092
+ const filename = (0, import_node_path10.basename)(filepath, ".md");
1093
+ return {
1094
+ frontmatter,
1095
+ content: parsed.content,
1096
+ filename,
1097
+ filepath
1098
+ };
1099
+ } catch (error) {
1099
1100
  throw new Error(
1100
- `Invalid "globs" field in ${filepath}: must be an array, got ${typeof obj.globs}`
1101
+ `Invalid frontmatter in ${filepath}: ${error instanceof Error ? error.message : String(error)}`
1101
1102
  );
1102
1103
  }
1103
- for (const glob of obj.globs) {
1104
- if (typeof glob !== "string") {
1105
- throw new Error(
1106
- `Invalid glob pattern in ${filepath}: all globs must be strings, got ${typeof glob}`
1107
- );
1108
- }
1109
- }
1110
1104
  }
1111
1105
 
1112
1106
  // src/core/validator.ts
@@ -1160,10 +1154,10 @@ async function validateRule(rule) {
1160
1154
  }
1161
1155
 
1162
1156
  // src/core/mcp-generator.ts
1163
- var import_node_path13 = __toESM(require("path"), 1);
1157
+ var path3 = __toESM(require("path"), 1);
1164
1158
 
1165
1159
  // src/generators/mcp/index.ts
1166
- init_claude();
1160
+ init_claudecode();
1167
1161
  init_cline();
1168
1162
  init_copilot();
1169
1163
  init_cursor();
@@ -1171,27 +1165,25 @@ init_geminicli();
1171
1165
  init_roo();
1172
1166
 
1173
1167
  // src/core/mcp-parser.ts
1174
- var import_node_fs = __toESM(require("fs"), 1);
1175
- var import_node_path12 = __toESM(require("path"), 1);
1168
+ var fs = __toESM(require("fs"), 1);
1169
+ var path2 = __toESM(require("path"), 1);
1176
1170
  function parseMcpConfig(projectRoot) {
1177
- const mcpPath = import_node_path12.default.join(projectRoot, ".rulesync", ".mcp.json");
1178
- if (!import_node_fs.default.existsSync(mcpPath)) {
1171
+ const mcpPath = path2.join(projectRoot, ".rulesync", ".mcp.json");
1172
+ if (!fs.existsSync(mcpPath)) {
1179
1173
  return null;
1180
1174
  }
1181
1175
  try {
1182
- const content = import_node_fs.default.readFileSync(mcpPath, "utf-8");
1176
+ const content = fs.readFileSync(mcpPath, "utf-8");
1183
1177
  const rawConfig = JSON.parse(content);
1184
1178
  if (rawConfig.servers && !rawConfig.mcpServers) {
1185
1179
  rawConfig.mcpServers = rawConfig.servers;
1186
1180
  delete rawConfig.servers;
1187
1181
  }
1188
- if (!rawConfig.mcpServers || typeof rawConfig.mcpServers !== "object") {
1189
- throw new Error("Invalid mcp.json: 'mcpServers' field must be an object");
1190
- }
1191
1182
  if (rawConfig.tools) {
1192
1183
  delete rawConfig.tools;
1193
1184
  }
1194
- return { mcpServers: rawConfig.mcpServers };
1185
+ const validatedConfig = RulesyncMcpConfigSchema.parse(rawConfig);
1186
+ return validatedConfig;
1195
1187
  } catch (error) {
1196
1188
  throw new Error(
1197
1189
  `Failed to parse mcp.json: ${error instanceof Error ? error.message : String(error)}`
@@ -1210,32 +1202,32 @@ async function generateMcpConfigs(projectRoot, baseDir) {
1210
1202
  const generators = [
1211
1203
  {
1212
1204
  tool: "claude-project",
1213
- path: import_node_path13.default.join(targetRoot, ".mcp.json"),
1205
+ path: path3.join(targetRoot, ".mcp.json"),
1214
1206
  generate: () => generateClaudeMcp(config)
1215
1207
  },
1216
1208
  {
1217
1209
  tool: "copilot-editor",
1218
- path: import_node_path13.default.join(targetRoot, ".vscode", "mcp.json"),
1210
+ path: path3.join(targetRoot, ".vscode", "mcp.json"),
1219
1211
  generate: () => generateCopilotMcp(config, "editor")
1220
1212
  },
1221
1213
  {
1222
1214
  tool: "cursor-project",
1223
- path: import_node_path13.default.join(targetRoot, ".cursor", "mcp.json"),
1215
+ path: path3.join(targetRoot, ".cursor", "mcp.json"),
1224
1216
  generate: () => generateCursorMcp(config)
1225
1217
  },
1226
1218
  {
1227
1219
  tool: "cline-project",
1228
- path: import_node_path13.default.join(targetRoot, ".cline", "mcp.json"),
1220
+ path: path3.join(targetRoot, ".cline", "mcp.json"),
1229
1221
  generate: () => generateClineMcp(config)
1230
1222
  },
1231
1223
  {
1232
1224
  tool: "gemini-project",
1233
- path: import_node_path13.default.join(targetRoot, ".gemini", "settings.json"),
1225
+ path: path3.join(targetRoot, ".gemini", "settings.json"),
1234
1226
  generate: () => generateGeminiCliMcp(config)
1235
1227
  },
1236
1228
  {
1237
1229
  tool: "roo-project",
1238
- path: import_node_path13.default.join(targetRoot, ".roo", "mcp.json"),
1230
+ path: path3.join(targetRoot, ".roo", "mcp.json"),
1239
1231
  generate: () => generateRooMcp(config)
1240
1232
  }
1241
1233
  ];
@@ -1392,10 +1384,10 @@ Generating configurations for base directory: ${baseDir}`);
1392
1384
  }
1393
1385
 
1394
1386
  // src/cli/commands/gitignore.ts
1395
- var import_node_fs2 = require("fs");
1396
- var import_node_path14 = require("path");
1387
+ var import_node_fs = require("fs");
1388
+ var import_node_path11 = require("path");
1397
1389
  var gitignoreCommand = async () => {
1398
- const gitignorePath = (0, import_node_path14.join)(process.cwd(), ".gitignore");
1390
+ const gitignorePath = (0, import_node_path11.join)(process.cwd(), ".gitignore");
1399
1391
  const rulesFilesToIgnore = [
1400
1392
  "# Generated by rulesync - AI tool configuration files",
1401
1393
  "**/.github/copilot-instructions.md",
@@ -1421,8 +1413,8 @@ var gitignoreCommand = async () => {
1421
1413
  "**/.roo/mcp.json"
1422
1414
  ];
1423
1415
  let gitignoreContent = "";
1424
- if ((0, import_node_fs2.existsSync)(gitignorePath)) {
1425
- gitignoreContent = (0, import_node_fs2.readFileSync)(gitignorePath, "utf-8");
1416
+ if ((0, import_node_fs.existsSync)(gitignorePath)) {
1417
+ gitignoreContent = (0, import_node_fs.readFileSync)(gitignorePath, "utf-8");
1426
1418
  }
1427
1419
  const linesToAdd = [];
1428
1420
  for (const rule of rulesFilesToIgnore) {
@@ -1439,7 +1431,7 @@ var gitignoreCommand = async () => {
1439
1431
  ${linesToAdd.join("\n")}
1440
1432
  ` : `${linesToAdd.join("\n")}
1441
1433
  `;
1442
- (0, import_node_fs2.writeFileSync)(gitignorePath, newContent);
1434
+ (0, import_node_fs.writeFileSync)(gitignorePath, newContent);
1443
1435
  console.log(`\u2705 Added ${linesToAdd.length} rules to .gitignore:`);
1444
1436
  for (const line of linesToAdd) {
1445
1437
  if (!line.startsWith("#")) {
@@ -1449,17 +1441,17 @@ ${linesToAdd.join("\n")}
1449
1441
  };
1450
1442
 
1451
1443
  // src/core/importer.ts
1452
- var import_node_path21 = require("path");
1444
+ var import_node_path18 = require("path");
1453
1445
  var import_gray_matter4 = __toESM(require("gray-matter"), 1);
1454
1446
 
1455
1447
  // src/parsers/claudecode.ts
1456
- var import_node_path15 = require("path");
1448
+ var import_node_path12 = require("path");
1457
1449
  async function parseClaudeConfiguration(baseDir = process.cwd()) {
1458
1450
  const errors = [];
1459
1451
  const rules = [];
1460
1452
  let ignorePatterns;
1461
1453
  let mcpServers;
1462
- const claudeFilePath = (0, import_node_path15.join)(baseDir, "CLAUDE.md");
1454
+ const claudeFilePath = (0, import_node_path12.join)(baseDir, "CLAUDE.md");
1463
1455
  if (!await fileExists(claudeFilePath)) {
1464
1456
  errors.push("CLAUDE.md file not found");
1465
1457
  return { rules, errors };
@@ -1470,12 +1462,12 @@ async function parseClaudeConfiguration(baseDir = process.cwd()) {
1470
1462
  if (mainRule) {
1471
1463
  rules.push(mainRule);
1472
1464
  }
1473
- const memoryDir = (0, import_node_path15.join)(baseDir, ".claude", "memories");
1465
+ const memoryDir = (0, import_node_path12.join)(baseDir, ".claude", "memories");
1474
1466
  if (await fileExists(memoryDir)) {
1475
1467
  const memoryRules = await parseClaudeMemoryFiles(memoryDir);
1476
1468
  rules.push(...memoryRules);
1477
1469
  }
1478
- const settingsPath = (0, import_node_path15.join)(baseDir, ".claude", "settings.json");
1470
+ const settingsPath = (0, import_node_path12.join)(baseDir, ".claude", "settings.json");
1479
1471
  if (await fileExists(settingsPath)) {
1480
1472
  const settingsResult = await parseClaudeSettings(settingsPath);
1481
1473
  if (settingsResult.ignorePatterns) {
@@ -1532,10 +1524,10 @@ async function parseClaudeMemoryFiles(memoryDir) {
1532
1524
  const files = await readdir2(memoryDir);
1533
1525
  for (const file of files) {
1534
1526
  if (file.endsWith(".md")) {
1535
- const filePath = (0, import_node_path15.join)(memoryDir, file);
1527
+ const filePath = (0, import_node_path12.join)(memoryDir, file);
1536
1528
  const content = await readFileContent(filePath);
1537
1529
  if (content.trim()) {
1538
- const filename = (0, import_node_path15.basename)(file, ".md");
1530
+ const filename = (0, import_node_path12.basename)(file, ".md");
1539
1531
  const frontmatter = {
1540
1532
  root: false,
1541
1533
  targets: ["claudecode"],
@@ -1564,6 +1556,9 @@ async function parseClaudeSettings(settingsPath) {
1564
1556
  const settings = JSON.parse(content);
1565
1557
  if (typeof settings === "object" && settings !== null && "permissions" in settings) {
1566
1558
  const permissions = settings.permissions;
1559
+ if (typeof permissions !== "object" || permissions === null) {
1560
+ return { ignorePatterns: [], errors: [] };
1561
+ }
1567
1562
  if (permissions && "deny" in permissions && Array.isArray(permissions.deny)) {
1568
1563
  const readPatterns = permissions.deny.filter(
1569
1564
  (rule) => typeof rule === "string" && rule.startsWith("Read(") && rule.endsWith(")")
@@ -1576,11 +1571,9 @@ async function parseClaudeSettings(settingsPath) {
1576
1571
  }
1577
1572
  }
1578
1573
  }
1579
- if (typeof settings === "object" && settings !== null && "mcpServers" in settings) {
1580
- const servers = settings.mcpServers;
1581
- if (servers && typeof servers === "object" && Object.keys(servers).length > 0) {
1582
- mcpServers = servers;
1583
- }
1574
+ const parseResult = RulesyncMcpConfigSchema.safeParse(settings);
1575
+ if (parseResult.success && Object.keys(parseResult.data.mcpServers).length > 0) {
1576
+ mcpServers = parseResult.data.mcpServers;
1584
1577
  }
1585
1578
  } catch (error) {
1586
1579
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -1594,11 +1587,11 @@ async function parseClaudeSettings(settingsPath) {
1594
1587
  }
1595
1588
 
1596
1589
  // src/parsers/cline.ts
1597
- var import_node_path16 = require("path");
1590
+ var import_node_path13 = require("path");
1598
1591
  async function parseClineConfiguration(baseDir = process.cwd()) {
1599
1592
  const errors = [];
1600
1593
  const rules = [];
1601
- const clineFilePath = (0, import_node_path16.join)(baseDir, ".cline", "instructions.md");
1594
+ const clineFilePath = (0, import_node_path13.join)(baseDir, ".cline", "instructions.md");
1602
1595
  if (await fileExists(clineFilePath)) {
1603
1596
  try {
1604
1597
  const content = await readFileContent(clineFilePath);
@@ -1621,14 +1614,14 @@ async function parseClineConfiguration(baseDir = process.cwd()) {
1621
1614
  errors.push(`Failed to parse .cline/instructions.md: ${errorMessage}`);
1622
1615
  }
1623
1616
  }
1624
- const clinerulesDirPath = (0, import_node_path16.join)(baseDir, ".clinerules");
1617
+ const clinerulesDirPath = (0, import_node_path13.join)(baseDir, ".clinerules");
1625
1618
  if (await fileExists(clinerulesDirPath)) {
1626
1619
  try {
1627
1620
  const { readdir: readdir2 } = await import("fs/promises");
1628
1621
  const files = await readdir2(clinerulesDirPath);
1629
1622
  for (const file of files) {
1630
1623
  if (file.endsWith(".md")) {
1631
- const filePath = (0, import_node_path16.join)(clinerulesDirPath, file);
1624
+ const filePath = (0, import_node_path13.join)(clinerulesDirPath, file);
1632
1625
  try {
1633
1626
  const content = await readFileContent(filePath);
1634
1627
  if (content.trim()) {
@@ -1664,12 +1657,12 @@ async function parseClineConfiguration(baseDir = process.cwd()) {
1664
1657
  }
1665
1658
 
1666
1659
  // src/parsers/copilot.ts
1667
- var import_node_path17 = require("path");
1660
+ var import_node_path14 = require("path");
1668
1661
  var import_gray_matter2 = __toESM(require("gray-matter"), 1);
1669
1662
  async function parseCopilotConfiguration(baseDir = process.cwd()) {
1670
1663
  const errors = [];
1671
1664
  const rules = [];
1672
- const copilotFilePath = (0, import_node_path17.join)(baseDir, ".github", "copilot-instructions.md");
1665
+ const copilotFilePath = (0, import_node_path14.join)(baseDir, ".github", "copilot-instructions.md");
1673
1666
  if (await fileExists(copilotFilePath)) {
1674
1667
  try {
1675
1668
  const rawContent = await readFileContent(copilotFilePath);
@@ -1694,19 +1687,19 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
1694
1687
  errors.push(`Failed to parse copilot-instructions.md: ${errorMessage}`);
1695
1688
  }
1696
1689
  }
1697
- const instructionsDir = (0, import_node_path17.join)(baseDir, ".github", "instructions");
1690
+ const instructionsDir = (0, import_node_path14.join)(baseDir, ".github", "instructions");
1698
1691
  if (await fileExists(instructionsDir)) {
1699
1692
  try {
1700
1693
  const { readdir: readdir2 } = await import("fs/promises");
1701
1694
  const files = await readdir2(instructionsDir);
1702
1695
  for (const file of files) {
1703
1696
  if (file.endsWith(".instructions.md")) {
1704
- const filePath = (0, import_node_path17.join)(instructionsDir, file);
1697
+ const filePath = (0, import_node_path14.join)(instructionsDir, file);
1705
1698
  const rawContent = await readFileContent(filePath);
1706
1699
  const parsed = (0, import_gray_matter2.default)(rawContent);
1707
1700
  const content = parsed.content.trim();
1708
1701
  if (content) {
1709
- const filename = (0, import_node_path17.basename)(file, ".instructions.md");
1702
+ const filename = (0, import_node_path14.basename)(file, ".instructions.md");
1710
1703
  const frontmatter = {
1711
1704
  root: false,
1712
1705
  targets: ["copilot"],
@@ -1736,19 +1729,28 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
1736
1729
  }
1737
1730
 
1738
1731
  // src/parsers/cursor.ts
1739
- var import_node_path18 = require("path");
1732
+ var import_node_path15 = require("path");
1740
1733
  var import_gray_matter3 = __toESM(require("gray-matter"), 1);
1741
1734
  var import_js_yaml = require("js-yaml");
1735
+ var import_v4_mini6 = require("zod/v4-mini");
1742
1736
  var customMatterOptions = {
1743
1737
  engines: {
1744
1738
  yaml: {
1745
1739
  parse: (str) => {
1746
1740
  try {
1747
- let preprocessed = str.replace(/^(\s*globs:\s*)\*\s*$/gm, '$1"*"').replace(/^(\s*globs:\s*)([^\s"'[\n][^"'[\n]*?)(\s*)$/gm, '$1"$2"$3');
1748
- return (0, import_js_yaml.load)(preprocessed, { schema: import_js_yaml.DEFAULT_SCHEMA });
1741
+ const preprocessed = str.replace(/^(\s*globs:\s*)\*\s*$/gm, '$1"*"').replace(/^(\s*globs:\s*)([^\s"'[\n][^"'[\n]*?)(\s*)$/gm, '$1"$2"$3');
1742
+ const result = (0, import_js_yaml.load)(preprocessed, { schema: import_js_yaml.DEFAULT_SCHEMA });
1743
+ if (typeof result === "object" && result !== null) {
1744
+ return result;
1745
+ }
1746
+ throw new Error("Failed to parse YAML: result is not an object");
1749
1747
  } catch (error) {
1750
1748
  try {
1751
- return (0, import_js_yaml.load)(str, { schema: import_js_yaml.FAILSAFE_SCHEMA });
1749
+ const result = (0, import_js_yaml.load)(str, { schema: import_js_yaml.FAILSAFE_SCHEMA });
1750
+ if (typeof result === "object" && result !== null) {
1751
+ return result;
1752
+ }
1753
+ throw new Error("Failed to parse YAML: result is not an object");
1752
1754
  } catch {
1753
1755
  throw error;
1754
1756
  }
@@ -1758,7 +1760,18 @@ var customMatterOptions = {
1758
1760
  }
1759
1761
  };
1760
1762
  function convertCursorMdcFrontmatter(cursorFrontmatter, _filename) {
1761
- const frontmatter = cursorFrontmatter;
1763
+ const FrontmatterSchema = import_v4_mini6.z.record(import_v4_mini6.z.string(), import_v4_mini6.z.unknown());
1764
+ const parseResult = FrontmatterSchema.safeParse(cursorFrontmatter);
1765
+ if (!parseResult.success) {
1766
+ return {
1767
+ root: false,
1768
+ targets: ["*"],
1769
+ description: "",
1770
+ globs: [],
1771
+ cursorRuleType: "manual"
1772
+ };
1773
+ }
1774
+ const frontmatter = parseResult.data;
1762
1775
  const description = normalizeValue(frontmatter?.description);
1763
1776
  const globs = normalizeGlobsValue(frontmatter?.globs);
1764
1777
  const alwaysApply = frontmatter?.alwaysApply === true || frontmatter?.alwaysApply === "true";
@@ -1793,7 +1806,7 @@ function convertCursorMdcFrontmatter(cursorFrontmatter, _filename) {
1793
1806
  return {
1794
1807
  root: false,
1795
1808
  targets: ["*"],
1796
- description,
1809
+ description: description || "",
1797
1810
  globs: [],
1798
1811
  cursorRuleType: "intelligently"
1799
1812
  };
@@ -1841,7 +1854,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
1841
1854
  const rules = [];
1842
1855
  let ignorePatterns;
1843
1856
  let mcpServers;
1844
- const cursorFilePath = (0, import_node_path18.join)(baseDir, ".cursorrules");
1857
+ const cursorFilePath = (0, import_node_path15.join)(baseDir, ".cursorrules");
1845
1858
  if (await fileExists(cursorFilePath)) {
1846
1859
  try {
1847
1860
  const rawContent = await readFileContent(cursorFilePath);
@@ -1862,20 +1875,20 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
1862
1875
  errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
1863
1876
  }
1864
1877
  }
1865
- const cursorRulesDir = (0, import_node_path18.join)(baseDir, ".cursor", "rules");
1878
+ const cursorRulesDir = (0, import_node_path15.join)(baseDir, ".cursor", "rules");
1866
1879
  if (await fileExists(cursorRulesDir)) {
1867
1880
  try {
1868
1881
  const { readdir: readdir2 } = await import("fs/promises");
1869
1882
  const files = await readdir2(cursorRulesDir);
1870
1883
  for (const file of files) {
1871
1884
  if (file.endsWith(".mdc")) {
1872
- const filePath = (0, import_node_path18.join)(cursorRulesDir, file);
1885
+ const filePath = (0, import_node_path15.join)(cursorRulesDir, file);
1873
1886
  try {
1874
1887
  const rawContent = await readFileContent(filePath);
1875
1888
  const parsed = (0, import_gray_matter3.default)(rawContent, customMatterOptions);
1876
1889
  const content = parsed.content.trim();
1877
1890
  if (content) {
1878
- const filename = (0, import_node_path18.basename)(file, ".mdc");
1891
+ const filename = (0, import_node_path15.basename)(file, ".mdc");
1879
1892
  const frontmatter = convertCursorMdcFrontmatter(parsed.data, filename);
1880
1893
  rules.push({
1881
1894
  frontmatter,
@@ -1898,7 +1911,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
1898
1911
  if (rules.length === 0) {
1899
1912
  errors.push("No Cursor configuration files found (.cursorrules or .cursor/rules/*.mdc)");
1900
1913
  }
1901
- const cursorIgnorePath = (0, import_node_path18.join)(baseDir, ".cursorignore");
1914
+ const cursorIgnorePath = (0, import_node_path15.join)(baseDir, ".cursorignore");
1902
1915
  if (await fileExists(cursorIgnorePath)) {
1903
1916
  try {
1904
1917
  const content = await readFileContent(cursorIgnorePath);
@@ -1911,13 +1924,14 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
1911
1924
  errors.push(`Failed to parse .cursorignore: ${errorMessage}`);
1912
1925
  }
1913
1926
  }
1914
- const cursorMcpPath = (0, import_node_path18.join)(baseDir, ".cursor", "mcp.json");
1927
+ const cursorMcpPath = (0, import_node_path15.join)(baseDir, ".cursor", "mcp.json");
1915
1928
  if (await fileExists(cursorMcpPath)) {
1916
1929
  try {
1917
1930
  const content = await readFileContent(cursorMcpPath);
1918
1931
  const mcp = JSON.parse(content);
1919
- if (mcp.mcpServers && Object.keys(mcp.mcpServers).length > 0) {
1920
- mcpServers = mcp.mcpServers;
1932
+ const parseResult = RulesyncMcpConfigSchema.safeParse(mcp);
1933
+ if (parseResult.success && Object.keys(parseResult.data.mcpServers).length > 0) {
1934
+ mcpServers = parseResult.data.mcpServers;
1921
1935
  }
1922
1936
  } catch (error) {
1923
1937
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -1933,13 +1947,13 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
1933
1947
  }
1934
1948
 
1935
1949
  // src/parsers/geminicli.ts
1936
- var import_node_path19 = require("path");
1950
+ var import_node_path16 = require("path");
1937
1951
  async function parseGeminiConfiguration(baseDir = process.cwd()) {
1938
1952
  const errors = [];
1939
1953
  const rules = [];
1940
1954
  let ignorePatterns;
1941
1955
  let mcpServers;
1942
- const geminiFilePath = (0, import_node_path19.join)(baseDir, "GEMINI.md");
1956
+ const geminiFilePath = (0, import_node_path16.join)(baseDir, "GEMINI.md");
1943
1957
  if (!await fileExists(geminiFilePath)) {
1944
1958
  errors.push("GEMINI.md file not found");
1945
1959
  return { rules, errors };
@@ -1950,12 +1964,12 @@ async function parseGeminiConfiguration(baseDir = process.cwd()) {
1950
1964
  if (mainRule) {
1951
1965
  rules.push(mainRule);
1952
1966
  }
1953
- const memoryDir = (0, import_node_path19.join)(baseDir, ".gemini", "memories");
1967
+ const memoryDir = (0, import_node_path16.join)(baseDir, ".gemini", "memories");
1954
1968
  if (await fileExists(memoryDir)) {
1955
1969
  const memoryRules = await parseGeminiMemoryFiles(memoryDir);
1956
1970
  rules.push(...memoryRules);
1957
1971
  }
1958
- const settingsPath = (0, import_node_path19.join)(baseDir, ".gemini", "settings.json");
1972
+ const settingsPath = (0, import_node_path16.join)(baseDir, ".gemini", "settings.json");
1959
1973
  if (await fileExists(settingsPath)) {
1960
1974
  const settingsResult = await parseGeminiSettings(settingsPath);
1961
1975
  if (settingsResult.ignorePatterns) {
@@ -1966,7 +1980,7 @@ async function parseGeminiConfiguration(baseDir = process.cwd()) {
1966
1980
  }
1967
1981
  errors.push(...settingsResult.errors);
1968
1982
  }
1969
- const aiexcludePath = (0, import_node_path19.join)(baseDir, ".aiexclude");
1983
+ const aiexcludePath = (0, import_node_path16.join)(baseDir, ".aiexclude");
1970
1984
  if (await fileExists(aiexcludePath)) {
1971
1985
  const aiexcludePatterns = await parseAiexclude(aiexcludePath);
1972
1986
  if (aiexcludePatterns.length > 0) {
@@ -2019,10 +2033,10 @@ async function parseGeminiMemoryFiles(memoryDir) {
2019
2033
  const files = await readdir2(memoryDir);
2020
2034
  for (const file of files) {
2021
2035
  if (file.endsWith(".md")) {
2022
- const filePath = (0, import_node_path19.join)(memoryDir, file);
2036
+ const filePath = (0, import_node_path16.join)(memoryDir, file);
2023
2037
  const content = await readFileContent(filePath);
2024
2038
  if (content.trim()) {
2025
- const filename = (0, import_node_path19.basename)(file, ".md");
2039
+ const filename = (0, import_node_path16.basename)(file, ".md");
2026
2040
  const frontmatter = {
2027
2041
  root: false,
2028
2042
  targets: ["geminicli"],
@@ -2048,8 +2062,9 @@ async function parseGeminiSettings(settingsPath) {
2048
2062
  try {
2049
2063
  const content = await readFileContent(settingsPath);
2050
2064
  const settings = JSON.parse(content);
2051
- if (settings.mcpServers && Object.keys(settings.mcpServers).length > 0) {
2052
- mcpServers = settings.mcpServers;
2065
+ const parseResult = RulesyncMcpConfigSchema.safeParse(settings);
2066
+ if (parseResult.success && Object.keys(parseResult.data.mcpServers).length > 0) {
2067
+ mcpServers = parseResult.data.mcpServers;
2053
2068
  }
2054
2069
  } catch (error) {
2055
2070
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -2071,11 +2086,11 @@ async function parseAiexclude(aiexcludePath) {
2071
2086
  }
2072
2087
 
2073
2088
  // src/parsers/roo.ts
2074
- var import_node_path20 = require("path");
2089
+ var import_node_path17 = require("path");
2075
2090
  async function parseRooConfiguration(baseDir = process.cwd()) {
2076
2091
  const errors = [];
2077
2092
  const rules = [];
2078
- const rooFilePath = (0, import_node_path20.join)(baseDir, ".roo", "instructions.md");
2093
+ const rooFilePath = (0, import_node_path17.join)(baseDir, ".roo", "instructions.md");
2079
2094
  if (await fileExists(rooFilePath)) {
2080
2095
  try {
2081
2096
  const content = await readFileContent(rooFilePath);
@@ -2098,14 +2113,14 @@ async function parseRooConfiguration(baseDir = process.cwd()) {
2098
2113
  errors.push(`Failed to parse .roo/instructions.md: ${errorMessage}`);
2099
2114
  }
2100
2115
  }
2101
- const rooRulesDir = (0, import_node_path20.join)(baseDir, ".roo", "rules");
2116
+ const rooRulesDir = (0, import_node_path17.join)(baseDir, ".roo", "rules");
2102
2117
  if (await fileExists(rooRulesDir)) {
2103
2118
  try {
2104
2119
  const { readdir: readdir2 } = await import("fs/promises");
2105
2120
  const files = await readdir2(rooRulesDir);
2106
2121
  for (const file of files) {
2107
2122
  if (file.endsWith(".md")) {
2108
- const filePath = (0, import_node_path20.join)(rooRulesDir, file);
2123
+ const filePath = (0, import_node_path17.join)(rooRulesDir, file);
2109
2124
  try {
2110
2125
  const content = await readFileContent(filePath);
2111
2126
  if (content.trim()) {
@@ -2206,7 +2221,7 @@ async function importConfiguration(options) {
2206
2221
  if (rules.length === 0 && !ignorePatterns && !mcpServers) {
2207
2222
  return { success: false, rulesCreated: 0, errors };
2208
2223
  }
2209
- const rulesDirPath = (0, import_node_path21.join)(baseDir, rulesDir);
2224
+ const rulesDirPath = (0, import_node_path18.join)(baseDir, rulesDir);
2210
2225
  try {
2211
2226
  const { mkdir: mkdir3 } = await import("fs/promises");
2212
2227
  await mkdir3(rulesDirPath, { recursive: true });
@@ -2220,7 +2235,7 @@ async function importConfiguration(options) {
2220
2235
  try {
2221
2236
  const baseFilename = `${tool}__${rule.filename}`;
2222
2237
  const filename = await generateUniqueFilename(rulesDirPath, baseFilename);
2223
- const filePath = (0, import_node_path21.join)(rulesDirPath, `${filename}.md`);
2238
+ const filePath = (0, import_node_path18.join)(rulesDirPath, `${filename}.md`);
2224
2239
  const content = generateRuleFileContent(rule);
2225
2240
  await writeFileContent(filePath, content);
2226
2241
  rulesCreated++;
@@ -2235,7 +2250,7 @@ async function importConfiguration(options) {
2235
2250
  let ignoreFileCreated = false;
2236
2251
  if (ignorePatterns && ignorePatterns.length > 0) {
2237
2252
  try {
2238
- const rulesyncignorePath = (0, import_node_path21.join)(baseDir, ".rulesyncignore");
2253
+ const rulesyncignorePath = (0, import_node_path18.join)(baseDir, ".rulesyncignore");
2239
2254
  const ignoreContent = `${ignorePatterns.join("\n")}
2240
2255
  `;
2241
2256
  await writeFileContent(rulesyncignorePath, ignoreContent);
@@ -2251,7 +2266,7 @@ async function importConfiguration(options) {
2251
2266
  let mcpFileCreated = false;
2252
2267
  if (mcpServers && Object.keys(mcpServers).length > 0) {
2253
2268
  try {
2254
- const mcpPath = (0, import_node_path21.join)(baseDir, rulesDir, ".mcp.json");
2269
+ const mcpPath = (0, import_node_path18.join)(baseDir, rulesDir, ".mcp.json");
2255
2270
  const mcpContent = `${JSON.stringify({ mcpServers }, null, 2)}
2256
2271
  `;
2257
2272
  await writeFileContent(mcpPath, mcpContent);
@@ -2279,7 +2294,7 @@ function generateRuleFileContent(rule) {
2279
2294
  async function generateUniqueFilename(rulesDir, baseFilename) {
2280
2295
  let filename = baseFilename;
2281
2296
  let counter = 1;
2282
- while (await fileExists((0, import_node_path21.join)(rulesDir, `${filename}.md`))) {
2297
+ while (await fileExists((0, import_node_path18.join)(rulesDir, `${filename}.md`))) {
2283
2298
  filename = `${baseFilename}-${counter}`;
2284
2299
  counter++;
2285
2300
  }
@@ -2344,7 +2359,7 @@ async function importCommand(options = {}) {
2344
2359
  }
2345
2360
 
2346
2361
  // src/cli/commands/init.ts
2347
- var import_node_path22 = require("path");
2362
+ var import_node_path19 = require("path");
2348
2363
  async function initCommand() {
2349
2364
  const aiRulesDir = ".rulesync";
2350
2365
  console.log("Initializing rulesync...");
@@ -2474,7 +2489,7 @@ globs: ["src/api/**/*.ts", "src/services/**/*.ts", "src/models/**/*.ts"]
2474
2489
  }
2475
2490
  ];
2476
2491
  for (const file of sampleFiles) {
2477
- const filepath = (0, import_node_path22.join)(aiRulesDir, file.filename);
2492
+ const filepath = (0, import_node_path19.join)(aiRulesDir, file.filename);
2478
2493
  if (!await fileExists(filepath)) {
2479
2494
  await writeFileContent(filepath, file.content);
2480
2495
  console.log(`Created ${filepath}`);
@@ -2509,9 +2524,11 @@ async function statusCommand() {
2509
2524
  for (const rule of rules) {
2510
2525
  const targets = rule.frontmatter.targets[0] === "*" ? config.defaultTargets : rule.frontmatter.targets;
2511
2526
  for (const target of targets) {
2512
- if (target in targetCounts) {
2513
- targetCounts[target]++;
2514
- }
2527
+ if (target === "copilot") targetCounts.copilot++;
2528
+ else if (target === "cursor") targetCounts.cursor++;
2529
+ else if (target === "cline") targetCounts.cline++;
2530
+ else if (target === "claudecode") targetCounts.claudecode++;
2531
+ else if (target === "roo") targetCounts.roo++;
2515
2532
  }
2516
2533
  }
2517
2534
  console.log("\n\u{1F3AF} Target tool coverage:");
@@ -2617,7 +2634,7 @@ async function watchCommand() {
2617
2634
 
2618
2635
  // src/cli/index.ts
2619
2636
  var program = new import_commander.Command();
2620
- program.name("rulesync").description("Unified AI rules management CLI tool").version("0.42.0");
2637
+ program.name("rulesync").description("Unified AI rules management CLI tool").version("0.44.0");
2621
2638
  program.command("init").description("Initialize rulesync in current directory").action(initCommand);
2622
2639
  program.command("add <filename>").description("Add a new rule file").action(addCommand);
2623
2640
  program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);