rulesync 0.62.0 → 0.63.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.
Files changed (3) hide show
  1. package/dist/index.cjs +315 -273
  2. package/dist/index.js +297 -256
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -41,12 +41,20 @@ var ClaudeSettingsSchema = z.looseObject({
41
41
  )
42
42
  });
43
43
 
44
- // src/types/commands.ts
44
+ // src/types/shared.ts
45
45
  import { z as z2 } from "zod/mini";
46
- var CommandFrontmatterSchema = z2.object({
46
+ var OutputSchema = z2.object({
47
+ tool: ToolTargetSchema,
48
+ filepath: z2.string(),
49
+ content: z2.string()
50
+ });
51
+ var BaseFrontmatterSchema = z2.object({
47
52
  description: z2.optional(z2.string())
48
53
  });
49
54
 
55
+ // src/types/commands.ts
56
+ var CommandFrontmatterSchema = BaseFrontmatterSchema;
57
+
50
58
  // src/types/config.ts
51
59
  import { z as z3 } from "zod/mini";
52
60
  var ConfigSchema = z3.object({
@@ -159,11 +167,6 @@ var RuleFrontmatterSchema = z6.object({
159
167
  windsurfOutputFormat: z6.optional(z6.enum(["single-file", "directory"])),
160
168
  tags: z6.optional(z6.array(z6.string()))
161
169
  });
162
- var GeneratedOutputSchema = z6.object({
163
- tool: ToolTargetSchema,
164
- filepath: z6.string(),
165
- content: z6.string()
166
- });
167
170
  var GenerateOptionsSchema = z6.object({
168
171
  targetTools: z6.optional(ToolTargetsSchema),
169
172
  outputDir: z6.optional(z6.string()),
@@ -388,6 +391,50 @@ function mergeWithCliOptions(config, cliOptions) {
388
391
  return merged;
389
392
  }
390
393
 
394
+ // src/utils/logger.ts
395
+ import { consola } from "consola";
396
+ var Logger = class {
397
+ _verbose = false;
398
+ console = consola.withDefaults({
399
+ tag: "rulesync"
400
+ });
401
+ setVerbose(verbose) {
402
+ this._verbose = verbose;
403
+ }
404
+ get verbose() {
405
+ return this._verbose;
406
+ }
407
+ // Regular log (always shown, regardless of verbose)
408
+ log(message, ...args) {
409
+ this.console.log(message, ...args);
410
+ }
411
+ // Info level (shown only in verbose mode)
412
+ info(message, ...args) {
413
+ if (this._verbose) {
414
+ this.console.info(message, ...args);
415
+ }
416
+ }
417
+ // Success (always shown)
418
+ success(message, ...args) {
419
+ this.console.success(message, ...args);
420
+ }
421
+ // Warning (always shown)
422
+ warn(message, ...args) {
423
+ this.console.warn(message, ...args);
424
+ }
425
+ // Error (always shown)
426
+ error(message, ...args) {
427
+ this.console.error(message, ...args);
428
+ }
429
+ // Debug level (shown only in verbose mode)
430
+ debug(message, ...args) {
431
+ if (this._verbose) {
432
+ this.console.debug(message, ...args);
433
+ }
434
+ }
435
+ };
436
+ var logger = new Logger();
437
+
391
438
  // src/cli/commands/add.ts
392
439
  function sanitizeFilename(filename) {
393
440
  return filename.endsWith(".md") ? filename.slice(0, -3) : filename;
@@ -417,11 +464,11 @@ async function addCommand(filename, options = {}) {
417
464
  await mkdir(rulesDir, { recursive: true });
418
465
  const template = generateRuleTemplate(sanitizedFilename);
419
466
  await writeFile(filePath, template, "utf8");
420
- console.log(`\u2705 Created rule file: ${filePath}`);
421
- console.log(`\u{1F4DD} Edit the file to customize your rules.`);
467
+ logger.success(`Created rule file: ${filePath}`);
468
+ logger.log(`\u{1F4DD} Edit the file to customize your rules.`);
422
469
  } catch (error) {
423
- console.error(
424
- `\u274C Failed to create rule file: ${error instanceof Error ? error.message : String(error)}`
470
+ logger.error(
471
+ `Failed to create rule file: ${error instanceof Error ? error.message : String(error)}`
425
472
  );
426
473
  process.exit(3);
427
474
  }
@@ -513,7 +560,7 @@ async function findRuleFiles(aiRulesDir) {
513
560
  async function removeDirectory(dirPath) {
514
561
  const dangerousPaths = [".", "/", "~", "src", "node_modules"];
515
562
  if (dangerousPaths.includes(dirPath) || dirPath === "") {
516
- console.warn(`Skipping deletion of dangerous path: ${dirPath}`);
563
+ logger.warn(`Skipping deletion of dangerous path: ${dirPath}`);
517
564
  return;
518
565
  }
519
566
  try {
@@ -521,7 +568,7 @@ async function removeDirectory(dirPath) {
521
568
  await rm(dirPath, { recursive: true, force: true });
522
569
  }
523
570
  } catch (error) {
524
- console.warn(`Failed to remove directory ${dirPath}:`, error);
571
+ logger.warn(`Failed to remove directory ${dirPath}:`, error);
525
572
  }
526
573
  }
527
574
  async function removeFile(filepath) {
@@ -530,7 +577,7 @@ async function removeFile(filepath) {
530
577
  await rm(filepath);
531
578
  }
532
579
  } catch (error) {
533
- console.warn(`Failed to remove file ${filepath}:`, error);
580
+ logger.warn(`Failed to remove file ${filepath}:`, error);
534
581
  }
535
582
  }
536
583
  async function removeClaudeGeneratedFiles() {
@@ -544,50 +591,6 @@ async function removeClaudeGeneratedFiles() {
544
591
  }
545
592
  }
546
593
 
547
- // src/utils/logger.ts
548
- import { consola } from "consola";
549
- var Logger = class {
550
- _verbose = false;
551
- console = consola.withDefaults({
552
- tag: "rulesync"
553
- });
554
- setVerbose(verbose) {
555
- this._verbose = verbose;
556
- }
557
- get verbose() {
558
- return this._verbose;
559
- }
560
- // Regular log (always shown, regardless of verbose)
561
- log(message, ...args) {
562
- this.console.log(message, ...args);
563
- }
564
- // Info level (shown only in verbose mode)
565
- info(message, ...args) {
566
- if (this._verbose) {
567
- this.console.info(message, ...args);
568
- }
569
- }
570
- // Success (always shown)
571
- success(message, ...args) {
572
- this.console.success(message, ...args);
573
- }
574
- // Warning (always shown)
575
- warn(message, ...args) {
576
- this.console.warn(message, ...args);
577
- }
578
- // Error (always shown)
579
- error(message, ...args) {
580
- this.console.error(message, ...args);
581
- }
582
- // Debug level (shown only in verbose mode)
583
- debug(message, ...args) {
584
- if (this._verbose) {
585
- this.console.debug(message, ...args);
586
- }
587
- }
588
- };
589
- var logger = new Logger();
590
-
591
594
  // src/cli/commands/config.ts
592
595
  async function configCommand(options = {}) {
593
596
  if (options.init) {
@@ -799,25 +802,68 @@ export default config;
799
802
  }
800
803
 
801
804
  // src/cli/commands/generate.ts
802
- import { join as join15 } from "path";
805
+ import { join as join13 } from "path";
803
806
 
804
807
  // src/core/command-generator.ts
805
- import { join as join6 } from "path";
808
+ import { join as join4 } from "path";
806
809
 
807
- // src/generators/commands/claudecode.ts
810
+ // src/utils/command-generators.ts
808
811
  import { join as join3 } from "path";
809
- var ClaudeCodeCommandGenerator = class {
810
- generate(command, outputDir) {
811
- const filepath = this.getOutputPath(command.filename, outputDir);
812
- const frontmatter = ["---"];
813
- if (command.frontmatter.description) {
814
- frontmatter.push(`description: ${command.frontmatter.description}`);
812
+ function generateYamlFrontmatter(command, options = {}) {
813
+ const frontmatter = ["---"];
814
+ if (options.includeDescription !== false && command.frontmatter.description) {
815
+ frontmatter.push(`description: ${command.frontmatter.description}`);
816
+ }
817
+ if (options.additionalFields) {
818
+ for (const field of options.additionalFields) {
819
+ frontmatter.push(`${field.key}: ${field.value}`);
815
820
  }
816
- frontmatter.push("---");
817
- const content = `${frontmatter.join("\n")}
821
+ }
822
+ frontmatter.push("---");
823
+ return frontmatter;
824
+ }
825
+ function buildCommandContent(command, frontmatterOptions) {
826
+ const frontmatter = generateYamlFrontmatter(command, frontmatterOptions);
827
+ return `${frontmatter.join("\n")}
818
828
 
819
829
  ${command.content.trim()}
820
830
  `;
831
+ }
832
+ function getFlattenedCommandPath(filename, baseDir, subdir) {
833
+ const flattenedName = filename.replace(/\//g, "-");
834
+ return join3(baseDir, subdir, `${flattenedName}.md`);
835
+ }
836
+ function getHierarchicalCommandPath(filename, baseDir, subdir, extension = "md") {
837
+ const nameWithoutExt = filename.replace(/\.[^/.]+$/, "");
838
+ const fileWithExt = `${nameWithoutExt}.${extension}`;
839
+ return join3(baseDir, subdir, fileWithExt);
840
+ }
841
+ function escapeTomlString(str) {
842
+ return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
843
+ }
844
+ var syntaxConverters = {
845
+ /**
846
+ * Convert Claude Code syntax to Gemini CLI syntax
847
+ */
848
+ toGeminiCli(content) {
849
+ let converted = content;
850
+ converted = converted.replace(/\$ARGUMENTS/g, "{{args}}");
851
+ converted = converted.replace(/!`([^`]+)`/g, "!{$1}");
852
+ return converted.trim();
853
+ },
854
+ /**
855
+ * Convert to Roo Code syntax (currently identical to Claude Code)
856
+ */
857
+ toRooCode(content) {
858
+ return content.trim();
859
+ }
860
+ };
861
+
862
+ // src/generators/commands/claudecode.ts
863
+ var ClaudeCodeCommandGenerator = class {
864
+ generate(command, outputDir) {
865
+ const filepath = this.getOutputPath(command.filename, outputDir);
866
+ const content = buildCommandContent(command);
821
867
  return {
822
868
  tool: "claudecode",
823
869
  filepath,
@@ -825,20 +871,18 @@ ${command.content.trim()}
825
871
  };
826
872
  }
827
873
  getOutputPath(filename, baseDir) {
828
- const flattenedName = filename.replace(/\//g, "-");
829
- return join3(baseDir, ".claude", "commands", `${flattenedName}.md`);
874
+ return getFlattenedCommandPath(filename, baseDir, ".claude/commands");
830
875
  }
831
876
  };
832
877
 
833
878
  // src/generators/commands/geminicli.ts
834
- import { join as join4 } from "path";
835
879
  var GeminiCliCommandGenerator = class {
836
880
  generate(command, outputDir) {
837
881
  const filepath = this.getOutputPath(command.filename, outputDir);
838
- const convertedContent = this.convertSyntax(command.content);
882
+ const convertedContent = syntaxConverters.toGeminiCli(command.content);
839
883
  const tomlLines = [];
840
884
  if (command.frontmatter.description) {
841
- tomlLines.push(`description = "${this.escapeTomlString(command.frontmatter.description)}"`);
885
+ tomlLines.push(`description = "${escapeTomlString(command.frontmatter.description)}"`);
842
886
  tomlLines.push("");
843
887
  }
844
888
  tomlLines.push(`prompt = """${convertedContent}"""`);
@@ -850,35 +894,15 @@ var GeminiCliCommandGenerator = class {
850
894
  };
851
895
  }
852
896
  getOutputPath(filename, baseDir) {
853
- const tomlFilename = filename.replace(/\.md$/, ".toml");
854
- const filenameWithExt = tomlFilename.endsWith(".toml") ? tomlFilename : `${tomlFilename}.toml`;
855
- return join4(baseDir, ".gemini", "commands", filenameWithExt);
856
- }
857
- convertSyntax(content) {
858
- let converted = content;
859
- converted = converted.replace(/\$ARGUMENTS/g, "{{args}}");
860
- converted = converted.replace(/!`([^`]+)`/g, "!{$1}");
861
- return converted.trim();
862
- }
863
- escapeTomlString(str) {
864
- return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
897
+ return getHierarchicalCommandPath(filename, baseDir, ".gemini/commands", "toml");
865
898
  }
866
899
  };
867
900
 
868
901
  // src/generators/commands/roo.ts
869
- import { join as join5 } from "path";
870
902
  var RooCommandGenerator = class {
871
903
  generate(command, outputDir) {
872
904
  const filepath = this.getOutputPath(command.filename, outputDir);
873
- const frontmatter = ["---"];
874
- if (command.frontmatter.description) {
875
- frontmatter.push(`description: ${command.frontmatter.description}`);
876
- }
877
- frontmatter.push("---");
878
- const content = `${frontmatter.join("\n")}
879
-
880
- ${command.content.trim()}
881
- `;
905
+ const content = buildCommandContent(command);
882
906
  return {
883
907
  tool: "roo",
884
908
  filepath,
@@ -886,8 +910,7 @@ ${command.content.trim()}
886
910
  };
887
911
  }
888
912
  getOutputPath(filename, baseDir) {
889
- const flattenedName = filename.replace(/\//g, "-");
890
- return join5(baseDir, ".roo", "commands", `${flattenedName}.md`);
913
+ return getFlattenedCommandPath(filename, baseDir, ".roo/commands");
891
914
  }
892
915
  };
893
916
 
@@ -903,7 +926,26 @@ function getCommandGenerator(tool) {
903
926
 
904
927
  // src/core/command-parser.ts
905
928
  import { basename } from "path";
929
+
930
+ // src/utils/frontmatter.ts
906
931
  import matter from "gray-matter";
932
+ function parseFrontmatter(content, options) {
933
+ const parsed = matter(content, options?.matterOptions);
934
+ return {
935
+ content: parsed.content.trim(),
936
+ data: parsed.data || {}
937
+ };
938
+ }
939
+ function extractArrayField(data, key, defaultValue = []) {
940
+ const value = data[key];
941
+ return Array.isArray(value) ? value : defaultValue;
942
+ }
943
+ function extractStringField(data, key, defaultValue) {
944
+ const value = data[key];
945
+ return typeof value === "string" ? value : defaultValue;
946
+ }
947
+
948
+ // src/core/command-parser.ts
907
949
  async function parseCommandsFromDirectory(commandsDir) {
908
950
  const commandFiles = await findFiles(commandsDir, ".md");
909
951
  const commands = [];
@@ -918,14 +960,14 @@ async function parseCommandsFromDirectory(commandsDir) {
918
960
  }
919
961
  }
920
962
  if (errors.length > 0) {
921
- console.warn(`\u26A0\uFE0F Command parsing errors:
963
+ logger.warn(`Command parsing errors:
922
964
  ${errors.join("\n")}`);
923
965
  }
924
966
  return commands;
925
967
  }
926
968
  async function parseCommandFile(filepath) {
927
969
  const content = await readFileContent(filepath);
928
- const parsed = matter(content);
970
+ const parsed = parseFrontmatter(content);
929
971
  try {
930
972
  const validatedData = CommandFrontmatterSchema.parse(parsed.data);
931
973
  const filename = basename(filepath, ".md");
@@ -946,7 +988,7 @@ async function parseCommandFile(filepath) {
946
988
 
947
989
  // src/core/command-generator.ts
948
990
  async function generateCommands(projectRoot, baseDir, targets) {
949
- const commandsDir = join6(projectRoot, ".rulesync", "commands");
991
+ const commandsDir = join4(projectRoot, ".rulesync", "commands");
950
992
  if (!await fileExists(commandsDir)) {
951
993
  return [];
952
994
  }
@@ -970,8 +1012,8 @@ async function generateCommands(projectRoot, baseDir, targets) {
970
1012
  outputs.push(output);
971
1013
  } catch (error) {
972
1014
  const errorMessage = error instanceof Error ? error.message : String(error);
973
- console.error(
974
- `\u274C Failed to generate ${target} command for ${command.filename}: ${errorMessage}`
1015
+ logger.error(
1016
+ `Failed to generate ${target} command for ${command.filename}: ${errorMessage}`
975
1017
  );
976
1018
  }
977
1019
  }
@@ -980,7 +1022,7 @@ async function generateCommands(projectRoot, baseDir, targets) {
980
1022
  }
981
1023
 
982
1024
  // src/generators/ignore/shared-factory.ts
983
- import { join as join7 } from "path";
1025
+ import { join as join5 } from "path";
984
1026
 
985
1027
  // src/generators/ignore/shared-helpers.ts
986
1028
  function extractIgnorePatternsFromRules(rules) {
@@ -1103,7 +1145,7 @@ function generateIgnoreFile(rules, config, ignoreConfig, baseDir) {
1103
1145
  const outputs = [];
1104
1146
  const content = generateIgnoreContent(rules, ignoreConfig);
1105
1147
  const outputPath = baseDir || process.cwd();
1106
- const filepath = join7(outputPath, ignoreConfig.filename);
1148
+ const filepath = join5(outputPath, ignoreConfig.filename);
1107
1149
  outputs.push({
1108
1150
  tool: ignoreConfig.tool,
1109
1151
  filepath,
@@ -1691,20 +1733,20 @@ function generateWindsurfIgnore(rules, config, baseDir) {
1691
1733
  }
1692
1734
 
1693
1735
  // src/generators/rules/augmentcode.ts
1694
- import { join as join10 } from "path";
1736
+ import { join as join8 } from "path";
1695
1737
 
1696
1738
  // src/generators/rules/shared-helpers.ts
1697
- import { join as join9 } from "path";
1739
+ import { join as join7 } from "path";
1698
1740
 
1699
1741
  // src/utils/ignore.ts
1700
- import { join as join8 } from "path";
1742
+ import { join as join6 } from "path";
1701
1743
  import micromatch from "micromatch";
1702
1744
  var cachedIgnorePatterns = null;
1703
1745
  async function loadIgnorePatterns(baseDir = process.cwd()) {
1704
1746
  if (cachedIgnorePatterns) {
1705
1747
  return cachedIgnorePatterns;
1706
1748
  }
1707
- const ignorePath = join8(baseDir, ".rulesyncignore");
1749
+ const ignorePath = join6(baseDir, ".rulesyncignore");
1708
1750
  if (!await fileExists(ignorePath)) {
1709
1751
  cachedIgnorePatterns = { patterns: [] };
1710
1752
  return cachedIgnorePatterns;
@@ -1715,7 +1757,7 @@ async function loadIgnorePatterns(baseDir = process.cwd()) {
1715
1757
  cachedIgnorePatterns = { patterns };
1716
1758
  return cachedIgnorePatterns;
1717
1759
  } catch (error) {
1718
- console.warn(`Failed to read .rulesyncignore: ${error}`);
1760
+ logger.warn(`Failed to read .rulesyncignore: ${error}`);
1719
1761
  cachedIgnorePatterns = { patterns: [] };
1720
1762
  return cachedIgnorePatterns;
1721
1763
  }
@@ -1758,7 +1800,7 @@ function addOutput(outputs, tool, config, baseDir, relativePath, content) {
1758
1800
  const outputDir = resolveOutputDir(config, tool, baseDir);
1759
1801
  outputs.push({
1760
1802
  tool,
1761
- filepath: join9(outputDir, relativePath),
1803
+ filepath: join7(outputDir, relativePath),
1762
1804
  content
1763
1805
  });
1764
1806
  }
@@ -1767,7 +1809,7 @@ async function generateRulesConfig(rules, config, generatorConfig, baseDir) {
1767
1809
  for (const rule of rules) {
1768
1810
  const content = generatorConfig.generateContent(rule);
1769
1811
  const outputDir = resolveOutputDir(config, generatorConfig.tool, baseDir);
1770
- const filepath = generatorConfig.pathResolver ? generatorConfig.pathResolver(rule, outputDir) : join9(outputDir, `${rule.filename}${generatorConfig.fileExtension}`);
1812
+ const filepath = generatorConfig.pathResolver ? generatorConfig.pathResolver(rule, outputDir) : join7(outputDir, `${rule.filename}${generatorConfig.fileExtension}`);
1771
1813
  outputs.push({
1772
1814
  tool: generatorConfig.tool,
1773
1815
  filepath,
@@ -1795,7 +1837,7 @@ async function generateComplexRules(rules, config, generatorConfig, baseDir) {
1795
1837
  for (const rule of detailRules) {
1796
1838
  const content = generatorConfig.generateDetailContent(rule);
1797
1839
  const filepath = resolvePath(
1798
- join9(generatorConfig.detailSubDir, `${rule.filename}.md`),
1840
+ join7(generatorConfig.detailSubDir, `${rule.filename}.md`),
1799
1841
  baseDir
1800
1842
  );
1801
1843
  outputs.push({
@@ -1858,7 +1900,7 @@ async function generateAugmentcodeConfig(rules, config, baseDir) {
1858
1900
  "augmentcode",
1859
1901
  config,
1860
1902
  baseDir,
1861
- join10(".augment", "rules", `${rule.filename}.md`),
1903
+ join8(".augment", "rules", `${rule.filename}.md`),
1862
1904
  generateRuleFile(rule)
1863
1905
  );
1864
1906
  });
@@ -1911,7 +1953,7 @@ function generateLegacyGuidelinesFile(allRules) {
1911
1953
  }
1912
1954
 
1913
1955
  // src/generators/rules/claudecode.ts
1914
- import { join as join11 } from "path";
1956
+ import { join as join9 } from "path";
1915
1957
  async function generateClaudecodeConfig(rules, config, baseDir) {
1916
1958
  const generatorConfig = {
1917
1959
  tool: "claudecode",
@@ -1923,7 +1965,7 @@ async function generateClaudecodeConfig(rules, config, baseDir) {
1923
1965
  generateDetailContent: generateMemoryFile,
1924
1966
  detailSubDir: ".claude/memories",
1925
1967
  updateAdditionalConfig: async (ignorePatterns, baseDir2) => {
1926
- const settingsPath = resolvePath(join11(".claude", "settings.json"), baseDir2);
1968
+ const settingsPath = resolvePath(join9(".claude", "settings.json"), baseDir2);
1927
1969
  await updateClaudeSettings(settingsPath, ignorePatterns);
1928
1970
  return [];
1929
1971
  }
@@ -1960,7 +2002,7 @@ async function updateClaudeSettings(settingsPath, ignorePatterns) {
1960
2002
  const content = await readFileContent(settingsPath);
1961
2003
  rawSettings = JSON.parse(content);
1962
2004
  } catch {
1963
- console.warn(`Failed to parse existing ${settingsPath}, creating new settings`);
2005
+ logger.warn(`Failed to parse existing ${settingsPath}, creating new settings`);
1964
2006
  rawSettings = {};
1965
2007
  }
1966
2008
  }
@@ -1983,11 +2025,11 @@ async function updateClaudeSettings(settingsPath, ignorePatterns) {
1983
2025
  settings.permissions.deny = Array.from(new Set(filteredDeny));
1984
2026
  const jsonContent = JSON.stringify(settings, null, 2);
1985
2027
  await writeFileContent(settingsPath, jsonContent);
1986
- console.log(`\u2705 Updated Claude Code settings: ${settingsPath}`);
2028
+ logger.success(`Updated Claude Code settings: ${settingsPath}`);
1987
2029
  }
1988
2030
 
1989
2031
  // src/generators/rules/generator-registry.ts
1990
- import { join as join12 } from "path";
2032
+ import { join as join10 } from "path";
1991
2033
  function determineCursorRuleType(frontmatter) {
1992
2034
  if (frontmatter.cursorRuleType) {
1993
2035
  return frontmatter.cursorRuleType;
@@ -2067,7 +2109,7 @@ var GENERATOR_REGISTRY = {
2067
2109
  },
2068
2110
  pathResolver: (rule, outputDir) => {
2069
2111
  const baseFilename = rule.filename.replace(/\.md$/, "");
2070
- return join12(outputDir, `${baseFilename}.instructions.md`);
2112
+ return join10(outputDir, `${baseFilename}.instructions.md`);
2071
2113
  }
2072
2114
  },
2073
2115
  cursor: {
@@ -2107,7 +2149,7 @@ var GENERATOR_REGISTRY = {
2107
2149
  return lines.join("\n");
2108
2150
  },
2109
2151
  pathResolver: (rule, outputDir) => {
2110
- return join12(outputDir, `${rule.filename}.mdc`);
2152
+ return join10(outputDir, `${rule.filename}.mdc`);
2111
2153
  }
2112
2154
  },
2113
2155
  codexcli: {
@@ -2143,10 +2185,10 @@ var GENERATOR_REGISTRY = {
2143
2185
  pathResolver: (rule, outputDir) => {
2144
2186
  const outputFormat = rule.frontmatter.windsurfOutputFormat || "directory";
2145
2187
  if (outputFormat === "single-file") {
2146
- return join12(outputDir, ".windsurf-rules");
2188
+ return join10(outputDir, ".windsurf-rules");
2147
2189
  } else {
2148
- const rulesDir = join12(outputDir, ".windsurf", "rules");
2149
- return join12(rulesDir, `${rule.filename}.md`);
2190
+ const rulesDir = join10(outputDir, ".windsurf", "rules");
2191
+ return join10(rulesDir, `${rule.filename}.md`);
2150
2192
  }
2151
2193
  }
2152
2194
  },
@@ -2264,7 +2306,7 @@ async function generateCodexConfig(rules, config, baseDir) {
2264
2306
  const concatenatedContent = generateConcatenatedCodexContent(sortedRules);
2265
2307
  if (concatenatedContent.trim()) {
2266
2308
  const outputDir = resolveOutputDir(config, "codexcli", baseDir);
2267
- const filepath = `${outputDir}/codex.md`;
2309
+ const filepath = `${outputDir}/AGENTS.md`;
2268
2310
  outputs.push({
2269
2311
  tool: "codexcli",
2270
2312
  filepath,
@@ -2370,14 +2412,12 @@ async function generateConfigurations(rules, config, targetTools, baseDir) {
2370
2412
  const toolsToGenerate = targetTools || config.defaultTargets;
2371
2413
  const rootFiles = rules.filter((rule) => rule.frontmatter.root === true);
2372
2414
  if (rootFiles.length === 0) {
2373
- console.warn(
2374
- "\u26A0\uFE0F Warning: No files with 'root: true' found. This may result in incomplete configurations."
2375
- );
2415
+ logger.warn("No files with 'root: true' found. This may result in incomplete configurations.");
2376
2416
  }
2377
2417
  for (const tool of toolsToGenerate) {
2378
2418
  const relevantRules = filterRulesForTool(rules, tool, config);
2379
2419
  if (relevantRules.length === 0) {
2380
- console.warn(`No rules found for tool: ${tool}`);
2420
+ logger.warn(`No rules found for tool: ${tool}`);
2381
2421
  continue;
2382
2422
  }
2383
2423
  const toolOutputs = await generateForTool(tool, relevantRules, config, baseDir);
@@ -2442,14 +2482,13 @@ async function generateForTool(tool, rules, config, baseDir) {
2442
2482
  return [...windsurfRulesOutputs, ...windsurfIgnoreOutputs];
2443
2483
  }
2444
2484
  default:
2445
- console.warn(`Unknown tool: ${tool}`);
2485
+ logger.warn(`Unknown tool: ${tool}`);
2446
2486
  return null;
2447
2487
  }
2448
2488
  }
2449
2489
 
2450
2490
  // src/core/parser.ts
2451
2491
  import { basename as basename2 } from "path";
2452
- import matter2 from "gray-matter";
2453
2492
  async function parseRulesFromDirectory(aiRulesDir) {
2454
2493
  const ignorePatterns = await loadIgnorePatterns();
2455
2494
  const allRuleFiles = await findRuleFiles(aiRulesDir);
@@ -2457,7 +2496,7 @@ async function parseRulesFromDirectory(aiRulesDir) {
2457
2496
  const rules = [];
2458
2497
  const errors = [];
2459
2498
  if (ignorePatterns.patterns.length > 0) {
2460
- console.log(`Loaded ${ignorePatterns.patterns.length} ignore patterns from .rulesyncignore`);
2499
+ logger.info(`Loaded ${ignorePatterns.patterns.length} ignore patterns from .rulesyncignore`);
2461
2500
  }
2462
2501
  for (const filepath of ruleFiles) {
2463
2502
  try {
@@ -2483,7 +2522,7 @@ ${errors.join("\n")}`);
2483
2522
  }
2484
2523
  async function parseRuleFile(filepath) {
2485
2524
  const content = await readFileContent(filepath);
2486
- const parsed = matter2(content);
2525
+ const parsed = parseFrontmatter(content);
2487
2526
  try {
2488
2527
  const validatedData = RuleFrontmatterSchema.parse(parsed.data);
2489
2528
  const frontmatter = {
@@ -2708,7 +2747,7 @@ async function generateCommand(options = {}) {
2708
2747
  logger.info("Deleting existing output directories...");
2709
2748
  const targetTools = config.defaultTargets;
2710
2749
  const deleteTasks = [];
2711
- const commandsDir = join15(config.aiRulesDir, "commands");
2750
+ const commandsDir = join13(config.aiRulesDir, "commands");
2712
2751
  const hasCommands = await fileExists(commandsDir);
2713
2752
  let hasCommandFiles = false;
2714
2753
  if (hasCommands) {
@@ -2723,12 +2762,12 @@ async function generateCommand(options = {}) {
2723
2762
  for (const tool of targetTools) {
2724
2763
  switch (tool) {
2725
2764
  case "augmentcode":
2726
- deleteTasks.push(removeDirectory(join15(".augment", "rules")));
2727
- deleteTasks.push(removeDirectory(join15(".augment", "ignore")));
2765
+ deleteTasks.push(removeDirectory(join13(".augment", "rules")));
2766
+ deleteTasks.push(removeDirectory(join13(".augment", "ignore")));
2728
2767
  break;
2729
2768
  case "augmentcode-legacy":
2730
2769
  deleteTasks.push(removeClaudeGeneratedFiles());
2731
- deleteTasks.push(removeDirectory(join15(".augment", "ignore")));
2770
+ deleteTasks.push(removeDirectory(join13(".augment", "ignore")));
2732
2771
  break;
2733
2772
  case "copilot":
2734
2773
  deleteTasks.push(removeDirectory(config.outputPaths.copilot));
@@ -2742,19 +2781,19 @@ async function generateCommand(options = {}) {
2742
2781
  case "claudecode":
2743
2782
  deleteTasks.push(removeClaudeGeneratedFiles());
2744
2783
  if (hasCommandFiles) {
2745
- deleteTasks.push(removeDirectory(join15(".claude", "commands")));
2784
+ deleteTasks.push(removeDirectory(join13(".claude", "commands")));
2746
2785
  }
2747
2786
  break;
2748
2787
  case "roo":
2749
2788
  deleteTasks.push(removeDirectory(config.outputPaths.roo));
2750
2789
  if (hasCommandFiles) {
2751
- deleteTasks.push(removeDirectory(join15(".roo", "commands")));
2790
+ deleteTasks.push(removeDirectory(join13(".roo", "commands")));
2752
2791
  }
2753
2792
  break;
2754
2793
  case "geminicli":
2755
2794
  deleteTasks.push(removeDirectory(config.outputPaths.geminicli));
2756
2795
  if (hasCommandFiles) {
2757
- deleteTasks.push(removeDirectory(join15(".gemini", "commands")));
2796
+ deleteTasks.push(removeDirectory(join13(".gemini", "commands")));
2758
2797
  }
2759
2798
  break;
2760
2799
  case "kiro":
@@ -2853,9 +2892,9 @@ Generating configurations for base directory: ${baseDir}`);
2853
2892
 
2854
2893
  // src/cli/commands/gitignore.ts
2855
2894
  import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
2856
- import { join as join16 } from "path";
2895
+ import { join as join14 } from "path";
2857
2896
  var gitignoreCommand = async () => {
2858
- const gitignorePath = join16(process.cwd(), ".gitignore");
2897
+ const gitignorePath = join14(process.cwd(), ".gitignore");
2859
2898
  const rulesFilesToIgnore = [
2860
2899
  "# Generated by rulesync - AI tool configuration files",
2861
2900
  "**/.github/copilot-instructions.md",
@@ -2867,7 +2906,7 @@ var gitignoreCommand = async () => {
2867
2906
  "**/CLAUDE.md",
2868
2907
  "**/.claude/memories/",
2869
2908
  "**/.claude/commands/",
2870
- "**/codex.md",
2909
+ "**/AGENTS.md",
2871
2910
  "**/.codexignore",
2872
2911
  "**/.roo/rules/",
2873
2912
  "**/.rooignore",
@@ -2903,7 +2942,7 @@ var gitignoreCommand = async () => {
2903
2942
  }
2904
2943
  }
2905
2944
  if (linesToAdd.length === 0) {
2906
- console.log("\u2705 .gitignore is already up to date");
2945
+ logger.success(".gitignore is already up to date");
2907
2946
  return;
2908
2947
  }
2909
2948
  const newContent = gitignoreContent ? `${gitignoreContent.trimEnd()}
@@ -2912,21 +2951,20 @@ ${linesToAdd.join("\n")}
2912
2951
  ` : `${linesToAdd.join("\n")}
2913
2952
  `;
2914
2953
  writeFileSync2(gitignorePath, newContent);
2915
- console.log(`\u2705 Added ${linesToAdd.length} rules to .gitignore:`);
2954
+ logger.success(`Added ${linesToAdd.length} rules to .gitignore:`);
2916
2955
  for (const line of linesToAdd) {
2917
2956
  if (!line.startsWith("#")) {
2918
- console.log(` ${line}`);
2957
+ logger.log(` ${line}`);
2919
2958
  }
2920
2959
  }
2921
2960
  };
2922
2961
 
2923
2962
  // src/core/importer.ts
2924
- import { join as join23 } from "path";
2925
- import matter7 from "gray-matter";
2963
+ import { join as join21 } from "path";
2964
+ import matter2 from "gray-matter";
2926
2965
 
2927
2966
  // src/parsers/augmentcode.ts
2928
- import { basename as basename3, join as join17 } from "path";
2929
- import matter3 from "gray-matter";
2967
+ import { basename as basename3, join as join15 } from "path";
2930
2968
 
2931
2969
  // src/utils/parser-helpers.ts
2932
2970
  function createParseResult() {
@@ -2974,7 +3012,7 @@ async function parseAugmentcodeLegacyConfiguration(baseDir = process.cwd()) {
2974
3012
  async function parseUnifiedAugmentcode(baseDir, config) {
2975
3013
  const result = createParseResult();
2976
3014
  if (config.rulesDir) {
2977
- const rulesDir = join17(baseDir, config.rulesDir);
3015
+ const rulesDir = join15(baseDir, config.rulesDir);
2978
3016
  if (await fileExists(rulesDir)) {
2979
3017
  const rulesResult = await parseAugmentRules(rulesDir, config);
2980
3018
  addRules(result, rulesResult.rules);
@@ -2987,7 +3025,7 @@ async function parseUnifiedAugmentcode(baseDir, config) {
2987
3025
  }
2988
3026
  }
2989
3027
  if (config.legacyFilePath) {
2990
- const legacyPath = join17(baseDir, config.legacyFilePath);
3028
+ const legacyPath = join15(baseDir, config.legacyFilePath);
2991
3029
  if (await fileExists(legacyPath)) {
2992
3030
  const legacyResult = await parseAugmentGuidelines(legacyPath, config);
2993
3031
  if (legacyResult.rule) {
@@ -3011,14 +3049,13 @@ async function parseAugmentRules(rulesDir, config) {
3011
3049
  const files = await readdir2(rulesDir);
3012
3050
  for (const file of files) {
3013
3051
  if (file.endsWith(".md") || file.endsWith(".mdc")) {
3014
- const filePath = join17(rulesDir, file);
3052
+ const filePath = join15(rulesDir, file);
3015
3053
  try {
3016
3054
  const rawContent = await readFileContent(filePath);
3017
- const parsed = matter3(rawContent);
3018
- const frontmatterData = parsed.data;
3019
- const ruleType = frontmatterData.type || "manual";
3020
- const description = frontmatterData.description || "";
3021
- const tags = Array.isArray(frontmatterData.tags) ? frontmatterData.tags : void 0;
3055
+ const parsed = parseFrontmatter(rawContent);
3056
+ const ruleType = extractStringField(parsed.data, "type", "manual");
3057
+ const description = extractStringField(parsed.data, "description", "");
3058
+ const tags = extractArrayField(parsed.data, "tags");
3022
3059
  const isRoot = ruleType === "always";
3023
3060
  const filename = basename3(file, file.endsWith(".mdc") ? ".mdc" : ".md");
3024
3061
  const frontmatter = {
@@ -3027,7 +3064,7 @@ async function parseAugmentRules(rulesDir, config) {
3027
3064
  description,
3028
3065
  globs: ["**/*"],
3029
3066
  // AugmentCode doesn't use specific globs in the same way
3030
- ...tags && { tags }
3067
+ ...tags.length > 0 && { tags }
3031
3068
  };
3032
3069
  rules.push({
3033
3070
  frontmatter,
@@ -3078,8 +3115,7 @@ async function parseAugmentGuidelines(guidelinesPath, config) {
3078
3115
  }
3079
3116
 
3080
3117
  // src/parsers/shared-helpers.ts
3081
- import { basename as basename4, join as join18 } from "path";
3082
- import matter4 from "gray-matter";
3118
+ import { basename as basename4, join as join16 } from "path";
3083
3119
  async function parseConfigurationFiles(baseDir = process.cwd(), config) {
3084
3120
  const errors = [];
3085
3121
  const rules = [];
@@ -3092,16 +3128,18 @@ async function parseConfigurationFiles(baseDir = process.cwd(), config) {
3092
3128
  let content;
3093
3129
  let frontmatter;
3094
3130
  if (mainFile.useFrontmatter) {
3095
- const parsed = matter4(rawContent);
3096
- content = parsed.content.trim();
3097
- const parsedFrontmatter = parsed.data;
3131
+ const parsed = parseFrontmatter(rawContent);
3132
+ content = parsed.content;
3098
3133
  frontmatter = {
3099
3134
  root: mainFile.isRoot ?? false,
3100
3135
  targets: [config.tool],
3101
- description: parsedFrontmatter.description || mainFile.description,
3102
- globs: Array.isArray(parsedFrontmatter.globs) ? parsedFrontmatter.globs : ["**/*"],
3103
- ...parsedFrontmatter.tags && { tags: parsedFrontmatter.tags }
3136
+ description: extractStringField(parsed.data, "description", mainFile.description),
3137
+ globs: extractArrayField(parsed.data, "globs", ["**/*"])
3104
3138
  };
3139
+ const tags = extractArrayField(parsed.data, "tags");
3140
+ if (tags.length > 0) {
3141
+ frontmatter.tags = tags;
3142
+ }
3105
3143
  } else {
3106
3144
  content = rawContent.trim();
3107
3145
  frontmatter = {
@@ -3134,23 +3172,29 @@ async function parseConfigurationFiles(baseDir = process.cwd(), config) {
3134
3172
  const files = await readdir2(dirPath);
3135
3173
  for (const file of files) {
3136
3174
  if (file.endsWith(dirConfig.filePattern)) {
3137
- const filePath = join18(dirPath, file);
3175
+ const filePath = join16(dirPath, file);
3138
3176
  const fileResult = await safeAsyncOperation(async () => {
3139
3177
  const rawContent = await readFileContent(filePath);
3140
3178
  let content;
3141
3179
  let frontmatter;
3142
3180
  const filename = file.replace(new RegExp(`\\${dirConfig.filePattern}$`), "");
3143
3181
  if (dirConfig.filePattern === ".instructions.md") {
3144
- const parsed = matter4(rawContent);
3145
- content = parsed.content.trim();
3146
- const parsedFrontmatter = parsed.data;
3182
+ const parsed = parseFrontmatter(rawContent);
3183
+ content = parsed.content;
3147
3184
  frontmatter = {
3148
3185
  root: false,
3149
3186
  targets: [config.tool],
3150
- description: parsedFrontmatter.description || `${dirConfig.description}: ${filename}`,
3151
- globs: Array.isArray(parsedFrontmatter.globs) ? parsedFrontmatter.globs : ["**/*"],
3152
- ...parsedFrontmatter.tags && { tags: parsedFrontmatter.tags }
3187
+ description: extractStringField(
3188
+ parsed.data,
3189
+ "description",
3190
+ `${dirConfig.description}: ${filename}`
3191
+ ),
3192
+ globs: extractArrayField(parsed.data, "globs", ["**/*"])
3153
3193
  };
3194
+ const tags = extractArrayField(parsed.data, "tags");
3195
+ if (tags.length > 0) {
3196
+ frontmatter.tags = tags;
3197
+ }
3154
3198
  } else {
3155
3199
  content = rawContent.trim();
3156
3200
  frontmatter = {
@@ -3279,7 +3323,7 @@ async function parseMemoryFiles(memoryDir, config) {
3279
3323
  const files = await readdir2(memoryDir);
3280
3324
  for (const file of files) {
3281
3325
  if (file.endsWith(".md")) {
3282
- const filePath = join18(memoryDir, file);
3326
+ const filePath = join16(memoryDir, file);
3283
3327
  const content = await readFileContent(filePath);
3284
3328
  if (content.trim()) {
3285
3329
  const filename = basename4(file, ".md");
@@ -3309,20 +3353,19 @@ async function parseCommandsFiles(commandsDir, config) {
3309
3353
  const files = await readdir2(commandsDir);
3310
3354
  for (const file of files) {
3311
3355
  if (file.endsWith(".md")) {
3312
- const filePath = join18(commandsDir, file);
3356
+ const filePath = join16(commandsDir, file);
3313
3357
  const content = await readFileContent(filePath);
3314
3358
  if (content.trim()) {
3315
3359
  const filename = basename4(file, ".md");
3316
3360
  let frontmatter;
3317
3361
  let ruleContent;
3318
3362
  try {
3319
- const parsed = matter4(content);
3320
- ruleContent = parsed.content.trim();
3321
- const parsedFrontmatter = parsed.data;
3363
+ const parsed = parseFrontmatter(content);
3364
+ ruleContent = parsed.content;
3322
3365
  frontmatter = {
3323
3366
  root: false,
3324
3367
  targets: [config.tool],
3325
- description: parsedFrontmatter.description || `Command: ${filename}`,
3368
+ description: extractStringField(parsed.data, "description", `Command: ${filename}`),
3326
3369
  globs: ["**/*"]
3327
3370
  };
3328
3371
  } catch {
@@ -3423,7 +3466,7 @@ async function parseClineConfiguration(baseDir = process.cwd()) {
3423
3466
  }
3424
3467
 
3425
3468
  // src/parsers/codexcli.ts
3426
- import { join as join19 } from "path";
3469
+ import { join as join17 } from "path";
3427
3470
 
3428
3471
  // src/parsers/copilot.ts
3429
3472
  async function parseCopilotConfiguration(baseDir = process.cwd()) {
@@ -3446,8 +3489,7 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
3446
3489
  }
3447
3490
 
3448
3491
  // src/parsers/cursor.ts
3449
- import { basename as basename5, join as join20 } from "path";
3450
- import matter5 from "gray-matter";
3492
+ import { basename as basename5, join as join18 } from "path";
3451
3493
  import { DEFAULT_SCHEMA, FAILSAFE_SCHEMA, load } from "js-yaml";
3452
3494
  import { z as z7 } from "zod/mini";
3453
3495
  var customMatterOptions = {
@@ -3571,12 +3613,12 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3571
3613
  const rules = [];
3572
3614
  let ignorePatterns;
3573
3615
  let mcpServers;
3574
- const cursorFilePath = join20(baseDir, ".cursorrules");
3616
+ const cursorFilePath = join18(baseDir, ".cursorrules");
3575
3617
  if (await fileExists(cursorFilePath)) {
3576
3618
  try {
3577
3619
  const rawContent = await readFileContent(cursorFilePath);
3578
- const parsed = matter5(rawContent, customMatterOptions);
3579
- const content = parsed.content.trim();
3620
+ const parsed = parseFrontmatter(rawContent, { matterOptions: customMatterOptions });
3621
+ const content = parsed.content;
3580
3622
  if (content) {
3581
3623
  const frontmatter = convertCursorMdcFrontmatter(parsed.data, "cursorrules");
3582
3624
  frontmatter.targets = ["cursor"];
@@ -3592,18 +3634,18 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3592
3634
  errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
3593
3635
  }
3594
3636
  }
3595
- const cursorRulesDir = join20(baseDir, ".cursor", "rules");
3637
+ const cursorRulesDir = join18(baseDir, ".cursor", "rules");
3596
3638
  if (await fileExists(cursorRulesDir)) {
3597
3639
  try {
3598
3640
  const { readdir: readdir2 } = await import("fs/promises");
3599
3641
  const files = await readdir2(cursorRulesDir);
3600
3642
  for (const file of files) {
3601
3643
  if (file.endsWith(".mdc")) {
3602
- const filePath = join20(cursorRulesDir, file);
3644
+ const filePath = join18(cursorRulesDir, file);
3603
3645
  try {
3604
3646
  const rawContent = await readFileContent(filePath);
3605
- const parsed = matter5(rawContent, customMatterOptions);
3606
- const content = parsed.content.trim();
3647
+ const parsed = parseFrontmatter(rawContent, { matterOptions: customMatterOptions });
3648
+ const content = parsed.content;
3607
3649
  if (content) {
3608
3650
  const filename = basename5(file, ".mdc");
3609
3651
  const frontmatter = convertCursorMdcFrontmatter(parsed.data, filename);
@@ -3628,7 +3670,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3628
3670
  if (rules.length === 0) {
3629
3671
  errors.push("No Cursor configuration files found (.cursorrules or .cursor/rules/*.mdc)");
3630
3672
  }
3631
- const cursorIgnorePath = join20(baseDir, ".cursorignore");
3673
+ const cursorIgnorePath = join18(baseDir, ".cursorignore");
3632
3674
  if (await fileExists(cursorIgnorePath)) {
3633
3675
  try {
3634
3676
  const content = await readFileContent(cursorIgnorePath);
@@ -3641,7 +3683,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3641
3683
  errors.push(`Failed to parse .cursorignore: ${errorMessage}`);
3642
3684
  }
3643
3685
  }
3644
- const cursorMcpPath = join20(baseDir, ".cursor", "mcp.json");
3686
+ const cursorMcpPath = join18(baseDir, ".cursor", "mcp.json");
3645
3687
  if (await fileExists(cursorMcpPath)) {
3646
3688
  try {
3647
3689
  const content = await readFileContent(cursorMcpPath);
@@ -3691,11 +3733,11 @@ async function parseGeminiConfiguration(baseDir = process.cwd()) {
3691
3733
  }
3692
3734
 
3693
3735
  // src/parsers/junie.ts
3694
- import { join as join21 } from "path";
3736
+ import { join as join19 } from "path";
3695
3737
  async function parseJunieConfiguration(baseDir = process.cwd()) {
3696
3738
  const errors = [];
3697
3739
  const rules = [];
3698
- const guidelinesPath = join21(baseDir, ".junie", "guidelines.md");
3740
+ const guidelinesPath = join19(baseDir, ".junie", "guidelines.md");
3699
3741
  if (!await fileExists(guidelinesPath)) {
3700
3742
  errors.push(".junie/guidelines.md file not found");
3701
3743
  return { rules, errors };
@@ -3748,8 +3790,7 @@ async function parseRooConfiguration(baseDir = process.cwd()) {
3748
3790
 
3749
3791
  // src/parsers/windsurf.ts
3750
3792
  import { readFile as readFile2 } from "fs/promises";
3751
- import { join as join22 } from "path";
3752
- import matter6 from "gray-matter";
3793
+ import { join as join20 } from "path";
3753
3794
 
3754
3795
  // src/core/importer.ts
3755
3796
  async function importConfiguration(options) {
@@ -3765,7 +3806,7 @@ async function importConfiguration(options) {
3765
3806
  let ignorePatterns;
3766
3807
  let mcpServers;
3767
3808
  if (verbose) {
3768
- console.log(`Importing ${tool} configuration from ${baseDir}...`);
3809
+ logger.log(`Importing ${tool} configuration from ${baseDir}...`);
3769
3810
  }
3770
3811
  try {
3771
3812
  switch (tool) {
@@ -3841,7 +3882,7 @@ async function importConfiguration(options) {
3841
3882
  if (rules.length === 0 && !ignorePatterns && !mcpServers) {
3842
3883
  return { success: false, rulesCreated: 0, errors };
3843
3884
  }
3844
- const rulesDirPath = join23(baseDir, rulesDir);
3885
+ const rulesDirPath = join21(baseDir, rulesDir);
3845
3886
  try {
3846
3887
  const { mkdir: mkdir3 } = await import("fs/promises");
3847
3888
  await mkdir3(rulesDirPath, { recursive: true });
@@ -3856,22 +3897,22 @@ async function importConfiguration(options) {
3856
3897
  const baseFilename = rule.filename;
3857
3898
  let targetDir = rulesDirPath;
3858
3899
  if (rule.type === "command") {
3859
- targetDir = join23(rulesDirPath, "commands");
3900
+ targetDir = join21(rulesDirPath, "commands");
3860
3901
  const { mkdir: mkdir3 } = await import("fs/promises");
3861
3902
  await mkdir3(targetDir, { recursive: true });
3862
3903
  } else {
3863
3904
  if (!useLegacyLocation) {
3864
- targetDir = join23(rulesDirPath, "rules");
3905
+ targetDir = join21(rulesDirPath, "rules");
3865
3906
  const { mkdir: mkdir3 } = await import("fs/promises");
3866
3907
  await mkdir3(targetDir, { recursive: true });
3867
3908
  }
3868
3909
  }
3869
- const filePath = join23(targetDir, `${baseFilename}.md`);
3910
+ const filePath = join21(targetDir, `${baseFilename}.md`);
3870
3911
  const content = generateRuleFileContent(rule);
3871
3912
  await writeFileContent(filePath, content);
3872
3913
  rulesCreated++;
3873
3914
  if (verbose) {
3874
- console.log(`\u2705 Created rule file: ${filePath}`);
3915
+ logger.success(`Created rule file: ${filePath}`);
3875
3916
  }
3876
3917
  } catch (error) {
3877
3918
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -3881,13 +3922,13 @@ async function importConfiguration(options) {
3881
3922
  let ignoreFileCreated = false;
3882
3923
  if (ignorePatterns && ignorePatterns.length > 0) {
3883
3924
  try {
3884
- const rulesyncignorePath = join23(baseDir, ".rulesyncignore");
3925
+ const rulesyncignorePath = join21(baseDir, ".rulesyncignore");
3885
3926
  const ignoreContent = `${ignorePatterns.join("\n")}
3886
3927
  `;
3887
3928
  await writeFileContent(rulesyncignorePath, ignoreContent);
3888
3929
  ignoreFileCreated = true;
3889
3930
  if (verbose) {
3890
- console.log(`\u2705 Created .rulesyncignore with ${ignorePatterns.length} patterns`);
3931
+ logger.success(`Created .rulesyncignore with ${ignorePatterns.length} patterns`);
3891
3932
  }
3892
3933
  } catch (error) {
3893
3934
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -3897,13 +3938,13 @@ async function importConfiguration(options) {
3897
3938
  let mcpFileCreated = false;
3898
3939
  if (mcpServers && Object.keys(mcpServers).length > 0) {
3899
3940
  try {
3900
- const mcpPath = join23(baseDir, rulesDir, ".mcp.json");
3941
+ const mcpPath = join21(baseDir, rulesDir, ".mcp.json");
3901
3942
  const mcpContent = `${JSON.stringify({ mcpServers }, null, 2)}
3902
3943
  `;
3903
3944
  await writeFileContent(mcpPath, mcpContent);
3904
3945
  mcpFileCreated = true;
3905
3946
  if (verbose) {
3906
- console.log(`\u2705 Created .mcp.json with ${Object.keys(mcpServers).length} servers`);
3947
+ logger.success(`Created .mcp.json with ${Object.keys(mcpServers).length} servers`);
3907
3948
  }
3908
3949
  } catch (error) {
3909
3950
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -3924,10 +3965,10 @@ function generateRuleFileContent(rule) {
3924
3965
  description: rule.frontmatter.description,
3925
3966
  targets: rule.frontmatter.targets
3926
3967
  };
3927
- const frontmatter2 = matter7.stringify("", simplifiedFrontmatter);
3968
+ const frontmatter2 = matter2.stringify("", simplifiedFrontmatter);
3928
3969
  return frontmatter2 + rule.content;
3929
3970
  }
3930
- const frontmatter = matter7.stringify("", rule.frontmatter);
3971
+ const frontmatter = matter2.stringify("", rule.frontmatter);
3931
3972
  return frontmatter + rule.content;
3932
3973
  }
3933
3974
 
@@ -3993,23 +4034,23 @@ async function importCommand(options = {}) {
3993
4034
  }
3994
4035
 
3995
4036
  // src/cli/commands/init.ts
3996
- import { join as join24 } from "path";
4037
+ import { join as join22 } from "path";
3997
4038
  async function initCommand(options = {}) {
3998
4039
  const configResult = await loadConfig();
3999
4040
  const config = configResult.config;
4000
4041
  const aiRulesDir = config.aiRulesDir;
4001
- console.log("Initializing rulesync...");
4042
+ logger.log("Initializing rulesync...");
4002
4043
  await ensureDir(aiRulesDir);
4003
4044
  const useLegacy = options.legacy ?? config.legacy ?? false;
4004
- const rulesDir = useLegacy ? aiRulesDir : join24(aiRulesDir, "rules");
4045
+ const rulesDir = useLegacy ? aiRulesDir : join22(aiRulesDir, "rules");
4005
4046
  if (!useLegacy) {
4006
4047
  await ensureDir(rulesDir);
4007
4048
  }
4008
4049
  await createSampleFiles(rulesDir);
4009
- console.log("\u2705 rulesync initialized successfully!");
4010
- console.log("\nNext steps:");
4011
- console.log(`1. Edit rule files in ${rulesDir}/`);
4012
- console.log("2. Run 'rulesync generate' to create configuration files");
4050
+ logger.success("rulesync initialized successfully!");
4051
+ logger.log("\nNext steps:");
4052
+ logger.log(`1. Edit rule files in ${rulesDir}/`);
4053
+ logger.log("2. Run 'rulesync generate' to create configuration files");
4013
4054
  }
4014
4055
  async function createSampleFiles(rulesDir) {
4015
4056
  const sampleFile = {
@@ -4047,36 +4088,36 @@ globs: ["**/*"]
4047
4088
  - Follow single responsibility principle
4048
4089
  `
4049
4090
  };
4050
- const filepath = join24(rulesDir, sampleFile.filename);
4091
+ const filepath = join22(rulesDir, sampleFile.filename);
4051
4092
  if (!await fileExists(filepath)) {
4052
4093
  await writeFileContent(filepath, sampleFile.content);
4053
- console.log(`Created ${filepath}`);
4094
+ logger.success(`Created ${filepath}`);
4054
4095
  } else {
4055
- console.log(`Skipped ${filepath} (already exists)`);
4096
+ logger.log(`Skipped ${filepath} (already exists)`);
4056
4097
  }
4057
4098
  }
4058
4099
 
4059
4100
  // src/cli/commands/status.ts
4060
4101
  async function statusCommand() {
4061
4102
  const config = getDefaultConfig();
4062
- console.log("rulesync Status");
4063
- console.log("===============");
4103
+ logger.log("rulesync Status");
4104
+ logger.log("===============");
4064
4105
  const rulesyncExists = await fileExists(config.aiRulesDir);
4065
- console.log(`
4106
+ logger.log(`
4066
4107
  \u{1F4C1} .rulesync directory: ${rulesyncExists ? "\u2705 Found" : "\u274C Not found"}`);
4067
4108
  if (!rulesyncExists) {
4068
- console.log("\n\u{1F4A1} Run 'rulesync init' to get started");
4109
+ logger.log("\n\u{1F4A1} Run 'rulesync init' to get started");
4069
4110
  return;
4070
4111
  }
4071
4112
  try {
4072
4113
  const rules = await parseRulesFromDirectory(config.aiRulesDir);
4073
- console.log(`
4114
+ logger.log(`
4074
4115
  \u{1F4CB} Rules: ${rules.length} total`);
4075
4116
  if (rules.length > 0) {
4076
4117
  const rootRules = rules.filter((r) => r.frontmatter.root).length;
4077
4118
  const nonRootRules = rules.length - rootRules;
4078
- console.log(` - Root rules: ${rootRules}`);
4079
- console.log(` - Non-root rules: ${nonRootRules}`);
4119
+ logger.log(` - Root rules: ${rootRules}`);
4120
+ logger.log(` - Non-root rules: ${nonRootRules}`);
4080
4121
  const targetCounts = { copilot: 0, cursor: 0, cline: 0, claudecode: 0, roo: 0 };
4081
4122
  for (const rule of rules) {
4082
4123
  const targets = rule.frontmatter.targets[0] === "*" ? config.defaultTargets : rule.frontmatter.targets;
@@ -4088,63 +4129,63 @@ async function statusCommand() {
4088
4129
  else if (target === "roo") targetCounts.roo++;
4089
4130
  }
4090
4131
  }
4091
- console.log("\n\u{1F3AF} Target tool coverage:");
4092
- console.log(` - Copilot: ${targetCounts.copilot} rules`);
4093
- console.log(` - Cursor: ${targetCounts.cursor} rules`);
4094
- console.log(` - Cline: ${targetCounts.cline} rules`);
4095
- console.log(` - Claude Code: ${targetCounts.claudecode} rules`);
4096
- console.log(` - Roo: ${targetCounts.roo} rules`);
4132
+ logger.log("\n\u{1F3AF} Target tool coverage:");
4133
+ logger.log(` - Copilot: ${targetCounts.copilot} rules`);
4134
+ logger.log(` - Cursor: ${targetCounts.cursor} rules`);
4135
+ logger.log(` - Cline: ${targetCounts.cline} rules`);
4136
+ logger.log(` - Claude Code: ${targetCounts.claudecode} rules`);
4137
+ logger.log(` - Roo: ${targetCounts.roo} rules`);
4097
4138
  }
4098
- console.log("\n\u{1F4E4} Generated files:");
4139
+ logger.log("\n\u{1F4E4} Generated files:");
4099
4140
  for (const [tool, outputPath] of Object.entries(config.outputPaths)) {
4100
4141
  const outputExists = await fileExists(outputPath);
4101
- console.log(` - ${tool}: ${outputExists ? "\u2705 Generated" : "\u274C Not found"}`);
4142
+ logger.log(` - ${tool}: ${outputExists ? "\u2705 Generated" : "\u274C Not found"}`);
4102
4143
  }
4103
4144
  if (rules.length > 0) {
4104
- console.log("\n\u{1F4A1} Run 'rulesync generate' to update configuration files");
4145
+ logger.log("\n\u{1F4A1} Run 'rulesync generate' to update configuration files");
4105
4146
  }
4106
4147
  } catch (error) {
4107
- console.error("\n\u274C Failed to get status:", error);
4148
+ logger.error("\nFailed to get status:", error);
4108
4149
  }
4109
4150
  }
4110
4151
 
4111
4152
  // src/cli/commands/validate.ts
4112
4153
  async function validateCommand() {
4113
4154
  const config = getDefaultConfig();
4114
- console.log("Validating rulesync configuration...");
4155
+ logger.log("Validating rulesync configuration...");
4115
4156
  if (!await fileExists(config.aiRulesDir)) {
4116
- console.error("\u274C .rulesync directory not found. Run 'rulesync init' first.");
4157
+ logger.error(".rulesync directory not found. Run 'rulesync init' first.");
4117
4158
  process.exit(1);
4118
4159
  }
4119
4160
  try {
4120
4161
  const rules = await parseRulesFromDirectory(config.aiRulesDir);
4121
4162
  if (rules.length === 0) {
4122
- console.warn("\u26A0\uFE0F No rules found in .rulesync directory");
4163
+ logger.warn("No rules found in .rulesync directory");
4123
4164
  return;
4124
4165
  }
4125
- console.log(`Found ${rules.length} rule(s), validating...`);
4166
+ logger.log(`Found ${rules.length} rule(s), validating...`);
4126
4167
  const validation = await validateRules(rules);
4127
4168
  if (validation.warnings.length > 0) {
4128
- console.log("\n\u26A0\uFE0F Warnings:");
4169
+ logger.log("\n\u26A0\uFE0F Warnings:");
4129
4170
  for (const warning of validation.warnings) {
4130
- console.log(` - ${warning}`);
4171
+ logger.log(` - ${warning}`);
4131
4172
  }
4132
4173
  }
4133
4174
  if (validation.errors.length > 0) {
4134
- console.log("\n\u274C Errors:");
4175
+ logger.log("\nErrors:");
4135
4176
  for (const error of validation.errors) {
4136
- console.log(` - ${error}`);
4177
+ logger.log(` - ${error}`);
4137
4178
  }
4138
4179
  }
4139
4180
  if (validation.isValid) {
4140
- console.log("\n\u2705 All rules are valid!");
4181
+ logger.success("\nAll rules are valid!");
4141
4182
  } else {
4142
- console.log(`
4143
- \u274C Validation failed with ${validation.errors.length} error(s)`);
4183
+ logger.log(`
4184
+ Validation failed with ${validation.errors.length} error(s)`);
4144
4185
  process.exit(1);
4145
4186
  }
4146
4187
  } catch (error) {
4147
- console.error("\u274C Failed to validate rules:", error);
4188
+ logger.error("Failed to validate rules:", error);
4148
4189
  process.exit(1);
4149
4190
  }
4150
4191
  }
@@ -4153,8 +4194,8 @@ async function validateCommand() {
4153
4194
  import { watch } from "chokidar";
4154
4195
  async function watchCommand() {
4155
4196
  const config = getDefaultConfig();
4156
- console.log("\u{1F440} Watching for changes in .rulesync directory...");
4157
- console.log("Press Ctrl+C to stop watching");
4197
+ logger.log("\u{1F440} Watching for changes in .rulesync directory...");
4198
+ logger.log("Press Ctrl+C to stop watching");
4158
4199
  await generateCommand({ verbose: false });
4159
4200
  const watcher = watch(`${config.aiRulesDir}/**/*.md`, {
4160
4201
  ignoreInitial: true,
@@ -4164,26 +4205,26 @@ async function watchCommand() {
4164
4205
  const handleChange = async (path5) => {
4165
4206
  if (isGenerating) return;
4166
4207
  isGenerating = true;
4167
- console.log(`
4208
+ logger.log(`
4168
4209
  \u{1F4DD} Detected change in ${path5}`);
4169
4210
  try {
4170
4211
  await generateCommand({ verbose: false });
4171
- console.log("\u2705 Regenerated configuration files");
4212
+ logger.success("Regenerated configuration files");
4172
4213
  } catch (error) {
4173
- console.error("\u274C Failed to regenerate:", error);
4214
+ logger.error("Failed to regenerate:", error);
4174
4215
  } finally {
4175
4216
  isGenerating = false;
4176
4217
  }
4177
4218
  };
4178
4219
  watcher.on("change", handleChange).on("add", handleChange).on("unlink", (path5) => {
4179
- console.log(`
4220
+ logger.log(`
4180
4221
  \u{1F5D1}\uFE0F Removed ${path5}`);
4181
4222
  handleChange(path5);
4182
4223
  }).on("error", (error) => {
4183
- console.error("\u274C Watcher error:", error);
4224
+ logger.error("Watcher error:", error);
4184
4225
  });
4185
4226
  process.on("SIGINT", () => {
4186
- console.log("\n\n\u{1F44B} Stopping watcher...");
4227
+ logger.log("\n\n\u{1F44B} Stopping watcher...");
4187
4228
  watcher.close();
4188
4229
  process.exit(0);
4189
4230
  });
@@ -4191,7 +4232,7 @@ async function watchCommand() {
4191
4232
 
4192
4233
  // src/cli/index.ts
4193
4234
  var program = new Command();
4194
- program.name("rulesync").description("Unified AI rules management CLI tool").version("0.62.0");
4235
+ program.name("rulesync").description("Unified AI rules management CLI tool").version("0.63.0");
4195
4236
  program.command("init").description("Initialize rulesync in current directory").option("--legacy", "Use legacy file location (.rulesync/*.md instead of .rulesync/rules/*.md)").action(initCommand);
4196
4237
  program.command("add <filename>").description("Add a new rule file").option("--legacy", "Use legacy file location (.rulesync/*.md instead of .rulesync/rules/*.md)").action(addCommand);
4197
4238
  program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);