rulesync 0.54.0 → 0.55.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 +401 -210
  2. package/dist/index.js +399 -208
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -435,6 +435,36 @@ function mergeWithCliOptions(config, cliOptions) {
435
435
  return merged;
436
436
  }
437
437
 
438
+ // src/utils/error.ts
439
+ function getErrorMessage(error) {
440
+ return error instanceof Error ? error.message : String(error);
441
+ }
442
+ function formatErrorWithContext(error, context) {
443
+ const errorMessage = getErrorMessage(error);
444
+ return `${context}: ${errorMessage}`;
445
+ }
446
+ function createErrorResult(error, context) {
447
+ const errorMessage = context ? formatErrorWithContext(error, context) : getErrorMessage(error);
448
+ return {
449
+ success: false,
450
+ error: errorMessage
451
+ };
452
+ }
453
+ function createSuccessResult(result) {
454
+ return {
455
+ success: true,
456
+ result
457
+ };
458
+ }
459
+ async function safeAsyncOperation(operation, errorContext) {
460
+ try {
461
+ const result = await operation();
462
+ return createSuccessResult(result);
463
+ } catch (error) {
464
+ return createErrorResult(error, errorContext);
465
+ }
466
+ }
467
+
438
468
  // src/utils/file.ts
439
469
  import { mkdir as mkdir2, readdir, readFile, rm, stat, writeFile as writeFile2 } from "fs/promises";
440
470
  import { dirname, join as join2 } from "path";
@@ -445,6 +475,9 @@ async function ensureDir(dirPath) {
445
475
  await mkdir2(dirPath, { recursive: true });
446
476
  }
447
477
  }
478
+ function resolvePath(relativePath, baseDir) {
479
+ return baseDir ? join2(baseDir, relativePath) : relativePath;
480
+ }
448
481
  async function readFileContent(filepath) {
449
482
  return readFile(filepath, "utf-8");
450
483
  }
@@ -1372,7 +1405,7 @@ function filterIgnoredFiles(files, ignorePatterns) {
1372
1405
 
1373
1406
  // src/generators/rules/shared-helpers.ts
1374
1407
  function resolveOutputDir(config, tool, baseDir) {
1375
- return baseDir ? join5(baseDir, config.outputPaths[tool]) : config.outputPaths[tool];
1408
+ return resolvePath(config.outputPaths[tool], baseDir);
1376
1409
  }
1377
1410
  function createOutputsArray() {
1378
1411
  return [];
@@ -1399,7 +1432,7 @@ async function generateRulesConfig(rules, config, generatorConfig, baseDir) {
1399
1432
  }
1400
1433
  const ignorePatterns = await loadIgnorePatterns(baseDir);
1401
1434
  if (ignorePatterns.patterns.length > 0) {
1402
- const ignorePath = baseDir ? join5(baseDir, generatorConfig.ignoreFileName) : generatorConfig.ignoreFileName;
1435
+ const ignorePath = resolvePath(generatorConfig.ignoreFileName, baseDir);
1403
1436
  const ignoreContent = generateIgnoreFile2(ignorePatterns.patterns, generatorConfig.tool);
1404
1437
  outputs.push({
1405
1438
  tool: generatorConfig.tool,
@@ -1417,7 +1450,10 @@ async function generateComplexRules(rules, config, generatorConfig, baseDir) {
1417
1450
  if (generatorConfig.generateDetailContent && generatorConfig.detailSubDir) {
1418
1451
  for (const rule of detailRules) {
1419
1452
  const content = generatorConfig.generateDetailContent(rule);
1420
- const filepath = baseDir ? join5(baseDir, generatorConfig.detailSubDir, `${rule.filename}.md`) : join5(generatorConfig.detailSubDir, `${rule.filename}.md`);
1453
+ const filepath = resolvePath(
1454
+ join5(generatorConfig.detailSubDir, `${rule.filename}.md`),
1455
+ baseDir
1456
+ );
1421
1457
  outputs.push({
1422
1458
  tool: generatorConfig.tool,
1423
1459
  filepath,
@@ -1427,7 +1463,7 @@ async function generateComplexRules(rules, config, generatorConfig, baseDir) {
1427
1463
  }
1428
1464
  if (generatorConfig.generateRootContent && generatorConfig.rootFilePath) {
1429
1465
  const rootContent = generatorConfig.generateRootContent(rootRule, detailRules, baseDir);
1430
- const rootFilepath = baseDir ? join5(baseDir, generatorConfig.rootFilePath) : generatorConfig.rootFilePath;
1466
+ const rootFilepath = resolvePath(generatorConfig.rootFilePath, baseDir);
1431
1467
  outputs.push({
1432
1468
  tool: generatorConfig.tool,
1433
1469
  filepath: rootFilepath,
@@ -1436,7 +1472,7 @@ async function generateComplexRules(rules, config, generatorConfig, baseDir) {
1436
1472
  }
1437
1473
  const ignorePatterns = await loadIgnorePatterns(baseDir);
1438
1474
  if (ignorePatterns.patterns.length > 0) {
1439
- const ignorePath = baseDir ? join5(baseDir, generatorConfig.ignoreFileName) : generatorConfig.ignoreFileName;
1475
+ const ignorePath = resolvePath(generatorConfig.ignoreFileName, baseDir);
1440
1476
  const ignoreContent = generateIgnoreFile2(ignorePatterns.patterns, generatorConfig.tool);
1441
1477
  outputs.push({
1442
1478
  tool: generatorConfig.tool,
@@ -1541,30 +1577,22 @@ function generateLegacyGuidelinesFile(allRules) {
1541
1577
  // src/generators/rules/claudecode.ts
1542
1578
  import { join as join7 } from "path";
1543
1579
  async function generateClaudecodeConfig(rules, config, baseDir) {
1544
- const outputs = [];
1545
- const rootRules = rules.filter((r) => r.frontmatter.root === true);
1546
- const detailRules = rules.filter((r) => r.frontmatter.root === false);
1547
- const claudeMdContent = generateClaudeMarkdown(rootRules, detailRules);
1548
- const claudeOutputDir = baseDir ? join7(baseDir, config.outputPaths.claudecode) : config.outputPaths.claudecode;
1549
- outputs.push({
1580
+ const generatorConfig = {
1550
1581
  tool: "claudecode",
1551
- filepath: join7(claudeOutputDir, "CLAUDE.md"),
1552
- content: claudeMdContent
1553
- });
1554
- for (const rule of detailRules) {
1555
- const memoryContent = generateMemoryFile(rule);
1556
- outputs.push({
1557
- tool: "claudecode",
1558
- filepath: join7(claudeOutputDir, ".claude", "memories", `${rule.filename}.md`),
1559
- content: memoryContent
1560
- });
1561
- }
1562
- const ignorePatterns = await loadIgnorePatterns(baseDir);
1563
- if (ignorePatterns.patterns.length > 0) {
1564
- const settingsPath = baseDir ? join7(baseDir, ".claude", "settings.json") : join7(".claude", "settings.json");
1565
- await updateClaudeSettings(settingsPath, ignorePatterns.patterns);
1566
- }
1567
- return outputs;
1582
+ fileExtension: ".md",
1583
+ ignoreFileName: ".aiignore",
1584
+ generateContent: generateMemoryFile,
1585
+ generateRootContent: (rootRule, detailRules) => generateClaudeMarkdown(rootRule ? [rootRule] : [], detailRules),
1586
+ rootFilePath: "CLAUDE.md",
1587
+ generateDetailContent: generateMemoryFile,
1588
+ detailSubDir: ".claude/memories",
1589
+ updateAdditionalConfig: async (ignorePatterns, baseDir2) => {
1590
+ const settingsPath = resolvePath(join7(".claude", "settings.json"), baseDir2);
1591
+ await updateClaudeSettings(settingsPath, ignorePatterns);
1592
+ return [];
1593
+ }
1594
+ };
1595
+ return generateComplexRules(rules, config, generatorConfig, baseDir);
1568
1596
  }
1569
1597
  function generateClaudeMarkdown(rootRules, detailRules) {
1570
1598
  const lines = [];
@@ -1624,54 +1652,222 @@ async function updateClaudeSettings(settingsPath, ignorePatterns) {
1624
1652
  console.log(`\u2705 Updated Claude Code settings: ${settingsPath}`);
1625
1653
  }
1626
1654
 
1627
- // src/generators/rules/cline.ts
1628
- async function generateClineConfig(rules, config, baseDir) {
1629
- return generateRulesConfig(
1630
- rules,
1631
- config,
1632
- {
1633
- tool: "cline",
1634
- fileExtension: ".md",
1635
- ignoreFileName: ".clineignore",
1636
- generateContent: (rule) => rule.content.trim()
1655
+ // src/generators/rules/generator-registry.ts
1656
+ import { join as join8 } from "path";
1657
+ var GENERATOR_REGISTRY = {
1658
+ // Simple generators - generate one file per rule
1659
+ cline: {
1660
+ type: "simple",
1661
+ tool: "cline",
1662
+ fileExtension: ".md",
1663
+ ignoreFileName: ".clineignore",
1664
+ generateContent: (rule) => rule.content.trim()
1665
+ },
1666
+ roo: {
1667
+ type: "simple",
1668
+ tool: "roo",
1669
+ fileExtension: ".md",
1670
+ ignoreFileName: ".rooignore",
1671
+ generateContent: (rule) => rule.content.trim()
1672
+ },
1673
+ kiro: {
1674
+ type: "simple",
1675
+ tool: "kiro",
1676
+ fileExtension: ".md",
1677
+ ignoreFileName: ".kiroignore",
1678
+ generateContent: (rule) => rule.content.trim()
1679
+ },
1680
+ augmentcode: {
1681
+ type: "simple",
1682
+ tool: "augmentcode",
1683
+ fileExtension: ".md",
1684
+ ignoreFileName: ".aiignore",
1685
+ generateContent: (rule) => rule.content.trim()
1686
+ },
1687
+ "augmentcode-legacy": {
1688
+ type: "simple",
1689
+ tool: "augmentcode-legacy",
1690
+ fileExtension: ".md",
1691
+ ignoreFileName: ".aiignore",
1692
+ generateContent: (rule) => rule.content.trim()
1693
+ },
1694
+ // Complex generators with custom content formatting
1695
+ copilot: {
1696
+ type: "simple",
1697
+ tool: "copilot",
1698
+ fileExtension: ".instructions.md",
1699
+ ignoreFileName: ".copilotignore",
1700
+ generateContent: (rule) => {
1701
+ const lines = [];
1702
+ lines.push("---");
1703
+ lines.push(`description: "${rule.frontmatter.description}"`);
1704
+ if (rule.frontmatter.globs.length > 0) {
1705
+ lines.push(`applyTo: "${rule.frontmatter.globs.join(", ")}"`);
1706
+ } else {
1707
+ lines.push('applyTo: "**"');
1708
+ }
1709
+ lines.push("---");
1710
+ lines.push(rule.content);
1711
+ return lines.join("\n");
1637
1712
  },
1638
- baseDir
1639
- );
1640
- }
1641
-
1642
- // src/generators/rules/codexcli.ts
1643
- async function generateCodexConfig(rules, config, baseDir) {
1644
- const generatorConfig = {
1713
+ pathResolver: (rule, outputDir) => {
1714
+ const baseFilename = rule.filename.replace(/\.md$/, "");
1715
+ return join8(outputDir, `${baseFilename}.instructions.md`);
1716
+ }
1717
+ },
1718
+ cursor: {
1719
+ type: "simple",
1720
+ tool: "cursor",
1721
+ fileExtension: ".md",
1722
+ ignoreFileName: ".cursorignore",
1723
+ generateContent: (rule) => rule.content.trim()
1724
+ },
1725
+ codexcli: {
1726
+ type: "simple",
1645
1727
  tool: "codexcli",
1646
1728
  fileExtension: ".md",
1647
1729
  ignoreFileName: ".codexignore",
1648
- generateContent: generateCodexInstructionsMarkdown,
1649
- pathResolver: (rule, outputDir) => {
1650
- if (rule.frontmatter.root === true) {
1651
- return `${outputDir}/codex.md`;
1730
+ generateContent: (rule) => rule.content.trim()
1731
+ },
1732
+ // Complex generators with root + detail pattern
1733
+ claudecode: {
1734
+ type: "complex",
1735
+ tool: "claudecode",
1736
+ fileExtension: ".md",
1737
+ ignoreFileName: ".aiignore",
1738
+ generateContent: (rule) => {
1739
+ const lines = [];
1740
+ if (rule.frontmatter.description) {
1741
+ lines.push(`# ${rule.frontmatter.description}
1742
+ `);
1652
1743
  }
1653
- return `${outputDir}/${rule.filename}.md`;
1744
+ lines.push(rule.content.trim());
1745
+ return lines.join("\n");
1654
1746
  }
1655
- };
1656
- return generateRulesConfig(rules, config, generatorConfig, baseDir);
1657
- }
1658
- function generateCodexInstructionsMarkdown(rule) {
1659
- const lines = [];
1660
- if (rule.frontmatter.root === false && rule.frontmatter.description && rule.frontmatter.description !== "Main instructions" && !rule.frontmatter.description.includes("Project-level Codex CLI instructions")) {
1661
- lines.push(`<!-- ${rule.frontmatter.description} -->`);
1662
- if (rule.content.trim()) {
1663
- lines.push("");
1747
+ // NOTE: Claude Code specific logic is handled in the actual generator file
1748
+ // due to complex settings.json manipulation requirements
1749
+ },
1750
+ geminicli: {
1751
+ type: "complex",
1752
+ tool: "geminicli",
1753
+ fileExtension: ".md",
1754
+ ignoreFileName: ".aiexclude",
1755
+ generateContent: (rule) => {
1756
+ const lines = [];
1757
+ if (rule.frontmatter.description) {
1758
+ lines.push(`# ${rule.frontmatter.description}
1759
+ `);
1760
+ }
1761
+ lines.push(rule.content.trim());
1762
+ return lines.join("\n");
1763
+ }
1764
+ // Complex generation handled by existing generator
1765
+ },
1766
+ junie: {
1767
+ type: "complex",
1768
+ tool: "junie",
1769
+ fileExtension: ".md",
1770
+ ignoreFileName: ".aiignore",
1771
+ generateContent: (rule) => {
1772
+ const lines = [];
1773
+ if (rule.frontmatter.description) {
1774
+ lines.push(`# ${rule.frontmatter.description}
1775
+ `);
1776
+ }
1777
+ lines.push(rule.content.trim());
1778
+ return lines.join("\n");
1664
1779
  }
1780
+ // Complex generation handled by existing generator
1665
1781
  }
1666
- const content = rule.content.trim();
1667
- if (content) {
1668
- lines.push(content);
1782
+ };
1783
+ async function generateFromRegistry(tool, rules, config, baseDir) {
1784
+ const generatorConfig = GENERATOR_REGISTRY[tool];
1785
+ if (!generatorConfig) {
1786
+ throw new Error(`No generator configuration found for tool: ${tool}`);
1787
+ }
1788
+ if (generatorConfig.type === "simple") {
1789
+ const ruleConfig = {
1790
+ tool: generatorConfig.tool,
1791
+ fileExtension: generatorConfig.fileExtension,
1792
+ ignoreFileName: generatorConfig.ignoreFileName,
1793
+ generateContent: generatorConfig.generateContent,
1794
+ ...generatorConfig.pathResolver && { pathResolver: generatorConfig.pathResolver }
1795
+ };
1796
+ return generateRulesConfig(rules, config, ruleConfig, baseDir);
1797
+ } else {
1798
+ const enhancedConfig = {
1799
+ tool: generatorConfig.tool,
1800
+ fileExtension: generatorConfig.fileExtension,
1801
+ ignoreFileName: generatorConfig.ignoreFileName,
1802
+ generateContent: generatorConfig.generateContent,
1803
+ ...generatorConfig.generateRootContent && {
1804
+ generateRootContent: generatorConfig.generateRootContent
1805
+ },
1806
+ ...generatorConfig.rootFilePath && { rootFilePath: generatorConfig.rootFilePath },
1807
+ ...generatorConfig.generateDetailContent && {
1808
+ generateDetailContent: generatorConfig.generateDetailContent
1809
+ },
1810
+ ...generatorConfig.detailSubDir && { detailSubDir: generatorConfig.detailSubDir },
1811
+ ...generatorConfig.updateAdditionalConfig && {
1812
+ updateAdditionalConfig: generatorConfig.updateAdditionalConfig
1813
+ }
1814
+ };
1815
+ return generateComplexRules(rules, config, enhancedConfig, baseDir);
1669
1816
  }
1670
- return lines.join("\n");
1817
+ }
1818
+
1819
+ // src/generators/rules/cline.ts
1820
+ async function generateClineConfig(rules, config, baseDir) {
1821
+ return generateFromRegistry("cline", rules, config, baseDir);
1822
+ }
1823
+
1824
+ // src/generators/rules/codexcli.ts
1825
+ async function generateCodexConfig(rules, config, baseDir) {
1826
+ const outputs = [];
1827
+ if (rules.length === 0) {
1828
+ return outputs;
1829
+ }
1830
+ const sortedRules = [...rules].sort((a, b) => {
1831
+ if (a.frontmatter.root === true && b.frontmatter.root !== true) return -1;
1832
+ if (a.frontmatter.root !== true && b.frontmatter.root === true) return 1;
1833
+ return 0;
1834
+ });
1835
+ const concatenatedContent = generateConcatenatedCodexContent(sortedRules);
1836
+ if (concatenatedContent.trim()) {
1837
+ const outputDir = resolveOutputDir(config, "codexcli", baseDir);
1838
+ const filepath = `${outputDir}/codex.md`;
1839
+ outputs.push({
1840
+ tool: "codexcli",
1841
+ filepath,
1842
+ content: concatenatedContent
1843
+ });
1844
+ }
1845
+ const ignorePatterns = await loadIgnorePatterns(baseDir);
1846
+ if (ignorePatterns.patterns.length > 0) {
1847
+ const ignorePath = resolvePath(".codexignore", baseDir);
1848
+ const ignoreContent = generateIgnoreFile2(ignorePatterns.patterns, "codexcli");
1849
+ outputs.push({
1850
+ tool: "codexcli",
1851
+ filepath: ignorePath,
1852
+ content: ignoreContent
1853
+ });
1854
+ }
1855
+ return outputs;
1856
+ }
1857
+ function generateConcatenatedCodexContent(rules) {
1858
+ const sections = [];
1859
+ for (const rule of rules) {
1860
+ const content = rule.content.trim();
1861
+ if (!content) {
1862
+ continue;
1863
+ }
1864
+ sections.push(content);
1865
+ }
1866
+ return sections.join("\n\n---\n\n");
1671
1867
  }
1672
1868
 
1673
1869
  // src/generators/rules/copilot.ts
1674
- import { join as join8 } from "path";
1870
+ import { join as join9 } from "path";
1675
1871
  async function generateCopilotConfig(rules, config, baseDir) {
1676
1872
  return generateComplexRulesConfig(
1677
1873
  rules,
@@ -1683,7 +1879,7 @@ async function generateCopilotConfig(rules, config, baseDir) {
1683
1879
  generateContent: generateCopilotMarkdown,
1684
1880
  getOutputPath: (rule, outputDir) => {
1685
1881
  const baseFilename = rule.filename.replace(/\.md$/, "");
1686
- return join8(outputDir, `${baseFilename}.instructions.md`);
1882
+ return join9(outputDir, `${baseFilename}.instructions.md`);
1687
1883
  }
1688
1884
  },
1689
1885
  baseDir
@@ -1704,7 +1900,7 @@ function generateCopilotMarkdown(rule) {
1704
1900
  }
1705
1901
 
1706
1902
  // src/generators/rules/cursor.ts
1707
- import { join as join9 } from "path";
1903
+ import { join as join10 } from "path";
1708
1904
  async function generateCursorConfig(rules, config, baseDir) {
1709
1905
  return generateComplexRulesConfig(
1710
1906
  rules,
@@ -1715,7 +1911,7 @@ async function generateCursorConfig(rules, config, baseDir) {
1715
1911
  ignoreFileName: ".cursorignore",
1716
1912
  generateContent: generateCursorMarkdown,
1717
1913
  getOutputPath: (rule, outputDir) => {
1718
- return join9(outputDir, `${rule.filename}.mdc`);
1914
+ return join10(outputDir, `${rule.filename}.mdc`);
1719
1915
  }
1720
1916
  },
1721
1917
  baseDir
@@ -1844,38 +2040,13 @@ function generateGuidelinesMarkdown(rootRule, detailRules) {
1844
2040
  }
1845
2041
 
1846
2042
  // src/generators/rules/kiro.ts
1847
- import { join as join10 } from "path";
1848
2043
  async function generateKiroConfig(rules, config, baseDir) {
1849
- const outputs = [];
1850
- for (const rule of rules) {
1851
- const content = generateKiroMarkdown(rule);
1852
- const outputDir = baseDir ? join10(baseDir, config.outputPaths.kiro) : config.outputPaths.kiro;
1853
- const filepath = join10(outputDir, `${rule.filename}.md`);
1854
- outputs.push({
1855
- tool: "kiro",
1856
- filepath,
1857
- content
1858
- });
1859
- }
1860
- return outputs;
1861
- }
1862
- function generateKiroMarkdown(rule) {
1863
- return rule.content.trim();
2044
+ return generateFromRegistry("kiro", rules, config, baseDir);
1864
2045
  }
1865
2046
 
1866
2047
  // src/generators/rules/roo.ts
1867
2048
  async function generateRooConfig(rules, config, baseDir) {
1868
- return generateRulesConfig(
1869
- rules,
1870
- config,
1871
- {
1872
- tool: "roo",
1873
- fileExtension: ".md",
1874
- ignoreFileName: ".rooignore",
1875
- generateContent: (rule) => rule.content.trim()
1876
- },
1877
- baseDir
1878
- );
2049
+ return generateFromRegistry("roo", rules, config, baseDir);
1879
2050
  }
1880
2051
 
1881
2052
  // src/core/generator.ts
@@ -2446,7 +2617,7 @@ ${linesToAdd.join("\n")}
2446
2617
  };
2447
2618
 
2448
2619
  // src/core/importer.ts
2449
- import { join as join21 } from "path";
2620
+ import { join as join20 } from "path";
2450
2621
  import matter5 from "gray-matter";
2451
2622
 
2452
2623
  // src/parsers/augmentcode.ts
@@ -2472,36 +2643,63 @@ function addRules(result, rules) {
2472
2643
  }
2473
2644
  result.rules.push(...rules);
2474
2645
  }
2475
- function handleParseError(error, context) {
2476
- const errorMessage = error instanceof Error ? error.message : String(error);
2477
- return `${context}: ${errorMessage}`;
2478
- }
2479
2646
  async function safeReadFile(operation, errorContext) {
2480
2647
  try {
2481
2648
  const result = await operation();
2482
- return { success: true, result };
2649
+ return createSuccessResult(result);
2483
2650
  } catch (error) {
2484
- return {
2485
- success: false,
2486
- error: handleParseError(error, errorContext)
2487
- };
2651
+ return createErrorResult(error, errorContext);
2488
2652
  }
2489
2653
  }
2490
2654
 
2491
2655
  // src/parsers/augmentcode.ts
2492
2656
  async function parseAugmentcodeConfiguration(baseDir = process.cwd()) {
2657
+ return parseUnifiedAugmentcode(baseDir, {
2658
+ rulesDir: ".augment/rules",
2659
+ targetName: "augmentcode",
2660
+ filenamePrefix: "augmentcode"
2661
+ });
2662
+ }
2663
+ async function parseAugmentcodeLegacyConfiguration(baseDir = process.cwd()) {
2664
+ return parseUnifiedAugmentcode(baseDir, {
2665
+ legacyFilePath: ".augment-guidelines",
2666
+ targetName: "augmentcode-legacy",
2667
+ filenamePrefix: "augmentcode-legacy"
2668
+ });
2669
+ }
2670
+ async function parseUnifiedAugmentcode(baseDir, config) {
2493
2671
  const result = createParseResult();
2494
- const rulesDir = join15(baseDir, ".augment", "rules");
2495
- if (await fileExists(rulesDir)) {
2496
- const rulesResult = await parseAugmentRules(rulesDir);
2497
- addRules(result, rulesResult.rules);
2498
- result.errors.push(...rulesResult.errors);
2499
- } else {
2500
- addError(result, "No AugmentCode configuration found. Expected .augment/rules/ directory.");
2672
+ if (config.rulesDir) {
2673
+ const rulesDir = join15(baseDir, config.rulesDir);
2674
+ if (await fileExists(rulesDir)) {
2675
+ const rulesResult = await parseAugmentRules(rulesDir, config);
2676
+ addRules(result, rulesResult.rules);
2677
+ result.errors.push(...rulesResult.errors);
2678
+ } else {
2679
+ addError(
2680
+ result,
2681
+ `No AugmentCode configuration found. Expected ${config.rulesDir} directory.`
2682
+ );
2683
+ }
2684
+ }
2685
+ if (config.legacyFilePath) {
2686
+ const legacyPath = join15(baseDir, config.legacyFilePath);
2687
+ if (await fileExists(legacyPath)) {
2688
+ const legacyResult = await parseAugmentGuidelines(legacyPath, config);
2689
+ if (legacyResult.rule) {
2690
+ addRule(result, legacyResult.rule);
2691
+ }
2692
+ result.errors.push(...legacyResult.errors);
2693
+ } else {
2694
+ addError(
2695
+ result,
2696
+ `No AugmentCode legacy configuration found. Expected ${config.legacyFilePath} file.`
2697
+ );
2698
+ }
2501
2699
  }
2502
2700
  return { rules: result.rules || [], errors: result.errors };
2503
2701
  }
2504
- async function parseAugmentRules(rulesDir) {
2702
+ async function parseAugmentRules(rulesDir, config) {
2505
2703
  const rules = [];
2506
2704
  const errors = [];
2507
2705
  try {
@@ -2521,7 +2719,7 @@ async function parseAugmentRules(rulesDir) {
2521
2719
  const filename = basename2(file, file.endsWith(".mdc") ? ".mdc" : ".md");
2522
2720
  const frontmatter = {
2523
2721
  root: isRoot,
2524
- targets: ["augmentcode"],
2722
+ targets: [config.targetName],
2525
2723
  description,
2526
2724
  globs: ["**/*"],
2527
2725
  // AugmentCode doesn't use specific globs in the same way
@@ -2530,7 +2728,7 @@ async function parseAugmentRules(rulesDir) {
2530
2728
  rules.push({
2531
2729
  frontmatter,
2532
2730
  content: parsed.content.trim(),
2533
- filename: `augmentcode-${ruleType}-${filename}`,
2731
+ filename: `${config.filenamePrefix}-${ruleType}-${filename}`,
2534
2732
  filepath: filePath
2535
2733
  });
2536
2734
  } catch (error) {
@@ -2541,50 +2739,33 @@ async function parseAugmentRules(rulesDir) {
2541
2739
  }
2542
2740
  } catch (error) {
2543
2741
  const errorMessage = error instanceof Error ? error.message : String(error);
2544
- errors.push(`Failed to read .augment/rules/ directory: ${errorMessage}`);
2742
+ errors.push(`Failed to read ${config.rulesDir || rulesDir} directory: ${errorMessage}`);
2545
2743
  }
2546
2744
  return { rules, errors };
2547
2745
  }
2548
-
2549
- // src/parsers/augmentcode-legacy.ts
2550
- import { join as join16 } from "path";
2551
- async function parseAugmentcodeLegacyConfiguration(baseDir = process.cwd()) {
2552
- const result = createParseResult();
2553
- const guidelinesPath = join16(baseDir, ".augment-guidelines");
2554
- if (await fileExists(guidelinesPath)) {
2555
- const guidelinesResult = await parseAugmentGuidelines(guidelinesPath);
2556
- if (guidelinesResult.rule) {
2557
- addRule(result, guidelinesResult.rule);
2558
- }
2559
- result.errors.push(...guidelinesResult.errors);
2560
- } else {
2561
- addError(
2562
- result,
2563
- "No AugmentCode legacy configuration found. Expected .augment-guidelines file."
2564
- );
2565
- }
2566
- return { rules: result.rules || [], errors: result.errors };
2567
- }
2568
- async function parseAugmentGuidelines(guidelinesPath) {
2569
- const parseResult = await safeReadFile(async () => {
2570
- const content = await readFileContent(guidelinesPath);
2571
- if (content.trim()) {
2572
- const frontmatter = {
2573
- root: true,
2574
- // Legacy guidelines become root rules
2575
- targets: ["augmentcode-legacy"],
2576
- description: "Legacy AugmentCode guidelines",
2577
- globs: ["**/*"]
2578
- };
2579
- return {
2580
- frontmatter,
2581
- content: content.trim(),
2582
- filename: "augmentcode-legacy-guidelines",
2583
- filepath: guidelinesPath
2584
- };
2585
- }
2586
- return null;
2587
- }, "Failed to parse .augment-guidelines");
2746
+ async function parseAugmentGuidelines(guidelinesPath, config) {
2747
+ const parseResult = await safeReadFile(
2748
+ async () => {
2749
+ const content = await readFileContent(guidelinesPath);
2750
+ if (content.trim()) {
2751
+ const frontmatter = {
2752
+ root: true,
2753
+ // Legacy guidelines become root rules
2754
+ targets: [config.targetName],
2755
+ description: "Legacy AugmentCode guidelines",
2756
+ globs: ["**/*"]
2757
+ };
2758
+ return {
2759
+ frontmatter,
2760
+ content: content.trim(),
2761
+ filename: `${config.filenamePrefix}-guidelines`,
2762
+ filepath: guidelinesPath
2763
+ };
2764
+ }
2765
+ return null;
2766
+ },
2767
+ `Failed to parse ${config.legacyFilePath || guidelinesPath}`
2768
+ );
2588
2769
  if (parseResult.success) {
2589
2770
  return { rule: parseResult.result || null, errors: [] };
2590
2771
  } else {
@@ -2593,33 +2774,36 @@ async function parseAugmentGuidelines(guidelinesPath) {
2593
2774
  }
2594
2775
 
2595
2776
  // src/parsers/shared-helpers.ts
2596
- import { basename as basename3, join as join17 } from "path";
2777
+ import { basename as basename3, join as join16 } from "path";
2597
2778
  import matter3 from "gray-matter";
2598
2779
  async function parseConfigurationFiles(baseDir = process.cwd(), config) {
2599
2780
  const errors = [];
2600
2781
  const rules = [];
2601
2782
  if (config.mainFile) {
2602
- const mainFilePath = join17(baseDir, config.mainFile.path);
2783
+ const mainFile = config.mainFile;
2784
+ const mainFilePath = resolvePath(mainFile.path, baseDir);
2603
2785
  if (await fileExists(mainFilePath)) {
2604
- try {
2786
+ const result = await safeAsyncOperation(async () => {
2605
2787
  const rawContent = await readFileContent(mainFilePath);
2606
2788
  let content;
2607
2789
  let frontmatter;
2608
- if (config.mainFile.useFrontmatter) {
2790
+ if (mainFile.useFrontmatter) {
2609
2791
  const parsed = matter3(rawContent);
2610
2792
  content = parsed.content.trim();
2793
+ const parsedFrontmatter = parsed.data;
2611
2794
  frontmatter = {
2612
- root: false,
2795
+ root: mainFile.isRoot ?? false,
2613
2796
  targets: [config.tool],
2614
- description: config.mainFile.description,
2615
- globs: ["**/*"]
2797
+ description: parsedFrontmatter.description || mainFile.description,
2798
+ globs: Array.isArray(parsedFrontmatter.globs) ? parsedFrontmatter.globs : ["**/*"],
2799
+ ...parsedFrontmatter.tags && { tags: parsedFrontmatter.tags }
2616
2800
  };
2617
2801
  } else {
2618
2802
  content = rawContent.trim();
2619
2803
  frontmatter = {
2620
- root: false,
2804
+ root: mainFile.isRoot ?? false,
2621
2805
  targets: [config.tool],
2622
- description: config.mainFile.description,
2806
+ description: mainFile.description,
2623
2807
  globs: ["**/*"]
2624
2808
  };
2625
2809
  }
@@ -2627,43 +2811,52 @@ async function parseConfigurationFiles(baseDir = process.cwd(), config) {
2627
2811
  rules.push({
2628
2812
  frontmatter,
2629
2813
  content,
2630
- filename: "instructions",
2814
+ filename: mainFile.filenameOverride || "instructions",
2631
2815
  filepath: mainFilePath
2632
2816
  });
2633
2817
  }
2634
- } catch (error) {
2635
- const errorMessage = error instanceof Error ? error.message : String(error);
2636
- errors.push(`Failed to parse ${config.mainFile.path}: ${errorMessage}`);
2818
+ }, `Failed to parse ${mainFile.path}`);
2819
+ if (!result.success) {
2820
+ errors.push(result.error);
2637
2821
  }
2638
2822
  }
2639
2823
  }
2640
2824
  if (config.directories) {
2641
2825
  for (const dirConfig of config.directories) {
2642
- const dirPath = join17(baseDir, dirConfig.directory);
2826
+ const dirPath = resolvePath(dirConfig.directory, baseDir);
2643
2827
  if (await fileExists(dirPath)) {
2644
- try {
2828
+ const result = await safeAsyncOperation(async () => {
2645
2829
  const { readdir: readdir2 } = await import("fs/promises");
2646
2830
  const files = await readdir2(dirPath);
2647
2831
  for (const file of files) {
2648
2832
  if (file.endsWith(dirConfig.filePattern)) {
2649
- const filePath = join17(dirPath, file);
2650
- try {
2833
+ const filePath = join16(dirPath, file);
2834
+ const fileResult = await safeAsyncOperation(async () => {
2651
2835
  const rawContent = await readFileContent(filePath);
2652
2836
  let content;
2837
+ let frontmatter;
2838
+ const filename = file.replace(new RegExp(`\\${dirConfig.filePattern}$`), "");
2653
2839
  if (dirConfig.filePattern === ".instructions.md") {
2654
2840
  const parsed = matter3(rawContent);
2655
2841
  content = parsed.content.trim();
2842
+ const parsedFrontmatter = parsed.data;
2843
+ frontmatter = {
2844
+ root: false,
2845
+ targets: [config.tool],
2846
+ description: parsedFrontmatter.description || `${dirConfig.description}: ${filename}`,
2847
+ globs: Array.isArray(parsedFrontmatter.globs) ? parsedFrontmatter.globs : ["**/*"],
2848
+ ...parsedFrontmatter.tags && { tags: parsedFrontmatter.tags }
2849
+ };
2656
2850
  } else {
2657
2851
  content = rawContent.trim();
2658
- }
2659
- if (content) {
2660
- const filename = file.replace(new RegExp(`\\${dirConfig.filePattern}$`), "");
2661
- const frontmatter = {
2852
+ frontmatter = {
2662
2853
  root: false,
2663
2854
  targets: [config.tool],
2664
2855
  description: `${dirConfig.description}: ${filename}`,
2665
2856
  globs: ["**/*"]
2666
2857
  };
2858
+ }
2859
+ if (content) {
2667
2860
  rules.push({
2668
2861
  frontmatter,
2669
2862
  content,
@@ -2671,15 +2864,15 @@ async function parseConfigurationFiles(baseDir = process.cwd(), config) {
2671
2864
  filepath: filePath
2672
2865
  });
2673
2866
  }
2674
- } catch (error) {
2675
- const errorMessage = error instanceof Error ? error.message : String(error);
2676
- errors.push(`Failed to parse ${filePath}: ${errorMessage}`);
2867
+ }, `Failed to parse ${filePath}`);
2868
+ if (!fileResult.success) {
2869
+ errors.push(fileResult.error);
2677
2870
  }
2678
2871
  }
2679
2872
  }
2680
- } catch (error) {
2681
- const errorMessage = error instanceof Error ? error.message : String(error);
2682
- errors.push(`Failed to parse ${dirConfig.directory} files: ${errorMessage}`);
2873
+ }, `Failed to parse ${dirConfig.directory} files`);
2874
+ if (!result.success) {
2875
+ errors.push(result.error);
2683
2876
  }
2684
2877
  }
2685
2878
  }
@@ -2694,7 +2887,7 @@ async function parseMemoryBasedConfiguration(baseDir = process.cwd(), config) {
2694
2887
  const rules = [];
2695
2888
  let ignorePatterns;
2696
2889
  let mcpServers;
2697
- const mainFilePath = join17(baseDir, config.mainFileName);
2890
+ const mainFilePath = resolvePath(config.mainFileName, baseDir);
2698
2891
  if (!await fileExists(mainFilePath)) {
2699
2892
  errors.push(`${config.mainFileName} file not found`);
2700
2893
  return { rules, errors };
@@ -2705,12 +2898,12 @@ async function parseMemoryBasedConfiguration(baseDir = process.cwd(), config) {
2705
2898
  if (mainRule) {
2706
2899
  rules.push(mainRule);
2707
2900
  }
2708
- const memoryDir = join17(baseDir, config.memoryDirPath);
2901
+ const memoryDir = resolvePath(config.memoryDirPath, baseDir);
2709
2902
  if (await fileExists(memoryDir)) {
2710
2903
  const memoryRules = await parseMemoryFiles(memoryDir, config);
2711
2904
  rules.push(...memoryRules);
2712
2905
  }
2713
- const settingsPath = join17(baseDir, config.settingsPath);
2906
+ const settingsPath = resolvePath(config.settingsPath, baseDir);
2714
2907
  if (await fileExists(settingsPath)) {
2715
2908
  const settingsResult = await parseSettingsFile(settingsPath, config.tool);
2716
2909
  if (settingsResult.ignorePatterns) {
@@ -2722,7 +2915,7 @@ async function parseMemoryBasedConfiguration(baseDir = process.cwd(), config) {
2722
2915
  errors.push(...settingsResult.errors);
2723
2916
  }
2724
2917
  if (config.additionalIgnoreFile) {
2725
- const additionalIgnorePath = join17(baseDir, config.additionalIgnoreFile.path);
2918
+ const additionalIgnorePath = resolvePath(config.additionalIgnoreFile.path, baseDir);
2726
2919
  if (await fileExists(additionalIgnorePath)) {
2727
2920
  const additionalPatterns = await config.additionalIgnoreFile.parser(additionalIgnorePath);
2728
2921
  if (additionalPatterns.length > 0) {
@@ -2731,8 +2924,7 @@ async function parseMemoryBasedConfiguration(baseDir = process.cwd(), config) {
2731
2924
  }
2732
2925
  }
2733
2926
  } catch (error) {
2734
- const errorMessage = error instanceof Error ? error.message : String(error);
2735
- errors.push(`Failed to parse ${config.tool} configuration: ${errorMessage}`);
2927
+ errors.push(`Failed to parse ${config.tool} configuration: ${getErrorMessage(error)}`);
2736
2928
  }
2737
2929
  return {
2738
2930
  rules,
@@ -2776,7 +2968,7 @@ async function parseMemoryFiles(memoryDir, config) {
2776
2968
  const files = await readdir2(memoryDir);
2777
2969
  for (const file of files) {
2778
2970
  if (file.endsWith(".md")) {
2779
- const filePath = join17(memoryDir, file);
2971
+ const filePath = join16(memoryDir, file);
2780
2972
  const content = await readFileContent(filePath);
2781
2973
  if (content.trim()) {
2782
2974
  const filename = basename3(file, ".md");
@@ -2828,8 +3020,7 @@ async function parseSettingsFile(settingsPath, tool) {
2828
3020
  mcpServers = parseResult.data.mcpServers;
2829
3021
  }
2830
3022
  } catch (error) {
2831
- const errorMessage = error instanceof Error ? error.message : String(error);
2832
- errors.push(`Failed to parse settings.json: ${errorMessage}`);
3023
+ errors.push(`Failed to parse settings.json: ${getErrorMessage(error)}`);
2833
3024
  }
2834
3025
  return {
2835
3026
  errors,
@@ -2872,7 +3063,7 @@ async function parseClineConfiguration(baseDir = process.cwd()) {
2872
3063
  }
2873
3064
 
2874
3065
  // src/parsers/codexcli.ts
2875
- import { join as join18 } from "path";
3066
+ import { join as join17 } from "path";
2876
3067
 
2877
3068
  // src/parsers/copilot.ts
2878
3069
  async function parseCopilotConfiguration(baseDir = process.cwd()) {
@@ -2895,7 +3086,7 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
2895
3086
  }
2896
3087
 
2897
3088
  // src/parsers/cursor.ts
2898
- import { basename as basename4, join as join19 } from "path";
3089
+ import { basename as basename4, join as join18 } from "path";
2899
3090
  import matter4 from "gray-matter";
2900
3091
  import { DEFAULT_SCHEMA, FAILSAFE_SCHEMA, load } from "js-yaml";
2901
3092
  import { z as z6 } from "zod/mini";
@@ -3020,7 +3211,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3020
3211
  const rules = [];
3021
3212
  let ignorePatterns;
3022
3213
  let mcpServers;
3023
- const cursorFilePath = join19(baseDir, ".cursorrules");
3214
+ const cursorFilePath = join18(baseDir, ".cursorrules");
3024
3215
  if (await fileExists(cursorFilePath)) {
3025
3216
  try {
3026
3217
  const rawContent = await readFileContent(cursorFilePath);
@@ -3041,14 +3232,14 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3041
3232
  errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
3042
3233
  }
3043
3234
  }
3044
- const cursorRulesDir = join19(baseDir, ".cursor", "rules");
3235
+ const cursorRulesDir = join18(baseDir, ".cursor", "rules");
3045
3236
  if (await fileExists(cursorRulesDir)) {
3046
3237
  try {
3047
3238
  const { readdir: readdir2 } = await import("fs/promises");
3048
3239
  const files = await readdir2(cursorRulesDir);
3049
3240
  for (const file of files) {
3050
3241
  if (file.endsWith(".mdc")) {
3051
- const filePath = join19(cursorRulesDir, file);
3242
+ const filePath = join18(cursorRulesDir, file);
3052
3243
  try {
3053
3244
  const rawContent = await readFileContent(filePath);
3054
3245
  const parsed = matter4(rawContent, customMatterOptions);
@@ -3077,7 +3268,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3077
3268
  if (rules.length === 0) {
3078
3269
  errors.push("No Cursor configuration files found (.cursorrules or .cursor/rules/*.mdc)");
3079
3270
  }
3080
- const cursorIgnorePath = join19(baseDir, ".cursorignore");
3271
+ const cursorIgnorePath = join18(baseDir, ".cursorignore");
3081
3272
  if (await fileExists(cursorIgnorePath)) {
3082
3273
  try {
3083
3274
  const content = await readFileContent(cursorIgnorePath);
@@ -3090,7 +3281,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3090
3281
  errors.push(`Failed to parse .cursorignore: ${errorMessage}`);
3091
3282
  }
3092
3283
  }
3093
- const cursorMcpPath = join19(baseDir, ".cursor", "mcp.json");
3284
+ const cursorMcpPath = join18(baseDir, ".cursor", "mcp.json");
3094
3285
  if (await fileExists(cursorMcpPath)) {
3095
3286
  try {
3096
3287
  const content = await readFileContent(cursorMcpPath);
@@ -3139,11 +3330,11 @@ async function parseGeminiConfiguration(baseDir = process.cwd()) {
3139
3330
  }
3140
3331
 
3141
3332
  // src/parsers/junie.ts
3142
- import { join as join20 } from "path";
3333
+ import { join as join19 } from "path";
3143
3334
  async function parseJunieConfiguration(baseDir = process.cwd()) {
3144
3335
  const errors = [];
3145
3336
  const rules = [];
3146
- const guidelinesPath = join20(baseDir, ".junie", "guidelines.md");
3337
+ const guidelinesPath = join19(baseDir, ".junie", "guidelines.md");
3147
3338
  if (!await fileExists(guidelinesPath)) {
3148
3339
  errors.push(".junie/guidelines.md file not found");
3149
3340
  return { rules, errors };
@@ -3278,7 +3469,7 @@ async function importConfiguration(options) {
3278
3469
  if (rules.length === 0 && !ignorePatterns && !mcpServers) {
3279
3470
  return { success: false, rulesCreated: 0, errors };
3280
3471
  }
3281
- const rulesDirPath = join21(baseDir, rulesDir);
3472
+ const rulesDirPath = join20(baseDir, rulesDir);
3282
3473
  try {
3283
3474
  const { mkdir: mkdir3 } = await import("fs/promises");
3284
3475
  await mkdir3(rulesDirPath, { recursive: true });
@@ -3292,7 +3483,7 @@ async function importConfiguration(options) {
3292
3483
  try {
3293
3484
  const baseFilename = rule.filename;
3294
3485
  const filename = await generateUniqueFilename(rulesDirPath, baseFilename);
3295
- const filePath = join21(rulesDirPath, `${filename}.md`);
3486
+ const filePath = join20(rulesDirPath, `${filename}.md`);
3296
3487
  const content = generateRuleFileContent(rule);
3297
3488
  await writeFileContent(filePath, content);
3298
3489
  rulesCreated++;
@@ -3307,7 +3498,7 @@ async function importConfiguration(options) {
3307
3498
  let ignoreFileCreated = false;
3308
3499
  if (ignorePatterns && ignorePatterns.length > 0) {
3309
3500
  try {
3310
- const rulesyncignorePath = join21(baseDir, ".rulesyncignore");
3501
+ const rulesyncignorePath = join20(baseDir, ".rulesyncignore");
3311
3502
  const ignoreContent = `${ignorePatterns.join("\n")}
3312
3503
  `;
3313
3504
  await writeFileContent(rulesyncignorePath, ignoreContent);
@@ -3323,7 +3514,7 @@ async function importConfiguration(options) {
3323
3514
  let mcpFileCreated = false;
3324
3515
  if (mcpServers && Object.keys(mcpServers).length > 0) {
3325
3516
  try {
3326
- const mcpPath = join21(baseDir, rulesDir, ".mcp.json");
3517
+ const mcpPath = join20(baseDir, rulesDir, ".mcp.json");
3327
3518
  const mcpContent = `${JSON.stringify({ mcpServers }, null, 2)}
3328
3519
  `;
3329
3520
  await writeFileContent(mcpPath, mcpContent);
@@ -3351,7 +3542,7 @@ function generateRuleFileContent(rule) {
3351
3542
  async function generateUniqueFilename(rulesDir, baseFilename) {
3352
3543
  let filename = baseFilename;
3353
3544
  let counter = 1;
3354
- while (await fileExists(join21(rulesDir, `${filename}.md`))) {
3545
+ while (await fileExists(join20(rulesDir, `${filename}.md`))) {
3355
3546
  filename = `${baseFilename}-${counter}`;
3356
3547
  counter++;
3357
3548
  }
@@ -3418,7 +3609,7 @@ async function importCommand(options = {}) {
3418
3609
  }
3419
3610
 
3420
3611
  // src/cli/commands/init.ts
3421
- import { join as join22 } from "path";
3612
+ import { join as join21 } from "path";
3422
3613
  async function initCommand() {
3423
3614
  const aiRulesDir = ".rulesync";
3424
3615
  console.log("Initializing rulesync...");
@@ -3465,7 +3656,7 @@ globs: ["**/*"]
3465
3656
  - Follow single responsibility principle
3466
3657
  `
3467
3658
  };
3468
- const filepath = join22(aiRulesDir, sampleFile.filename);
3659
+ const filepath = join21(aiRulesDir, sampleFile.filename);
3469
3660
  if (!await fileExists(filepath)) {
3470
3661
  await writeFileContent(filepath, sampleFile.content);
3471
3662
  console.log(`Created ${filepath}`);
@@ -3609,7 +3800,7 @@ async function watchCommand() {
3609
3800
 
3610
3801
  // src/cli/index.ts
3611
3802
  var program = new Command();
3612
- program.name("rulesync").description("Unified AI rules management CLI tool").version("0.54.0");
3803
+ program.name("rulesync").description("Unified AI rules management CLI tool").version("0.55.0");
3613
3804
  program.command("init").description("Initialize rulesync in current directory").action(initCommand);
3614
3805
  program.command("add <filename>").description("Add a new rule file").action(addCommand);
3615
3806
  program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);