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.cjs CHANGED
@@ -35,11 +35,11 @@ function isToolTarget(target) {
35
35
  if (!target) return false;
36
36
  return ALL_TOOL_TARGETS.some((validTarget) => validTarget === target);
37
37
  }
38
- var import_mini3, ALL_TOOL_TARGETS, ToolTargetSchema, ToolTargetsSchema, WildcardTargetSchema, RulesyncTargetsSchema;
38
+ var import_mini2, ALL_TOOL_TARGETS, ToolTargetSchema, ToolTargetsSchema, WildcardTargetSchema, RulesyncTargetsSchema;
39
39
  var init_tool_targets = __esm({
40
40
  "src/types/tool-targets.ts"() {
41
41
  "use strict";
42
- import_mini3 = require("zod/mini");
42
+ import_mini2 = require("zod/mini");
43
43
  ALL_TOOL_TARGETS = [
44
44
  "augmentcode",
45
45
  "augmentcode-legacy",
@@ -54,10 +54,10 @@ var init_tool_targets = __esm({
54
54
  "junie",
55
55
  "windsurf"
56
56
  ];
57
- ToolTargetSchema = import_mini3.z.enum(ALL_TOOL_TARGETS);
58
- ToolTargetsSchema = import_mini3.z.array(ToolTargetSchema);
59
- WildcardTargetSchema = import_mini3.z.tuple([import_mini3.z.literal("*")]);
60
- RulesyncTargetsSchema = import_mini3.z.union([ToolTargetsSchema, WildcardTargetSchema]);
57
+ ToolTargetSchema = import_mini2.z.enum(ALL_TOOL_TARGETS);
58
+ ToolTargetsSchema = import_mini2.z.array(ToolTargetSchema);
59
+ WildcardTargetSchema = import_mini2.z.tuple([import_mini2.z.literal("*")]);
60
+ RulesyncTargetsSchema = import_mini2.z.union([ToolTargetsSchema, WildcardTargetSchema]);
61
61
  }
62
62
  });
63
63
 
@@ -1071,11 +1071,20 @@ var ClaudeSettingsSchema = import_mini.z.looseObject({
1071
1071
  )
1072
1072
  });
1073
1073
 
1074
- // src/types/commands.ts
1075
- var import_mini2 = require("zod/mini");
1076
- var CommandFrontmatterSchema = import_mini2.z.object({
1077
- description: import_mini2.z.optional(import_mini2.z.string())
1074
+ // src/types/shared.ts
1075
+ var import_mini3 = require("zod/mini");
1076
+ init_tool_targets();
1077
+ var OutputSchema = import_mini3.z.object({
1078
+ tool: ToolTargetSchema,
1079
+ filepath: import_mini3.z.string(),
1080
+ content: import_mini3.z.string()
1078
1081
  });
1082
+ var BaseFrontmatterSchema = import_mini3.z.object({
1083
+ description: import_mini3.z.optional(import_mini3.z.string())
1084
+ });
1085
+
1086
+ // src/types/commands.ts
1087
+ var CommandFrontmatterSchema = BaseFrontmatterSchema;
1079
1088
 
1080
1089
  // src/types/config.ts
1081
1090
  var import_mini4 = require("zod/mini");
@@ -1193,11 +1202,6 @@ var RuleFrontmatterSchema = import_mini7.z.object({
1193
1202
  windsurfOutputFormat: import_mini7.z.optional(import_mini7.z.enum(["single-file", "directory"])),
1194
1203
  tags: import_mini7.z.optional(import_mini7.z.array(import_mini7.z.string()))
1195
1204
  });
1196
- var GeneratedOutputSchema = import_mini7.z.object({
1197
- tool: ToolTargetSchema,
1198
- filepath: import_mini7.z.string(),
1199
- content: import_mini7.z.string()
1200
- });
1201
1205
  var GenerateOptionsSchema = import_mini7.z.object({
1202
1206
  targetTools: import_mini7.z.optional(ToolTargetsSchema),
1203
1207
  outputDir: import_mini7.z.optional(import_mini7.z.string()),
@@ -1426,6 +1430,50 @@ function mergeWithCliOptions(config, cliOptions) {
1426
1430
  return merged;
1427
1431
  }
1428
1432
 
1433
+ // src/utils/logger.ts
1434
+ var import_consola = require("consola");
1435
+ var Logger = class {
1436
+ _verbose = false;
1437
+ console = import_consola.consola.withDefaults({
1438
+ tag: "rulesync"
1439
+ });
1440
+ setVerbose(verbose) {
1441
+ this._verbose = verbose;
1442
+ }
1443
+ get verbose() {
1444
+ return this._verbose;
1445
+ }
1446
+ // Regular log (always shown, regardless of verbose)
1447
+ log(message, ...args) {
1448
+ this.console.log(message, ...args);
1449
+ }
1450
+ // Info level (shown only in verbose mode)
1451
+ info(message, ...args) {
1452
+ if (this._verbose) {
1453
+ this.console.info(message, ...args);
1454
+ }
1455
+ }
1456
+ // Success (always shown)
1457
+ success(message, ...args) {
1458
+ this.console.success(message, ...args);
1459
+ }
1460
+ // Warning (always shown)
1461
+ warn(message, ...args) {
1462
+ this.console.warn(message, ...args);
1463
+ }
1464
+ // Error (always shown)
1465
+ error(message, ...args) {
1466
+ this.console.error(message, ...args);
1467
+ }
1468
+ // Debug level (shown only in verbose mode)
1469
+ debug(message, ...args) {
1470
+ if (this._verbose) {
1471
+ this.console.debug(message, ...args);
1472
+ }
1473
+ }
1474
+ };
1475
+ var logger = new Logger();
1476
+
1429
1477
  // src/cli/commands/add.ts
1430
1478
  function sanitizeFilename(filename) {
1431
1479
  return filename.endsWith(".md") ? filename.slice(0, -3) : filename;
@@ -1455,11 +1503,11 @@ async function addCommand(filename, options = {}) {
1455
1503
  await (0, import_promises.mkdir)(rulesDir, { recursive: true });
1456
1504
  const template = generateRuleTemplate(sanitizedFilename);
1457
1505
  await (0, import_promises.writeFile)(filePath, template, "utf8");
1458
- console.log(`\u2705 Created rule file: ${filePath}`);
1459
- console.log(`\u{1F4DD} Edit the file to customize your rules.`);
1506
+ logger.success(`Created rule file: ${filePath}`);
1507
+ logger.log(`\u{1F4DD} Edit the file to customize your rules.`);
1460
1508
  } catch (error) {
1461
- console.error(
1462
- `\u274C Failed to create rule file: ${error instanceof Error ? error.message : String(error)}`
1509
+ logger.error(
1510
+ `Failed to create rule file: ${error instanceof Error ? error.message : String(error)}`
1463
1511
  );
1464
1512
  process.exit(3);
1465
1513
  }
@@ -1551,7 +1599,7 @@ async function findRuleFiles(aiRulesDir) {
1551
1599
  async function removeDirectory(dirPath) {
1552
1600
  const dangerousPaths = [".", "/", "~", "src", "node_modules"];
1553
1601
  if (dangerousPaths.includes(dirPath) || dirPath === "") {
1554
- console.warn(`Skipping deletion of dangerous path: ${dirPath}`);
1602
+ logger.warn(`Skipping deletion of dangerous path: ${dirPath}`);
1555
1603
  return;
1556
1604
  }
1557
1605
  try {
@@ -1559,7 +1607,7 @@ async function removeDirectory(dirPath) {
1559
1607
  await (0, import_promises2.rm)(dirPath, { recursive: true, force: true });
1560
1608
  }
1561
1609
  } catch (error) {
1562
- console.warn(`Failed to remove directory ${dirPath}:`, error);
1610
+ logger.warn(`Failed to remove directory ${dirPath}:`, error);
1563
1611
  }
1564
1612
  }
1565
1613
  async function removeFile(filepath) {
@@ -1568,7 +1616,7 @@ async function removeFile(filepath) {
1568
1616
  await (0, import_promises2.rm)(filepath);
1569
1617
  }
1570
1618
  } catch (error) {
1571
- console.warn(`Failed to remove file ${filepath}:`, error);
1619
+ logger.warn(`Failed to remove file ${filepath}:`, error);
1572
1620
  }
1573
1621
  }
1574
1622
  async function removeClaudeGeneratedFiles() {
@@ -1582,50 +1630,6 @@ async function removeClaudeGeneratedFiles() {
1582
1630
  }
1583
1631
  }
1584
1632
 
1585
- // src/utils/logger.ts
1586
- var import_consola = require("consola");
1587
- var Logger = class {
1588
- _verbose = false;
1589
- console = import_consola.consola.withDefaults({
1590
- tag: "rulesync"
1591
- });
1592
- setVerbose(verbose) {
1593
- this._verbose = verbose;
1594
- }
1595
- get verbose() {
1596
- return this._verbose;
1597
- }
1598
- // Regular log (always shown, regardless of verbose)
1599
- log(message, ...args) {
1600
- this.console.log(message, ...args);
1601
- }
1602
- // Info level (shown only in verbose mode)
1603
- info(message, ...args) {
1604
- if (this._verbose) {
1605
- this.console.info(message, ...args);
1606
- }
1607
- }
1608
- // Success (always shown)
1609
- success(message, ...args) {
1610
- this.console.success(message, ...args);
1611
- }
1612
- // Warning (always shown)
1613
- warn(message, ...args) {
1614
- this.console.warn(message, ...args);
1615
- }
1616
- // Error (always shown)
1617
- error(message, ...args) {
1618
- this.console.error(message, ...args);
1619
- }
1620
- // Debug level (shown only in verbose mode)
1621
- debug(message, ...args) {
1622
- if (this._verbose) {
1623
- this.console.debug(message, ...args);
1624
- }
1625
- }
1626
- };
1627
- var logger = new Logger();
1628
-
1629
1633
  // src/cli/commands/config.ts
1630
1634
  async function configCommand(options = {}) {
1631
1635
  if (options.init) {
@@ -1837,25 +1841,68 @@ export default config;
1837
1841
  }
1838
1842
 
1839
1843
  // src/cli/commands/generate.ts
1840
- var import_node_path15 = require("path");
1844
+ var import_node_path13 = require("path");
1841
1845
 
1842
1846
  // src/core/command-generator.ts
1843
- var import_node_path7 = require("path");
1847
+ var import_node_path5 = require("path");
1844
1848
 
1845
- // src/generators/commands/claudecode.ts
1849
+ // src/utils/command-generators.ts
1846
1850
  var import_node_path3 = require("path");
1847
- var ClaudeCodeCommandGenerator = class {
1848
- generate(command, outputDir) {
1849
- const filepath = this.getOutputPath(command.filename, outputDir);
1850
- const frontmatter = ["---"];
1851
- if (command.frontmatter.description) {
1852
- frontmatter.push(`description: ${command.frontmatter.description}`);
1851
+ function generateYamlFrontmatter(command, options = {}) {
1852
+ const frontmatter = ["---"];
1853
+ if (options.includeDescription !== false && command.frontmatter.description) {
1854
+ frontmatter.push(`description: ${command.frontmatter.description}`);
1855
+ }
1856
+ if (options.additionalFields) {
1857
+ for (const field of options.additionalFields) {
1858
+ frontmatter.push(`${field.key}: ${field.value}`);
1853
1859
  }
1854
- frontmatter.push("---");
1855
- const content = `${frontmatter.join("\n")}
1860
+ }
1861
+ frontmatter.push("---");
1862
+ return frontmatter;
1863
+ }
1864
+ function buildCommandContent(command, frontmatterOptions) {
1865
+ const frontmatter = generateYamlFrontmatter(command, frontmatterOptions);
1866
+ return `${frontmatter.join("\n")}
1856
1867
 
1857
1868
  ${command.content.trim()}
1858
1869
  `;
1870
+ }
1871
+ function getFlattenedCommandPath(filename, baseDir, subdir) {
1872
+ const flattenedName = filename.replace(/\//g, "-");
1873
+ return (0, import_node_path3.join)(baseDir, subdir, `${flattenedName}.md`);
1874
+ }
1875
+ function getHierarchicalCommandPath(filename, baseDir, subdir, extension = "md") {
1876
+ const nameWithoutExt = filename.replace(/\.[^/.]+$/, "");
1877
+ const fileWithExt = `${nameWithoutExt}.${extension}`;
1878
+ return (0, import_node_path3.join)(baseDir, subdir, fileWithExt);
1879
+ }
1880
+ function escapeTomlString(str) {
1881
+ return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
1882
+ }
1883
+ var syntaxConverters = {
1884
+ /**
1885
+ * Convert Claude Code syntax to Gemini CLI syntax
1886
+ */
1887
+ toGeminiCli(content) {
1888
+ let converted = content;
1889
+ converted = converted.replace(/\$ARGUMENTS/g, "{{args}}");
1890
+ converted = converted.replace(/!`([^`]+)`/g, "!{$1}");
1891
+ return converted.trim();
1892
+ },
1893
+ /**
1894
+ * Convert to Roo Code syntax (currently identical to Claude Code)
1895
+ */
1896
+ toRooCode(content) {
1897
+ return content.trim();
1898
+ }
1899
+ };
1900
+
1901
+ // src/generators/commands/claudecode.ts
1902
+ var ClaudeCodeCommandGenerator = class {
1903
+ generate(command, outputDir) {
1904
+ const filepath = this.getOutputPath(command.filename, outputDir);
1905
+ const content = buildCommandContent(command);
1859
1906
  return {
1860
1907
  tool: "claudecode",
1861
1908
  filepath,
@@ -1863,20 +1910,18 @@ ${command.content.trim()}
1863
1910
  };
1864
1911
  }
1865
1912
  getOutputPath(filename, baseDir) {
1866
- const flattenedName = filename.replace(/\//g, "-");
1867
- return (0, import_node_path3.join)(baseDir, ".claude", "commands", `${flattenedName}.md`);
1913
+ return getFlattenedCommandPath(filename, baseDir, ".claude/commands");
1868
1914
  }
1869
1915
  };
1870
1916
 
1871
1917
  // src/generators/commands/geminicli.ts
1872
- var import_node_path4 = require("path");
1873
1918
  var GeminiCliCommandGenerator = class {
1874
1919
  generate(command, outputDir) {
1875
1920
  const filepath = this.getOutputPath(command.filename, outputDir);
1876
- const convertedContent = this.convertSyntax(command.content);
1921
+ const convertedContent = syntaxConverters.toGeminiCli(command.content);
1877
1922
  const tomlLines = [];
1878
1923
  if (command.frontmatter.description) {
1879
- tomlLines.push(`description = "${this.escapeTomlString(command.frontmatter.description)}"`);
1924
+ tomlLines.push(`description = "${escapeTomlString(command.frontmatter.description)}"`);
1880
1925
  tomlLines.push("");
1881
1926
  }
1882
1927
  tomlLines.push(`prompt = """${convertedContent}"""`);
@@ -1888,35 +1933,15 @@ var GeminiCliCommandGenerator = class {
1888
1933
  };
1889
1934
  }
1890
1935
  getOutputPath(filename, baseDir) {
1891
- const tomlFilename = filename.replace(/\.md$/, ".toml");
1892
- const filenameWithExt = tomlFilename.endsWith(".toml") ? tomlFilename : `${tomlFilename}.toml`;
1893
- return (0, import_node_path4.join)(baseDir, ".gemini", "commands", filenameWithExt);
1894
- }
1895
- convertSyntax(content) {
1896
- let converted = content;
1897
- converted = converted.replace(/\$ARGUMENTS/g, "{{args}}");
1898
- converted = converted.replace(/!`([^`]+)`/g, "!{$1}");
1899
- return converted.trim();
1900
- }
1901
- escapeTomlString(str) {
1902
- return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
1936
+ return getHierarchicalCommandPath(filename, baseDir, ".gemini/commands", "toml");
1903
1937
  }
1904
1938
  };
1905
1939
 
1906
1940
  // src/generators/commands/roo.ts
1907
- var import_node_path5 = require("path");
1908
1941
  var RooCommandGenerator = class {
1909
1942
  generate(command, outputDir) {
1910
1943
  const filepath = this.getOutputPath(command.filename, outputDir);
1911
- const frontmatter = ["---"];
1912
- if (command.frontmatter.description) {
1913
- frontmatter.push(`description: ${command.frontmatter.description}`);
1914
- }
1915
- frontmatter.push("---");
1916
- const content = `${frontmatter.join("\n")}
1917
-
1918
- ${command.content.trim()}
1919
- `;
1944
+ const content = buildCommandContent(command);
1920
1945
  return {
1921
1946
  tool: "roo",
1922
1947
  filepath,
@@ -1924,8 +1949,7 @@ ${command.content.trim()}
1924
1949
  };
1925
1950
  }
1926
1951
  getOutputPath(filename, baseDir) {
1927
- const flattenedName = filename.replace(/\//g, "-");
1928
- return (0, import_node_path5.join)(baseDir, ".roo", "commands", `${flattenedName}.md`);
1952
+ return getFlattenedCommandPath(filename, baseDir, ".roo/commands");
1929
1953
  }
1930
1954
  };
1931
1955
 
@@ -1940,8 +1964,27 @@ function getCommandGenerator(tool) {
1940
1964
  }
1941
1965
 
1942
1966
  // src/core/command-parser.ts
1943
- var import_node_path6 = require("path");
1967
+ var import_node_path4 = require("path");
1968
+
1969
+ // src/utils/frontmatter.ts
1944
1970
  var import_gray_matter = __toESM(require("gray-matter"), 1);
1971
+ function parseFrontmatter(content, options) {
1972
+ const parsed = (0, import_gray_matter.default)(content, options?.matterOptions);
1973
+ return {
1974
+ content: parsed.content.trim(),
1975
+ data: parsed.data || {}
1976
+ };
1977
+ }
1978
+ function extractArrayField(data, key, defaultValue = []) {
1979
+ const value = data[key];
1980
+ return Array.isArray(value) ? value : defaultValue;
1981
+ }
1982
+ function extractStringField(data, key, defaultValue) {
1983
+ const value = data[key];
1984
+ return typeof value === "string" ? value : defaultValue;
1985
+ }
1986
+
1987
+ // src/core/command-parser.ts
1945
1988
  async function parseCommandsFromDirectory(commandsDir) {
1946
1989
  const commandFiles = await findFiles(commandsDir, ".md");
1947
1990
  const commands = [];
@@ -1956,17 +1999,17 @@ async function parseCommandsFromDirectory(commandsDir) {
1956
1999
  }
1957
2000
  }
1958
2001
  if (errors.length > 0) {
1959
- console.warn(`\u26A0\uFE0F Command parsing errors:
2002
+ logger.warn(`Command parsing errors:
1960
2003
  ${errors.join("\n")}`);
1961
2004
  }
1962
2005
  return commands;
1963
2006
  }
1964
2007
  async function parseCommandFile(filepath) {
1965
2008
  const content = await readFileContent(filepath);
1966
- const parsed = (0, import_gray_matter.default)(content);
2009
+ const parsed = parseFrontmatter(content);
1967
2010
  try {
1968
2011
  const validatedData = CommandFrontmatterSchema.parse(parsed.data);
1969
- const filename = (0, import_node_path6.basename)(filepath, ".md");
2012
+ const filename = (0, import_node_path4.basename)(filepath, ".md");
1970
2013
  return {
1971
2014
  frontmatter: {
1972
2015
  description: validatedData.description
@@ -1984,7 +2027,7 @@ async function parseCommandFile(filepath) {
1984
2027
 
1985
2028
  // src/core/command-generator.ts
1986
2029
  async function generateCommands(projectRoot, baseDir, targets) {
1987
- const commandsDir = (0, import_node_path7.join)(projectRoot, ".rulesync", "commands");
2030
+ const commandsDir = (0, import_node_path5.join)(projectRoot, ".rulesync", "commands");
1988
2031
  if (!await fileExists(commandsDir)) {
1989
2032
  return [];
1990
2033
  }
@@ -2008,8 +2051,8 @@ async function generateCommands(projectRoot, baseDir, targets) {
2008
2051
  outputs.push(output);
2009
2052
  } catch (error) {
2010
2053
  const errorMessage = error instanceof Error ? error.message : String(error);
2011
- console.error(
2012
- `\u274C Failed to generate ${target} command for ${command.filename}: ${errorMessage}`
2054
+ logger.error(
2055
+ `Failed to generate ${target} command for ${command.filename}: ${errorMessage}`
2013
2056
  );
2014
2057
  }
2015
2058
  }
@@ -2018,7 +2061,7 @@ async function generateCommands(projectRoot, baseDir, targets) {
2018
2061
  }
2019
2062
 
2020
2063
  // src/generators/ignore/shared-factory.ts
2021
- var import_node_path8 = require("path");
2064
+ var import_node_path6 = require("path");
2022
2065
 
2023
2066
  // src/generators/ignore/shared-helpers.ts
2024
2067
  function extractIgnorePatternsFromRules(rules) {
@@ -2141,7 +2184,7 @@ function generateIgnoreFile(rules, config, ignoreConfig, baseDir) {
2141
2184
  const outputs = [];
2142
2185
  const content = generateIgnoreContent(rules, ignoreConfig);
2143
2186
  const outputPath = baseDir || process.cwd();
2144
- const filepath = (0, import_node_path8.join)(outputPath, ignoreConfig.filename);
2187
+ const filepath = (0, import_node_path6.join)(outputPath, ignoreConfig.filename);
2145
2188
  outputs.push({
2146
2189
  tool: ignoreConfig.tool,
2147
2190
  filepath,
@@ -2729,20 +2772,20 @@ function generateWindsurfIgnore(rules, config, baseDir) {
2729
2772
  }
2730
2773
 
2731
2774
  // src/generators/rules/augmentcode.ts
2732
- var import_node_path11 = require("path");
2775
+ var import_node_path9 = require("path");
2733
2776
 
2734
2777
  // src/generators/rules/shared-helpers.ts
2735
- var import_node_path10 = require("path");
2778
+ var import_node_path8 = require("path");
2736
2779
 
2737
2780
  // src/utils/ignore.ts
2738
- var import_node_path9 = require("path");
2781
+ var import_node_path7 = require("path");
2739
2782
  var import_micromatch = __toESM(require("micromatch"), 1);
2740
2783
  var cachedIgnorePatterns = null;
2741
2784
  async function loadIgnorePatterns(baseDir = process.cwd()) {
2742
2785
  if (cachedIgnorePatterns) {
2743
2786
  return cachedIgnorePatterns;
2744
2787
  }
2745
- const ignorePath = (0, import_node_path9.join)(baseDir, ".rulesyncignore");
2788
+ const ignorePath = (0, import_node_path7.join)(baseDir, ".rulesyncignore");
2746
2789
  if (!await fileExists(ignorePath)) {
2747
2790
  cachedIgnorePatterns = { patterns: [] };
2748
2791
  return cachedIgnorePatterns;
@@ -2753,7 +2796,7 @@ async function loadIgnorePatterns(baseDir = process.cwd()) {
2753
2796
  cachedIgnorePatterns = { patterns };
2754
2797
  return cachedIgnorePatterns;
2755
2798
  } catch (error) {
2756
- console.warn(`Failed to read .rulesyncignore: ${error}`);
2799
+ logger.warn(`Failed to read .rulesyncignore: ${error}`);
2757
2800
  cachedIgnorePatterns = { patterns: [] };
2758
2801
  return cachedIgnorePatterns;
2759
2802
  }
@@ -2796,7 +2839,7 @@ function addOutput(outputs, tool, config, baseDir, relativePath, content) {
2796
2839
  const outputDir = resolveOutputDir(config, tool, baseDir);
2797
2840
  outputs.push({
2798
2841
  tool,
2799
- filepath: (0, import_node_path10.join)(outputDir, relativePath),
2842
+ filepath: (0, import_node_path8.join)(outputDir, relativePath),
2800
2843
  content
2801
2844
  });
2802
2845
  }
@@ -2805,7 +2848,7 @@ async function generateRulesConfig(rules, config, generatorConfig, baseDir) {
2805
2848
  for (const rule of rules) {
2806
2849
  const content = generatorConfig.generateContent(rule);
2807
2850
  const outputDir = resolveOutputDir(config, generatorConfig.tool, baseDir);
2808
- const filepath = generatorConfig.pathResolver ? generatorConfig.pathResolver(rule, outputDir) : (0, import_node_path10.join)(outputDir, `${rule.filename}${generatorConfig.fileExtension}`);
2851
+ const filepath = generatorConfig.pathResolver ? generatorConfig.pathResolver(rule, outputDir) : (0, import_node_path8.join)(outputDir, `${rule.filename}${generatorConfig.fileExtension}`);
2809
2852
  outputs.push({
2810
2853
  tool: generatorConfig.tool,
2811
2854
  filepath,
@@ -2833,7 +2876,7 @@ async function generateComplexRules(rules, config, generatorConfig, baseDir) {
2833
2876
  for (const rule of detailRules) {
2834
2877
  const content = generatorConfig.generateDetailContent(rule);
2835
2878
  const filepath = resolvePath(
2836
- (0, import_node_path10.join)(generatorConfig.detailSubDir, `${rule.filename}.md`),
2879
+ (0, import_node_path8.join)(generatorConfig.detailSubDir, `${rule.filename}.md`),
2837
2880
  baseDir
2838
2881
  );
2839
2882
  outputs.push({
@@ -2896,7 +2939,7 @@ async function generateAugmentcodeConfig(rules, config, baseDir) {
2896
2939
  "augmentcode",
2897
2940
  config,
2898
2941
  baseDir,
2899
- (0, import_node_path11.join)(".augment", "rules", `${rule.filename}.md`),
2942
+ (0, import_node_path9.join)(".augment", "rules", `${rule.filename}.md`),
2900
2943
  generateRuleFile(rule)
2901
2944
  );
2902
2945
  });
@@ -2949,7 +2992,7 @@ function generateLegacyGuidelinesFile(allRules) {
2949
2992
  }
2950
2993
 
2951
2994
  // src/generators/rules/claudecode.ts
2952
- var import_node_path12 = require("path");
2995
+ var import_node_path10 = require("path");
2953
2996
  async function generateClaudecodeConfig(rules, config, baseDir) {
2954
2997
  const generatorConfig = {
2955
2998
  tool: "claudecode",
@@ -2961,7 +3004,7 @@ async function generateClaudecodeConfig(rules, config, baseDir) {
2961
3004
  generateDetailContent: generateMemoryFile,
2962
3005
  detailSubDir: ".claude/memories",
2963
3006
  updateAdditionalConfig: async (ignorePatterns, baseDir2) => {
2964
- const settingsPath = resolvePath((0, import_node_path12.join)(".claude", "settings.json"), baseDir2);
3007
+ const settingsPath = resolvePath((0, import_node_path10.join)(".claude", "settings.json"), baseDir2);
2965
3008
  await updateClaudeSettings(settingsPath, ignorePatterns);
2966
3009
  return [];
2967
3010
  }
@@ -2998,7 +3041,7 @@ async function updateClaudeSettings(settingsPath, ignorePatterns) {
2998
3041
  const content = await readFileContent(settingsPath);
2999
3042
  rawSettings = JSON.parse(content);
3000
3043
  } catch {
3001
- console.warn(`Failed to parse existing ${settingsPath}, creating new settings`);
3044
+ logger.warn(`Failed to parse existing ${settingsPath}, creating new settings`);
3002
3045
  rawSettings = {};
3003
3046
  }
3004
3047
  }
@@ -3021,11 +3064,11 @@ async function updateClaudeSettings(settingsPath, ignorePatterns) {
3021
3064
  settings.permissions.deny = Array.from(new Set(filteredDeny));
3022
3065
  const jsonContent = JSON.stringify(settings, null, 2);
3023
3066
  await writeFileContent(settingsPath, jsonContent);
3024
- console.log(`\u2705 Updated Claude Code settings: ${settingsPath}`);
3067
+ logger.success(`Updated Claude Code settings: ${settingsPath}`);
3025
3068
  }
3026
3069
 
3027
3070
  // src/generators/rules/generator-registry.ts
3028
- var import_node_path13 = require("path");
3071
+ var import_node_path11 = require("path");
3029
3072
  function determineCursorRuleType(frontmatter) {
3030
3073
  if (frontmatter.cursorRuleType) {
3031
3074
  return frontmatter.cursorRuleType;
@@ -3105,7 +3148,7 @@ var GENERATOR_REGISTRY = {
3105
3148
  },
3106
3149
  pathResolver: (rule, outputDir) => {
3107
3150
  const baseFilename = rule.filename.replace(/\.md$/, "");
3108
- return (0, import_node_path13.join)(outputDir, `${baseFilename}.instructions.md`);
3151
+ return (0, import_node_path11.join)(outputDir, `${baseFilename}.instructions.md`);
3109
3152
  }
3110
3153
  },
3111
3154
  cursor: {
@@ -3145,7 +3188,7 @@ var GENERATOR_REGISTRY = {
3145
3188
  return lines.join("\n");
3146
3189
  },
3147
3190
  pathResolver: (rule, outputDir) => {
3148
- return (0, import_node_path13.join)(outputDir, `${rule.filename}.mdc`);
3191
+ return (0, import_node_path11.join)(outputDir, `${rule.filename}.mdc`);
3149
3192
  }
3150
3193
  },
3151
3194
  codexcli: {
@@ -3181,10 +3224,10 @@ var GENERATOR_REGISTRY = {
3181
3224
  pathResolver: (rule, outputDir) => {
3182
3225
  const outputFormat = rule.frontmatter.windsurfOutputFormat || "directory";
3183
3226
  if (outputFormat === "single-file") {
3184
- return (0, import_node_path13.join)(outputDir, ".windsurf-rules");
3227
+ return (0, import_node_path11.join)(outputDir, ".windsurf-rules");
3185
3228
  } else {
3186
- const rulesDir = (0, import_node_path13.join)(outputDir, ".windsurf", "rules");
3187
- return (0, import_node_path13.join)(rulesDir, `${rule.filename}.md`);
3229
+ const rulesDir = (0, import_node_path11.join)(outputDir, ".windsurf", "rules");
3230
+ return (0, import_node_path11.join)(rulesDir, `${rule.filename}.md`);
3188
3231
  }
3189
3232
  }
3190
3233
  },
@@ -3302,7 +3345,7 @@ async function generateCodexConfig(rules, config, baseDir) {
3302
3345
  const concatenatedContent = generateConcatenatedCodexContent(sortedRules);
3303
3346
  if (concatenatedContent.trim()) {
3304
3347
  const outputDir = resolveOutputDir(config, "codexcli", baseDir);
3305
- const filepath = `${outputDir}/codex.md`;
3348
+ const filepath = `${outputDir}/AGENTS.md`;
3306
3349
  outputs.push({
3307
3350
  tool: "codexcli",
3308
3351
  filepath,
@@ -3408,14 +3451,12 @@ async function generateConfigurations(rules, config, targetTools, baseDir) {
3408
3451
  const toolsToGenerate = targetTools || config.defaultTargets;
3409
3452
  const rootFiles = rules.filter((rule) => rule.frontmatter.root === true);
3410
3453
  if (rootFiles.length === 0) {
3411
- console.warn(
3412
- "\u26A0\uFE0F Warning: No files with 'root: true' found. This may result in incomplete configurations."
3413
- );
3454
+ logger.warn("No files with 'root: true' found. This may result in incomplete configurations.");
3414
3455
  }
3415
3456
  for (const tool of toolsToGenerate) {
3416
3457
  const relevantRules = filterRulesForTool(rules, tool, config);
3417
3458
  if (relevantRules.length === 0) {
3418
- console.warn(`No rules found for tool: ${tool}`);
3459
+ logger.warn(`No rules found for tool: ${tool}`);
3419
3460
  continue;
3420
3461
  }
3421
3462
  const toolOutputs = await generateForTool(tool, relevantRules, config, baseDir);
@@ -3480,14 +3521,13 @@ async function generateForTool(tool, rules, config, baseDir) {
3480
3521
  return [...windsurfRulesOutputs, ...windsurfIgnoreOutputs];
3481
3522
  }
3482
3523
  default:
3483
- console.warn(`Unknown tool: ${tool}`);
3524
+ logger.warn(`Unknown tool: ${tool}`);
3484
3525
  return null;
3485
3526
  }
3486
3527
  }
3487
3528
 
3488
3529
  // src/core/parser.ts
3489
- var import_node_path14 = require("path");
3490
- var import_gray_matter2 = __toESM(require("gray-matter"), 1);
3530
+ var import_node_path12 = require("path");
3491
3531
  async function parseRulesFromDirectory(aiRulesDir) {
3492
3532
  const ignorePatterns = await loadIgnorePatterns();
3493
3533
  const allRuleFiles = await findRuleFiles(aiRulesDir);
@@ -3495,7 +3535,7 @@ async function parseRulesFromDirectory(aiRulesDir) {
3495
3535
  const rules = [];
3496
3536
  const errors = [];
3497
3537
  if (ignorePatterns.patterns.length > 0) {
3498
- console.log(`Loaded ${ignorePatterns.patterns.length} ignore patterns from .rulesyncignore`);
3538
+ logger.info(`Loaded ${ignorePatterns.patterns.length} ignore patterns from .rulesyncignore`);
3499
3539
  }
3500
3540
  for (const filepath of ruleFiles) {
3501
3541
  try {
@@ -3521,7 +3561,7 @@ ${errors.join("\n")}`);
3521
3561
  }
3522
3562
  async function parseRuleFile(filepath) {
3523
3563
  const content = await readFileContent(filepath);
3524
- const parsed = (0, import_gray_matter2.default)(content);
3564
+ const parsed = parseFrontmatter(content);
3525
3565
  try {
3526
3566
  const validatedData = RuleFrontmatterSchema.parse(parsed.data);
3527
3567
  const frontmatter = {
@@ -3540,7 +3580,7 @@ async function parseRuleFile(filepath) {
3540
3580
  },
3541
3581
  ...validatedData.tags !== void 0 && { tags: validatedData.tags }
3542
3582
  };
3543
- const filename = (0, import_node_path14.basename)(filepath, ".md");
3583
+ const filename = (0, import_node_path12.basename)(filepath, ".md");
3544
3584
  return {
3545
3585
  frontmatter,
3546
3586
  content: parsed.content,
@@ -3759,7 +3799,7 @@ async function generateCommand(options = {}) {
3759
3799
  logger.info("Deleting existing output directories...");
3760
3800
  const targetTools = config.defaultTargets;
3761
3801
  const deleteTasks = [];
3762
- const commandsDir = (0, import_node_path15.join)(config.aiRulesDir, "commands");
3802
+ const commandsDir = (0, import_node_path13.join)(config.aiRulesDir, "commands");
3763
3803
  const hasCommands = await fileExists(commandsDir);
3764
3804
  let hasCommandFiles = false;
3765
3805
  if (hasCommands) {
@@ -3774,12 +3814,12 @@ async function generateCommand(options = {}) {
3774
3814
  for (const tool of targetTools) {
3775
3815
  switch (tool) {
3776
3816
  case "augmentcode":
3777
- deleteTasks.push(removeDirectory((0, import_node_path15.join)(".augment", "rules")));
3778
- deleteTasks.push(removeDirectory((0, import_node_path15.join)(".augment", "ignore")));
3817
+ deleteTasks.push(removeDirectory((0, import_node_path13.join)(".augment", "rules")));
3818
+ deleteTasks.push(removeDirectory((0, import_node_path13.join)(".augment", "ignore")));
3779
3819
  break;
3780
3820
  case "augmentcode-legacy":
3781
3821
  deleteTasks.push(removeClaudeGeneratedFiles());
3782
- deleteTasks.push(removeDirectory((0, import_node_path15.join)(".augment", "ignore")));
3822
+ deleteTasks.push(removeDirectory((0, import_node_path13.join)(".augment", "ignore")));
3783
3823
  break;
3784
3824
  case "copilot":
3785
3825
  deleteTasks.push(removeDirectory(config.outputPaths.copilot));
@@ -3793,19 +3833,19 @@ async function generateCommand(options = {}) {
3793
3833
  case "claudecode":
3794
3834
  deleteTasks.push(removeClaudeGeneratedFiles());
3795
3835
  if (hasCommandFiles) {
3796
- deleteTasks.push(removeDirectory((0, import_node_path15.join)(".claude", "commands")));
3836
+ deleteTasks.push(removeDirectory((0, import_node_path13.join)(".claude", "commands")));
3797
3837
  }
3798
3838
  break;
3799
3839
  case "roo":
3800
3840
  deleteTasks.push(removeDirectory(config.outputPaths.roo));
3801
3841
  if (hasCommandFiles) {
3802
- deleteTasks.push(removeDirectory((0, import_node_path15.join)(".roo", "commands")));
3842
+ deleteTasks.push(removeDirectory((0, import_node_path13.join)(".roo", "commands")));
3803
3843
  }
3804
3844
  break;
3805
3845
  case "geminicli":
3806
3846
  deleteTasks.push(removeDirectory(config.outputPaths.geminicli));
3807
3847
  if (hasCommandFiles) {
3808
- deleteTasks.push(removeDirectory((0, import_node_path15.join)(".gemini", "commands")));
3848
+ deleteTasks.push(removeDirectory((0, import_node_path13.join)(".gemini", "commands")));
3809
3849
  }
3810
3850
  break;
3811
3851
  case "kiro":
@@ -3904,9 +3944,9 @@ Generating configurations for base directory: ${baseDir}`);
3904
3944
 
3905
3945
  // src/cli/commands/gitignore.ts
3906
3946
  var import_node_fs2 = require("fs");
3907
- var import_node_path16 = require("path");
3947
+ var import_node_path14 = require("path");
3908
3948
  var gitignoreCommand = async () => {
3909
- const gitignorePath = (0, import_node_path16.join)(process.cwd(), ".gitignore");
3949
+ const gitignorePath = (0, import_node_path14.join)(process.cwd(), ".gitignore");
3910
3950
  const rulesFilesToIgnore = [
3911
3951
  "# Generated by rulesync - AI tool configuration files",
3912
3952
  "**/.github/copilot-instructions.md",
@@ -3918,7 +3958,7 @@ var gitignoreCommand = async () => {
3918
3958
  "**/CLAUDE.md",
3919
3959
  "**/.claude/memories/",
3920
3960
  "**/.claude/commands/",
3921
- "**/codex.md",
3961
+ "**/AGENTS.md",
3922
3962
  "**/.codexignore",
3923
3963
  "**/.roo/rules/",
3924
3964
  "**/.rooignore",
@@ -3954,7 +3994,7 @@ var gitignoreCommand = async () => {
3954
3994
  }
3955
3995
  }
3956
3996
  if (linesToAdd.length === 0) {
3957
- console.log("\u2705 .gitignore is already up to date");
3997
+ logger.success(".gitignore is already up to date");
3958
3998
  return;
3959
3999
  }
3960
4000
  const newContent = gitignoreContent ? `${gitignoreContent.trimEnd()}
@@ -3963,21 +4003,20 @@ ${linesToAdd.join("\n")}
3963
4003
  ` : `${linesToAdd.join("\n")}
3964
4004
  `;
3965
4005
  (0, import_node_fs2.writeFileSync)(gitignorePath, newContent);
3966
- console.log(`\u2705 Added ${linesToAdd.length} rules to .gitignore:`);
4006
+ logger.success(`Added ${linesToAdd.length} rules to .gitignore:`);
3967
4007
  for (const line of linesToAdd) {
3968
4008
  if (!line.startsWith("#")) {
3969
- console.log(` ${line}`);
4009
+ logger.log(` ${line}`);
3970
4010
  }
3971
4011
  }
3972
4012
  };
3973
4013
 
3974
4014
  // src/core/importer.ts
3975
- var import_node_path23 = require("path");
3976
- var import_gray_matter7 = __toESM(require("gray-matter"), 1);
4015
+ var import_node_path21 = require("path");
4016
+ var import_gray_matter2 = __toESM(require("gray-matter"), 1);
3977
4017
 
3978
4018
  // src/parsers/augmentcode.ts
3979
- var import_node_path17 = require("path");
3980
- var import_gray_matter3 = __toESM(require("gray-matter"), 1);
4019
+ var import_node_path15 = require("path");
3981
4020
 
3982
4021
  // src/utils/parser-helpers.ts
3983
4022
  function createParseResult() {
@@ -4025,7 +4064,7 @@ async function parseAugmentcodeLegacyConfiguration(baseDir = process.cwd()) {
4025
4064
  async function parseUnifiedAugmentcode(baseDir, config) {
4026
4065
  const result = createParseResult();
4027
4066
  if (config.rulesDir) {
4028
- const rulesDir = (0, import_node_path17.join)(baseDir, config.rulesDir);
4067
+ const rulesDir = (0, import_node_path15.join)(baseDir, config.rulesDir);
4029
4068
  if (await fileExists(rulesDir)) {
4030
4069
  const rulesResult = await parseAugmentRules(rulesDir, config);
4031
4070
  addRules(result, rulesResult.rules);
@@ -4038,7 +4077,7 @@ async function parseUnifiedAugmentcode(baseDir, config) {
4038
4077
  }
4039
4078
  }
4040
4079
  if (config.legacyFilePath) {
4041
- const legacyPath = (0, import_node_path17.join)(baseDir, config.legacyFilePath);
4080
+ const legacyPath = (0, import_node_path15.join)(baseDir, config.legacyFilePath);
4042
4081
  if (await fileExists(legacyPath)) {
4043
4082
  const legacyResult = await parseAugmentGuidelines(legacyPath, config);
4044
4083
  if (legacyResult.rule) {
@@ -4062,23 +4101,22 @@ async function parseAugmentRules(rulesDir, config) {
4062
4101
  const files = await readdir2(rulesDir);
4063
4102
  for (const file of files) {
4064
4103
  if (file.endsWith(".md") || file.endsWith(".mdc")) {
4065
- const filePath = (0, import_node_path17.join)(rulesDir, file);
4104
+ const filePath = (0, import_node_path15.join)(rulesDir, file);
4066
4105
  try {
4067
4106
  const rawContent = await readFileContent(filePath);
4068
- const parsed = (0, import_gray_matter3.default)(rawContent);
4069
- const frontmatterData = parsed.data;
4070
- const ruleType = frontmatterData.type || "manual";
4071
- const description = frontmatterData.description || "";
4072
- const tags = Array.isArray(frontmatterData.tags) ? frontmatterData.tags : void 0;
4107
+ const parsed = parseFrontmatter(rawContent);
4108
+ const ruleType = extractStringField(parsed.data, "type", "manual");
4109
+ const description = extractStringField(parsed.data, "description", "");
4110
+ const tags = extractArrayField(parsed.data, "tags");
4073
4111
  const isRoot = ruleType === "always";
4074
- const filename = (0, import_node_path17.basename)(file, file.endsWith(".mdc") ? ".mdc" : ".md");
4112
+ const filename = (0, import_node_path15.basename)(file, file.endsWith(".mdc") ? ".mdc" : ".md");
4075
4113
  const frontmatter = {
4076
4114
  root: isRoot,
4077
4115
  targets: [config.targetName],
4078
4116
  description,
4079
4117
  globs: ["**/*"],
4080
4118
  // AugmentCode doesn't use specific globs in the same way
4081
- ...tags && { tags }
4119
+ ...tags.length > 0 && { tags }
4082
4120
  };
4083
4121
  rules.push({
4084
4122
  frontmatter,
@@ -4129,8 +4167,7 @@ async function parseAugmentGuidelines(guidelinesPath, config) {
4129
4167
  }
4130
4168
 
4131
4169
  // src/parsers/shared-helpers.ts
4132
- var import_node_path18 = require("path");
4133
- var import_gray_matter4 = __toESM(require("gray-matter"), 1);
4170
+ var import_node_path16 = require("path");
4134
4171
  async function parseConfigurationFiles(baseDir = process.cwd(), config) {
4135
4172
  const errors = [];
4136
4173
  const rules = [];
@@ -4143,16 +4180,18 @@ async function parseConfigurationFiles(baseDir = process.cwd(), config) {
4143
4180
  let content;
4144
4181
  let frontmatter;
4145
4182
  if (mainFile.useFrontmatter) {
4146
- const parsed = (0, import_gray_matter4.default)(rawContent);
4147
- content = parsed.content.trim();
4148
- const parsedFrontmatter = parsed.data;
4183
+ const parsed = parseFrontmatter(rawContent);
4184
+ content = parsed.content;
4149
4185
  frontmatter = {
4150
4186
  root: mainFile.isRoot ?? false,
4151
4187
  targets: [config.tool],
4152
- description: parsedFrontmatter.description || mainFile.description,
4153
- globs: Array.isArray(parsedFrontmatter.globs) ? parsedFrontmatter.globs : ["**/*"],
4154
- ...parsedFrontmatter.tags && { tags: parsedFrontmatter.tags }
4188
+ description: extractStringField(parsed.data, "description", mainFile.description),
4189
+ globs: extractArrayField(parsed.data, "globs", ["**/*"])
4155
4190
  };
4191
+ const tags = extractArrayField(parsed.data, "tags");
4192
+ if (tags.length > 0) {
4193
+ frontmatter.tags = tags;
4194
+ }
4156
4195
  } else {
4157
4196
  content = rawContent.trim();
4158
4197
  frontmatter = {
@@ -4185,23 +4224,29 @@ async function parseConfigurationFiles(baseDir = process.cwd(), config) {
4185
4224
  const files = await readdir2(dirPath);
4186
4225
  for (const file of files) {
4187
4226
  if (file.endsWith(dirConfig.filePattern)) {
4188
- const filePath = (0, import_node_path18.join)(dirPath, file);
4227
+ const filePath = (0, import_node_path16.join)(dirPath, file);
4189
4228
  const fileResult = await safeAsyncOperation(async () => {
4190
4229
  const rawContent = await readFileContent(filePath);
4191
4230
  let content;
4192
4231
  let frontmatter;
4193
4232
  const filename = file.replace(new RegExp(`\\${dirConfig.filePattern}$`), "");
4194
4233
  if (dirConfig.filePattern === ".instructions.md") {
4195
- const parsed = (0, import_gray_matter4.default)(rawContent);
4196
- content = parsed.content.trim();
4197
- const parsedFrontmatter = parsed.data;
4234
+ const parsed = parseFrontmatter(rawContent);
4235
+ content = parsed.content;
4198
4236
  frontmatter = {
4199
4237
  root: false,
4200
4238
  targets: [config.tool],
4201
- description: parsedFrontmatter.description || `${dirConfig.description}: ${filename}`,
4202
- globs: Array.isArray(parsedFrontmatter.globs) ? parsedFrontmatter.globs : ["**/*"],
4203
- ...parsedFrontmatter.tags && { tags: parsedFrontmatter.tags }
4239
+ description: extractStringField(
4240
+ parsed.data,
4241
+ "description",
4242
+ `${dirConfig.description}: ${filename}`
4243
+ ),
4244
+ globs: extractArrayField(parsed.data, "globs", ["**/*"])
4204
4245
  };
4246
+ const tags = extractArrayField(parsed.data, "tags");
4247
+ if (tags.length > 0) {
4248
+ frontmatter.tags = tags;
4249
+ }
4205
4250
  } else {
4206
4251
  content = rawContent.trim();
4207
4252
  frontmatter = {
@@ -4330,10 +4375,10 @@ async function parseMemoryFiles(memoryDir, config) {
4330
4375
  const files = await readdir2(memoryDir);
4331
4376
  for (const file of files) {
4332
4377
  if (file.endsWith(".md")) {
4333
- const filePath = (0, import_node_path18.join)(memoryDir, file);
4378
+ const filePath = (0, import_node_path16.join)(memoryDir, file);
4334
4379
  const content = await readFileContent(filePath);
4335
4380
  if (content.trim()) {
4336
- const filename = (0, import_node_path18.basename)(file, ".md");
4381
+ const filename = (0, import_node_path16.basename)(file, ".md");
4337
4382
  const frontmatter = {
4338
4383
  root: false,
4339
4384
  targets: [config.tool],
@@ -4360,20 +4405,19 @@ async function parseCommandsFiles(commandsDir, config) {
4360
4405
  const files = await readdir2(commandsDir);
4361
4406
  for (const file of files) {
4362
4407
  if (file.endsWith(".md")) {
4363
- const filePath = (0, import_node_path18.join)(commandsDir, file);
4408
+ const filePath = (0, import_node_path16.join)(commandsDir, file);
4364
4409
  const content = await readFileContent(filePath);
4365
4410
  if (content.trim()) {
4366
- const filename = (0, import_node_path18.basename)(file, ".md");
4411
+ const filename = (0, import_node_path16.basename)(file, ".md");
4367
4412
  let frontmatter;
4368
4413
  let ruleContent;
4369
4414
  try {
4370
- const parsed = (0, import_gray_matter4.default)(content);
4371
- ruleContent = parsed.content.trim();
4372
- const parsedFrontmatter = parsed.data;
4415
+ const parsed = parseFrontmatter(content);
4416
+ ruleContent = parsed.content;
4373
4417
  frontmatter = {
4374
4418
  root: false,
4375
4419
  targets: [config.tool],
4376
- description: parsedFrontmatter.description || `Command: ${filename}`,
4420
+ description: extractStringField(parsed.data, "description", `Command: ${filename}`),
4377
4421
  globs: ["**/*"]
4378
4422
  };
4379
4423
  } catch {
@@ -4474,7 +4518,7 @@ async function parseClineConfiguration(baseDir = process.cwd()) {
4474
4518
  }
4475
4519
 
4476
4520
  // src/parsers/codexcli.ts
4477
- var import_node_path19 = require("path");
4521
+ var import_node_path17 = require("path");
4478
4522
 
4479
4523
  // src/parsers/copilot.ts
4480
4524
  async function parseCopilotConfiguration(baseDir = process.cwd()) {
@@ -4497,8 +4541,7 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
4497
4541
  }
4498
4542
 
4499
4543
  // src/parsers/cursor.ts
4500
- var import_node_path20 = require("path");
4501
- var import_gray_matter5 = __toESM(require("gray-matter"), 1);
4544
+ var import_node_path18 = require("path");
4502
4545
  var import_js_yaml = require("js-yaml");
4503
4546
  var import_mini8 = require("zod/mini");
4504
4547
  var customMatterOptions = {
@@ -4622,12 +4665,12 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
4622
4665
  const rules = [];
4623
4666
  let ignorePatterns;
4624
4667
  let mcpServers;
4625
- const cursorFilePath = (0, import_node_path20.join)(baseDir, ".cursorrules");
4668
+ const cursorFilePath = (0, import_node_path18.join)(baseDir, ".cursorrules");
4626
4669
  if (await fileExists(cursorFilePath)) {
4627
4670
  try {
4628
4671
  const rawContent = await readFileContent(cursorFilePath);
4629
- const parsed = (0, import_gray_matter5.default)(rawContent, customMatterOptions);
4630
- const content = parsed.content.trim();
4672
+ const parsed = parseFrontmatter(rawContent, { matterOptions: customMatterOptions });
4673
+ const content = parsed.content;
4631
4674
  if (content) {
4632
4675
  const frontmatter = convertCursorMdcFrontmatter(parsed.data, "cursorrules");
4633
4676
  frontmatter.targets = ["cursor"];
@@ -4643,20 +4686,20 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
4643
4686
  errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
4644
4687
  }
4645
4688
  }
4646
- const cursorRulesDir = (0, import_node_path20.join)(baseDir, ".cursor", "rules");
4689
+ const cursorRulesDir = (0, import_node_path18.join)(baseDir, ".cursor", "rules");
4647
4690
  if (await fileExists(cursorRulesDir)) {
4648
4691
  try {
4649
4692
  const { readdir: readdir2 } = await import("fs/promises");
4650
4693
  const files = await readdir2(cursorRulesDir);
4651
4694
  for (const file of files) {
4652
4695
  if (file.endsWith(".mdc")) {
4653
- const filePath = (0, import_node_path20.join)(cursorRulesDir, file);
4696
+ const filePath = (0, import_node_path18.join)(cursorRulesDir, file);
4654
4697
  try {
4655
4698
  const rawContent = await readFileContent(filePath);
4656
- const parsed = (0, import_gray_matter5.default)(rawContent, customMatterOptions);
4657
- const content = parsed.content.trim();
4699
+ const parsed = parseFrontmatter(rawContent, { matterOptions: customMatterOptions });
4700
+ const content = parsed.content;
4658
4701
  if (content) {
4659
- const filename = (0, import_node_path20.basename)(file, ".mdc");
4702
+ const filename = (0, import_node_path18.basename)(file, ".mdc");
4660
4703
  const frontmatter = convertCursorMdcFrontmatter(parsed.data, filename);
4661
4704
  rules.push({
4662
4705
  frontmatter,
@@ -4679,7 +4722,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
4679
4722
  if (rules.length === 0) {
4680
4723
  errors.push("No Cursor configuration files found (.cursorrules or .cursor/rules/*.mdc)");
4681
4724
  }
4682
- const cursorIgnorePath = (0, import_node_path20.join)(baseDir, ".cursorignore");
4725
+ const cursorIgnorePath = (0, import_node_path18.join)(baseDir, ".cursorignore");
4683
4726
  if (await fileExists(cursorIgnorePath)) {
4684
4727
  try {
4685
4728
  const content = await readFileContent(cursorIgnorePath);
@@ -4692,7 +4735,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
4692
4735
  errors.push(`Failed to parse .cursorignore: ${errorMessage}`);
4693
4736
  }
4694
4737
  }
4695
- const cursorMcpPath = (0, import_node_path20.join)(baseDir, ".cursor", "mcp.json");
4738
+ const cursorMcpPath = (0, import_node_path18.join)(baseDir, ".cursor", "mcp.json");
4696
4739
  if (await fileExists(cursorMcpPath)) {
4697
4740
  try {
4698
4741
  const content = await readFileContent(cursorMcpPath);
@@ -4742,11 +4785,11 @@ async function parseGeminiConfiguration(baseDir = process.cwd()) {
4742
4785
  }
4743
4786
 
4744
4787
  // src/parsers/junie.ts
4745
- var import_node_path21 = require("path");
4788
+ var import_node_path19 = require("path");
4746
4789
  async function parseJunieConfiguration(baseDir = process.cwd()) {
4747
4790
  const errors = [];
4748
4791
  const rules = [];
4749
- const guidelinesPath = (0, import_node_path21.join)(baseDir, ".junie", "guidelines.md");
4792
+ const guidelinesPath = (0, import_node_path19.join)(baseDir, ".junie", "guidelines.md");
4750
4793
  if (!await fileExists(guidelinesPath)) {
4751
4794
  errors.push(".junie/guidelines.md file not found");
4752
4795
  return { rules, errors };
@@ -4799,8 +4842,7 @@ async function parseRooConfiguration(baseDir = process.cwd()) {
4799
4842
 
4800
4843
  // src/parsers/windsurf.ts
4801
4844
  var import_promises3 = require("fs/promises");
4802
- var import_node_path22 = require("path");
4803
- var import_gray_matter6 = __toESM(require("gray-matter"), 1);
4845
+ var import_node_path20 = require("path");
4804
4846
 
4805
4847
  // src/core/importer.ts
4806
4848
  async function importConfiguration(options) {
@@ -4816,7 +4858,7 @@ async function importConfiguration(options) {
4816
4858
  let ignorePatterns;
4817
4859
  let mcpServers;
4818
4860
  if (verbose) {
4819
- console.log(`Importing ${tool} configuration from ${baseDir}...`);
4861
+ logger.log(`Importing ${tool} configuration from ${baseDir}...`);
4820
4862
  }
4821
4863
  try {
4822
4864
  switch (tool) {
@@ -4892,7 +4934,7 @@ async function importConfiguration(options) {
4892
4934
  if (rules.length === 0 && !ignorePatterns && !mcpServers) {
4893
4935
  return { success: false, rulesCreated: 0, errors };
4894
4936
  }
4895
- const rulesDirPath = (0, import_node_path23.join)(baseDir, rulesDir);
4937
+ const rulesDirPath = (0, import_node_path21.join)(baseDir, rulesDir);
4896
4938
  try {
4897
4939
  const { mkdir: mkdir3 } = await import("fs/promises");
4898
4940
  await mkdir3(rulesDirPath, { recursive: true });
@@ -4907,22 +4949,22 @@ async function importConfiguration(options) {
4907
4949
  const baseFilename = rule.filename;
4908
4950
  let targetDir = rulesDirPath;
4909
4951
  if (rule.type === "command") {
4910
- targetDir = (0, import_node_path23.join)(rulesDirPath, "commands");
4952
+ targetDir = (0, import_node_path21.join)(rulesDirPath, "commands");
4911
4953
  const { mkdir: mkdir3 } = await import("fs/promises");
4912
4954
  await mkdir3(targetDir, { recursive: true });
4913
4955
  } else {
4914
4956
  if (!useLegacyLocation) {
4915
- targetDir = (0, import_node_path23.join)(rulesDirPath, "rules");
4957
+ targetDir = (0, import_node_path21.join)(rulesDirPath, "rules");
4916
4958
  const { mkdir: mkdir3 } = await import("fs/promises");
4917
4959
  await mkdir3(targetDir, { recursive: true });
4918
4960
  }
4919
4961
  }
4920
- const filePath = (0, import_node_path23.join)(targetDir, `${baseFilename}.md`);
4962
+ const filePath = (0, import_node_path21.join)(targetDir, `${baseFilename}.md`);
4921
4963
  const content = generateRuleFileContent(rule);
4922
4964
  await writeFileContent(filePath, content);
4923
4965
  rulesCreated++;
4924
4966
  if (verbose) {
4925
- console.log(`\u2705 Created rule file: ${filePath}`);
4967
+ logger.success(`Created rule file: ${filePath}`);
4926
4968
  }
4927
4969
  } catch (error) {
4928
4970
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -4932,13 +4974,13 @@ async function importConfiguration(options) {
4932
4974
  let ignoreFileCreated = false;
4933
4975
  if (ignorePatterns && ignorePatterns.length > 0) {
4934
4976
  try {
4935
- const rulesyncignorePath = (0, import_node_path23.join)(baseDir, ".rulesyncignore");
4977
+ const rulesyncignorePath = (0, import_node_path21.join)(baseDir, ".rulesyncignore");
4936
4978
  const ignoreContent = `${ignorePatterns.join("\n")}
4937
4979
  `;
4938
4980
  await writeFileContent(rulesyncignorePath, ignoreContent);
4939
4981
  ignoreFileCreated = true;
4940
4982
  if (verbose) {
4941
- console.log(`\u2705 Created .rulesyncignore with ${ignorePatterns.length} patterns`);
4983
+ logger.success(`Created .rulesyncignore with ${ignorePatterns.length} patterns`);
4942
4984
  }
4943
4985
  } catch (error) {
4944
4986
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -4948,13 +4990,13 @@ async function importConfiguration(options) {
4948
4990
  let mcpFileCreated = false;
4949
4991
  if (mcpServers && Object.keys(mcpServers).length > 0) {
4950
4992
  try {
4951
- const mcpPath = (0, import_node_path23.join)(baseDir, rulesDir, ".mcp.json");
4993
+ const mcpPath = (0, import_node_path21.join)(baseDir, rulesDir, ".mcp.json");
4952
4994
  const mcpContent = `${JSON.stringify({ mcpServers }, null, 2)}
4953
4995
  `;
4954
4996
  await writeFileContent(mcpPath, mcpContent);
4955
4997
  mcpFileCreated = true;
4956
4998
  if (verbose) {
4957
- console.log(`\u2705 Created .mcp.json with ${Object.keys(mcpServers).length} servers`);
4999
+ logger.success(`Created .mcp.json with ${Object.keys(mcpServers).length} servers`);
4958
5000
  }
4959
5001
  } catch (error) {
4960
5002
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -4975,10 +5017,10 @@ function generateRuleFileContent(rule) {
4975
5017
  description: rule.frontmatter.description,
4976
5018
  targets: rule.frontmatter.targets
4977
5019
  };
4978
- const frontmatter2 = import_gray_matter7.default.stringify("", simplifiedFrontmatter);
5020
+ const frontmatter2 = import_gray_matter2.default.stringify("", simplifiedFrontmatter);
4979
5021
  return frontmatter2 + rule.content;
4980
5022
  }
4981
- const frontmatter = import_gray_matter7.default.stringify("", rule.frontmatter);
5023
+ const frontmatter = import_gray_matter2.default.stringify("", rule.frontmatter);
4982
5024
  return frontmatter + rule.content;
4983
5025
  }
4984
5026
 
@@ -5044,23 +5086,23 @@ async function importCommand(options = {}) {
5044
5086
  }
5045
5087
 
5046
5088
  // src/cli/commands/init.ts
5047
- var import_node_path24 = require("path");
5089
+ var import_node_path22 = require("path");
5048
5090
  async function initCommand(options = {}) {
5049
5091
  const configResult = await loadConfig();
5050
5092
  const config = configResult.config;
5051
5093
  const aiRulesDir = config.aiRulesDir;
5052
- console.log("Initializing rulesync...");
5094
+ logger.log("Initializing rulesync...");
5053
5095
  await ensureDir(aiRulesDir);
5054
5096
  const useLegacy = options.legacy ?? config.legacy ?? false;
5055
- const rulesDir = useLegacy ? aiRulesDir : (0, import_node_path24.join)(aiRulesDir, "rules");
5097
+ const rulesDir = useLegacy ? aiRulesDir : (0, import_node_path22.join)(aiRulesDir, "rules");
5056
5098
  if (!useLegacy) {
5057
5099
  await ensureDir(rulesDir);
5058
5100
  }
5059
5101
  await createSampleFiles(rulesDir);
5060
- console.log("\u2705 rulesync initialized successfully!");
5061
- console.log("\nNext steps:");
5062
- console.log(`1. Edit rule files in ${rulesDir}/`);
5063
- console.log("2. Run 'rulesync generate' to create configuration files");
5102
+ logger.success("rulesync initialized successfully!");
5103
+ logger.log("\nNext steps:");
5104
+ logger.log(`1. Edit rule files in ${rulesDir}/`);
5105
+ logger.log("2. Run 'rulesync generate' to create configuration files");
5064
5106
  }
5065
5107
  async function createSampleFiles(rulesDir) {
5066
5108
  const sampleFile = {
@@ -5098,36 +5140,36 @@ globs: ["**/*"]
5098
5140
  - Follow single responsibility principle
5099
5141
  `
5100
5142
  };
5101
- const filepath = (0, import_node_path24.join)(rulesDir, sampleFile.filename);
5143
+ const filepath = (0, import_node_path22.join)(rulesDir, sampleFile.filename);
5102
5144
  if (!await fileExists(filepath)) {
5103
5145
  await writeFileContent(filepath, sampleFile.content);
5104
- console.log(`Created ${filepath}`);
5146
+ logger.success(`Created ${filepath}`);
5105
5147
  } else {
5106
- console.log(`Skipped ${filepath} (already exists)`);
5148
+ logger.log(`Skipped ${filepath} (already exists)`);
5107
5149
  }
5108
5150
  }
5109
5151
 
5110
5152
  // src/cli/commands/status.ts
5111
5153
  async function statusCommand() {
5112
5154
  const config = getDefaultConfig();
5113
- console.log("rulesync Status");
5114
- console.log("===============");
5155
+ logger.log("rulesync Status");
5156
+ logger.log("===============");
5115
5157
  const rulesyncExists = await fileExists(config.aiRulesDir);
5116
- console.log(`
5158
+ logger.log(`
5117
5159
  \u{1F4C1} .rulesync directory: ${rulesyncExists ? "\u2705 Found" : "\u274C Not found"}`);
5118
5160
  if (!rulesyncExists) {
5119
- console.log("\n\u{1F4A1} Run 'rulesync init' to get started");
5161
+ logger.log("\n\u{1F4A1} Run 'rulesync init' to get started");
5120
5162
  return;
5121
5163
  }
5122
5164
  try {
5123
5165
  const rules = await parseRulesFromDirectory(config.aiRulesDir);
5124
- console.log(`
5166
+ logger.log(`
5125
5167
  \u{1F4CB} Rules: ${rules.length} total`);
5126
5168
  if (rules.length > 0) {
5127
5169
  const rootRules = rules.filter((r) => r.frontmatter.root).length;
5128
5170
  const nonRootRules = rules.length - rootRules;
5129
- console.log(` - Root rules: ${rootRules}`);
5130
- console.log(` - Non-root rules: ${nonRootRules}`);
5171
+ logger.log(` - Root rules: ${rootRules}`);
5172
+ logger.log(` - Non-root rules: ${nonRootRules}`);
5131
5173
  const targetCounts = { copilot: 0, cursor: 0, cline: 0, claudecode: 0, roo: 0 };
5132
5174
  for (const rule of rules) {
5133
5175
  const targets = rule.frontmatter.targets[0] === "*" ? config.defaultTargets : rule.frontmatter.targets;
@@ -5139,63 +5181,63 @@ async function statusCommand() {
5139
5181
  else if (target === "roo") targetCounts.roo++;
5140
5182
  }
5141
5183
  }
5142
- console.log("\n\u{1F3AF} Target tool coverage:");
5143
- console.log(` - Copilot: ${targetCounts.copilot} rules`);
5144
- console.log(` - Cursor: ${targetCounts.cursor} rules`);
5145
- console.log(` - Cline: ${targetCounts.cline} rules`);
5146
- console.log(` - Claude Code: ${targetCounts.claudecode} rules`);
5147
- console.log(` - Roo: ${targetCounts.roo} rules`);
5148
- }
5149
- console.log("\n\u{1F4E4} Generated files:");
5184
+ logger.log("\n\u{1F3AF} Target tool coverage:");
5185
+ logger.log(` - Copilot: ${targetCounts.copilot} rules`);
5186
+ logger.log(` - Cursor: ${targetCounts.cursor} rules`);
5187
+ logger.log(` - Cline: ${targetCounts.cline} rules`);
5188
+ logger.log(` - Claude Code: ${targetCounts.claudecode} rules`);
5189
+ logger.log(` - Roo: ${targetCounts.roo} rules`);
5190
+ }
5191
+ logger.log("\n\u{1F4E4} Generated files:");
5150
5192
  for (const [tool, outputPath] of Object.entries(config.outputPaths)) {
5151
5193
  const outputExists = await fileExists(outputPath);
5152
- console.log(` - ${tool}: ${outputExists ? "\u2705 Generated" : "\u274C Not found"}`);
5194
+ logger.log(` - ${tool}: ${outputExists ? "\u2705 Generated" : "\u274C Not found"}`);
5153
5195
  }
5154
5196
  if (rules.length > 0) {
5155
- console.log("\n\u{1F4A1} Run 'rulesync generate' to update configuration files");
5197
+ logger.log("\n\u{1F4A1} Run 'rulesync generate' to update configuration files");
5156
5198
  }
5157
5199
  } catch (error) {
5158
- console.error("\n\u274C Failed to get status:", error);
5200
+ logger.error("\nFailed to get status:", error);
5159
5201
  }
5160
5202
  }
5161
5203
 
5162
5204
  // src/cli/commands/validate.ts
5163
5205
  async function validateCommand() {
5164
5206
  const config = getDefaultConfig();
5165
- console.log("Validating rulesync configuration...");
5207
+ logger.log("Validating rulesync configuration...");
5166
5208
  if (!await fileExists(config.aiRulesDir)) {
5167
- console.error("\u274C .rulesync directory not found. Run 'rulesync init' first.");
5209
+ logger.error(".rulesync directory not found. Run 'rulesync init' first.");
5168
5210
  process.exit(1);
5169
5211
  }
5170
5212
  try {
5171
5213
  const rules = await parseRulesFromDirectory(config.aiRulesDir);
5172
5214
  if (rules.length === 0) {
5173
- console.warn("\u26A0\uFE0F No rules found in .rulesync directory");
5215
+ logger.warn("No rules found in .rulesync directory");
5174
5216
  return;
5175
5217
  }
5176
- console.log(`Found ${rules.length} rule(s), validating...`);
5218
+ logger.log(`Found ${rules.length} rule(s), validating...`);
5177
5219
  const validation = await validateRules(rules);
5178
5220
  if (validation.warnings.length > 0) {
5179
- console.log("\n\u26A0\uFE0F Warnings:");
5221
+ logger.log("\n\u26A0\uFE0F Warnings:");
5180
5222
  for (const warning of validation.warnings) {
5181
- console.log(` - ${warning}`);
5223
+ logger.log(` - ${warning}`);
5182
5224
  }
5183
5225
  }
5184
5226
  if (validation.errors.length > 0) {
5185
- console.log("\n\u274C Errors:");
5227
+ logger.log("\nErrors:");
5186
5228
  for (const error of validation.errors) {
5187
- console.log(` - ${error}`);
5229
+ logger.log(` - ${error}`);
5188
5230
  }
5189
5231
  }
5190
5232
  if (validation.isValid) {
5191
- console.log("\n\u2705 All rules are valid!");
5233
+ logger.success("\nAll rules are valid!");
5192
5234
  } else {
5193
- console.log(`
5194
- \u274C Validation failed with ${validation.errors.length} error(s)`);
5235
+ logger.log(`
5236
+ Validation failed with ${validation.errors.length} error(s)`);
5195
5237
  process.exit(1);
5196
5238
  }
5197
5239
  } catch (error) {
5198
- console.error("\u274C Failed to validate rules:", error);
5240
+ logger.error("Failed to validate rules:", error);
5199
5241
  process.exit(1);
5200
5242
  }
5201
5243
  }
@@ -5204,8 +5246,8 @@ async function validateCommand() {
5204
5246
  var import_chokidar = require("chokidar");
5205
5247
  async function watchCommand() {
5206
5248
  const config = getDefaultConfig();
5207
- console.log("\u{1F440} Watching for changes in .rulesync directory...");
5208
- console.log("Press Ctrl+C to stop watching");
5249
+ logger.log("\u{1F440} Watching for changes in .rulesync directory...");
5250
+ logger.log("Press Ctrl+C to stop watching");
5209
5251
  await generateCommand({ verbose: false });
5210
5252
  const watcher = (0, import_chokidar.watch)(`${config.aiRulesDir}/**/*.md`, {
5211
5253
  ignoreInitial: true,
@@ -5215,26 +5257,26 @@ async function watchCommand() {
5215
5257
  const handleChange = async (path5) => {
5216
5258
  if (isGenerating) return;
5217
5259
  isGenerating = true;
5218
- console.log(`
5260
+ logger.log(`
5219
5261
  \u{1F4DD} Detected change in ${path5}`);
5220
5262
  try {
5221
5263
  await generateCommand({ verbose: false });
5222
- console.log("\u2705 Regenerated configuration files");
5264
+ logger.success("Regenerated configuration files");
5223
5265
  } catch (error) {
5224
- console.error("\u274C Failed to regenerate:", error);
5266
+ logger.error("Failed to regenerate:", error);
5225
5267
  } finally {
5226
5268
  isGenerating = false;
5227
5269
  }
5228
5270
  };
5229
5271
  watcher.on("change", handleChange).on("add", handleChange).on("unlink", (path5) => {
5230
- console.log(`
5272
+ logger.log(`
5231
5273
  \u{1F5D1}\uFE0F Removed ${path5}`);
5232
5274
  handleChange(path5);
5233
5275
  }).on("error", (error) => {
5234
- console.error("\u274C Watcher error:", error);
5276
+ logger.error("Watcher error:", error);
5235
5277
  });
5236
5278
  process.on("SIGINT", () => {
5237
- console.log("\n\n\u{1F44B} Stopping watcher...");
5279
+ logger.log("\n\n\u{1F44B} Stopping watcher...");
5238
5280
  watcher.close();
5239
5281
  process.exit(0);
5240
5282
  });
@@ -5242,7 +5284,7 @@ async function watchCommand() {
5242
5284
 
5243
5285
  // src/cli/index.ts
5244
5286
  var program = new import_commander.Command();
5245
- program.name("rulesync").description("Unified AI rules management CLI tool").version("0.62.0");
5287
+ program.name("rulesync").description("Unified AI rules management CLI tool").version("0.63.0");
5246
5288
  program.command("init").description("Initialize rulesync in current directory").option("--legacy", "Use legacy file location (.rulesync/*.md instead of .rulesync/rules/*.md)").action(initCommand);
5247
5289
  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);
5248
5290
  program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);