rulesync 0.61.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.
package/dist/index.js CHANGED
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import "./chunk-U63N3YDS.js";
2
+ import "./chunk-M7NL7G7A.js";
3
3
  import "./chunk-LXTA7DBA.js";
4
4
  import "./chunk-PCATT4UZ.js";
5
- import "./chunk-ICMPPX55.js";
5
+ import "./chunk-NETSYSMD.js";
6
6
  import "./chunk-YTU3SCQO.js";
7
- import "./chunk-3ZLMXJTX.js";
8
- import "./chunk-74TYZWHJ.js";
9
- import "./chunk-C5LFJFPS.js";
7
+ import "./chunk-U4PLVMCG.js";
8
+ import "./chunk-UEAYL4NT.js";
9
+ import "./chunk-2CW2KFB3.js";
10
10
  import "./chunk-KUGTKMNW.js";
11
- import "./chunk-DA3XULAD.js";
12
- import "./chunk-LRYVNLH5.js";
13
- import "./chunk-2KYIOT5S.js";
11
+ import "./chunk-4PSTOKKD.js";
12
+ import "./chunk-MDYDKNXQ.js";
13
+ import "./chunk-GQTMTBX4.js";
14
14
  import {
15
15
  ALL_TOOL_TARGETS,
16
16
  RulesyncTargetsSchema,
@@ -26,75 +26,9 @@ import { Command } from "commander";
26
26
  import { mkdir, writeFile } from "fs/promises";
27
27
  import * as path from "path";
28
28
 
29
- // src/utils/config.ts
30
- function getDefaultConfig() {
31
- return {
32
- aiRulesDir: ".rulesync",
33
- outputPaths: {
34
- augmentcode: ".",
35
- "augmentcode-legacy": ".",
36
- copilot: ".github/instructions",
37
- cursor: ".cursor/rules",
38
- cline: ".clinerules",
39
- claudecode: ".",
40
- codexcli: ".",
41
- roo: ".roo/rules",
42
- geminicli: ".gemini/memories",
43
- kiro: ".kiro/steering",
44
- junie: ".",
45
- windsurf: "."
46
- },
47
- watchEnabled: false,
48
- defaultTargets: ALL_TOOL_TARGETS.filter((tool) => tool !== "augmentcode-legacy")
49
- };
50
- }
51
- function resolveTargets(targets, config) {
52
- if (targets.length === 1 && targets[0] === "*") {
53
- return config.defaultTargets;
54
- }
55
- const validatedTargets = ToolTargetsSchema.parse(targets);
56
- return validatedTargets;
57
- }
58
-
59
- // src/cli/commands/add.ts
60
- function sanitizeFilename(filename) {
61
- return filename.endsWith(".md") ? filename.slice(0, -3) : filename;
62
- }
63
- function generateRuleTemplate(filename) {
64
- return `---
65
- root: false
66
- targets: ["*"]
67
- description: "Rules for ${filename}"
68
- globs: []
69
- ---
70
-
71
- # ${filename.charAt(0).toUpperCase() + filename.slice(1)} Rules
72
-
73
- Add your rules here.
74
- `;
75
- }
76
- async function addCommand(filename) {
77
- try {
78
- const config = getDefaultConfig();
79
- const sanitizedFilename = sanitizeFilename(filename);
80
- const rulesDir = config.aiRulesDir;
81
- const filePath = path.join(rulesDir, `${sanitizedFilename}.md`);
82
- await mkdir(rulesDir, { recursive: true });
83
- const template = generateRuleTemplate(sanitizedFilename);
84
- await writeFile(filePath, template, "utf8");
85
- console.log(`\u2705 Created rule file: ${filePath}`);
86
- console.log(`\u{1F4DD} Edit the file to customize your rules.`);
87
- } catch (error) {
88
- console.error(
89
- `\u274C Failed to create rule file: ${error instanceof Error ? error.message : String(error)}`
90
- );
91
- process.exit(3);
92
- }
93
- }
94
-
95
- // src/cli/commands/config.ts
96
- import { writeFileSync } from "fs";
97
- import path2 from "path";
29
+ // src/utils/config-loader.ts
30
+ import { loadConfig as loadC12Config } from "c12";
31
+ import { $ZodError } from "zod/v4/core";
98
32
 
99
33
  // src/types/claudecode.ts
100
34
  import { z } from "zod/mini";
@@ -107,12 +41,20 @@ var ClaudeSettingsSchema = z.looseObject({
107
41
  )
108
42
  });
109
43
 
110
- // src/types/commands.ts
44
+ // src/types/shared.ts
111
45
  import { z as z2 } from "zod/mini";
112
- var CommandFrontmatterSchema = z2.object({
46
+ var OutputSchema = z2.object({
47
+ tool: ToolTargetSchema,
48
+ filepath: z2.string(),
49
+ content: z2.string()
50
+ });
51
+ var BaseFrontmatterSchema = z2.object({
113
52
  description: z2.optional(z2.string())
114
53
  });
115
54
 
55
+ // src/types/commands.ts
56
+ var CommandFrontmatterSchema = BaseFrontmatterSchema;
57
+
116
58
  // src/types/config.ts
117
59
  import { z as z3 } from "zod/mini";
118
60
  var ConfigSchema = z3.object({
@@ -121,7 +63,8 @@ var ConfigSchema = z3.object({
121
63
  watchEnabled: z3.boolean(),
122
64
  defaultTargets: ToolTargetsSchema,
123
65
  claudecodeCommands: z3.optional(z3.string()),
124
- geminicliCommands: z3.optional(z3.string())
66
+ geminicliCommands: z3.optional(z3.string()),
67
+ legacy: z3.optional(z3.boolean())
125
68
  });
126
69
 
127
70
  // src/types/config-options.ts
@@ -150,6 +93,7 @@ var ConfigOptionsSchema = z4.object({
150
93
  verbose: z4.optional(z4.boolean()),
151
94
  delete: z4.optional(z4.boolean()),
152
95
  baseDir: z4.optional(z4.union([z4.string(), z4.array(z4.string())])),
96
+ legacy: z4.optional(z4.boolean()),
153
97
  watch: z4.optional(
154
98
  z4.object({
155
99
  enabled: z4.optional(z4.boolean()),
@@ -169,6 +113,7 @@ var MergedConfigSchema = z4.object({
169
113
  delete: z4.optional(z4.boolean()),
170
114
  baseDir: z4.optional(z4.union([z4.string(), z4.array(z4.string())])),
171
115
  configPath: z4.optional(z4.string()),
116
+ legacy: z4.optional(z4.boolean()),
172
117
  watch: z4.optional(
173
118
  z4.object({
174
119
  enabled: z4.optional(z4.boolean()),
@@ -222,20 +167,44 @@ var RuleFrontmatterSchema = z6.object({
222
167
  windsurfOutputFormat: z6.optional(z6.enum(["single-file", "directory"])),
223
168
  tags: z6.optional(z6.array(z6.string()))
224
169
  });
225
- var GeneratedOutputSchema = z6.object({
226
- tool: ToolTargetSchema,
227
- filepath: z6.string(),
228
- content: z6.string()
229
- });
230
170
  var GenerateOptionsSchema = z6.object({
231
171
  targetTools: z6.optional(ToolTargetsSchema),
232
172
  outputDir: z6.optional(z6.string()),
233
173
  watch: z6.optional(z6.boolean())
234
174
  });
235
175
 
176
+ // src/utils/config.ts
177
+ function getDefaultConfig() {
178
+ return {
179
+ aiRulesDir: ".rulesync",
180
+ outputPaths: {
181
+ augmentcode: ".",
182
+ "augmentcode-legacy": ".",
183
+ copilot: ".github/instructions",
184
+ cursor: ".cursor/rules",
185
+ cline: ".clinerules",
186
+ claudecode: ".",
187
+ codexcli: ".",
188
+ roo: ".roo/rules",
189
+ geminicli: ".gemini/memories",
190
+ kiro: ".kiro/steering",
191
+ junie: ".",
192
+ windsurf: "."
193
+ },
194
+ watchEnabled: false,
195
+ defaultTargets: ALL_TOOL_TARGETS.filter((tool) => tool !== "augmentcode-legacy"),
196
+ legacy: false
197
+ };
198
+ }
199
+ function resolveTargets(targets, config) {
200
+ if (targets.length === 1 && targets[0] === "*") {
201
+ return config.defaultTargets;
202
+ }
203
+ const validatedTargets = ToolTargetsSchema.parse(targets);
204
+ return validatedTargets;
205
+ }
206
+
236
207
  // src/utils/config-loader.ts
237
- import { loadConfig as loadC12Config } from "c12";
238
- import { $ZodError } from "zod/v4/core";
239
208
  var MODULE_NAME = "rulesync";
240
209
  async function loadConfig(options = {}) {
241
210
  const defaultConfig = getDefaultConfig();
@@ -422,6 +391,93 @@ function mergeWithCliOptions(config, cliOptions) {
422
391
  return merged;
423
392
  }
424
393
 
394
+ // src/utils/logger.ts
395
+ import { consola } from "consola";
396
+ var Logger = class {
397
+ _verbose = false;
398
+ console = consola.withDefaults({
399
+ tag: "rulesync"
400
+ });
401
+ setVerbose(verbose) {
402
+ this._verbose = verbose;
403
+ }
404
+ get verbose() {
405
+ return this._verbose;
406
+ }
407
+ // Regular log (always shown, regardless of verbose)
408
+ log(message, ...args) {
409
+ this.console.log(message, ...args);
410
+ }
411
+ // Info level (shown only in verbose mode)
412
+ info(message, ...args) {
413
+ if (this._verbose) {
414
+ this.console.info(message, ...args);
415
+ }
416
+ }
417
+ // Success (always shown)
418
+ success(message, ...args) {
419
+ this.console.success(message, ...args);
420
+ }
421
+ // Warning (always shown)
422
+ warn(message, ...args) {
423
+ this.console.warn(message, ...args);
424
+ }
425
+ // Error (always shown)
426
+ error(message, ...args) {
427
+ this.console.error(message, ...args);
428
+ }
429
+ // Debug level (shown only in verbose mode)
430
+ debug(message, ...args) {
431
+ if (this._verbose) {
432
+ this.console.debug(message, ...args);
433
+ }
434
+ }
435
+ };
436
+ var logger = new Logger();
437
+
438
+ // src/cli/commands/add.ts
439
+ function sanitizeFilename(filename) {
440
+ return filename.endsWith(".md") ? filename.slice(0, -3) : filename;
441
+ }
442
+ function generateRuleTemplate(filename) {
443
+ return `---
444
+ root: false
445
+ targets: ["*"]
446
+ description: "Rules for ${filename}"
447
+ globs: []
448
+ ---
449
+
450
+ # ${filename.charAt(0).toUpperCase() + filename.slice(1)} Rules
451
+
452
+ Add your rules here.
453
+ `;
454
+ }
455
+ async function addCommand(filename, options = {}) {
456
+ try {
457
+ const configResult = await loadConfig();
458
+ const config = configResult.config;
459
+ const sanitizedFilename = sanitizeFilename(filename);
460
+ const aiRulesDir = config.aiRulesDir;
461
+ const useLegacy = options.legacy ?? config.legacy ?? false;
462
+ const rulesDir = useLegacy ? aiRulesDir : path.join(aiRulesDir, "rules");
463
+ const filePath = path.join(rulesDir, `${sanitizedFilename}.md`);
464
+ await mkdir(rulesDir, { recursive: true });
465
+ const template = generateRuleTemplate(sanitizedFilename);
466
+ await writeFile(filePath, template, "utf8");
467
+ logger.success(`Created rule file: ${filePath}`);
468
+ logger.log(`\u{1F4DD} Edit the file to customize your rules.`);
469
+ } catch (error) {
470
+ logger.error(
471
+ `Failed to create rule file: ${error instanceof Error ? error.message : String(error)}`
472
+ );
473
+ process.exit(3);
474
+ }
475
+ }
476
+
477
+ // src/cli/commands/config.ts
478
+ import { writeFileSync } from "fs";
479
+ import path2 from "path";
480
+
425
481
  // src/utils/error.ts
426
482
  function getErrorMessage(error) {
427
483
  return error instanceof Error ? error.message : String(error);
@@ -488,10 +544,23 @@ async function findFiles(dir, extension = ".md") {
488
544
  return [];
489
545
  }
490
546
  }
547
+ async function findRuleFiles(aiRulesDir) {
548
+ const rulesDir = join2(aiRulesDir, "rules");
549
+ const newLocationFiles = await findFiles(rulesDir, ".md");
550
+ const legacyLocationFiles = await findFiles(aiRulesDir, ".md");
551
+ const newLocationBasenames = new Set(
552
+ newLocationFiles.map((file) => file.split("/").pop()?.replace(/\.md$/, ""))
553
+ );
554
+ const filteredLegacyFiles = legacyLocationFiles.filter((file) => {
555
+ const basename6 = file.split("/").pop()?.replace(/\.md$/, "");
556
+ return !newLocationBasenames.has(basename6);
557
+ });
558
+ return [...newLocationFiles, ...filteredLegacyFiles];
559
+ }
491
560
  async function removeDirectory(dirPath) {
492
561
  const dangerousPaths = [".", "/", "~", "src", "node_modules"];
493
562
  if (dangerousPaths.includes(dirPath) || dirPath === "") {
494
- console.warn(`Skipping deletion of dangerous path: ${dirPath}`);
563
+ logger.warn(`Skipping deletion of dangerous path: ${dirPath}`);
495
564
  return;
496
565
  }
497
566
  try {
@@ -499,7 +568,7 @@ async function removeDirectory(dirPath) {
499
568
  await rm(dirPath, { recursive: true, force: true });
500
569
  }
501
570
  } catch (error) {
502
- console.warn(`Failed to remove directory ${dirPath}:`, error);
571
+ logger.warn(`Failed to remove directory ${dirPath}:`, error);
503
572
  }
504
573
  }
505
574
  async function removeFile(filepath) {
@@ -508,7 +577,7 @@ async function removeFile(filepath) {
508
577
  await rm(filepath);
509
578
  }
510
579
  } catch (error) {
511
- console.warn(`Failed to remove file ${filepath}:`, error);
580
+ logger.warn(`Failed to remove file ${filepath}:`, error);
512
581
  }
513
582
  }
514
583
  async function removeClaudeGeneratedFiles() {
@@ -531,50 +600,50 @@ async function configCommand(options = {}) {
531
600
  await showConfig();
532
601
  }
533
602
  async function showConfig() {
534
- console.log("Loading configuration...\n");
603
+ logger.log("Loading configuration...\n");
535
604
  try {
536
605
  const result = await loadConfig();
537
606
  if (result.isEmpty) {
538
- console.log("No configuration file found. Using default configuration.\n");
607
+ logger.log("No configuration file found. Using default configuration.\n");
539
608
  } else {
540
- console.log(`Configuration loaded from: ${result.filepath}
609
+ logger.log(`Configuration loaded from: ${result.filepath}
541
610
  `);
542
611
  }
543
- console.log("Current configuration:");
544
- console.log("=====================");
612
+ logger.log("Current configuration:");
613
+ logger.log("=====================");
545
614
  const config = result.config;
546
- console.log(`
615
+ logger.log(`
547
616
  AI Rules Directory: ${config.aiRulesDir}`);
548
- console.log(`
617
+ logger.log(`
549
618
  Default Targets: ${config.defaultTargets.join(", ")}`);
550
619
  if (config.exclude && config.exclude.length > 0) {
551
- console.log(`Excluded Targets: ${config.exclude.join(", ")}`);
620
+ logger.log(`Excluded Targets: ${config.exclude.join(", ")}`);
552
621
  }
553
- console.log("\nOutput Paths:");
622
+ logger.log("\nOutput Paths:");
554
623
  for (const [tool, outputPath] of Object.entries(config.outputPaths)) {
555
- console.log(` ${tool}: ${outputPath}`);
624
+ logger.log(` ${tool}: ${outputPath}`);
556
625
  }
557
626
  if (config.baseDir) {
558
627
  const dirs = Array.isArray(config.baseDir) ? config.baseDir : [config.baseDir];
559
- console.log(`
628
+ logger.log(`
560
629
  Base Directories: ${dirs.join(", ")}`);
561
630
  }
562
- console.log(`
631
+ logger.log(`
563
632
  Verbose: ${config.verbose || false}`);
564
- console.log(`Delete before generate: ${config.delete || false}`);
633
+ logger.log(`Delete before generate: ${config.delete || false}`);
565
634
  if (config.watch) {
566
- console.log("\nWatch Configuration:");
567
- console.log(` Enabled: ${config.watch.enabled || false}`);
635
+ logger.log("\nWatch Configuration:");
636
+ logger.log(` Enabled: ${config.watch.enabled || false}`);
568
637
  if (config.watch.interval) {
569
- console.log(` Interval: ${config.watch.interval}ms`);
638
+ logger.log(` Interval: ${config.watch.interval}ms`);
570
639
  }
571
640
  if (config.watch.ignore && config.watch.ignore.length > 0) {
572
- console.log(` Ignore patterns: ${config.watch.ignore.join(", ")}`);
641
+ logger.log(` Ignore patterns: ${config.watch.ignore.join(", ")}`);
573
642
  }
574
643
  }
575
- console.log("\nTip: Use 'rulesync config init' to create a configuration file.");
644
+ logger.log("\nTip: Use 'rulesync config init' to create a configuration file.");
576
645
  } catch (error) {
577
- console.error(
646
+ logger.error(
578
647
  "\u274C Failed to load configuration:",
579
648
  error instanceof Error ? error.message : String(error)
580
649
  );
@@ -595,7 +664,7 @@ async function initConfig(options) {
595
664
  const validFormats = Object.keys(FORMAT_CONFIG);
596
665
  const selectedFormat = options.format || "jsonc";
597
666
  if (!validFormats.includes(selectedFormat)) {
598
- console.error(
667
+ logger.error(
599
668
  `\u274C Invalid format: ${selectedFormat}. Valid formats are: ${validFormats.join(", ")}`
600
669
  );
601
670
  process.exit(1);
@@ -611,7 +680,7 @@ async function initConfig(options) {
611
680
  if (result.success) {
612
681
  validTargets.push(result.data);
613
682
  } else {
614
- console.error(`\u274C Invalid target: ${target}`);
683
+ logger.error(`\u274C Invalid target: ${target}`);
615
684
  process.exit(1);
616
685
  }
617
686
  }
@@ -625,7 +694,7 @@ async function initConfig(options) {
625
694
  if (result.success) {
626
695
  validExcludes.push(result.data);
627
696
  } else {
628
- console.error(`\u274C Invalid exclude target: ${exclude}`);
697
+ logger.error(`\u274C Invalid exclude target: ${exclude}`);
629
698
  process.exit(1);
630
699
  }
631
700
  }
@@ -648,18 +717,18 @@ async function initConfig(options) {
648
717
  try {
649
718
  const fs2 = await import("fs/promises");
650
719
  await fs2.access(filepath);
651
- console.error(`\u274C Configuration file already exists: ${filepath}`);
652
- console.log("Remove the existing file or choose a different format.");
720
+ logger.error(`\u274C Configuration file already exists: ${filepath}`);
721
+ logger.log("Remove the existing file or choose a different format.");
653
722
  process.exit(1);
654
723
  } catch {
655
724
  }
656
725
  try {
657
726
  writeFileSync(filepath, content, "utf-8");
658
- console.log(`\u2705 Created configuration file: ${filepath}`);
659
- console.log("\nYou can now customize the configuration to fit your needs.");
660
- console.log("Run 'rulesync generate' to use the new configuration.");
727
+ logger.success(`Created configuration file: ${filepath}`);
728
+ logger.log("\nYou can now customize the configuration to fit your needs.");
729
+ logger.log("Run 'rulesync generate' to use the new configuration.");
661
730
  } catch (error) {
662
- console.error(
731
+ logger.error(
663
732
  `\u274C Failed to create configuration file: ${error instanceof Error ? error.message : String(error)}`
664
733
  );
665
734
  process.exit(1);
@@ -733,25 +802,68 @@ export default config;
733
802
  }
734
803
 
735
804
  // src/cli/commands/generate.ts
736
- import { join as join15 } from "path";
805
+ import { join as join13 } from "path";
737
806
 
738
807
  // src/core/command-generator.ts
739
- import { join as join6 } from "path";
808
+ import { join as join4 } from "path";
740
809
 
741
- // src/generators/commands/claudecode.ts
810
+ // src/utils/command-generators.ts
742
811
  import { join as join3 } from "path";
743
- var ClaudeCodeCommandGenerator = class {
744
- generate(command, outputDir) {
745
- const filepath = this.getOutputPath(command.filename, outputDir);
746
- const frontmatter = ["---"];
747
- if (command.frontmatter.description) {
748
- frontmatter.push(`description: ${command.frontmatter.description}`);
812
+ function generateYamlFrontmatter(command, options = {}) {
813
+ const frontmatter = ["---"];
814
+ if (options.includeDescription !== false && command.frontmatter.description) {
815
+ frontmatter.push(`description: ${command.frontmatter.description}`);
816
+ }
817
+ if (options.additionalFields) {
818
+ for (const field of options.additionalFields) {
819
+ frontmatter.push(`${field.key}: ${field.value}`);
749
820
  }
750
- frontmatter.push("---");
751
- const content = `${frontmatter.join("\n")}
821
+ }
822
+ frontmatter.push("---");
823
+ return frontmatter;
824
+ }
825
+ function buildCommandContent(command, frontmatterOptions) {
826
+ const frontmatter = generateYamlFrontmatter(command, frontmatterOptions);
827
+ return `${frontmatter.join("\n")}
752
828
 
753
829
  ${command.content.trim()}
754
830
  `;
831
+ }
832
+ function getFlattenedCommandPath(filename, baseDir, subdir) {
833
+ const flattenedName = filename.replace(/\//g, "-");
834
+ return join3(baseDir, subdir, `${flattenedName}.md`);
835
+ }
836
+ function getHierarchicalCommandPath(filename, baseDir, subdir, extension = "md") {
837
+ const nameWithoutExt = filename.replace(/\.[^/.]+$/, "");
838
+ const fileWithExt = `${nameWithoutExt}.${extension}`;
839
+ return join3(baseDir, subdir, fileWithExt);
840
+ }
841
+ function escapeTomlString(str) {
842
+ return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
843
+ }
844
+ var syntaxConverters = {
845
+ /**
846
+ * Convert Claude Code syntax to Gemini CLI syntax
847
+ */
848
+ toGeminiCli(content) {
849
+ let converted = content;
850
+ converted = converted.replace(/\$ARGUMENTS/g, "{{args}}");
851
+ converted = converted.replace(/!`([^`]+)`/g, "!{$1}");
852
+ return converted.trim();
853
+ },
854
+ /**
855
+ * Convert to Roo Code syntax (currently identical to Claude Code)
856
+ */
857
+ toRooCode(content) {
858
+ return content.trim();
859
+ }
860
+ };
861
+
862
+ // src/generators/commands/claudecode.ts
863
+ var ClaudeCodeCommandGenerator = class {
864
+ generate(command, outputDir) {
865
+ const filepath = this.getOutputPath(command.filename, outputDir);
866
+ const content = buildCommandContent(command);
755
867
  return {
756
868
  tool: "claudecode",
757
869
  filepath,
@@ -759,20 +871,18 @@ ${command.content.trim()}
759
871
  };
760
872
  }
761
873
  getOutputPath(filename, baseDir) {
762
- const flattenedName = filename.replace(/\//g, "-");
763
- return join3(baseDir, ".claude", "commands", `${flattenedName}.md`);
874
+ return getFlattenedCommandPath(filename, baseDir, ".claude/commands");
764
875
  }
765
876
  };
766
877
 
767
878
  // src/generators/commands/geminicli.ts
768
- import { join as join4 } from "path";
769
879
  var GeminiCliCommandGenerator = class {
770
880
  generate(command, outputDir) {
771
881
  const filepath = this.getOutputPath(command.filename, outputDir);
772
- const convertedContent = this.convertSyntax(command.content);
882
+ const convertedContent = syntaxConverters.toGeminiCli(command.content);
773
883
  const tomlLines = [];
774
884
  if (command.frontmatter.description) {
775
- tomlLines.push(`description = "${this.escapeTomlString(command.frontmatter.description)}"`);
885
+ tomlLines.push(`description = "${escapeTomlString(command.frontmatter.description)}"`);
776
886
  tomlLines.push("");
777
887
  }
778
888
  tomlLines.push(`prompt = """${convertedContent}"""`);
@@ -784,41 +894,15 @@ var GeminiCliCommandGenerator = class {
784
894
  };
785
895
  }
786
896
  getOutputPath(filename, baseDir) {
787
- const tomlFilename = filename.replace(/\.md$/, ".toml");
788
- const filenameWithExt = tomlFilename.endsWith(".toml") ? tomlFilename : `${tomlFilename}.toml`;
789
- return join4(baseDir, ".gemini", "commands", filenameWithExt);
790
- }
791
- convertSyntax(content) {
792
- let converted = content;
793
- converted = converted.replace(/\$ARGUMENTS/g, "{{args}}");
794
- converted = converted.replace(/!`([^`]+)`/g, "!{$1}");
795
- const atSyntaxMatches = converted.match(/@[^\s]+/g);
796
- if (atSyntaxMatches) {
797
- console.warn(
798
- `\u26A0\uFE0F Warning: @ syntax found (${atSyntaxMatches.join(", ")}). Gemini CLI does not support file content injection. Consider using shell commands or remove these references.`
799
- );
800
- }
801
- return converted.trim();
802
- }
803
- escapeTomlString(str) {
804
- return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
897
+ return getHierarchicalCommandPath(filename, baseDir, ".gemini/commands", "toml");
805
898
  }
806
899
  };
807
900
 
808
901
  // src/generators/commands/roo.ts
809
- import { join as join5 } from "path";
810
902
  var RooCommandGenerator = class {
811
903
  generate(command, outputDir) {
812
904
  const filepath = this.getOutputPath(command.filename, outputDir);
813
- const frontmatter = ["---"];
814
- if (command.frontmatter.description) {
815
- frontmatter.push(`description: ${command.frontmatter.description}`);
816
- }
817
- frontmatter.push("---");
818
- const content = `${frontmatter.join("\n")}
819
-
820
- ${command.content.trim()}
821
- `;
905
+ const content = buildCommandContent(command);
822
906
  return {
823
907
  tool: "roo",
824
908
  filepath,
@@ -826,8 +910,7 @@ ${command.content.trim()}
826
910
  };
827
911
  }
828
912
  getOutputPath(filename, baseDir) {
829
- const flattenedName = filename.replace(/\//g, "-");
830
- return join5(baseDir, ".roo", "commands", `${flattenedName}.md`);
913
+ return getFlattenedCommandPath(filename, baseDir, ".roo/commands");
831
914
  }
832
915
  };
833
916
 
@@ -843,7 +926,26 @@ function getCommandGenerator(tool) {
843
926
 
844
927
  // src/core/command-parser.ts
845
928
  import { basename } from "path";
929
+
930
+ // src/utils/frontmatter.ts
846
931
  import matter from "gray-matter";
932
+ function parseFrontmatter(content, options) {
933
+ const parsed = matter(content, options?.matterOptions);
934
+ return {
935
+ content: parsed.content.trim(),
936
+ data: parsed.data || {}
937
+ };
938
+ }
939
+ function extractArrayField(data, key, defaultValue = []) {
940
+ const value = data[key];
941
+ return Array.isArray(value) ? value : defaultValue;
942
+ }
943
+ function extractStringField(data, key, defaultValue) {
944
+ const value = data[key];
945
+ return typeof value === "string" ? value : defaultValue;
946
+ }
947
+
948
+ // src/core/command-parser.ts
847
949
  async function parseCommandsFromDirectory(commandsDir) {
848
950
  const commandFiles = await findFiles(commandsDir, ".md");
849
951
  const commands = [];
@@ -858,14 +960,14 @@ async function parseCommandsFromDirectory(commandsDir) {
858
960
  }
859
961
  }
860
962
  if (errors.length > 0) {
861
- console.warn(`\u26A0\uFE0F Command parsing errors:
963
+ logger.warn(`Command parsing errors:
862
964
  ${errors.join("\n")}`);
863
965
  }
864
966
  return commands;
865
967
  }
866
968
  async function parseCommandFile(filepath) {
867
969
  const content = await readFileContent(filepath);
868
- const parsed = matter(content);
970
+ const parsed = parseFrontmatter(content);
869
971
  try {
870
972
  const validatedData = CommandFrontmatterSchema.parse(parsed.data);
871
973
  const filename = basename(filepath, ".md");
@@ -886,7 +988,7 @@ async function parseCommandFile(filepath) {
886
988
 
887
989
  // src/core/command-generator.ts
888
990
  async function generateCommands(projectRoot, baseDir, targets) {
889
- const commandsDir = join6(projectRoot, ".rulesync", "commands");
991
+ const commandsDir = join4(projectRoot, ".rulesync", "commands");
890
992
  if (!await fileExists(commandsDir)) {
891
993
  return [];
892
994
  }
@@ -910,8 +1012,8 @@ async function generateCommands(projectRoot, baseDir, targets) {
910
1012
  outputs.push(output);
911
1013
  } catch (error) {
912
1014
  const errorMessage = error instanceof Error ? error.message : String(error);
913
- console.error(
914
- `\u274C Failed to generate ${target} command for ${command.filename}: ${errorMessage}`
1015
+ logger.error(
1016
+ `Failed to generate ${target} command for ${command.filename}: ${errorMessage}`
915
1017
  );
916
1018
  }
917
1019
  }
@@ -920,7 +1022,7 @@ async function generateCommands(projectRoot, baseDir, targets) {
920
1022
  }
921
1023
 
922
1024
  // src/generators/ignore/shared-factory.ts
923
- import { join as join7 } from "path";
1025
+ import { join as join5 } from "path";
924
1026
 
925
1027
  // src/generators/ignore/shared-helpers.ts
926
1028
  function extractIgnorePatternsFromRules(rules) {
@@ -1043,7 +1145,7 @@ function generateIgnoreFile(rules, config, ignoreConfig, baseDir) {
1043
1145
  const outputs = [];
1044
1146
  const content = generateIgnoreContent(rules, ignoreConfig);
1045
1147
  const outputPath = baseDir || process.cwd();
1046
- const filepath = join7(outputPath, ignoreConfig.filename);
1148
+ const filepath = join5(outputPath, ignoreConfig.filename);
1047
1149
  outputs.push({
1048
1150
  tool: ignoreConfig.tool,
1049
1151
  filepath,
@@ -1631,20 +1733,20 @@ function generateWindsurfIgnore(rules, config, baseDir) {
1631
1733
  }
1632
1734
 
1633
1735
  // src/generators/rules/augmentcode.ts
1634
- import { join as join10 } from "path";
1736
+ import { join as join8 } from "path";
1635
1737
 
1636
1738
  // src/generators/rules/shared-helpers.ts
1637
- import { join as join9 } from "path";
1739
+ import { join as join7 } from "path";
1638
1740
 
1639
1741
  // src/utils/ignore.ts
1640
- import { join as join8 } from "path";
1742
+ import { join as join6 } from "path";
1641
1743
  import micromatch from "micromatch";
1642
1744
  var cachedIgnorePatterns = null;
1643
1745
  async function loadIgnorePatterns(baseDir = process.cwd()) {
1644
1746
  if (cachedIgnorePatterns) {
1645
1747
  return cachedIgnorePatterns;
1646
1748
  }
1647
- const ignorePath = join8(baseDir, ".rulesyncignore");
1749
+ const ignorePath = join6(baseDir, ".rulesyncignore");
1648
1750
  if (!await fileExists(ignorePath)) {
1649
1751
  cachedIgnorePatterns = { patterns: [] };
1650
1752
  return cachedIgnorePatterns;
@@ -1655,7 +1757,7 @@ async function loadIgnorePatterns(baseDir = process.cwd()) {
1655
1757
  cachedIgnorePatterns = { patterns };
1656
1758
  return cachedIgnorePatterns;
1657
1759
  } catch (error) {
1658
- console.warn(`Failed to read .rulesyncignore: ${error}`);
1760
+ logger.warn(`Failed to read .rulesyncignore: ${error}`);
1659
1761
  cachedIgnorePatterns = { patterns: [] };
1660
1762
  return cachedIgnorePatterns;
1661
1763
  }
@@ -1698,7 +1800,7 @@ function addOutput(outputs, tool, config, baseDir, relativePath, content) {
1698
1800
  const outputDir = resolveOutputDir(config, tool, baseDir);
1699
1801
  outputs.push({
1700
1802
  tool,
1701
- filepath: join9(outputDir, relativePath),
1803
+ filepath: join7(outputDir, relativePath),
1702
1804
  content
1703
1805
  });
1704
1806
  }
@@ -1707,7 +1809,7 @@ async function generateRulesConfig(rules, config, generatorConfig, baseDir) {
1707
1809
  for (const rule of rules) {
1708
1810
  const content = generatorConfig.generateContent(rule);
1709
1811
  const outputDir = resolveOutputDir(config, generatorConfig.tool, baseDir);
1710
- const filepath = generatorConfig.pathResolver ? generatorConfig.pathResolver(rule, outputDir) : join9(outputDir, `${rule.filename}${generatorConfig.fileExtension}`);
1812
+ const filepath = generatorConfig.pathResolver ? generatorConfig.pathResolver(rule, outputDir) : join7(outputDir, `${rule.filename}${generatorConfig.fileExtension}`);
1711
1813
  outputs.push({
1712
1814
  tool: generatorConfig.tool,
1713
1815
  filepath,
@@ -1735,7 +1837,7 @@ async function generateComplexRules(rules, config, generatorConfig, baseDir) {
1735
1837
  for (const rule of detailRules) {
1736
1838
  const content = generatorConfig.generateDetailContent(rule);
1737
1839
  const filepath = resolvePath(
1738
- join9(generatorConfig.detailSubDir, `${rule.filename}.md`),
1840
+ join7(generatorConfig.detailSubDir, `${rule.filename}.md`),
1739
1841
  baseDir
1740
1842
  );
1741
1843
  outputs.push({
@@ -1798,7 +1900,7 @@ async function generateAugmentcodeConfig(rules, config, baseDir) {
1798
1900
  "augmentcode",
1799
1901
  config,
1800
1902
  baseDir,
1801
- join10(".augment", "rules", `${rule.filename}.md`),
1903
+ join8(".augment", "rules", `${rule.filename}.md`),
1802
1904
  generateRuleFile(rule)
1803
1905
  );
1804
1906
  });
@@ -1851,7 +1953,7 @@ function generateLegacyGuidelinesFile(allRules) {
1851
1953
  }
1852
1954
 
1853
1955
  // src/generators/rules/claudecode.ts
1854
- import { join as join11 } from "path";
1956
+ import { join as join9 } from "path";
1855
1957
  async function generateClaudecodeConfig(rules, config, baseDir) {
1856
1958
  const generatorConfig = {
1857
1959
  tool: "claudecode",
@@ -1863,7 +1965,7 @@ async function generateClaudecodeConfig(rules, config, baseDir) {
1863
1965
  generateDetailContent: generateMemoryFile,
1864
1966
  detailSubDir: ".claude/memories",
1865
1967
  updateAdditionalConfig: async (ignorePatterns, baseDir2) => {
1866
- const settingsPath = resolvePath(join11(".claude", "settings.json"), baseDir2);
1968
+ const settingsPath = resolvePath(join9(".claude", "settings.json"), baseDir2);
1867
1969
  await updateClaudeSettings(settingsPath, ignorePatterns);
1868
1970
  return [];
1869
1971
  }
@@ -1900,7 +2002,7 @@ async function updateClaudeSettings(settingsPath, ignorePatterns) {
1900
2002
  const content = await readFileContent(settingsPath);
1901
2003
  rawSettings = JSON.parse(content);
1902
2004
  } catch {
1903
- console.warn(`Failed to parse existing ${settingsPath}, creating new settings`);
2005
+ logger.warn(`Failed to parse existing ${settingsPath}, creating new settings`);
1904
2006
  rawSettings = {};
1905
2007
  }
1906
2008
  }
@@ -1923,11 +2025,11 @@ async function updateClaudeSettings(settingsPath, ignorePatterns) {
1923
2025
  settings.permissions.deny = Array.from(new Set(filteredDeny));
1924
2026
  const jsonContent = JSON.stringify(settings, null, 2);
1925
2027
  await writeFileContent(settingsPath, jsonContent);
1926
- console.log(`\u2705 Updated Claude Code settings: ${settingsPath}`);
2028
+ logger.success(`Updated Claude Code settings: ${settingsPath}`);
1927
2029
  }
1928
2030
 
1929
2031
  // src/generators/rules/generator-registry.ts
1930
- import { join as join12 } from "path";
2032
+ import { join as join10 } from "path";
1931
2033
  function determineCursorRuleType(frontmatter) {
1932
2034
  if (frontmatter.cursorRuleType) {
1933
2035
  return frontmatter.cursorRuleType;
@@ -2007,7 +2109,7 @@ var GENERATOR_REGISTRY = {
2007
2109
  },
2008
2110
  pathResolver: (rule, outputDir) => {
2009
2111
  const baseFilename = rule.filename.replace(/\.md$/, "");
2010
- return join12(outputDir, `${baseFilename}.instructions.md`);
2112
+ return join10(outputDir, `${baseFilename}.instructions.md`);
2011
2113
  }
2012
2114
  },
2013
2115
  cursor: {
@@ -2047,7 +2149,7 @@ var GENERATOR_REGISTRY = {
2047
2149
  return lines.join("\n");
2048
2150
  },
2049
2151
  pathResolver: (rule, outputDir) => {
2050
- return join12(outputDir, `${rule.filename}.mdc`);
2152
+ return join10(outputDir, `${rule.filename}.mdc`);
2051
2153
  }
2052
2154
  },
2053
2155
  codexcli: {
@@ -2083,10 +2185,10 @@ var GENERATOR_REGISTRY = {
2083
2185
  pathResolver: (rule, outputDir) => {
2084
2186
  const outputFormat = rule.frontmatter.windsurfOutputFormat || "directory";
2085
2187
  if (outputFormat === "single-file") {
2086
- return join12(outputDir, ".windsurf-rules");
2188
+ return join10(outputDir, ".windsurf-rules");
2087
2189
  } else {
2088
- const rulesDir = join12(outputDir, ".windsurf", "rules");
2089
- return join12(rulesDir, `${rule.filename}.md`);
2190
+ const rulesDir = join10(outputDir, ".windsurf", "rules");
2191
+ return join10(rulesDir, `${rule.filename}.md`);
2090
2192
  }
2091
2193
  }
2092
2194
  },
@@ -2204,7 +2306,7 @@ async function generateCodexConfig(rules, config, baseDir) {
2204
2306
  const concatenatedContent = generateConcatenatedCodexContent(sortedRules);
2205
2307
  if (concatenatedContent.trim()) {
2206
2308
  const outputDir = resolveOutputDir(config, "codexcli", baseDir);
2207
- const filepath = `${outputDir}/codex.md`;
2309
+ const filepath = `${outputDir}/AGENTS.md`;
2208
2310
  outputs.push({
2209
2311
  tool: "codexcli",
2210
2312
  filepath,
@@ -2310,14 +2412,12 @@ async function generateConfigurations(rules, config, targetTools, baseDir) {
2310
2412
  const toolsToGenerate = targetTools || config.defaultTargets;
2311
2413
  const rootFiles = rules.filter((rule) => rule.frontmatter.root === true);
2312
2414
  if (rootFiles.length === 0) {
2313
- console.warn(
2314
- "\u26A0\uFE0F Warning: No files with 'root: true' found. This may result in incomplete configurations."
2315
- );
2415
+ logger.warn("No files with 'root: true' found. This may result in incomplete configurations.");
2316
2416
  }
2317
2417
  for (const tool of toolsToGenerate) {
2318
2418
  const relevantRules = filterRulesForTool(rules, tool, config);
2319
2419
  if (relevantRules.length === 0) {
2320
- console.warn(`No rules found for tool: ${tool}`);
2420
+ logger.warn(`No rules found for tool: ${tool}`);
2321
2421
  continue;
2322
2422
  }
2323
2423
  const toolOutputs = await generateForTool(tool, relevantRules, config, baseDir);
@@ -2382,22 +2482,21 @@ async function generateForTool(tool, rules, config, baseDir) {
2382
2482
  return [...windsurfRulesOutputs, ...windsurfIgnoreOutputs];
2383
2483
  }
2384
2484
  default:
2385
- console.warn(`Unknown tool: ${tool}`);
2485
+ logger.warn(`Unknown tool: ${tool}`);
2386
2486
  return null;
2387
2487
  }
2388
2488
  }
2389
2489
 
2390
2490
  // src/core/parser.ts
2391
2491
  import { basename as basename2 } from "path";
2392
- import matter2 from "gray-matter";
2393
2492
  async function parseRulesFromDirectory(aiRulesDir) {
2394
2493
  const ignorePatterns = await loadIgnorePatterns();
2395
- const allRuleFiles = await findFiles(aiRulesDir, ".md");
2494
+ const allRuleFiles = await findRuleFiles(aiRulesDir);
2396
2495
  const ruleFiles = filterIgnoredFiles(allRuleFiles, ignorePatterns.patterns);
2397
2496
  const rules = [];
2398
2497
  const errors = [];
2399
2498
  if (ignorePatterns.patterns.length > 0) {
2400
- console.log(`Loaded ${ignorePatterns.patterns.length} ignore patterns from .rulesyncignore`);
2499
+ logger.info(`Loaded ${ignorePatterns.patterns.length} ignore patterns from .rulesyncignore`);
2401
2500
  }
2402
2501
  for (const filepath of ruleFiles) {
2403
2502
  try {
@@ -2423,7 +2522,7 @@ ${errors.join("\n")}`);
2423
2522
  }
2424
2523
  async function parseRuleFile(filepath) {
2425
2524
  const content = await readFileContent(filepath);
2426
- const parsed = matter2(content);
2525
+ const parsed = parseFrontmatter(content);
2427
2526
  try {
2428
2527
  const validatedData = RuleFrontmatterSchema.parse(parsed.data);
2429
2528
  const frontmatter = {
@@ -2548,22 +2647,22 @@ async function generateMcpConfigurations(mcpConfig, baseDir, targetTools) {
2548
2647
  servers,
2549
2648
  dir
2550
2649
  ),
2551
- claudecode: async (servers, dir) => (await import("./claudecode-XKHMZT7R.js")).generateClaudeMcpConfiguration(
2650
+ claudecode: async (servers, dir) => (await import("./claudecode-YTEFACCT.js")).generateClaudeMcpConfiguration(
2552
2651
  servers,
2553
2652
  dir
2554
2653
  ),
2555
2654
  copilot: async (servers, dir) => (await import("./copilot-MOR3HHJX.js")).generateCopilotMcpConfiguration(servers, dir),
2556
- cursor: async (servers, dir) => (await import("./cursor-WWHUW5AD.js")).generateCursorMcpConfiguration(servers, dir),
2557
- cline: async (servers, dir) => (await import("./cline-FNWPJ7K4.js")).generateClineMcpConfiguration(servers, dir),
2558
- codexcli: async (servers, dir) => (await import("./codexcli-FDFHY66P.js")).generateCodexMcpConfiguration(servers, dir),
2655
+ cursor: async (servers, dir) => (await import("./cursor-YJGH7W24.js")).generateCursorMcpConfiguration(servers, dir),
2656
+ cline: async (servers, dir) => (await import("./cline-CKNUDEA3.js")).generateClineMcpConfiguration(servers, dir),
2657
+ codexcli: async (servers, dir) => (await import("./codexcli-7SDGYI7D.js")).generateCodexMcpConfiguration(servers, dir),
2559
2658
  roo: async (servers, dir) => (await import("./roo-L3QTTIPO.js")).generateRooMcpConfiguration(servers, dir),
2560
- geminicli: async (servers, dir) => (await import("./geminicli-7TIDQ62D.js")).generateGeminiCliMcpConfiguration(
2659
+ geminicli: async (servers, dir) => (await import("./geminicli-E7KZTZ2G.js")).generateGeminiCliMcpConfiguration(
2561
2660
  servers,
2562
2661
  dir
2563
2662
  ),
2564
2663
  kiro: async (servers, dir) => (await import("./kiro-YDHXY2MA.js")).generateKiroMcpConfiguration(servers, dir),
2565
- junie: async (servers, dir) => (await import("./junie-VMNDWBNB.js")).generateJunieMcpConfiguration(servers, dir),
2566
- windsurf: async (servers, dir) => (await import("./windsurf-KOSK4MZJ.js")).generateWindsurfMcpConfiguration(
2664
+ junie: async (servers, dir) => (await import("./junie-5LEQU4BO.js")).generateJunieMcpConfiguration(servers, dir),
2665
+ windsurf: async (servers, dir) => (await import("./windsurf-4P6HEUBV.js")).generateWindsurfMcpConfiguration(
2567
2666
  servers,
2568
2667
  dir
2569
2668
  )
@@ -2598,6 +2697,7 @@ async function generateCommand(options = {}) {
2598
2697
  ...options.baseDirs !== void 0 && { baseDirs: options.baseDirs }
2599
2698
  };
2600
2699
  const config = mergeWithCliOptions(configResult.config, cliOptions);
2700
+ logger.setVerbose(config.verbose || false);
2601
2701
  if (options.tools && options.tools.length > 0) {
2602
2702
  const configTargets = config.defaultTargets;
2603
2703
  const cliTools = options.tools;
@@ -2606,18 +2706,18 @@ async function generateCommand(options = {}) {
2606
2706
  const notInConfig = cliTools.filter((tool) => !configTargetsSet.has(tool));
2607
2707
  const notInCli = configTargets.filter((tool) => !cliToolsSet.has(tool));
2608
2708
  if (notInConfig.length > 0 || notInCli.length > 0) {
2609
- console.warn("\u26A0\uFE0F Warning: CLI tool selection differs from configuration!");
2610
- console.warn(` Config targets: ${configTargets.join(", ")}`);
2611
- console.warn(` CLI specified: ${cliTools.join(", ")}`);
2709
+ logger.warn("\u26A0\uFE0F Warning: CLI tool selection differs from configuration!");
2710
+ logger.warn(` Config targets: ${configTargets.join(", ")}`);
2711
+ logger.warn(` CLI specified: ${cliTools.join(", ")}`);
2612
2712
  if (notInConfig.length > 0) {
2613
- console.warn(` Tools specified but not in config: ${notInConfig.join(", ")}`);
2713
+ logger.warn(` Tools specified but not in config: ${notInConfig.join(", ")}`);
2614
2714
  }
2615
2715
  if (notInCli.length > 0) {
2616
- console.warn(` Tools in config but not specified: ${notInCli.join(", ")}`);
2716
+ logger.warn(` Tools in config but not specified: ${notInCli.join(", ")}`);
2617
2717
  }
2618
- console.warn("\n The configuration file targets will be used.");
2619
- console.warn(" To change targets, update your rulesync config file.");
2620
- console.warn("");
2718
+ logger.warn("\n The configuration file targets will be used.");
2719
+ logger.warn(" To change targets, update your rulesync config file.");
2720
+ logger.warn("");
2621
2721
  }
2622
2722
  }
2623
2723
  let baseDirs;
@@ -2628,42 +2728,46 @@ async function generateCommand(options = {}) {
2628
2728
  } else {
2629
2729
  baseDirs = [process.cwd()];
2630
2730
  }
2631
- if (config.verbose && configResult.filepath) {
2632
- console.log(`Loaded configuration from: ${configResult.filepath}`);
2633
- }
2634
- console.log("Generating configuration files...");
2731
+ logger.info(`Loaded configuration from: ${configResult.filepath}`);
2732
+ logger.log("Generating configuration files...");
2635
2733
  if (!await fileExists(config.aiRulesDir)) {
2636
- console.error("\u274C .rulesync directory not found. Run 'rulesync init' first.");
2734
+ logger.error("\u274C .rulesync directory not found. Run 'rulesync init' first.");
2637
2735
  process.exit(1);
2638
2736
  }
2639
2737
  try {
2640
- if (config.verbose) {
2641
- console.log(`Parsing rules from ${config.aiRulesDir}...`);
2642
- }
2738
+ logger.info(`Parsing rules from ${config.aiRulesDir}...`);
2643
2739
  const rules = await parseRulesFromDirectory(config.aiRulesDir);
2644
2740
  if (rules.length === 0) {
2645
- console.warn("\u26A0\uFE0F No rules found in .rulesync directory");
2741
+ logger.warn("\u26A0\uFE0F No rules found in .rulesync directory");
2646
2742
  return;
2647
2743
  }
2648
- if (config.verbose) {
2649
- console.log(`Found ${rules.length} rule(s)`);
2650
- console.log(`Base directories: ${baseDirs.join(", ")}`);
2651
- }
2744
+ logger.info(`Found ${rules.length} rule(s)`);
2745
+ logger.info(`Base directories: ${baseDirs.join(", ")}`);
2652
2746
  if (config.delete) {
2653
- if (config.verbose) {
2654
- console.log("Deleting existing output directories...");
2655
- }
2747
+ logger.info("Deleting existing output directories...");
2656
2748
  const targetTools = config.defaultTargets;
2657
2749
  const deleteTasks = [];
2750
+ const commandsDir = join13(config.aiRulesDir, "commands");
2751
+ const hasCommands = await fileExists(commandsDir);
2752
+ let hasCommandFiles = false;
2753
+ if (hasCommands) {
2754
+ const { readdir: readdir2 } = await import("fs/promises");
2755
+ try {
2756
+ const files = await readdir2(commandsDir);
2757
+ hasCommandFiles = files.some((file) => file.endsWith(".md"));
2758
+ } catch {
2759
+ hasCommandFiles = false;
2760
+ }
2761
+ }
2658
2762
  for (const tool of targetTools) {
2659
2763
  switch (tool) {
2660
2764
  case "augmentcode":
2661
- deleteTasks.push(removeDirectory(join15(".augment", "rules")));
2662
- deleteTasks.push(removeDirectory(join15(".augment", "ignore")));
2765
+ deleteTasks.push(removeDirectory(join13(".augment", "rules")));
2766
+ deleteTasks.push(removeDirectory(join13(".augment", "ignore")));
2663
2767
  break;
2664
2768
  case "augmentcode-legacy":
2665
2769
  deleteTasks.push(removeClaudeGeneratedFiles());
2666
- deleteTasks.push(removeDirectory(join15(".augment", "ignore")));
2770
+ deleteTasks.push(removeDirectory(join13(".augment", "ignore")));
2667
2771
  break;
2668
2772
  case "copilot":
2669
2773
  deleteTasks.push(removeDirectory(config.outputPaths.copilot));
@@ -2676,15 +2780,21 @@ async function generateCommand(options = {}) {
2676
2780
  break;
2677
2781
  case "claudecode":
2678
2782
  deleteTasks.push(removeClaudeGeneratedFiles());
2679
- deleteTasks.push(removeDirectory(join15(".claude", "commands")));
2783
+ if (hasCommandFiles) {
2784
+ deleteTasks.push(removeDirectory(join13(".claude", "commands")));
2785
+ }
2680
2786
  break;
2681
2787
  case "roo":
2682
2788
  deleteTasks.push(removeDirectory(config.outputPaths.roo));
2683
- deleteTasks.push(removeDirectory(join15(".roo", "commands")));
2789
+ if (hasCommandFiles) {
2790
+ deleteTasks.push(removeDirectory(join13(".roo", "commands")));
2791
+ }
2684
2792
  break;
2685
2793
  case "geminicli":
2686
2794
  deleteTasks.push(removeDirectory(config.outputPaths.geminicli));
2687
- deleteTasks.push(removeDirectory(join15(".gemini", "commands")));
2795
+ if (hasCommandFiles) {
2796
+ deleteTasks.push(removeDirectory(join13(".gemini", "commands")));
2797
+ }
2688
2798
  break;
2689
2799
  case "kiro":
2690
2800
  deleteTasks.push(removeDirectory(config.outputPaths.kiro));
@@ -2695,44 +2805,34 @@ async function generateCommand(options = {}) {
2695
2805
  }
2696
2806
  }
2697
2807
  await Promise.all(deleteTasks);
2698
- if (config.verbose) {
2699
- console.log("Deleted existing output directories");
2700
- }
2808
+ logger.info("Deleted existing output directories");
2701
2809
  }
2702
2810
  let totalOutputs = 0;
2703
2811
  for (const baseDir of baseDirs) {
2704
- if (config.verbose) {
2705
- console.log(`
2812
+ logger.info(`
2706
2813
  Generating configurations for base directory: ${baseDir}`);
2707
- }
2708
2814
  const outputs = await generateConfigurations(rules, config, config.defaultTargets, baseDir);
2709
2815
  if (outputs.length === 0) {
2710
- if (config.verbose) {
2711
- console.warn(`\u26A0\uFE0F No configurations generated for ${baseDir}`);
2712
- }
2816
+ logger.warn(`\u26A0\uFE0F No configurations generated for ${baseDir}`);
2713
2817
  continue;
2714
2818
  }
2715
2819
  for (const output of outputs) {
2716
2820
  await writeFileContent(output.filepath, output.content);
2717
- console.log(`\u2705 Generated ${output.tool} configuration: ${output.filepath}`);
2821
+ logger.success(`Generated ${output.tool} configuration: ${output.filepath}`);
2718
2822
  }
2719
2823
  totalOutputs += outputs.length;
2720
2824
  }
2721
2825
  if (totalOutputs === 0) {
2722
- console.warn("\u26A0\uFE0F No configurations generated");
2826
+ logger.warn("\u26A0\uFE0F No configurations generated");
2723
2827
  return;
2724
2828
  }
2725
- if (config.verbose) {
2726
- console.log("\nGenerating MCP configurations...");
2727
- }
2829
+ logger.info("\nGenerating MCP configurations...");
2728
2830
  let totalMcpOutputs = 0;
2729
2831
  for (const baseDir of baseDirs) {
2730
2832
  try {
2731
2833
  const mcpConfig = parseMcpConfig(process.cwd());
2732
2834
  if (!mcpConfig || !mcpConfig.mcpServers || Object.keys(mcpConfig.mcpServers).length === 0) {
2733
- if (config.verbose) {
2734
- console.log(`No MCP configuration found for ${baseDir}`);
2735
- }
2835
+ logger.info(`No MCP configuration found for ${baseDir}`);
2736
2836
  continue;
2737
2837
  }
2738
2838
  const mcpResults = await generateMcpConfigurations(
@@ -2741,27 +2841,21 @@ Generating configurations for base directory: ${baseDir}`);
2741
2841
  config.defaultTargets
2742
2842
  );
2743
2843
  if (mcpResults.length === 0) {
2744
- if (config.verbose) {
2745
- console.log(`No MCP configurations generated for ${baseDir}`);
2746
- }
2844
+ logger.info(`No MCP configurations generated for ${baseDir}`);
2747
2845
  continue;
2748
2846
  }
2749
2847
  for (const result of mcpResults) {
2750
2848
  await writeFileContent(result.filepath, result.content);
2751
- console.log(`\u2705 Generated ${result.tool} MCP configuration: ${result.filepath}`);
2849
+ logger.success(`Generated ${result.tool} MCP configuration: ${result.filepath}`);
2752
2850
  totalMcpOutputs++;
2753
2851
  }
2754
2852
  } catch (error) {
2755
- if (config.verbose) {
2756
- console.error(
2757
- `\u274C Failed to generate MCP configurations: ${error instanceof Error ? error.message : String(error)}`
2758
- );
2759
- }
2853
+ logger.error(
2854
+ `\u274C Failed to generate MCP configurations: ${error instanceof Error ? error.message : String(error)}`
2855
+ );
2760
2856
  }
2761
2857
  }
2762
- if (config.verbose) {
2763
- console.log("\nGenerating command files...");
2764
- }
2858
+ logger.info("\nGenerating command files...");
2765
2859
  let totalCommandOutputs = 0;
2766
2860
  for (const baseDir of baseDirs) {
2767
2861
  const commandResults = await generateCommands(
@@ -2770,14 +2864,12 @@ Generating configurations for base directory: ${baseDir}`);
2770
2864
  config.defaultTargets
2771
2865
  );
2772
2866
  if (commandResults.length === 0) {
2773
- if (config.verbose) {
2774
- console.log(`No commands found for ${baseDir}`);
2775
- }
2867
+ logger.info(`No commands found for ${baseDir}`);
2776
2868
  continue;
2777
2869
  }
2778
2870
  for (const result of commandResults) {
2779
2871
  await writeFileContent(result.filepath, result.content);
2780
- console.log(`\u2705 Generated ${result.tool} command: ${result.filepath}`);
2872
+ logger.success(`Generated ${result.tool} command: ${result.filepath}`);
2781
2873
  totalCommandOutputs++;
2782
2874
  }
2783
2875
  }
@@ -2787,22 +2879,22 @@ Generating configurations for base directory: ${baseDir}`);
2787
2879
  if (totalOutputs > 0) parts.push(`${totalOutputs} configurations`);
2788
2880
  if (totalMcpOutputs > 0) parts.push(`${totalMcpOutputs} MCP configurations`);
2789
2881
  if (totalCommandOutputs > 0) parts.push(`${totalCommandOutputs} commands`);
2790
- console.log(
2882
+ logger.success(
2791
2883
  `
2792
2884
  \u{1F389} All done! Generated ${totalGenerated} file(s) total (${parts.join(" + ")})`
2793
2885
  );
2794
2886
  }
2795
2887
  } catch (error) {
2796
- console.error("\u274C Failed to generate configurations:", error);
2888
+ logger.error("\u274C Failed to generate configurations:", error);
2797
2889
  process.exit(1);
2798
2890
  }
2799
2891
  }
2800
2892
 
2801
2893
  // src/cli/commands/gitignore.ts
2802
2894
  import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
2803
- import { join as join16 } from "path";
2895
+ import { join as join14 } from "path";
2804
2896
  var gitignoreCommand = async () => {
2805
- const gitignorePath = join16(process.cwd(), ".gitignore");
2897
+ const gitignorePath = join14(process.cwd(), ".gitignore");
2806
2898
  const rulesFilesToIgnore = [
2807
2899
  "# Generated by rulesync - AI tool configuration files",
2808
2900
  "**/.github/copilot-instructions.md",
@@ -2814,7 +2906,7 @@ var gitignoreCommand = async () => {
2814
2906
  "**/CLAUDE.md",
2815
2907
  "**/.claude/memories/",
2816
2908
  "**/.claude/commands/",
2817
- "**/codex.md",
2909
+ "**/AGENTS.md",
2818
2910
  "**/.codexignore",
2819
2911
  "**/.roo/rules/",
2820
2912
  "**/.rooignore",
@@ -2850,7 +2942,7 @@ var gitignoreCommand = async () => {
2850
2942
  }
2851
2943
  }
2852
2944
  if (linesToAdd.length === 0) {
2853
- console.log("\u2705 .gitignore is already up to date");
2945
+ logger.success(".gitignore is already up to date");
2854
2946
  return;
2855
2947
  }
2856
2948
  const newContent = gitignoreContent ? `${gitignoreContent.trimEnd()}
@@ -2859,21 +2951,20 @@ ${linesToAdd.join("\n")}
2859
2951
  ` : `${linesToAdd.join("\n")}
2860
2952
  `;
2861
2953
  writeFileSync2(gitignorePath, newContent);
2862
- console.log(`\u2705 Added ${linesToAdd.length} rules to .gitignore:`);
2954
+ logger.success(`Added ${linesToAdd.length} rules to .gitignore:`);
2863
2955
  for (const line of linesToAdd) {
2864
2956
  if (!line.startsWith("#")) {
2865
- console.log(` ${line}`);
2957
+ logger.log(` ${line}`);
2866
2958
  }
2867
2959
  }
2868
2960
  };
2869
2961
 
2870
2962
  // src/core/importer.ts
2871
- import { join as join23 } from "path";
2872
- import matter7 from "gray-matter";
2963
+ import { join as join21 } from "path";
2964
+ import matter2 from "gray-matter";
2873
2965
 
2874
2966
  // src/parsers/augmentcode.ts
2875
- import { basename as basename3, join as join17 } from "path";
2876
- import matter3 from "gray-matter";
2967
+ import { basename as basename3, join as join15 } from "path";
2877
2968
 
2878
2969
  // src/utils/parser-helpers.ts
2879
2970
  function createParseResult() {
@@ -2921,7 +3012,7 @@ async function parseAugmentcodeLegacyConfiguration(baseDir = process.cwd()) {
2921
3012
  async function parseUnifiedAugmentcode(baseDir, config) {
2922
3013
  const result = createParseResult();
2923
3014
  if (config.rulesDir) {
2924
- const rulesDir = join17(baseDir, config.rulesDir);
3015
+ const rulesDir = join15(baseDir, config.rulesDir);
2925
3016
  if (await fileExists(rulesDir)) {
2926
3017
  const rulesResult = await parseAugmentRules(rulesDir, config);
2927
3018
  addRules(result, rulesResult.rules);
@@ -2934,7 +3025,7 @@ async function parseUnifiedAugmentcode(baseDir, config) {
2934
3025
  }
2935
3026
  }
2936
3027
  if (config.legacyFilePath) {
2937
- const legacyPath = join17(baseDir, config.legacyFilePath);
3028
+ const legacyPath = join15(baseDir, config.legacyFilePath);
2938
3029
  if (await fileExists(legacyPath)) {
2939
3030
  const legacyResult = await parseAugmentGuidelines(legacyPath, config);
2940
3031
  if (legacyResult.rule) {
@@ -2958,14 +3049,13 @@ async function parseAugmentRules(rulesDir, config) {
2958
3049
  const files = await readdir2(rulesDir);
2959
3050
  for (const file of files) {
2960
3051
  if (file.endsWith(".md") || file.endsWith(".mdc")) {
2961
- const filePath = join17(rulesDir, file);
3052
+ const filePath = join15(rulesDir, file);
2962
3053
  try {
2963
3054
  const rawContent = await readFileContent(filePath);
2964
- const parsed = matter3(rawContent);
2965
- const frontmatterData = parsed.data;
2966
- const ruleType = frontmatterData.type || "manual";
2967
- const description = frontmatterData.description || "";
2968
- const tags = Array.isArray(frontmatterData.tags) ? frontmatterData.tags : void 0;
3055
+ const parsed = parseFrontmatter(rawContent);
3056
+ const ruleType = extractStringField(parsed.data, "type", "manual");
3057
+ const description = extractStringField(parsed.data, "description", "");
3058
+ const tags = extractArrayField(parsed.data, "tags");
2969
3059
  const isRoot = ruleType === "always";
2970
3060
  const filename = basename3(file, file.endsWith(".mdc") ? ".mdc" : ".md");
2971
3061
  const frontmatter = {
@@ -2974,7 +3064,7 @@ async function parseAugmentRules(rulesDir, config) {
2974
3064
  description,
2975
3065
  globs: ["**/*"],
2976
3066
  // AugmentCode doesn't use specific globs in the same way
2977
- ...tags && { tags }
3067
+ ...tags.length > 0 && { tags }
2978
3068
  };
2979
3069
  rules.push({
2980
3070
  frontmatter,
@@ -3025,8 +3115,7 @@ async function parseAugmentGuidelines(guidelinesPath, config) {
3025
3115
  }
3026
3116
 
3027
3117
  // src/parsers/shared-helpers.ts
3028
- import { basename as basename4, join as join18 } from "path";
3029
- import matter4 from "gray-matter";
3118
+ import { basename as basename4, join as join16 } from "path";
3030
3119
  async function parseConfigurationFiles(baseDir = process.cwd(), config) {
3031
3120
  const errors = [];
3032
3121
  const rules = [];
@@ -3039,16 +3128,18 @@ async function parseConfigurationFiles(baseDir = process.cwd(), config) {
3039
3128
  let content;
3040
3129
  let frontmatter;
3041
3130
  if (mainFile.useFrontmatter) {
3042
- const parsed = matter4(rawContent);
3043
- content = parsed.content.trim();
3044
- const parsedFrontmatter = parsed.data;
3131
+ const parsed = parseFrontmatter(rawContent);
3132
+ content = parsed.content;
3045
3133
  frontmatter = {
3046
3134
  root: mainFile.isRoot ?? false,
3047
3135
  targets: [config.tool],
3048
- description: parsedFrontmatter.description || mainFile.description,
3049
- globs: Array.isArray(parsedFrontmatter.globs) ? parsedFrontmatter.globs : ["**/*"],
3050
- ...parsedFrontmatter.tags && { tags: parsedFrontmatter.tags }
3136
+ description: extractStringField(parsed.data, "description", mainFile.description),
3137
+ globs: extractArrayField(parsed.data, "globs", ["**/*"])
3051
3138
  };
3139
+ const tags = extractArrayField(parsed.data, "tags");
3140
+ if (tags.length > 0) {
3141
+ frontmatter.tags = tags;
3142
+ }
3052
3143
  } else {
3053
3144
  content = rawContent.trim();
3054
3145
  frontmatter = {
@@ -3081,23 +3172,29 @@ async function parseConfigurationFiles(baseDir = process.cwd(), config) {
3081
3172
  const files = await readdir2(dirPath);
3082
3173
  for (const file of files) {
3083
3174
  if (file.endsWith(dirConfig.filePattern)) {
3084
- const filePath = join18(dirPath, file);
3175
+ const filePath = join16(dirPath, file);
3085
3176
  const fileResult = await safeAsyncOperation(async () => {
3086
3177
  const rawContent = await readFileContent(filePath);
3087
3178
  let content;
3088
3179
  let frontmatter;
3089
3180
  const filename = file.replace(new RegExp(`\\${dirConfig.filePattern}$`), "");
3090
3181
  if (dirConfig.filePattern === ".instructions.md") {
3091
- const parsed = matter4(rawContent);
3092
- content = parsed.content.trim();
3093
- const parsedFrontmatter = parsed.data;
3182
+ const parsed = parseFrontmatter(rawContent);
3183
+ content = parsed.content;
3094
3184
  frontmatter = {
3095
3185
  root: false,
3096
3186
  targets: [config.tool],
3097
- description: parsedFrontmatter.description || `${dirConfig.description}: ${filename}`,
3098
- globs: Array.isArray(parsedFrontmatter.globs) ? parsedFrontmatter.globs : ["**/*"],
3099
- ...parsedFrontmatter.tags && { tags: parsedFrontmatter.tags }
3187
+ description: extractStringField(
3188
+ parsed.data,
3189
+ "description",
3190
+ `${dirConfig.description}: ${filename}`
3191
+ ),
3192
+ globs: extractArrayField(parsed.data, "globs", ["**/*"])
3100
3193
  };
3194
+ const tags = extractArrayField(parsed.data, "tags");
3195
+ if (tags.length > 0) {
3196
+ frontmatter.tags = tags;
3197
+ }
3101
3198
  } else {
3102
3199
  content = rawContent.trim();
3103
3200
  frontmatter = {
@@ -3226,7 +3323,7 @@ async function parseMemoryFiles(memoryDir, config) {
3226
3323
  const files = await readdir2(memoryDir);
3227
3324
  for (const file of files) {
3228
3325
  if (file.endsWith(".md")) {
3229
- const filePath = join18(memoryDir, file);
3326
+ const filePath = join16(memoryDir, file);
3230
3327
  const content = await readFileContent(filePath);
3231
3328
  if (content.trim()) {
3232
3329
  const filename = basename4(file, ".md");
@@ -3256,20 +3353,19 @@ async function parseCommandsFiles(commandsDir, config) {
3256
3353
  const files = await readdir2(commandsDir);
3257
3354
  for (const file of files) {
3258
3355
  if (file.endsWith(".md")) {
3259
- const filePath = join18(commandsDir, file);
3356
+ const filePath = join16(commandsDir, file);
3260
3357
  const content = await readFileContent(filePath);
3261
3358
  if (content.trim()) {
3262
3359
  const filename = basename4(file, ".md");
3263
3360
  let frontmatter;
3264
3361
  let ruleContent;
3265
3362
  try {
3266
- const parsed = matter4(content);
3267
- ruleContent = parsed.content.trim();
3268
- const parsedFrontmatter = parsed.data;
3363
+ const parsed = parseFrontmatter(content);
3364
+ ruleContent = parsed.content;
3269
3365
  frontmatter = {
3270
3366
  root: false,
3271
3367
  targets: [config.tool],
3272
- description: parsedFrontmatter.description || `Command: ${filename}`,
3368
+ description: extractStringField(parsed.data, "description", `Command: ${filename}`),
3273
3369
  globs: ["**/*"]
3274
3370
  };
3275
3371
  } catch {
@@ -3370,7 +3466,7 @@ async function parseClineConfiguration(baseDir = process.cwd()) {
3370
3466
  }
3371
3467
 
3372
3468
  // src/parsers/codexcli.ts
3373
- import { join as join19 } from "path";
3469
+ import { join as join17 } from "path";
3374
3470
 
3375
3471
  // src/parsers/copilot.ts
3376
3472
  async function parseCopilotConfiguration(baseDir = process.cwd()) {
@@ -3393,8 +3489,7 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
3393
3489
  }
3394
3490
 
3395
3491
  // src/parsers/cursor.ts
3396
- import { basename as basename5, join as join20 } from "path";
3397
- import matter5 from "gray-matter";
3492
+ import { basename as basename5, join as join18 } from "path";
3398
3493
  import { DEFAULT_SCHEMA, FAILSAFE_SCHEMA, load } from "js-yaml";
3399
3494
  import { z as z7 } from "zod/mini";
3400
3495
  var customMatterOptions = {
@@ -3518,12 +3613,12 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3518
3613
  const rules = [];
3519
3614
  let ignorePatterns;
3520
3615
  let mcpServers;
3521
- const cursorFilePath = join20(baseDir, ".cursorrules");
3616
+ const cursorFilePath = join18(baseDir, ".cursorrules");
3522
3617
  if (await fileExists(cursorFilePath)) {
3523
3618
  try {
3524
3619
  const rawContent = await readFileContent(cursorFilePath);
3525
- const parsed = matter5(rawContent, customMatterOptions);
3526
- const content = parsed.content.trim();
3620
+ const parsed = parseFrontmatter(rawContent, { matterOptions: customMatterOptions });
3621
+ const content = parsed.content;
3527
3622
  if (content) {
3528
3623
  const frontmatter = convertCursorMdcFrontmatter(parsed.data, "cursorrules");
3529
3624
  frontmatter.targets = ["cursor"];
@@ -3539,18 +3634,18 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3539
3634
  errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
3540
3635
  }
3541
3636
  }
3542
- const cursorRulesDir = join20(baseDir, ".cursor", "rules");
3637
+ const cursorRulesDir = join18(baseDir, ".cursor", "rules");
3543
3638
  if (await fileExists(cursorRulesDir)) {
3544
3639
  try {
3545
3640
  const { readdir: readdir2 } = await import("fs/promises");
3546
3641
  const files = await readdir2(cursorRulesDir);
3547
3642
  for (const file of files) {
3548
3643
  if (file.endsWith(".mdc")) {
3549
- const filePath = join20(cursorRulesDir, file);
3644
+ const filePath = join18(cursorRulesDir, file);
3550
3645
  try {
3551
3646
  const rawContent = await readFileContent(filePath);
3552
- const parsed = matter5(rawContent, customMatterOptions);
3553
- const content = parsed.content.trim();
3647
+ const parsed = parseFrontmatter(rawContent, { matterOptions: customMatterOptions });
3648
+ const content = parsed.content;
3554
3649
  if (content) {
3555
3650
  const filename = basename5(file, ".mdc");
3556
3651
  const frontmatter = convertCursorMdcFrontmatter(parsed.data, filename);
@@ -3575,7 +3670,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3575
3670
  if (rules.length === 0) {
3576
3671
  errors.push("No Cursor configuration files found (.cursorrules or .cursor/rules/*.mdc)");
3577
3672
  }
3578
- const cursorIgnorePath = join20(baseDir, ".cursorignore");
3673
+ const cursorIgnorePath = join18(baseDir, ".cursorignore");
3579
3674
  if (await fileExists(cursorIgnorePath)) {
3580
3675
  try {
3581
3676
  const content = await readFileContent(cursorIgnorePath);
@@ -3588,7 +3683,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3588
3683
  errors.push(`Failed to parse .cursorignore: ${errorMessage}`);
3589
3684
  }
3590
3685
  }
3591
- const cursorMcpPath = join20(baseDir, ".cursor", "mcp.json");
3686
+ const cursorMcpPath = join18(baseDir, ".cursor", "mcp.json");
3592
3687
  if (await fileExists(cursorMcpPath)) {
3593
3688
  try {
3594
3689
  const content = await readFileContent(cursorMcpPath);
@@ -3638,11 +3733,11 @@ async function parseGeminiConfiguration(baseDir = process.cwd()) {
3638
3733
  }
3639
3734
 
3640
3735
  // src/parsers/junie.ts
3641
- import { join as join21 } from "path";
3736
+ import { join as join19 } from "path";
3642
3737
  async function parseJunieConfiguration(baseDir = process.cwd()) {
3643
3738
  const errors = [];
3644
3739
  const rules = [];
3645
- const guidelinesPath = join21(baseDir, ".junie", "guidelines.md");
3740
+ const guidelinesPath = join19(baseDir, ".junie", "guidelines.md");
3646
3741
  if (!await fileExists(guidelinesPath)) {
3647
3742
  errors.push(".junie/guidelines.md file not found");
3648
3743
  return { rules, errors };
@@ -3695,18 +3790,23 @@ async function parseRooConfiguration(baseDir = process.cwd()) {
3695
3790
 
3696
3791
  // src/parsers/windsurf.ts
3697
3792
  import { readFile as readFile2 } from "fs/promises";
3698
- import { join as join22 } from "path";
3699
- import matter6 from "gray-matter";
3793
+ import { join as join20 } from "path";
3700
3794
 
3701
3795
  // src/core/importer.ts
3702
3796
  async function importConfiguration(options) {
3703
- const { tool, baseDir = process.cwd(), rulesDir = ".rulesync", verbose = false } = options;
3797
+ const {
3798
+ tool,
3799
+ baseDir = process.cwd(),
3800
+ rulesDir = ".rulesync",
3801
+ verbose = false,
3802
+ useLegacyLocation = false
3803
+ } = options;
3704
3804
  const errors = [];
3705
3805
  let rules = [];
3706
3806
  let ignorePatterns;
3707
3807
  let mcpServers;
3708
3808
  if (verbose) {
3709
- console.log(`Importing ${tool} configuration from ${baseDir}...`);
3809
+ logger.log(`Importing ${tool} configuration from ${baseDir}...`);
3710
3810
  }
3711
3811
  try {
3712
3812
  switch (tool) {
@@ -3782,7 +3882,7 @@ async function importConfiguration(options) {
3782
3882
  if (rules.length === 0 && !ignorePatterns && !mcpServers) {
3783
3883
  return { success: false, rulesCreated: 0, errors };
3784
3884
  }
3785
- const rulesDirPath = join23(baseDir, rulesDir);
3885
+ const rulesDirPath = join21(baseDir, rulesDir);
3786
3886
  try {
3787
3887
  const { mkdir: mkdir3 } = await import("fs/promises");
3788
3888
  await mkdir3(rulesDirPath, { recursive: true });
@@ -3797,16 +3897,22 @@ async function importConfiguration(options) {
3797
3897
  const baseFilename = rule.filename;
3798
3898
  let targetDir = rulesDirPath;
3799
3899
  if (rule.type === "command") {
3800
- targetDir = join23(rulesDirPath, "commands");
3900
+ targetDir = join21(rulesDirPath, "commands");
3801
3901
  const { mkdir: mkdir3 } = await import("fs/promises");
3802
3902
  await mkdir3(targetDir, { recursive: true });
3903
+ } else {
3904
+ if (!useLegacyLocation) {
3905
+ targetDir = join21(rulesDirPath, "rules");
3906
+ const { mkdir: mkdir3 } = await import("fs/promises");
3907
+ await mkdir3(targetDir, { recursive: true });
3908
+ }
3803
3909
  }
3804
- const filePath = join23(targetDir, `${baseFilename}.md`);
3910
+ const filePath = join21(targetDir, `${baseFilename}.md`);
3805
3911
  const content = generateRuleFileContent(rule);
3806
3912
  await writeFileContent(filePath, content);
3807
3913
  rulesCreated++;
3808
3914
  if (verbose) {
3809
- console.log(`\u2705 Created rule file: ${filePath}`);
3915
+ logger.success(`Created rule file: ${filePath}`);
3810
3916
  }
3811
3917
  } catch (error) {
3812
3918
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -3816,13 +3922,13 @@ async function importConfiguration(options) {
3816
3922
  let ignoreFileCreated = false;
3817
3923
  if (ignorePatterns && ignorePatterns.length > 0) {
3818
3924
  try {
3819
- const rulesyncignorePath = join23(baseDir, ".rulesyncignore");
3925
+ const rulesyncignorePath = join21(baseDir, ".rulesyncignore");
3820
3926
  const ignoreContent = `${ignorePatterns.join("\n")}
3821
3927
  `;
3822
3928
  await writeFileContent(rulesyncignorePath, ignoreContent);
3823
3929
  ignoreFileCreated = true;
3824
3930
  if (verbose) {
3825
- console.log(`\u2705 Created .rulesyncignore with ${ignorePatterns.length} patterns`);
3931
+ logger.success(`Created .rulesyncignore with ${ignorePatterns.length} patterns`);
3826
3932
  }
3827
3933
  } catch (error) {
3828
3934
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -3832,13 +3938,13 @@ async function importConfiguration(options) {
3832
3938
  let mcpFileCreated = false;
3833
3939
  if (mcpServers && Object.keys(mcpServers).length > 0) {
3834
3940
  try {
3835
- const mcpPath = join23(baseDir, rulesDir, ".mcp.json");
3941
+ const mcpPath = join21(baseDir, rulesDir, ".mcp.json");
3836
3942
  const mcpContent = `${JSON.stringify({ mcpServers }, null, 2)}
3837
3943
  `;
3838
3944
  await writeFileContent(mcpPath, mcpContent);
3839
3945
  mcpFileCreated = true;
3840
3946
  if (verbose) {
3841
- console.log(`\u2705 Created .mcp.json with ${Object.keys(mcpServers).length} servers`);
3947
+ logger.success(`Created .mcp.json with ${Object.keys(mcpServers).length} servers`);
3842
3948
  }
3843
3949
  } catch (error) {
3844
3950
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -3859,15 +3965,16 @@ function generateRuleFileContent(rule) {
3859
3965
  description: rule.frontmatter.description,
3860
3966
  targets: rule.frontmatter.targets
3861
3967
  };
3862
- const frontmatter2 = matter7.stringify("", simplifiedFrontmatter);
3968
+ const frontmatter2 = matter2.stringify("", simplifiedFrontmatter);
3863
3969
  return frontmatter2 + rule.content;
3864
3970
  }
3865
- const frontmatter = matter7.stringify("", rule.frontmatter);
3971
+ const frontmatter = matter2.stringify("", rule.frontmatter);
3866
3972
  return frontmatter + rule.content;
3867
3973
  }
3868
3974
 
3869
3975
  // src/cli/commands/import.ts
3870
3976
  async function importCommand(options = {}) {
3977
+ logger.setVerbose(options.verbose || false);
3871
3978
  const tools = [];
3872
3979
  if (options.augmentcode) tools.push("augmentcode");
3873
3980
  if (options["augmentcode-legacy"]) tools.push("augmentcode-legacy");
@@ -3878,66 +3985,74 @@ async function importCommand(options = {}) {
3878
3985
  if (options.roo) tools.push("roo");
3879
3986
  if (options.geminicli) tools.push("geminicli");
3880
3987
  if (tools.length === 0) {
3881
- console.error(
3988
+ logger.error(
3882
3989
  "\u274C Please specify one tool to import from (--augmentcode, --augmentcode-legacy, --claudecode, --cursor, --copilot, --cline, --roo, --geminicli)"
3883
3990
  );
3884
3991
  process.exit(1);
3885
3992
  }
3886
3993
  if (tools.length > 1) {
3887
- console.error(
3994
+ logger.error(
3888
3995
  "\u274C Only one tool can be specified at a time. Please run the import command separately for each tool."
3889
3996
  );
3890
3997
  process.exit(1);
3891
3998
  }
3892
3999
  const tool = tools[0];
3893
4000
  if (!tool) {
3894
- console.error("Error: No tool specified");
4001
+ logger.error("Error: No tool specified");
3895
4002
  process.exit(1);
3896
4003
  }
3897
- console.log(`Importing configuration files from ${tool}...`);
4004
+ logger.log(`Importing configuration files from ${tool}...`);
3898
4005
  try {
3899
4006
  const result = await importConfiguration({
3900
4007
  tool,
3901
- verbose: options.verbose ?? false
4008
+ verbose: options.verbose ?? false,
4009
+ useLegacyLocation: options.legacy ?? false
3902
4010
  });
3903
4011
  if (result.success) {
3904
- console.log(`\u2705 Imported ${result.rulesCreated} rule(s) from ${tool}`);
4012
+ logger.success(`Imported ${result.rulesCreated} rule(s) from ${tool}`);
3905
4013
  if (result.ignoreFileCreated) {
3906
- console.log("\u2705 Created .rulesyncignore file from ignore patterns");
4014
+ logger.success("Created .rulesyncignore file from ignore patterns");
3907
4015
  }
3908
4016
  if (result.mcpFileCreated) {
3909
- console.log("\u2705 Created .rulesync/.mcp.json file from MCP configuration");
4017
+ logger.success("Created .rulesync/.mcp.json file from MCP configuration");
3910
4018
  }
3911
- console.log("You can now run 'rulesync generate' to create tool-specific configurations.");
4019
+ logger.log("You can now run 'rulesync generate' to create tool-specific configurations.");
3912
4020
  } else if (result.errors.length > 0) {
3913
- console.warn(`\u26A0\uFE0F Failed to import from ${tool}: ${result.errors[0]}`);
3914
- if (options.verbose && result.errors.length > 1) {
3915
- console.log("\nDetailed errors:");
4021
+ logger.warn(`\u26A0\uFE0F Failed to import from ${tool}: ${result.errors[0]}`);
4022
+ if (result.errors.length > 1) {
4023
+ logger.info("\nDetailed errors:");
3916
4024
  for (const error of result.errors) {
3917
- console.log(` - ${error}`);
4025
+ logger.info(` - ${error}`);
3918
4026
  }
3919
4027
  }
3920
4028
  }
3921
4029
  } catch (error) {
3922
4030
  const errorMessage = error instanceof Error ? error.message : String(error);
3923
- console.error(`\u274C Error importing from ${tool}: ${errorMessage}`);
4031
+ logger.error(`\u274C Error importing from ${tool}: ${errorMessage}`);
3924
4032
  process.exit(1);
3925
4033
  }
3926
4034
  }
3927
4035
 
3928
4036
  // src/cli/commands/init.ts
3929
- import { join as join24 } from "path";
3930
- async function initCommand() {
3931
- const aiRulesDir = ".rulesync";
3932
- console.log("Initializing rulesync...");
4037
+ import { join as join22 } from "path";
4038
+ async function initCommand(options = {}) {
4039
+ const configResult = await loadConfig();
4040
+ const config = configResult.config;
4041
+ const aiRulesDir = config.aiRulesDir;
4042
+ logger.log("Initializing rulesync...");
3933
4043
  await ensureDir(aiRulesDir);
3934
- await createSampleFiles(aiRulesDir);
3935
- console.log("\u2705 rulesync initialized successfully!");
3936
- console.log("\nNext steps:");
3937
- console.log("1. Edit rule files in .rulesync/");
3938
- console.log("2. Run 'rulesync generate' to create configuration files");
4044
+ const useLegacy = options.legacy ?? config.legacy ?? false;
4045
+ const rulesDir = useLegacy ? aiRulesDir : join22(aiRulesDir, "rules");
4046
+ if (!useLegacy) {
4047
+ await ensureDir(rulesDir);
4048
+ }
4049
+ await createSampleFiles(rulesDir);
4050
+ logger.success("rulesync initialized successfully!");
4051
+ logger.log("\nNext steps:");
4052
+ logger.log(`1. Edit rule files in ${rulesDir}/`);
4053
+ logger.log("2. Run 'rulesync generate' to create configuration files");
3939
4054
  }
3940
- async function createSampleFiles(aiRulesDir) {
4055
+ async function createSampleFiles(rulesDir) {
3941
4056
  const sampleFile = {
3942
4057
  filename: "overview.md",
3943
4058
  content: `---
@@ -3973,36 +4088,36 @@ globs: ["**/*"]
3973
4088
  - Follow single responsibility principle
3974
4089
  `
3975
4090
  };
3976
- const filepath = join24(aiRulesDir, sampleFile.filename);
4091
+ const filepath = join22(rulesDir, sampleFile.filename);
3977
4092
  if (!await fileExists(filepath)) {
3978
4093
  await writeFileContent(filepath, sampleFile.content);
3979
- console.log(`Created ${filepath}`);
4094
+ logger.success(`Created ${filepath}`);
3980
4095
  } else {
3981
- console.log(`Skipped ${filepath} (already exists)`);
4096
+ logger.log(`Skipped ${filepath} (already exists)`);
3982
4097
  }
3983
4098
  }
3984
4099
 
3985
4100
  // src/cli/commands/status.ts
3986
4101
  async function statusCommand() {
3987
4102
  const config = getDefaultConfig();
3988
- console.log("rulesync Status");
3989
- console.log("===============");
4103
+ logger.log("rulesync Status");
4104
+ logger.log("===============");
3990
4105
  const rulesyncExists = await fileExists(config.aiRulesDir);
3991
- console.log(`
4106
+ logger.log(`
3992
4107
  \u{1F4C1} .rulesync directory: ${rulesyncExists ? "\u2705 Found" : "\u274C Not found"}`);
3993
4108
  if (!rulesyncExists) {
3994
- console.log("\n\u{1F4A1} Run 'rulesync init' to get started");
4109
+ logger.log("\n\u{1F4A1} Run 'rulesync init' to get started");
3995
4110
  return;
3996
4111
  }
3997
4112
  try {
3998
4113
  const rules = await parseRulesFromDirectory(config.aiRulesDir);
3999
- console.log(`
4114
+ logger.log(`
4000
4115
  \u{1F4CB} Rules: ${rules.length} total`);
4001
4116
  if (rules.length > 0) {
4002
4117
  const rootRules = rules.filter((r) => r.frontmatter.root).length;
4003
4118
  const nonRootRules = rules.length - rootRules;
4004
- console.log(` - Root rules: ${rootRules}`);
4005
- console.log(` - Non-root rules: ${nonRootRules}`);
4119
+ logger.log(` - Root rules: ${rootRules}`);
4120
+ logger.log(` - Non-root rules: ${nonRootRules}`);
4006
4121
  const targetCounts = { copilot: 0, cursor: 0, cline: 0, claudecode: 0, roo: 0 };
4007
4122
  for (const rule of rules) {
4008
4123
  const targets = rule.frontmatter.targets[0] === "*" ? config.defaultTargets : rule.frontmatter.targets;
@@ -4014,63 +4129,63 @@ async function statusCommand() {
4014
4129
  else if (target === "roo") targetCounts.roo++;
4015
4130
  }
4016
4131
  }
4017
- console.log("\n\u{1F3AF} Target tool coverage:");
4018
- console.log(` - Copilot: ${targetCounts.copilot} rules`);
4019
- console.log(` - Cursor: ${targetCounts.cursor} rules`);
4020
- console.log(` - Cline: ${targetCounts.cline} rules`);
4021
- console.log(` - Claude Code: ${targetCounts.claudecode} rules`);
4022
- console.log(` - Roo: ${targetCounts.roo} rules`);
4132
+ logger.log("\n\u{1F3AF} Target tool coverage:");
4133
+ logger.log(` - Copilot: ${targetCounts.copilot} rules`);
4134
+ logger.log(` - Cursor: ${targetCounts.cursor} rules`);
4135
+ logger.log(` - Cline: ${targetCounts.cline} rules`);
4136
+ logger.log(` - Claude Code: ${targetCounts.claudecode} rules`);
4137
+ logger.log(` - Roo: ${targetCounts.roo} rules`);
4023
4138
  }
4024
- console.log("\n\u{1F4E4} Generated files:");
4139
+ logger.log("\n\u{1F4E4} Generated files:");
4025
4140
  for (const [tool, outputPath] of Object.entries(config.outputPaths)) {
4026
4141
  const outputExists = await fileExists(outputPath);
4027
- console.log(` - ${tool}: ${outputExists ? "\u2705 Generated" : "\u274C Not found"}`);
4142
+ logger.log(` - ${tool}: ${outputExists ? "\u2705 Generated" : "\u274C Not found"}`);
4028
4143
  }
4029
4144
  if (rules.length > 0) {
4030
- console.log("\n\u{1F4A1} Run 'rulesync generate' to update configuration files");
4145
+ logger.log("\n\u{1F4A1} Run 'rulesync generate' to update configuration files");
4031
4146
  }
4032
4147
  } catch (error) {
4033
- console.error("\n\u274C Failed to get status:", error);
4148
+ logger.error("\nFailed to get status:", error);
4034
4149
  }
4035
4150
  }
4036
4151
 
4037
4152
  // src/cli/commands/validate.ts
4038
4153
  async function validateCommand() {
4039
4154
  const config = getDefaultConfig();
4040
- console.log("Validating rulesync configuration...");
4155
+ logger.log("Validating rulesync configuration...");
4041
4156
  if (!await fileExists(config.aiRulesDir)) {
4042
- console.error("\u274C .rulesync directory not found. Run 'rulesync init' first.");
4157
+ logger.error(".rulesync directory not found. Run 'rulesync init' first.");
4043
4158
  process.exit(1);
4044
4159
  }
4045
4160
  try {
4046
4161
  const rules = await parseRulesFromDirectory(config.aiRulesDir);
4047
4162
  if (rules.length === 0) {
4048
- console.warn("\u26A0\uFE0F No rules found in .rulesync directory");
4163
+ logger.warn("No rules found in .rulesync directory");
4049
4164
  return;
4050
4165
  }
4051
- console.log(`Found ${rules.length} rule(s), validating...`);
4166
+ logger.log(`Found ${rules.length} rule(s), validating...`);
4052
4167
  const validation = await validateRules(rules);
4053
4168
  if (validation.warnings.length > 0) {
4054
- console.log("\n\u26A0\uFE0F Warnings:");
4169
+ logger.log("\n\u26A0\uFE0F Warnings:");
4055
4170
  for (const warning of validation.warnings) {
4056
- console.log(` - ${warning}`);
4171
+ logger.log(` - ${warning}`);
4057
4172
  }
4058
4173
  }
4059
4174
  if (validation.errors.length > 0) {
4060
- console.log("\n\u274C Errors:");
4175
+ logger.log("\nErrors:");
4061
4176
  for (const error of validation.errors) {
4062
- console.log(` - ${error}`);
4177
+ logger.log(` - ${error}`);
4063
4178
  }
4064
4179
  }
4065
4180
  if (validation.isValid) {
4066
- console.log("\n\u2705 All rules are valid!");
4181
+ logger.success("\nAll rules are valid!");
4067
4182
  } else {
4068
- console.log(`
4069
- \u274C Validation failed with ${validation.errors.length} error(s)`);
4183
+ logger.log(`
4184
+ Validation failed with ${validation.errors.length} error(s)`);
4070
4185
  process.exit(1);
4071
4186
  }
4072
4187
  } catch (error) {
4073
- console.error("\u274C Failed to validate rules:", error);
4188
+ logger.error("Failed to validate rules:", error);
4074
4189
  process.exit(1);
4075
4190
  }
4076
4191
  }
@@ -4079,8 +4194,8 @@ async function validateCommand() {
4079
4194
  import { watch } from "chokidar";
4080
4195
  async function watchCommand() {
4081
4196
  const config = getDefaultConfig();
4082
- console.log("\u{1F440} Watching for changes in .rulesync directory...");
4083
- console.log("Press Ctrl+C to stop watching");
4197
+ logger.log("\u{1F440} Watching for changes in .rulesync directory...");
4198
+ logger.log("Press Ctrl+C to stop watching");
4084
4199
  await generateCommand({ verbose: false });
4085
4200
  const watcher = watch(`${config.aiRulesDir}/**/*.md`, {
4086
4201
  ignoreInitial: true,
@@ -4090,26 +4205,26 @@ async function watchCommand() {
4090
4205
  const handleChange = async (path5) => {
4091
4206
  if (isGenerating) return;
4092
4207
  isGenerating = true;
4093
- console.log(`
4208
+ logger.log(`
4094
4209
  \u{1F4DD} Detected change in ${path5}`);
4095
4210
  try {
4096
4211
  await generateCommand({ verbose: false });
4097
- console.log("\u2705 Regenerated configuration files");
4212
+ logger.success("Regenerated configuration files");
4098
4213
  } catch (error) {
4099
- console.error("\u274C Failed to regenerate:", error);
4214
+ logger.error("Failed to regenerate:", error);
4100
4215
  } finally {
4101
4216
  isGenerating = false;
4102
4217
  }
4103
4218
  };
4104
4219
  watcher.on("change", handleChange).on("add", handleChange).on("unlink", (path5) => {
4105
- console.log(`
4220
+ logger.log(`
4106
4221
  \u{1F5D1}\uFE0F Removed ${path5}`);
4107
4222
  handleChange(path5);
4108
4223
  }).on("error", (error) => {
4109
- console.error("\u274C Watcher error:", error);
4224
+ logger.error("Watcher error:", error);
4110
4225
  });
4111
4226
  process.on("SIGINT", () => {
4112
- console.log("\n\n\u{1F44B} Stopping watcher...");
4227
+ logger.log("\n\n\u{1F44B} Stopping watcher...");
4113
4228
  watcher.close();
4114
4229
  process.exit(0);
4115
4230
  });
@@ -4117,11 +4232,11 @@ async function watchCommand() {
4117
4232
 
4118
4233
  // src/cli/index.ts
4119
4234
  var program = new Command();
4120
- program.name("rulesync").description("Unified AI rules management CLI tool").version("0.61.0");
4121
- program.command("init").description("Initialize rulesync in current directory").action(initCommand);
4122
- program.command("add <filename>").description("Add a new rule file").action(addCommand);
4235
+ program.name("rulesync").description("Unified AI rules management CLI tool").version("0.63.0");
4236
+ program.command("init").description("Initialize rulesync in current directory").option("--legacy", "Use legacy file location (.rulesync/*.md instead of .rulesync/rules/*.md)").action(initCommand);
4237
+ program.command("add <filename>").description("Add a new rule file").option("--legacy", "Use legacy file location (.rulesync/*.md instead of .rulesync/rules/*.md)").action(addCommand);
4123
4238
  program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
4124
- program.command("import").description("Import configurations from AI tools to rulesync format").option("--augmentcode", "Import from AugmentCode (.augment/rules/)").option("--augmentcode-legacy", "Import from AugmentCode legacy format (.augment-guidelines)").option("--claudecode", "Import from Claude Code (CLAUDE.md)").option("--cursor", "Import from Cursor (.cursorrules)").option("--copilot", "Import from GitHub Copilot (.github/copilot-instructions.md)").option("--cline", "Import from Cline (.cline/instructions.md)").option("--roo", "Import from Roo Code (.roo/instructions.md)").option("--geminicli", "Import from Gemini CLI (GEMINI.md)").option("--junie", "Import from JetBrains Junie (.junie/guidelines.md)").option("-v, --verbose", "Verbose output").action(importCommand);
4239
+ program.command("import").description("Import configurations from AI tools to rulesync format").option("--augmentcode", "Import from AugmentCode (.augment/rules/)").option("--augmentcode-legacy", "Import from AugmentCode legacy format (.augment-guidelines)").option("--claudecode", "Import from Claude Code (CLAUDE.md)").option("--cursor", "Import from Cursor (.cursorrules)").option("--copilot", "Import from GitHub Copilot (.github/copilot-instructions.md)").option("--cline", "Import from Cline (.cline/instructions.md)").option("--roo", "Import from Roo Code (.roo/instructions.md)").option("--geminicli", "Import from Gemini CLI (GEMINI.md)").option("--junie", "Import from JetBrains Junie (.junie/guidelines.md)").option("-v, --verbose", "Verbose output").option("--legacy", "Use legacy file location (.rulesync/*.md instead of .rulesync/rules/*.md)").action(importCommand);
4125
4240
  program.command("generate").description("Generate configuration files for AI tools").option("--augmentcode", "Generate only for AugmentCode").option("--augmentcode-legacy", "Generate only for AugmentCode legacy format").option("--copilot", "Generate only for GitHub Copilot").option("--cursor", "Generate only for Cursor").option("--cline", "Generate only for Cline").option("--codexcli", "Generate only for OpenAI Codex CLI").option("--claudecode", "Generate only for Claude Code").option("--roo", "Generate only for Roo Code").option("--geminicli", "Generate only for Gemini CLI").option("--junie", "Generate only for JetBrains Junie").option("--kiro", "Generate only for Kiro IDE").option("--windsurf", "Generate only for Windsurf").option("--delete", "Delete all existing files in output directories before generating").option(
4126
4241
  "-b, --base-dir <paths>",
4127
4242
  "Base directories to generate files (comma-separated for multiple paths)"