rulesync 0.62.0 → 0.64.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 (33) hide show
  1. package/README.md +15 -3
  2. package/dist/amazonqcli-WVGYACHI.js +9 -0
  3. package/dist/{augmentcode-HIZIQG2W.js → augmentcode-DTHPPXWO.js} +2 -2
  4. package/dist/{chunk-M7NL7G7A.js → chunk-4NAQ5HL4.js} +1 -1
  5. package/dist/{chunk-GQTMTBX4.js → chunk-6LSN7HSJ.js} +76 -4
  6. package/dist/chunk-DMTCLQ4T.js +17 -0
  7. package/dist/{chunk-YTU3SCQO.js → chunk-E2J3UBBK.js} +9 -3
  8. package/dist/{chunk-LXTA7DBA.js → chunk-EID75W45.js} +1 -1
  9. package/dist/{chunk-U4PLVMCG.js → chunk-FVPZQEWP.js} +1 -1
  10. package/dist/{chunk-NETSYSMD.js → chunk-HHJIL3YZ.js} +1 -1
  11. package/dist/{chunk-UEAYL4NT.js → chunk-JX55DU6Y.js} +1 -1
  12. package/dist/{chunk-KUGTKMNW.js → chunk-KKWJVA56.js} +5 -2
  13. package/dist/chunk-LURFNGH4.js +17 -0
  14. package/dist/{chunk-AUUSMVCT.js → chunk-LYVES5YR.js} +2 -0
  15. package/dist/{chunk-4PSTOKKD.js → chunk-TBXG53FV.js} +1 -1
  16. package/dist/{chunk-2CW2KFB3.js → chunk-TQOL7OKY.js} +1 -1
  17. package/dist/chunk-YPJW7Z5M.js +210 -0
  18. package/dist/{claudecode-YTEFACCT.js → claudecode-SSYLLUXX.js} +3 -3
  19. package/dist/{cline-CKNUDEA3.js → cline-5EUGKNZ6.js} +3 -3
  20. package/dist/{codexcli-7SDGYI7D.js → codexcli-IGM2ADYK.js} +3 -3
  21. package/dist/{copilot-MOR3HHJX.js → copilot-HSQO7ZCJ.js} +2 -2
  22. package/dist/{cursor-YJGH7W24.js → cursor-ZB3XNGBK.js} +3 -3
  23. package/dist/{geminicli-E7KZTZ2G.js → geminicli-FNRKH5GX.js} +3 -3
  24. package/dist/index.cjs +1144 -694
  25. package/dist/index.js +815 -609
  26. package/dist/{junie-5LEQU4BO.js → junie-3YGOSOGF.js} +3 -3
  27. package/dist/{kiro-YDHXY2MA.js → kiro-B6WZNLY4.js} +2 -2
  28. package/dist/opencode-SZETJ62M.js +17 -0
  29. package/dist/{roo-L3QTTIPO.js → roo-KLTWVAKE.js} +3 -2
  30. package/dist/{windsurf-4P6HEUBV.js → windsurf-IZEKUAID.js} +3 -3
  31. package/package.json +2 -1
  32. package/dist/chunk-MDYDKNXQ.js +0 -61
  33. package/dist/chunk-PCATT4UZ.js +0 -78
package/dist/index.js CHANGED
@@ -1,23 +1,35 @@
1
1
  #!/usr/bin/env node
2
- import "./chunk-M7NL7G7A.js";
3
- import "./chunk-LXTA7DBA.js";
4
- import "./chunk-PCATT4UZ.js";
5
- import "./chunk-NETSYSMD.js";
6
- import "./chunk-YTU3SCQO.js";
7
- import "./chunk-U4PLVMCG.js";
8
- import "./chunk-UEAYL4NT.js";
9
- import "./chunk-2CW2KFB3.js";
10
- import "./chunk-KUGTKMNW.js";
11
- import "./chunk-4PSTOKKD.js";
12
- import "./chunk-MDYDKNXQ.js";
13
- import "./chunk-GQTMTBX4.js";
2
+ import "./chunk-DMTCLQ4T.js";
3
+ import "./chunk-4NAQ5HL4.js";
4
+ import "./chunk-EID75W45.js";
5
+ import "./chunk-LURFNGH4.js";
6
+ import "./chunk-HHJIL3YZ.js";
7
+ import {
8
+ ensureDir,
9
+ fileExists,
10
+ findFiles,
11
+ findRuleFiles,
12
+ logger,
13
+ readFileContent,
14
+ removeClaudeGeneratedFiles,
15
+ removeDirectory,
16
+ resolvePath,
17
+ writeFileContent
18
+ } from "./chunk-YPJW7Z5M.js";
19
+ import "./chunk-E2J3UBBK.js";
20
+ import "./chunk-FVPZQEWP.js";
21
+ import "./chunk-JX55DU6Y.js";
22
+ import "./chunk-TQOL7OKY.js";
23
+ import "./chunk-KKWJVA56.js";
24
+ import "./chunk-TBXG53FV.js";
25
+ import "./chunk-6LSN7HSJ.js";
14
26
  import {
15
27
  ALL_TOOL_TARGETS,
16
28
  RulesyncTargetsSchema,
17
29
  ToolTargetSchema,
18
30
  ToolTargetsSchema,
19
31
  isToolTarget
20
- } from "./chunk-AUUSMVCT.js";
32
+ } from "./chunk-LYVES5YR.js";
21
33
 
22
34
  // src/cli/index.ts
23
35
  import { Command } from "commander";
@@ -41,12 +53,20 @@ var ClaudeSettingsSchema = z.looseObject({
41
53
  )
42
54
  });
43
55
 
44
- // src/types/commands.ts
56
+ // src/types/shared.ts
45
57
  import { z as z2 } from "zod/mini";
46
- var CommandFrontmatterSchema = z2.object({
58
+ var OutputSchema = z2.object({
59
+ tool: ToolTargetSchema,
60
+ filepath: z2.string(),
61
+ content: z2.string()
62
+ });
63
+ var BaseFrontmatterSchema = z2.object({
47
64
  description: z2.optional(z2.string())
48
65
  });
49
66
 
67
+ // src/types/commands.ts
68
+ var CommandFrontmatterSchema = BaseFrontmatterSchema;
69
+
50
70
  // src/types/config.ts
51
71
  import { z as z3 } from "zod/mini";
52
72
  var ConfigSchema = z3.object({
@@ -62,6 +82,7 @@ var ConfigSchema = z3.object({
62
82
  // src/types/config-options.ts
63
83
  import { z as z4 } from "zod/mini";
64
84
  var OutputPathsSchema = z4.object({
85
+ amazonqcli: z4.optional(z4.string()),
65
86
  augmentcode: z4.optional(z4.string()),
66
87
  "augmentcode-legacy": z4.optional(z4.string()),
67
88
  copilot: z4.optional(z4.string()),
@@ -69,6 +90,7 @@ var OutputPathsSchema = z4.object({
69
90
  cline: z4.optional(z4.string()),
70
91
  claudecode: z4.optional(z4.string()),
71
92
  codexcli: z4.optional(z4.string()),
93
+ opencode: z4.optional(z4.string()),
72
94
  roo: z4.optional(z4.string()),
73
95
  geminicli: z4.optional(z4.string()),
74
96
  kiro: z4.optional(z4.string()),
@@ -119,7 +141,7 @@ var MergedConfigSchema = z4.object({
119
141
  import { z as z5 } from "zod/mini";
120
142
  var McpTransportTypeSchema = z5.enum(["stdio", "sse", "http"]);
121
143
  var McpServerBaseSchema = z5.object({
122
- command: z5.optional(z5.string()),
144
+ command: z5.optional(z5.union([z5.string(), z5.array(z5.string())])),
123
145
  args: z5.optional(z5.array(z5.string())),
124
146
  url: z5.optional(z5.string()),
125
147
  httpUrl: z5.optional(z5.string()),
@@ -159,11 +181,6 @@ var RuleFrontmatterSchema = z6.object({
159
181
  windsurfOutputFormat: z6.optional(z6.enum(["single-file", "directory"])),
160
182
  tags: z6.optional(z6.array(z6.string()))
161
183
  });
162
- var GeneratedOutputSchema = z6.object({
163
- tool: ToolTargetSchema,
164
- filepath: z6.string(),
165
- content: z6.string()
166
- });
167
184
  var GenerateOptionsSchema = z6.object({
168
185
  targetTools: z6.optional(ToolTargetsSchema),
169
186
  outputDir: z6.optional(z6.string()),
@@ -175,6 +192,7 @@ function getDefaultConfig() {
175
192
  return {
176
193
  aiRulesDir: ".rulesync",
177
194
  outputPaths: {
195
+ amazonqcli: ".amazonq/rules",
178
196
  augmentcode: ".",
179
197
  "augmentcode-legacy": ".",
180
198
  copilot: ".github/instructions",
@@ -182,6 +200,7 @@ function getDefaultConfig() {
182
200
  cline: ".clinerules",
183
201
  claudecode: ".",
184
202
  codexcli: ".",
203
+ opencode: ".",
185
204
  roo: ".roo/rules",
186
205
  geminicli: ".gemini/memories",
187
206
  kiro: ".kiro/steering",
@@ -417,11 +436,11 @@ async function addCommand(filename, options = {}) {
417
436
  await mkdir(rulesDir, { recursive: true });
418
437
  const template = generateRuleTemplate(sanitizedFilename);
419
438
  await writeFile(filePath, template, "utf8");
420
- console.log(`\u2705 Created rule file: ${filePath}`);
421
- console.log(`\u{1F4DD} Edit the file to customize your rules.`);
439
+ logger.success(`Created rule file: ${filePath}`);
440
+ logger.log(`\u{1F4DD} Edit the file to customize your rules.`);
422
441
  } catch (error) {
423
- console.error(
424
- `\u274C Failed to create rule file: ${error instanceof Error ? error.message : String(error)}`
442
+ logger.error(
443
+ `Failed to create rule file: ${error instanceof Error ? error.message : String(error)}`
425
444
  );
426
445
  process.exit(3);
427
446
  }
@@ -461,133 +480,6 @@ async function safeAsyncOperation(operation, errorContext) {
461
480
  }
462
481
  }
463
482
 
464
- // src/utils/file.ts
465
- import { mkdir as mkdir2, readdir, readFile, rm, stat, writeFile as writeFile2 } from "fs/promises";
466
- import { dirname, join as join2 } from "path";
467
- async function ensureDir(dirPath) {
468
- try {
469
- await stat(dirPath);
470
- } catch {
471
- await mkdir2(dirPath, { recursive: true });
472
- }
473
- }
474
- function resolvePath(relativePath, baseDir) {
475
- return baseDir ? join2(baseDir, relativePath) : relativePath;
476
- }
477
- async function readFileContent(filepath) {
478
- return readFile(filepath, "utf-8");
479
- }
480
- async function writeFileContent(filepath, content) {
481
- await ensureDir(dirname(filepath));
482
- await writeFile2(filepath, content, "utf-8");
483
- }
484
- async function fileExists(filepath) {
485
- try {
486
- await stat(filepath);
487
- return true;
488
- } catch {
489
- return false;
490
- }
491
- }
492
- async function findFiles(dir, extension = ".md") {
493
- try {
494
- const files = await readdir(dir);
495
- return files.filter((file) => file.endsWith(extension)).map((file) => join2(dir, file));
496
- } catch {
497
- return [];
498
- }
499
- }
500
- async function findRuleFiles(aiRulesDir) {
501
- const rulesDir = join2(aiRulesDir, "rules");
502
- const newLocationFiles = await findFiles(rulesDir, ".md");
503
- const legacyLocationFiles = await findFiles(aiRulesDir, ".md");
504
- const newLocationBasenames = new Set(
505
- newLocationFiles.map((file) => file.split("/").pop()?.replace(/\.md$/, ""))
506
- );
507
- const filteredLegacyFiles = legacyLocationFiles.filter((file) => {
508
- const basename6 = file.split("/").pop()?.replace(/\.md$/, "");
509
- return !newLocationBasenames.has(basename6);
510
- });
511
- return [...newLocationFiles, ...filteredLegacyFiles];
512
- }
513
- async function removeDirectory(dirPath) {
514
- const dangerousPaths = [".", "/", "~", "src", "node_modules"];
515
- if (dangerousPaths.includes(dirPath) || dirPath === "") {
516
- console.warn(`Skipping deletion of dangerous path: ${dirPath}`);
517
- return;
518
- }
519
- try {
520
- if (await fileExists(dirPath)) {
521
- await rm(dirPath, { recursive: true, force: true });
522
- }
523
- } catch (error) {
524
- console.warn(`Failed to remove directory ${dirPath}:`, error);
525
- }
526
- }
527
- async function removeFile(filepath) {
528
- try {
529
- if (await fileExists(filepath)) {
530
- await rm(filepath);
531
- }
532
- } catch (error) {
533
- console.warn(`Failed to remove file ${filepath}:`, error);
534
- }
535
- }
536
- async function removeClaudeGeneratedFiles() {
537
- const filesToRemove = ["CLAUDE.md", ".claude/memories"];
538
- for (const fileOrDir of filesToRemove) {
539
- if (fileOrDir.endsWith("/memories")) {
540
- await removeDirectory(fileOrDir);
541
- } else {
542
- await removeFile(fileOrDir);
543
- }
544
- }
545
- }
546
-
547
- // src/utils/logger.ts
548
- import { consola } from "consola";
549
- var Logger = class {
550
- _verbose = false;
551
- console = consola.withDefaults({
552
- tag: "rulesync"
553
- });
554
- setVerbose(verbose) {
555
- this._verbose = verbose;
556
- }
557
- get verbose() {
558
- return this._verbose;
559
- }
560
- // Regular log (always shown, regardless of verbose)
561
- log(message, ...args) {
562
- this.console.log(message, ...args);
563
- }
564
- // Info level (shown only in verbose mode)
565
- info(message, ...args) {
566
- if (this._verbose) {
567
- this.console.info(message, ...args);
568
- }
569
- }
570
- // Success (always shown)
571
- success(message, ...args) {
572
- this.console.success(message, ...args);
573
- }
574
- // Warning (always shown)
575
- warn(message, ...args) {
576
- this.console.warn(message, ...args);
577
- }
578
- // Error (always shown)
579
- error(message, ...args) {
580
- this.console.error(message, ...args);
581
- }
582
- // Debug level (shown only in verbose mode)
583
- debug(message, ...args) {
584
- if (this._verbose) {
585
- this.console.debug(message, ...args);
586
- }
587
- }
588
- };
589
- var logger = new Logger();
590
-
591
483
  // src/cli/commands/config.ts
592
484
  async function configCommand(options = {}) {
593
485
  if (options.init) {
@@ -799,96 +691,161 @@ export default config;
799
691
  }
800
692
 
801
693
  // src/cli/commands/generate.ts
802
- import { join as join15 } from "path";
694
+ import { join as join12 } from "path";
803
695
 
804
696
  // src/core/command-generator.ts
805
- import { join as join6 } from "path";
806
-
807
- // src/generators/commands/claudecode.ts
808
697
  import { join as join3 } from "path";
809
- var ClaudeCodeCommandGenerator = class {
810
- generate(command, outputDir) {
811
- const filepath = this.getOutputPath(command.filename, outputDir);
812
- const frontmatter = ["---"];
813
- if (command.frontmatter.description) {
814
- frontmatter.push(`description: ${command.frontmatter.description}`);
698
+
699
+ // src/utils/command-generators.ts
700
+ import { join as join2 } from "path";
701
+ function generateYamlFrontmatter(command, options) {
702
+ const frontmatterLines = ["---"];
703
+ if (options?.includeDescription && command.frontmatter.description) {
704
+ frontmatterLines.push(`description: ${command.frontmatter.description}`);
705
+ }
706
+ if (options?.additionalFields) {
707
+ for (const field of options.additionalFields) {
708
+ frontmatterLines.push(`${field.key}: ${field.value}`);
815
709
  }
816
- frontmatter.push("---");
817
- const content = `${frontmatter.join("\n")}
710
+ }
711
+ frontmatterLines.push("---");
712
+ return frontmatterLines;
713
+ }
714
+ function buildCommandContent(command, frontmatterOptions) {
715
+ const frontmatter = generateYamlFrontmatter(command, frontmatterOptions);
716
+ return `${frontmatter.join("\n")}
818
717
 
819
718
  ${command.content.trim()}
820
719
  `;
720
+ }
721
+ function getFlattenedCommandPath(filename, baseDir, subdir) {
722
+ const flattenedName = filename.replace(/\//g, "-");
723
+ return join2(baseDir, subdir, `${flattenedName}.md`);
724
+ }
725
+ function getHierarchicalCommandPath(filename, baseDir, subdir, extension = "md") {
726
+ const nameWithoutExt = filename.replace(/\.[^/.]+$/, "");
727
+ const fileWithExt = `${nameWithoutExt}.${extension}`;
728
+ return join2(baseDir, subdir, fileWithExt);
729
+ }
730
+ function escapeTomlString(str) {
731
+ return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
732
+ }
733
+ var syntaxConverters = {
734
+ /**
735
+ * Convert Claude Code syntax to Gemini CLI syntax
736
+ */
737
+ toGeminiCli(content) {
738
+ let converted = content;
739
+ converted = converted.replace(/\$ARGUMENTS/g, "{{args}}");
740
+ converted = converted.replace(/!`([^`]+)`/g, "!{$1}");
741
+ return converted.trim();
742
+ },
743
+ /**
744
+ * Convert to Roo Code syntax (currently identical to Claude Code)
745
+ */
746
+ toRooCode(content) {
747
+ return content.trim();
748
+ }
749
+ };
750
+
751
+ // src/generators/commands/base.ts
752
+ var BaseCommandGenerator = class {
753
+ /**
754
+ * Generate command output for the specified tool
755
+ */
756
+ generate(command, outputDir) {
757
+ const filepath = this.getOutputPath(command.filename, outputDir);
758
+ const content = this.processContent(command);
821
759
  return {
822
- tool: "claudecode",
760
+ tool: this.getToolName(),
823
761
  filepath,
824
762
  content
825
763
  };
826
764
  }
765
+ /**
766
+ * Get the output path for the command file
767
+ * Override this method if custom path logic is needed
768
+ */
827
769
  getOutputPath(filename, baseDir) {
828
- const flattenedName = filename.replace(/\//g, "-");
829
- return join3(baseDir, ".claude", "commands", `${flattenedName}.md`);
770
+ if (this.supportsHierarchy()) {
771
+ return getHierarchicalCommandPath(
772
+ filename,
773
+ baseDir,
774
+ this.getCommandsDirectory(),
775
+ this.getFileExtension()
776
+ );
777
+ } else {
778
+ return getFlattenedCommandPath(filename, baseDir, this.getCommandsDirectory());
779
+ }
780
+ }
781
+ /**
782
+ * Whether this tool supports hierarchical directory structure
783
+ * Override to return true for tools that support nested commands
784
+ */
785
+ supportsHierarchy() {
786
+ return false;
787
+ }
788
+ /**
789
+ * Get file extension for the target tool
790
+ * Override if tool uses different extension than .md
791
+ */
792
+ getFileExtension() {
793
+ return "md";
794
+ }
795
+ };
796
+
797
+ // src/generators/commands/claudecode.ts
798
+ var ClaudeCodeCommandGenerator = class extends BaseCommandGenerator {
799
+ getToolName() {
800
+ return "claudecode";
801
+ }
802
+ getCommandsDirectory() {
803
+ return ".claude/commands";
804
+ }
805
+ processContent(command) {
806
+ return buildCommandContent(command, { includeDescription: true });
830
807
  }
808
+ // Uses flattened structure by default (supportsHierarchy returns false)
831
809
  };
832
810
 
833
811
  // src/generators/commands/geminicli.ts
834
- import { join as join4 } from "path";
835
- var GeminiCliCommandGenerator = class {
836
- generate(command, outputDir) {
837
- const filepath = this.getOutputPath(command.filename, outputDir);
838
- const convertedContent = this.convertSyntax(command.content);
812
+ var GeminiCliCommandGenerator = class extends BaseCommandGenerator {
813
+ getToolName() {
814
+ return "geminicli";
815
+ }
816
+ getCommandsDirectory() {
817
+ return ".gemini/commands";
818
+ }
819
+ processContent(command) {
820
+ const convertedContent = syntaxConverters.toGeminiCli(command.content);
839
821
  const tomlLines = [];
840
822
  if (command.frontmatter.description) {
841
- tomlLines.push(`description = "${this.escapeTomlString(command.frontmatter.description)}"`);
823
+ tomlLines.push(`description = "${escapeTomlString(command.frontmatter.description)}"`);
842
824
  tomlLines.push("");
843
825
  }
844
826
  tomlLines.push(`prompt = """${convertedContent}"""`);
845
- const content = tomlLines.join("\n") + "\n";
846
- return {
847
- tool: "geminicli",
848
- filepath,
849
- content
850
- };
851
- }
852
- getOutputPath(filename, baseDir) {
853
- const tomlFilename = filename.replace(/\.md$/, ".toml");
854
- const filenameWithExt = tomlFilename.endsWith(".toml") ? tomlFilename : `${tomlFilename}.toml`;
855
- return join4(baseDir, ".gemini", "commands", filenameWithExt);
827
+ return tomlLines.join("\n") + "\n";
856
828
  }
857
- convertSyntax(content) {
858
- let converted = content;
859
- converted = converted.replace(/\$ARGUMENTS/g, "{{args}}");
860
- converted = converted.replace(/!`([^`]+)`/g, "!{$1}");
861
- return converted.trim();
829
+ supportsHierarchy() {
830
+ return true;
862
831
  }
863
- escapeTomlString(str) {
864
- return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
832
+ getFileExtension() {
833
+ return "toml";
865
834
  }
866
835
  };
867
836
 
868
837
  // src/generators/commands/roo.ts
869
- import { join as join5 } from "path";
870
- var RooCommandGenerator = class {
871
- generate(command, outputDir) {
872
- const filepath = this.getOutputPath(command.filename, outputDir);
873
- const frontmatter = ["---"];
874
- if (command.frontmatter.description) {
875
- frontmatter.push(`description: ${command.frontmatter.description}`);
876
- }
877
- frontmatter.push("---");
878
- const content = `${frontmatter.join("\n")}
879
-
880
- ${command.content.trim()}
881
- `;
882
- return {
883
- tool: "roo",
884
- filepath,
885
- content
886
- };
838
+ var RooCommandGenerator = class extends BaseCommandGenerator {
839
+ getToolName() {
840
+ return "roo";
887
841
  }
888
- getOutputPath(filename, baseDir) {
889
- const flattenedName = filename.replace(/\//g, "-");
890
- return join5(baseDir, ".roo", "commands", `${flattenedName}.md`);
842
+ getCommandsDirectory() {
843
+ return ".roo/commands";
844
+ }
845
+ processContent(command) {
846
+ return buildCommandContent(command, { includeDescription: true });
891
847
  }
848
+ // Uses flattened structure by default (supportsHierarchy returns false)
892
849
  };
893
850
 
894
851
  // src/generators/commands/index.ts
@@ -903,7 +860,26 @@ function getCommandGenerator(tool) {
903
860
 
904
861
  // src/core/command-parser.ts
905
862
  import { basename } from "path";
863
+
864
+ // src/utils/frontmatter.ts
906
865
  import matter from "gray-matter";
866
+ function parseFrontmatter(content, options) {
867
+ const parsed = matter(content, options?.matterOptions);
868
+ return {
869
+ content: parsed.content.trim(),
870
+ data: parsed.data || {}
871
+ };
872
+ }
873
+ function extractArrayField(data, key, defaultValue = []) {
874
+ const value = data[key];
875
+ return Array.isArray(value) ? value : defaultValue;
876
+ }
877
+ function extractStringField(data, key, defaultValue) {
878
+ const value = data[key];
879
+ return typeof value === "string" ? value : defaultValue;
880
+ }
881
+
882
+ // src/core/command-parser.ts
907
883
  async function parseCommandsFromDirectory(commandsDir) {
908
884
  const commandFiles = await findFiles(commandsDir, ".md");
909
885
  const commands = [];
@@ -918,14 +894,14 @@ async function parseCommandsFromDirectory(commandsDir) {
918
894
  }
919
895
  }
920
896
  if (errors.length > 0) {
921
- console.warn(`\u26A0\uFE0F Command parsing errors:
897
+ logger.warn(`Command parsing errors:
922
898
  ${errors.join("\n")}`);
923
899
  }
924
900
  return commands;
925
901
  }
926
902
  async function parseCommandFile(filepath) {
927
903
  const content = await readFileContent(filepath);
928
- const parsed = matter(content);
904
+ const parsed = parseFrontmatter(content);
929
905
  try {
930
906
  const validatedData = CommandFrontmatterSchema.parse(parsed.data);
931
907
  const filename = basename(filepath, ".md");
@@ -946,7 +922,7 @@ async function parseCommandFile(filepath) {
946
922
 
947
923
  // src/core/command-generator.ts
948
924
  async function generateCommands(projectRoot, baseDir, targets) {
949
- const commandsDir = join6(projectRoot, ".rulesync", "commands");
925
+ const commandsDir = join3(projectRoot, ".rulesync", "commands");
950
926
  if (!await fileExists(commandsDir)) {
951
927
  return [];
952
928
  }
@@ -970,8 +946,8 @@ async function generateCommands(projectRoot, baseDir, targets) {
970
946
  outputs.push(output);
971
947
  } catch (error) {
972
948
  const errorMessage = error instanceof Error ? error.message : String(error);
973
- console.error(
974
- `\u274C Failed to generate ${target} command for ${command.filename}: ${errorMessage}`
949
+ logger.error(
950
+ `Failed to generate ${target} command for ${command.filename}: ${errorMessage}`
975
951
  );
976
952
  }
977
953
  }
@@ -980,7 +956,7 @@ async function generateCommands(projectRoot, baseDir, targets) {
980
956
  }
981
957
 
982
958
  // src/generators/ignore/shared-factory.ts
983
- import { join as join7 } from "path";
959
+ import { join as join4 } from "path";
984
960
 
985
961
  // src/generators/ignore/shared-helpers.ts
986
962
  function extractIgnorePatternsFromRules(rules) {
@@ -1103,7 +1079,7 @@ function generateIgnoreFile(rules, config, ignoreConfig, baseDir) {
1103
1079
  const outputs = [];
1104
1080
  const content = generateIgnoreContent(rules, ignoreConfig);
1105
1081
  const outputPath = baseDir || process.cwd();
1106
- const filepath = join7(outputPath, ignoreConfig.filename);
1082
+ const filepath = join4(outputPath, ignoreConfig.filename);
1107
1083
  outputs.push({
1108
1084
  tool: ignoreConfig.tool,
1109
1085
  filepath,
@@ -1690,21 +1666,18 @@ function generateWindsurfIgnore(rules, config, baseDir) {
1690
1666
  return generateIgnoreFile(rules, config, ignoreConfigs.windsurf, baseDir);
1691
1667
  }
1692
1668
 
1693
- // src/generators/rules/augmentcode.ts
1694
- import { join as join10 } from "path";
1695
-
1696
1669
  // src/generators/rules/shared-helpers.ts
1697
- import { join as join9 } from "path";
1670
+ import { join as join6 } from "path";
1698
1671
 
1699
1672
  // src/utils/ignore.ts
1700
- import { join as join8 } from "path";
1673
+ import { join as join5 } from "path";
1701
1674
  import micromatch from "micromatch";
1702
1675
  var cachedIgnorePatterns = null;
1703
1676
  async function loadIgnorePatterns(baseDir = process.cwd()) {
1704
1677
  if (cachedIgnorePatterns) {
1705
1678
  return cachedIgnorePatterns;
1706
1679
  }
1707
- const ignorePath = join8(baseDir, ".rulesyncignore");
1680
+ const ignorePath = join5(baseDir, ".rulesyncignore");
1708
1681
  if (!await fileExists(ignorePath)) {
1709
1682
  cachedIgnorePatterns = { patterns: [] };
1710
1683
  return cachedIgnorePatterns;
@@ -1715,7 +1688,7 @@ async function loadIgnorePatterns(baseDir = process.cwd()) {
1715
1688
  cachedIgnorePatterns = { patterns };
1716
1689
  return cachedIgnorePatterns;
1717
1690
  } catch (error) {
1718
- console.warn(`Failed to read .rulesyncignore: ${error}`);
1691
+ logger.warn(`Failed to read .rulesyncignore: ${error}`);
1719
1692
  cachedIgnorePatterns = { patterns: [] };
1720
1693
  return cachedIgnorePatterns;
1721
1694
  }
@@ -1758,7 +1731,7 @@ function addOutput(outputs, tool, config, baseDir, relativePath, content) {
1758
1731
  const outputDir = resolveOutputDir(config, tool, baseDir);
1759
1732
  outputs.push({
1760
1733
  tool,
1761
- filepath: join9(outputDir, relativePath),
1734
+ filepath: join6(outputDir, relativePath),
1762
1735
  content
1763
1736
  });
1764
1737
  }
@@ -1767,7 +1740,7 @@ async function generateRulesConfig(rules, config, generatorConfig, baseDir) {
1767
1740
  for (const rule of rules) {
1768
1741
  const content = generatorConfig.generateContent(rule);
1769
1742
  const outputDir = resolveOutputDir(config, generatorConfig.tool, baseDir);
1770
- const filepath = generatorConfig.pathResolver ? generatorConfig.pathResolver(rule, outputDir) : join9(outputDir, `${rule.filename}${generatorConfig.fileExtension}`);
1743
+ const filepath = generatorConfig.pathResolver ? generatorConfig.pathResolver(rule, outputDir) : join6(outputDir, `${rule.filename}${generatorConfig.fileExtension}`);
1771
1744
  outputs.push({
1772
1745
  tool: generatorConfig.tool,
1773
1746
  filepath,
@@ -1795,7 +1768,7 @@ async function generateComplexRules(rules, config, generatorConfig, baseDir) {
1795
1768
  for (const rule of detailRules) {
1796
1769
  const content = generatorConfig.generateDetailContent(rule);
1797
1770
  const filepath = resolvePath(
1798
- join9(generatorConfig.detailSubDir, `${rule.filename}.md`),
1771
+ join6(generatorConfig.detailSubDir, `${rule.filename}.md`),
1799
1772
  baseDir
1800
1773
  );
1801
1774
  outputs.push({
@@ -1849,7 +1822,61 @@ function generateIgnoreFile2(patterns, tool) {
1849
1822
  return lines.join("\n");
1850
1823
  }
1851
1824
 
1825
+ // src/generators/rules/amazonqcli.ts
1826
+ async function generateAmazonqcliConfig(rules, config, baseDir) {
1827
+ const generatorConfig = {
1828
+ tool: "amazonqcli",
1829
+ fileExtension: ".md",
1830
+ generateContent: generateRuleFile,
1831
+ generateRootContent: generateMainRulesFile,
1832
+ rootFilePath: ".amazonq/rules/main.md",
1833
+ generateDetailContent: generateRuleFile,
1834
+ detailSubDir: ".amazonq/rules"
1835
+ };
1836
+ return generateComplexRules(rules, config, generatorConfig, baseDir);
1837
+ }
1838
+ function generateMainRulesFile(rootRule, detailRules) {
1839
+ const lines = [];
1840
+ if (detailRules.length > 0) {
1841
+ lines.push("# Amazon Q Developer CLI Project Rules");
1842
+ lines.push("");
1843
+ lines.push("This file contains the main project rules. See also:");
1844
+ lines.push("");
1845
+ for (const rule of detailRules) {
1846
+ lines.push(`- ${rule.filename}.md: ${rule.frontmatter.description}`);
1847
+ }
1848
+ lines.push("");
1849
+ }
1850
+ if (rootRule) {
1851
+ if (detailRules.length > 0) {
1852
+ lines.push("## Overview");
1853
+ lines.push("");
1854
+ }
1855
+ lines.push(rootRule.content);
1856
+ lines.push("");
1857
+ } else if (detailRules.length === 0) {
1858
+ lines.push("# Amazon Q Developer CLI Project Rules");
1859
+ lines.push("");
1860
+ lines.push("This file contains project-specific rules and context for Amazon Q Developer CLI.");
1861
+ lines.push("");
1862
+ lines.push("## Development Standards");
1863
+ lines.push("");
1864
+ lines.push("Add your project-specific development standards here.");
1865
+ lines.push("");
1866
+ }
1867
+ return lines.join("\n").trim() + "\n";
1868
+ }
1869
+ function generateRuleFile(rule) {
1870
+ const lines = [];
1871
+ lines.push(`# ${rule.frontmatter.description || rule.filename}`);
1872
+ lines.push("");
1873
+ lines.push(rule.content.trim());
1874
+ lines.push("");
1875
+ return lines.join("\n");
1876
+ }
1877
+
1852
1878
  // src/generators/rules/augmentcode.ts
1879
+ import { join as join7 } from "path";
1853
1880
  async function generateAugmentcodeConfig(rules, config, baseDir) {
1854
1881
  const outputs = createOutputsArray();
1855
1882
  rules.forEach((rule) => {
@@ -1858,13 +1885,13 @@ async function generateAugmentcodeConfig(rules, config, baseDir) {
1858
1885
  "augmentcode",
1859
1886
  config,
1860
1887
  baseDir,
1861
- join10(".augment", "rules", `${rule.filename}.md`),
1862
- generateRuleFile(rule)
1888
+ join7(".augment", "rules", `${rule.filename}.md`),
1889
+ generateRuleFile2(rule)
1863
1890
  );
1864
1891
  });
1865
1892
  return outputs;
1866
1893
  }
1867
- function generateRuleFile(rule) {
1894
+ function generateRuleFile2(rule) {
1868
1895
  const lines = [];
1869
1896
  lines.push("---");
1870
1897
  let ruleType = "manual";
@@ -1911,7 +1938,7 @@ function generateLegacyGuidelinesFile(allRules) {
1911
1938
  }
1912
1939
 
1913
1940
  // src/generators/rules/claudecode.ts
1914
- import { join as join11 } from "path";
1941
+ import { join as join8 } from "path";
1915
1942
  async function generateClaudecodeConfig(rules, config, baseDir) {
1916
1943
  const generatorConfig = {
1917
1944
  tool: "claudecode",
@@ -1923,7 +1950,7 @@ async function generateClaudecodeConfig(rules, config, baseDir) {
1923
1950
  generateDetailContent: generateMemoryFile,
1924
1951
  detailSubDir: ".claude/memories",
1925
1952
  updateAdditionalConfig: async (ignorePatterns, baseDir2) => {
1926
- const settingsPath = resolvePath(join11(".claude", "settings.json"), baseDir2);
1953
+ const settingsPath = resolvePath(join8(".claude", "settings.json"), baseDir2);
1927
1954
  await updateClaudeSettings(settingsPath, ignorePatterns);
1928
1955
  return [];
1929
1956
  }
@@ -1960,7 +1987,7 @@ async function updateClaudeSettings(settingsPath, ignorePatterns) {
1960
1987
  const content = await readFileContent(settingsPath);
1961
1988
  rawSettings = JSON.parse(content);
1962
1989
  } catch {
1963
- console.warn(`Failed to parse existing ${settingsPath}, creating new settings`);
1990
+ logger.warn(`Failed to parse existing ${settingsPath}, creating new settings`);
1964
1991
  rawSettings = {};
1965
1992
  }
1966
1993
  }
@@ -1983,11 +2010,11 @@ async function updateClaudeSettings(settingsPath, ignorePatterns) {
1983
2010
  settings.permissions.deny = Array.from(new Set(filteredDeny));
1984
2011
  const jsonContent = JSON.stringify(settings, null, 2);
1985
2012
  await writeFileContent(settingsPath, jsonContent);
1986
- console.log(`\u2705 Updated Claude Code settings: ${settingsPath}`);
2013
+ logger.success(`Updated Claude Code settings: ${settingsPath}`);
1987
2014
  }
1988
2015
 
1989
2016
  // src/generators/rules/generator-registry.ts
1990
- import { join as join12 } from "path";
2017
+ import { join as join9 } from "path";
1991
2018
  function determineCursorRuleType(frontmatter) {
1992
2019
  if (frontmatter.cursorRuleType) {
1993
2020
  return frontmatter.cursorRuleType;
@@ -2011,6 +2038,22 @@ function determineCursorRuleType(frontmatter) {
2011
2038
  }
2012
2039
  var GENERATOR_REGISTRY = {
2013
2040
  // Simple generators - generate one file per rule
2041
+ amazonqcli: {
2042
+ type: "complex",
2043
+ tool: "amazonqcli",
2044
+ fileExtension: ".md",
2045
+ // ignoreFileName omitted - Amazon Q CLI doesn't have native ignore file support yet
2046
+ generateContent: (rule) => {
2047
+ const lines = [];
2048
+ if (rule.frontmatter.description) {
2049
+ lines.push(`# ${rule.frontmatter.description}
2050
+ `);
2051
+ }
2052
+ lines.push(rule.content.trim());
2053
+ return lines.join("\n");
2054
+ }
2055
+ // Complex generation handled by existing generator
2056
+ },
2014
2057
  cline: {
2015
2058
  type: "simple",
2016
2059
  tool: "cline",
@@ -2067,7 +2110,7 @@ var GENERATOR_REGISTRY = {
2067
2110
  },
2068
2111
  pathResolver: (rule, outputDir) => {
2069
2112
  const baseFilename = rule.filename.replace(/\.md$/, "");
2070
- return join12(outputDir, `${baseFilename}.instructions.md`);
2113
+ return join9(outputDir, `${baseFilename}.instructions.md`);
2071
2114
  }
2072
2115
  },
2073
2116
  cursor: {
@@ -2107,7 +2150,7 @@ var GENERATOR_REGISTRY = {
2107
2150
  return lines.join("\n");
2108
2151
  },
2109
2152
  pathResolver: (rule, outputDir) => {
2110
- return join12(outputDir, `${rule.filename}.mdc`);
2153
+ return join9(outputDir, `${rule.filename}.mdc`);
2111
2154
  }
2112
2155
  },
2113
2156
  codexcli: {
@@ -2143,10 +2186,10 @@ var GENERATOR_REGISTRY = {
2143
2186
  pathResolver: (rule, outputDir) => {
2144
2187
  const outputFormat = rule.frontmatter.windsurfOutputFormat || "directory";
2145
2188
  if (outputFormat === "single-file") {
2146
- return join12(outputDir, ".windsurf-rules");
2189
+ return join9(outputDir, ".windsurf-rules");
2147
2190
  } else {
2148
- const rulesDir = join12(outputDir, ".windsurf", "rules");
2149
- return join12(rulesDir, `${rule.filename}.md`);
2191
+ const rulesDir = join9(outputDir, ".windsurf", "rules");
2192
+ return join9(rulesDir, `${rule.filename}.md`);
2150
2193
  }
2151
2194
  }
2152
2195
  },
@@ -2177,6 +2220,22 @@ var GENERATOR_REGISTRY = {
2177
2220
  const lines = [];
2178
2221
  if (rule.frontmatter.description) {
2179
2222
  lines.push(`# ${rule.frontmatter.description}
2223
+ `);
2224
+ }
2225
+ lines.push(rule.content.trim());
2226
+ return lines.join("\n");
2227
+ }
2228
+ // Complex generation handled by existing generator
2229
+ },
2230
+ opencode: {
2231
+ type: "complex",
2232
+ tool: "opencode",
2233
+ fileExtension: ".md",
2234
+ // ignoreFileName omitted - OpenCode doesn't use dedicated ignore files
2235
+ generateContent: (rule) => {
2236
+ const lines = [];
2237
+ if (rule.frontmatter.description) {
2238
+ lines.push(`# ${rule.frontmatter.description}
2180
2239
  `);
2181
2240
  }
2182
2241
  lines.push(rule.content.trim());
@@ -2219,8 +2278,8 @@ async function generateFromRegistry(tool, rules, config, baseDir) {
2219
2278
  const enhancedConfig = {
2220
2279
  tool: generatorConfig.tool,
2221
2280
  fileExtension: generatorConfig.fileExtension,
2222
- ignoreFileName: generatorConfig.ignoreFileName,
2223
2281
  generateContent: generatorConfig.generateContent,
2282
+ ...generatorConfig.ignoreFileName && { ignoreFileName: generatorConfig.ignoreFileName },
2224
2283
  ...generatorConfig.generateRootContent && {
2225
2284
  generateRootContent: generatorConfig.generateRootContent
2226
2285
  },
@@ -2250,51 +2309,91 @@ var generateWindsurfConfig = createSimpleGenerator("windsurf");
2250
2309
  var generateKiroConfig = createSimpleGenerator("kiro");
2251
2310
  var generateRooConfig = createSimpleGenerator("roo");
2252
2311
 
2312
+ // src/utils/xml-document-generator.ts
2313
+ import { XMLBuilder } from "fast-xml-parser";
2314
+ function generateRootMarkdownWithXmlDocs(rootRule, memoryRules, config) {
2315
+ const lines = [];
2316
+ if (memoryRules.length > 0) {
2317
+ lines.push(
2318
+ "Please also reference the following documents as needed. In this case, `@` stands for the project root directory."
2319
+ );
2320
+ lines.push("");
2321
+ const documentsData = {
2322
+ Documents: {
2323
+ Document: memoryRules.map((rule) => {
2324
+ const relativePath = `@${config.memorySubDir}/${rule.filename}.md`;
2325
+ const document = {
2326
+ Path: relativePath,
2327
+ Description: rule.frontmatter.description
2328
+ };
2329
+ if (rule.frontmatter.globs.length > 0) {
2330
+ document.FilePatterns = rule.frontmatter.globs.join(", ");
2331
+ }
2332
+ return document;
2333
+ })
2334
+ }
2335
+ };
2336
+ const builder = new XMLBuilder({
2337
+ format: true,
2338
+ ignoreAttributes: false,
2339
+ suppressEmptyNode: false
2340
+ });
2341
+ const xmlContent = builder.build(documentsData);
2342
+ lines.push(xmlContent);
2343
+ lines.push("");
2344
+ lines.push("");
2345
+ }
2346
+ if (rootRule) {
2347
+ lines.push(rootRule.content.trim());
2348
+ } else if (memoryRules.length === 0) {
2349
+ lines.push(`# ${config.fallbackTitle}`);
2350
+ lines.push("");
2351
+ lines.push("No configuration rules have been defined yet.");
2352
+ }
2353
+ return lines.join("\n");
2354
+ }
2355
+
2253
2356
  // src/generators/rules/codexcli.ts
2254
2357
  async function generateCodexConfig(rules, config, baseDir) {
2255
2358
  const outputs = [];
2256
- if (rules.length === 0) {
2257
- return outputs;
2258
- }
2259
- const sortedRules = [...rules].sort((a, b) => {
2260
- if (a.frontmatter.root === true && b.frontmatter.root !== true) return -1;
2261
- if (a.frontmatter.root !== true && b.frontmatter.root === true) return 1;
2262
- return 0;
2263
- });
2264
- const concatenatedContent = generateConcatenatedCodexContent(sortedRules);
2265
- if (concatenatedContent.trim()) {
2266
- const outputDir = resolveOutputDir(config, "codexcli", baseDir);
2267
- const filepath = `${outputDir}/codex.md`;
2268
- outputs.push({
2269
- tool: "codexcli",
2270
- filepath,
2271
- content: concatenatedContent
2272
- });
2273
- }
2274
- const ignorePatterns = await loadIgnorePatterns(baseDir);
2275
- if (ignorePatterns.patterns.length > 0) {
2276
- const ignorePath = resolvePath(".codexignore", baseDir);
2277
- const ignoreContent = generateIgnoreFile2(ignorePatterns.patterns, "codexcli");
2278
- outputs.push({
2359
+ const nonEmptyRules = rules.filter((rule) => rule.content.trim().length > 0);
2360
+ if (nonEmptyRules.length > 0) {
2361
+ const generatorConfig = {
2279
2362
  tool: "codexcli",
2280
- filepath: ignorePath,
2281
- content: ignoreContent
2282
- });
2363
+ fileExtension: ".md",
2364
+ ignoreFileName: ".codexignore",
2365
+ generateContent: generateCodexMemoryMarkdown,
2366
+ generateDetailContent: generateCodexMemoryMarkdown,
2367
+ generateRootContent: generateCodexRootMarkdown,
2368
+ rootFilePath: "AGENTS.md",
2369
+ detailSubDir: ".codex/memories"
2370
+ };
2371
+ const ruleOutputs = await generateComplexRules(nonEmptyRules, config, generatorConfig, baseDir);
2372
+ outputs.push(...ruleOutputs);
2373
+ } else {
2374
+ const ignorePatterns = await loadIgnorePatterns(baseDir);
2375
+ if (ignorePatterns.patterns.length > 0) {
2376
+ const ignorePath = resolvePath(".codexignore", baseDir);
2377
+ const ignoreContent = generateIgnoreFile2(ignorePatterns.patterns, "codexcli");
2378
+ outputs.push({
2379
+ tool: "codexcli",
2380
+ filepath: ignorePath,
2381
+ content: ignoreContent
2382
+ });
2383
+ }
2283
2384
  }
2284
2385
  return outputs;
2285
2386
  }
2286
- function generateConcatenatedCodexContent(rules) {
2287
- const sections = [];
2288
- for (const rule of rules) {
2289
- const content = rule.content.trim();
2290
- if (!content) {
2291
- continue;
2292
- }
2293
- sections.push(content);
2294
- }
2295
- return sections.join("\n\n---\n\n");
2387
+ function generateCodexMemoryMarkdown(rule) {
2388
+ return rule.content.trim();
2296
2389
  }
2297
-
2390
+ function generateCodexRootMarkdown(rootRule, memoryRules, _baseDir) {
2391
+ return generateRootMarkdownWithXmlDocs(rootRule, memoryRules, {
2392
+ memorySubDir: ".codex/memories",
2393
+ fallbackTitle: "OpenAI Codex CLI Configuration"
2394
+ });
2395
+ }
2396
+
2298
2397
  // src/generators/rules/geminicli.ts
2299
2398
  async function generateGeminiConfig(rules, config, baseDir) {
2300
2399
  const generatorConfig = {
@@ -2313,28 +2412,10 @@ function generateGeminiMemoryMarkdown(rule) {
2313
2412
  return rule.content.trim();
2314
2413
  }
2315
2414
  function generateGeminiRootMarkdown(rootRule, memoryRules, _baseDir) {
2316
- const lines = [];
2317
- if (memoryRules.length > 0) {
2318
- lines.push("Please also reference the following documents as needed:");
2319
- lines.push("");
2320
- lines.push("| Document | Description | File Patterns |");
2321
- lines.push("|----------|-------------|---------------|");
2322
- for (const rule of memoryRules) {
2323
- const relativePath = `@.gemini/memories/${rule.filename}.md`;
2324
- const filePatterns = rule.frontmatter.globs.length > 0 ? rule.frontmatter.globs.join(", ") : "-";
2325
- lines.push(`| ${relativePath} | ${rule.frontmatter.description} | ${filePatterns} |`);
2326
- }
2327
- lines.push("");
2328
- lines.push("");
2329
- }
2330
- if (rootRule) {
2331
- lines.push(rootRule.content.trim());
2332
- } else if (memoryRules.length === 0) {
2333
- lines.push("# Gemini CLI Configuration");
2334
- lines.push("");
2335
- lines.push("No configuration rules have been defined yet.");
2336
- }
2337
- return lines.join("\n");
2415
+ return generateRootMarkdownWithXmlDocs(rootRule, memoryRules, {
2416
+ memorySubDir: ".gemini/memories",
2417
+ fallbackTitle: "Gemini CLI Configuration"
2418
+ });
2338
2419
  }
2339
2420
 
2340
2421
  // src/generators/rules/junie.ts
@@ -2364,20 +2445,42 @@ function generateGuidelinesMarkdown(rootRule, detailRules) {
2364
2445
  return lines.join("\n").trim();
2365
2446
  }
2366
2447
 
2448
+ // src/generators/rules/opencode.ts
2449
+ async function generateOpenCodeConfig(rules, config, baseDir) {
2450
+ const generatorConfig = {
2451
+ tool: "opencode",
2452
+ fileExtension: ".md",
2453
+ // ignoreFileName omitted - OpenCode doesn't use dedicated ignore files
2454
+ generateContent: generateOpenCodeMarkdown,
2455
+ generateDetailContent: generateOpenCodeMarkdown,
2456
+ generateRootContent: generateOpenCodeRootMarkdown,
2457
+ rootFilePath: "AGENTS.md",
2458
+ detailSubDir: ".opencode/memories"
2459
+ };
2460
+ return generateComplexRules(rules, config, generatorConfig, baseDir);
2461
+ }
2462
+ function generateOpenCodeMarkdown(rule) {
2463
+ return rule.content.trim();
2464
+ }
2465
+ function generateOpenCodeRootMarkdown(rootRule, memoryRules, _baseDir) {
2466
+ return generateRootMarkdownWithXmlDocs(rootRule, memoryRules, {
2467
+ memorySubDir: ".opencode/memories",
2468
+ fallbackTitle: "OpenCode Configuration"
2469
+ });
2470
+ }
2471
+
2367
2472
  // src/core/generator.ts
2368
2473
  async function generateConfigurations(rules, config, targetTools, baseDir) {
2369
2474
  const outputs = createOutputsArray();
2370
2475
  const toolsToGenerate = targetTools || config.defaultTargets;
2371
2476
  const rootFiles = rules.filter((rule) => rule.frontmatter.root === true);
2372
2477
  if (rootFiles.length === 0) {
2373
- console.warn(
2374
- "\u26A0\uFE0F Warning: No files with 'root: true' found. This may result in incomplete configurations."
2375
- );
2478
+ logger.warn("No files with 'root: true' found. This may result in incomplete configurations.");
2376
2479
  }
2377
2480
  for (const tool of toolsToGenerate) {
2378
2481
  const relevantRules = filterRulesForTool(rules, tool, config);
2379
2482
  if (relevantRules.length === 0) {
2380
- console.warn(`No rules found for tool: ${tool}`);
2483
+ logger.warn(`No rules found for tool: ${tool}`);
2381
2484
  continue;
2382
2485
  }
2383
2486
  const toolOutputs = await generateForTool(tool, relevantRules, config, baseDir);
@@ -2398,6 +2501,8 @@ function filterRulesForTool(rules, tool, config) {
2398
2501
  }
2399
2502
  async function generateForTool(tool, rules, config, baseDir) {
2400
2503
  switch (tool) {
2504
+ case "amazonqcli":
2505
+ return await generateAmazonqcliConfig(rules, config, baseDir);
2401
2506
  case "augmentcode": {
2402
2507
  const augmentRulesOutputs = await generateAugmentcodeConfig(rules, config, baseDir);
2403
2508
  const augmentIgnoreOutputs = await generateAugmentCodeIgnoreFiles(rules, config, baseDir);
@@ -2436,20 +2541,21 @@ async function generateForTool(tool, rules, config, baseDir) {
2436
2541
  const kiroIgnoreOutputs = await generateKiroIgnoreFiles(rules, config, baseDir);
2437
2542
  return [...kiroRulesOutputs, ...kiroIgnoreOutputs];
2438
2543
  }
2544
+ case "opencode":
2545
+ return generateOpenCodeConfig(rules, config, baseDir);
2439
2546
  case "windsurf": {
2440
2547
  const windsurfRulesOutputs = await generateWindsurfConfig(rules, config, baseDir);
2441
2548
  const windsurfIgnoreOutputs = await generateWindsurfIgnore(rules, config, baseDir);
2442
2549
  return [...windsurfRulesOutputs, ...windsurfIgnoreOutputs];
2443
2550
  }
2444
2551
  default:
2445
- console.warn(`Unknown tool: ${tool}`);
2552
+ logger.warn(`Unknown tool: ${tool}`);
2446
2553
  return null;
2447
2554
  }
2448
2555
  }
2449
2556
 
2450
2557
  // src/core/parser.ts
2451
2558
  import { basename as basename2 } from "path";
2452
- import matter2 from "gray-matter";
2453
2559
  async function parseRulesFromDirectory(aiRulesDir) {
2454
2560
  const ignorePatterns = await loadIgnorePatterns();
2455
2561
  const allRuleFiles = await findRuleFiles(aiRulesDir);
@@ -2457,7 +2563,7 @@ async function parseRulesFromDirectory(aiRulesDir) {
2457
2563
  const rules = [];
2458
2564
  const errors = [];
2459
2565
  if (ignorePatterns.patterns.length > 0) {
2460
- console.log(`Loaded ${ignorePatterns.patterns.length} ignore patterns from .rulesyncignore`);
2566
+ logger.info(`Loaded ${ignorePatterns.patterns.length} ignore patterns from .rulesyncignore`);
2461
2567
  }
2462
2568
  for (const filepath of ruleFiles) {
2463
2569
  try {
@@ -2483,7 +2589,7 @@ ${errors.join("\n")}`);
2483
2589
  }
2484
2590
  async function parseRuleFile(filepath) {
2485
2591
  const content = await readFileContent(filepath);
2486
- const parsed = matter2(content);
2592
+ const parsed = parseFrontmatter(content);
2487
2593
  try {
2488
2594
  const validatedData = RuleFrontmatterSchema.parse(parsed.data);
2489
2595
  const frontmatter = {
@@ -2600,30 +2706,63 @@ function parseMcpConfig(projectRoot) {
2600
2706
  async function generateMcpConfigurations(mcpConfig, baseDir, targetTools) {
2601
2707
  const outputs = [];
2602
2708
  const toolMap = {
2603
- augmentcode: async (servers, dir) => (await import("./augmentcode-HIZIQG2W.js")).generateAugmentcodeMcpConfiguration(
2709
+ amazonqcli: async (servers, dir) => {
2710
+ const config = {
2711
+ aiRulesDir: ".rulesync",
2712
+ outputPaths: {
2713
+ amazonqcli: ".amazonq/rules",
2714
+ augmentcode: ".",
2715
+ "augmentcode-legacy": ".",
2716
+ copilot: ".github/instructions",
2717
+ cursor: ".cursor/rules",
2718
+ cline: ".clinerules",
2719
+ claudecode: ".",
2720
+ codexcli: ".",
2721
+ opencode: ".",
2722
+ roo: ".roo/rules",
2723
+ geminicli: ".gemini/memories",
2724
+ kiro: ".kiro/steering",
2725
+ junie: ".",
2726
+ windsurf: "."
2727
+ },
2728
+ watchEnabled: false,
2729
+ defaultTargets: []
2730
+ };
2731
+ const results = await (await import("./amazonqcli-WVGYACHI.js")).generateAmazonqcliMcp(
2732
+ servers,
2733
+ config,
2734
+ dir
2735
+ );
2736
+ return results.map((result) => ({ filepath: result.filepath, content: result.content }));
2737
+ },
2738
+ augmentcode: async (servers, dir) => (await import("./augmentcode-DTHPPXWO.js")).generateAugmentcodeMcpConfiguration(
2739
+ servers,
2740
+ dir
2741
+ ),
2742
+ "augmentcode-legacy": async (servers, dir) => (await import("./augmentcode-DTHPPXWO.js")).generateAugmentcodeMcpConfiguration(
2604
2743
  servers,
2605
2744
  dir
2606
2745
  ),
2607
- "augmentcode-legacy": async (servers, dir) => (await import("./augmentcode-HIZIQG2W.js")).generateAugmentcodeMcpConfiguration(
2746
+ claudecode: async (servers, dir) => (await import("./claudecode-SSYLLUXX.js")).generateClaudeMcpConfiguration(
2608
2747
  servers,
2609
2748
  dir
2610
2749
  ),
2611
- claudecode: async (servers, dir) => (await import("./claudecode-YTEFACCT.js")).generateClaudeMcpConfiguration(
2750
+ copilot: async (servers, dir) => (await import("./copilot-HSQO7ZCJ.js")).generateCopilotMcpConfiguration(servers, dir),
2751
+ cursor: async (servers, dir) => (await import("./cursor-ZB3XNGBK.js")).generateCursorMcpConfiguration(servers, dir),
2752
+ cline: async (servers, dir) => (await import("./cline-5EUGKNZ6.js")).generateClineMcpConfiguration(servers, dir),
2753
+ codexcli: async (servers, dir) => (await import("./codexcli-IGM2ADYK.js")).generateCodexMcpConfiguration(servers, dir),
2754
+ opencode: async (servers, dir) => (await import("./opencode-SZETJ62M.js")).generateOpenCodeMcpConfiguration(
2612
2755
  servers,
2613
2756
  dir
2614
2757
  ),
2615
- copilot: async (servers, dir) => (await import("./copilot-MOR3HHJX.js")).generateCopilotMcpConfiguration(servers, dir),
2616
- cursor: async (servers, dir) => (await import("./cursor-YJGH7W24.js")).generateCursorMcpConfiguration(servers, dir),
2617
- cline: async (servers, dir) => (await import("./cline-CKNUDEA3.js")).generateClineMcpConfiguration(servers, dir),
2618
- codexcli: async (servers, dir) => (await import("./codexcli-7SDGYI7D.js")).generateCodexMcpConfiguration(servers, dir),
2619
- roo: async (servers, dir) => (await import("./roo-L3QTTIPO.js")).generateRooMcpConfiguration(servers, dir),
2620
- geminicli: async (servers, dir) => (await import("./geminicli-E7KZTZ2G.js")).generateGeminiCliMcpConfiguration(
2758
+ roo: async (servers, dir) => (await import("./roo-KLTWVAKE.js")).generateRooMcpConfiguration(servers, dir),
2759
+ geminicli: async (servers, dir) => (await import("./geminicli-FNRKH5GX.js")).generateGeminiCliMcpConfiguration(
2621
2760
  servers,
2622
2761
  dir
2623
2762
  ),
2624
- kiro: async (servers, dir) => (await import("./kiro-YDHXY2MA.js")).generateKiroMcpConfiguration(servers, dir),
2625
- junie: async (servers, dir) => (await import("./junie-5LEQU4BO.js")).generateJunieMcpConfiguration(servers, dir),
2626
- windsurf: async (servers, dir) => (await import("./windsurf-4P6HEUBV.js")).generateWindsurfMcpConfiguration(
2763
+ kiro: async (servers, dir) => (await import("./kiro-B6WZNLY4.js")).generateKiroMcpConfiguration(servers, dir),
2764
+ junie: async (servers, dir) => (await import("./junie-3YGOSOGF.js")).generateJunieMcpConfiguration(servers, dir),
2765
+ windsurf: async (servers, dir) => (await import("./windsurf-IZEKUAID.js")).generateWindsurfMcpConfiguration(
2627
2766
  servers,
2628
2767
  dir
2629
2768
  )
@@ -2708,13 +2847,13 @@ async function generateCommand(options = {}) {
2708
2847
  logger.info("Deleting existing output directories...");
2709
2848
  const targetTools = config.defaultTargets;
2710
2849
  const deleteTasks = [];
2711
- const commandsDir = join15(config.aiRulesDir, "commands");
2850
+ const commandsDir = join12(config.aiRulesDir, "commands");
2712
2851
  const hasCommands = await fileExists(commandsDir);
2713
2852
  let hasCommandFiles = false;
2714
2853
  if (hasCommands) {
2715
- const { readdir: readdir2 } = await import("fs/promises");
2854
+ const { readdir } = await import("fs/promises");
2716
2855
  try {
2717
- const files = await readdir2(commandsDir);
2856
+ const files = await readdir(commandsDir);
2718
2857
  hasCommandFiles = files.some((file) => file.endsWith(".md"));
2719
2858
  } catch {
2720
2859
  hasCommandFiles = false;
@@ -2723,12 +2862,12 @@ async function generateCommand(options = {}) {
2723
2862
  for (const tool of targetTools) {
2724
2863
  switch (tool) {
2725
2864
  case "augmentcode":
2726
- deleteTasks.push(removeDirectory(join15(".augment", "rules")));
2727
- deleteTasks.push(removeDirectory(join15(".augment", "ignore")));
2865
+ deleteTasks.push(removeDirectory(join12(".augment", "rules")));
2866
+ deleteTasks.push(removeDirectory(join12(".augment", "ignore")));
2728
2867
  break;
2729
2868
  case "augmentcode-legacy":
2730
2869
  deleteTasks.push(removeClaudeGeneratedFiles());
2731
- deleteTasks.push(removeDirectory(join15(".augment", "ignore")));
2870
+ deleteTasks.push(removeDirectory(join12(".augment", "ignore")));
2732
2871
  break;
2733
2872
  case "copilot":
2734
2873
  deleteTasks.push(removeDirectory(config.outputPaths.copilot));
@@ -2742,24 +2881,27 @@ async function generateCommand(options = {}) {
2742
2881
  case "claudecode":
2743
2882
  deleteTasks.push(removeClaudeGeneratedFiles());
2744
2883
  if (hasCommandFiles) {
2745
- deleteTasks.push(removeDirectory(join15(".claude", "commands")));
2884
+ deleteTasks.push(removeDirectory(join12(".claude", "commands")));
2746
2885
  }
2747
2886
  break;
2748
2887
  case "roo":
2749
2888
  deleteTasks.push(removeDirectory(config.outputPaths.roo));
2750
2889
  if (hasCommandFiles) {
2751
- deleteTasks.push(removeDirectory(join15(".roo", "commands")));
2890
+ deleteTasks.push(removeDirectory(join12(".roo", "commands")));
2752
2891
  }
2753
2892
  break;
2754
2893
  case "geminicli":
2755
2894
  deleteTasks.push(removeDirectory(config.outputPaths.geminicli));
2756
2895
  if (hasCommandFiles) {
2757
- deleteTasks.push(removeDirectory(join15(".gemini", "commands")));
2896
+ deleteTasks.push(removeDirectory(join12(".gemini", "commands")));
2758
2897
  }
2759
2898
  break;
2760
2899
  case "kiro":
2761
2900
  deleteTasks.push(removeDirectory(config.outputPaths.kiro));
2762
2901
  break;
2902
+ case "opencode":
2903
+ deleteTasks.push(removeDirectory(config.outputPaths.opencode));
2904
+ break;
2763
2905
  case "windsurf":
2764
2906
  deleteTasks.push(removeDirectory(config.outputPaths.windsurf));
2765
2907
  break;
@@ -2853,11 +2995,13 @@ Generating configurations for base directory: ${baseDir}`);
2853
2995
 
2854
2996
  // src/cli/commands/gitignore.ts
2855
2997
  import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
2856
- import { join as join16 } from "path";
2998
+ import { join as join13 } from "path";
2857
2999
  var gitignoreCommand = async () => {
2858
- const gitignorePath = join16(process.cwd(), ".gitignore");
3000
+ const gitignorePath = join13(process.cwd(), ".gitignore");
2859
3001
  const rulesFilesToIgnore = [
2860
3002
  "# Generated by rulesync - AI tool configuration files",
3003
+ "**/.amazonq/rules/",
3004
+ "**/.amazonq/mcp.json",
2861
3005
  "**/.github/copilot-instructions.md",
2862
3006
  "**/.github/instructions/",
2863
3007
  "**/.cursor/rules/",
@@ -2867,7 +3011,7 @@ var gitignoreCommand = async () => {
2867
3011
  "**/CLAUDE.md",
2868
3012
  "**/.claude/memories/",
2869
3013
  "**/.claude/commands/",
2870
- "**/codex.md",
3014
+ "**/AGENTS.md",
2871
3015
  "**/.codexignore",
2872
3016
  "**/.roo/rules/",
2873
3017
  "**/.rooignore",
@@ -2883,6 +3027,9 @@ var gitignoreCommand = async () => {
2883
3027
  "**/.augment-guidelines",
2884
3028
  "**/.junie/guidelines.md",
2885
3029
  "**/.noai",
3030
+ "**/.opencode/memories/",
3031
+ "**/.opencode/commands/",
3032
+ "**/opencode.json",
2886
3033
  "**/.mcp.json",
2887
3034
  "!.rulesync/.mcp.json",
2888
3035
  "**/.cursor/mcp.json",
@@ -2903,7 +3050,7 @@ var gitignoreCommand = async () => {
2903
3050
  }
2904
3051
  }
2905
3052
  if (linesToAdd.length === 0) {
2906
- console.log("\u2705 .gitignore is already up to date");
3053
+ logger.success(".gitignore is already up to date");
2907
3054
  return;
2908
3055
  }
2909
3056
  const newContent = gitignoreContent ? `${gitignoreContent.trimEnd()}
@@ -2912,174 +3059,20 @@ ${linesToAdd.join("\n")}
2912
3059
  ` : `${linesToAdd.join("\n")}
2913
3060
  `;
2914
3061
  writeFileSync2(gitignorePath, newContent);
2915
- console.log(`\u2705 Added ${linesToAdd.length} rules to .gitignore:`);
3062
+ logger.success(`Added ${linesToAdd.length} rules to .gitignore:`);
2916
3063
  for (const line of linesToAdd) {
2917
3064
  if (!line.startsWith("#")) {
2918
- console.log(` ${line}`);
3065
+ logger.log(` ${line}`);
2919
3066
  }
2920
3067
  }
2921
3068
  };
2922
3069
 
2923
3070
  // src/core/importer.ts
2924
- import { join as join23 } from "path";
2925
- import matter7 from "gray-matter";
2926
-
2927
- // src/parsers/augmentcode.ts
2928
- import { basename as basename3, join as join17 } from "path";
2929
- import matter3 from "gray-matter";
2930
-
2931
- // src/utils/parser-helpers.ts
2932
- function createParseResult() {
2933
- return { rules: [], errors: [] };
2934
- }
2935
- function addError(result, error) {
2936
- result.errors.push(error);
2937
- }
2938
- function addRule(result, rule) {
2939
- if (!result.rules) {
2940
- result.rules = [];
2941
- }
2942
- result.rules.push(rule);
2943
- }
2944
- function addRules(result, rules) {
2945
- if (!result.rules) {
2946
- result.rules = [];
2947
- }
2948
- result.rules.push(...rules);
2949
- }
2950
- async function safeReadFile(operation, errorContext) {
2951
- try {
2952
- const result = await operation();
2953
- return createSuccessResult(result);
2954
- } catch (error) {
2955
- return createErrorResult(error, errorContext);
2956
- }
2957
- }
2958
-
2959
- // src/parsers/augmentcode.ts
2960
- async function parseAugmentcodeConfiguration(baseDir = process.cwd()) {
2961
- return parseUnifiedAugmentcode(baseDir, {
2962
- rulesDir: ".augment/rules",
2963
- targetName: "augmentcode",
2964
- filenamePrefix: "augmentcode"
2965
- });
2966
- }
2967
- async function parseAugmentcodeLegacyConfiguration(baseDir = process.cwd()) {
2968
- return parseUnifiedAugmentcode(baseDir, {
2969
- legacyFilePath: ".augment-guidelines",
2970
- targetName: "augmentcode-legacy",
2971
- filenamePrefix: "augmentcode-legacy"
2972
- });
2973
- }
2974
- async function parseUnifiedAugmentcode(baseDir, config) {
2975
- const result = createParseResult();
2976
- if (config.rulesDir) {
2977
- const rulesDir = join17(baseDir, config.rulesDir);
2978
- if (await fileExists(rulesDir)) {
2979
- const rulesResult = await parseAugmentRules(rulesDir, config);
2980
- addRules(result, rulesResult.rules);
2981
- result.errors.push(...rulesResult.errors);
2982
- } else {
2983
- addError(
2984
- result,
2985
- `No AugmentCode configuration found. Expected ${config.rulesDir} directory.`
2986
- );
2987
- }
2988
- }
2989
- if (config.legacyFilePath) {
2990
- const legacyPath = join17(baseDir, config.legacyFilePath);
2991
- if (await fileExists(legacyPath)) {
2992
- const legacyResult = await parseAugmentGuidelines(legacyPath, config);
2993
- if (legacyResult.rule) {
2994
- addRule(result, legacyResult.rule);
2995
- }
2996
- result.errors.push(...legacyResult.errors);
2997
- } else {
2998
- addError(
2999
- result,
3000
- `No AugmentCode legacy configuration found. Expected ${config.legacyFilePath} file.`
3001
- );
3002
- }
3003
- }
3004
- return { rules: result.rules || [], errors: result.errors };
3005
- }
3006
- async function parseAugmentRules(rulesDir, config) {
3007
- const rules = [];
3008
- const errors = [];
3009
- try {
3010
- const { readdir: readdir2 } = await import("fs/promises");
3011
- const files = await readdir2(rulesDir);
3012
- for (const file of files) {
3013
- if (file.endsWith(".md") || file.endsWith(".mdc")) {
3014
- const filePath = join17(rulesDir, file);
3015
- try {
3016
- const rawContent = await readFileContent(filePath);
3017
- const parsed = matter3(rawContent);
3018
- const frontmatterData = parsed.data;
3019
- const ruleType = frontmatterData.type || "manual";
3020
- const description = frontmatterData.description || "";
3021
- const tags = Array.isArray(frontmatterData.tags) ? frontmatterData.tags : void 0;
3022
- const isRoot = ruleType === "always";
3023
- const filename = basename3(file, file.endsWith(".mdc") ? ".mdc" : ".md");
3024
- const frontmatter = {
3025
- root: isRoot,
3026
- targets: [config.targetName],
3027
- description,
3028
- globs: ["**/*"],
3029
- // AugmentCode doesn't use specific globs in the same way
3030
- ...tags && { tags }
3031
- };
3032
- rules.push({
3033
- frontmatter,
3034
- content: parsed.content.trim(),
3035
- filename: `${config.filenamePrefix}-${ruleType}-${filename}`,
3036
- filepath: filePath
3037
- });
3038
- } catch (error) {
3039
- const errorMessage = error instanceof Error ? error.message : String(error);
3040
- errors.push(`Failed to parse ${filePath}: ${errorMessage}`);
3041
- }
3042
- }
3043
- }
3044
- } catch (error) {
3045
- const errorMessage = error instanceof Error ? error.message : String(error);
3046
- errors.push(`Failed to read ${config.rulesDir || rulesDir} directory: ${errorMessage}`);
3047
- }
3048
- return { rules, errors };
3049
- }
3050
- async function parseAugmentGuidelines(guidelinesPath, config) {
3051
- const parseResult = await safeReadFile(
3052
- async () => {
3053
- const content = await readFileContent(guidelinesPath);
3054
- if (content.trim()) {
3055
- const frontmatter = {
3056
- root: true,
3057
- // Legacy guidelines become root rules
3058
- targets: [config.targetName],
3059
- description: "Legacy AugmentCode guidelines",
3060
- globs: ["**/*"]
3061
- };
3062
- return {
3063
- frontmatter,
3064
- content: content.trim(),
3065
- filename: `${config.filenamePrefix}-guidelines`,
3066
- filepath: guidelinesPath
3067
- };
3068
- }
3069
- return null;
3070
- },
3071
- `Failed to parse ${config.legacyFilePath || guidelinesPath}`
3072
- );
3073
- if (parseResult.success) {
3074
- return { rule: parseResult.result || null, errors: [] };
3075
- } else {
3076
- return { rule: null, errors: [parseResult.error || "Unknown error"] };
3077
- }
3078
- }
3071
+ import { join as join20 } from "path";
3072
+ import matter2 from "gray-matter";
3079
3073
 
3080
3074
  // src/parsers/shared-helpers.ts
3081
- import { basename as basename4, join as join18 } from "path";
3082
- import matter4 from "gray-matter";
3075
+ import { basename as basename3, join as join14 } from "path";
3083
3076
  async function parseConfigurationFiles(baseDir = process.cwd(), config) {
3084
3077
  const errors = [];
3085
3078
  const rules = [];
@@ -3092,16 +3085,18 @@ async function parseConfigurationFiles(baseDir = process.cwd(), config) {
3092
3085
  let content;
3093
3086
  let frontmatter;
3094
3087
  if (mainFile.useFrontmatter) {
3095
- const parsed = matter4(rawContent);
3096
- content = parsed.content.trim();
3097
- const parsedFrontmatter = parsed.data;
3088
+ const parsed = parseFrontmatter(rawContent);
3089
+ content = parsed.content;
3098
3090
  frontmatter = {
3099
3091
  root: mainFile.isRoot ?? false,
3100
3092
  targets: [config.tool],
3101
- description: parsedFrontmatter.description || mainFile.description,
3102
- globs: Array.isArray(parsedFrontmatter.globs) ? parsedFrontmatter.globs : ["**/*"],
3103
- ...parsedFrontmatter.tags && { tags: parsedFrontmatter.tags }
3093
+ description: extractStringField(parsed.data, "description", mainFile.description),
3094
+ globs: extractArrayField(parsed.data, "globs", ["**/*"])
3104
3095
  };
3096
+ const tags = extractArrayField(parsed.data, "tags");
3097
+ if (tags.length > 0) {
3098
+ frontmatter.tags = tags;
3099
+ }
3105
3100
  } else {
3106
3101
  content = rawContent.trim();
3107
3102
  frontmatter = {
@@ -3130,27 +3125,33 @@ async function parseConfigurationFiles(baseDir = process.cwd(), config) {
3130
3125
  const dirPath = resolvePath(dirConfig.directory, baseDir);
3131
3126
  if (await fileExists(dirPath)) {
3132
3127
  const result = await safeAsyncOperation(async () => {
3133
- const { readdir: readdir2 } = await import("fs/promises");
3134
- const files = await readdir2(dirPath);
3128
+ const { readdir } = await import("fs/promises");
3129
+ const files = await readdir(dirPath);
3135
3130
  for (const file of files) {
3136
3131
  if (file.endsWith(dirConfig.filePattern)) {
3137
- const filePath = join18(dirPath, file);
3132
+ const filePath = join14(dirPath, file);
3138
3133
  const fileResult = await safeAsyncOperation(async () => {
3139
3134
  const rawContent = await readFileContent(filePath);
3140
3135
  let content;
3141
3136
  let frontmatter;
3142
3137
  const filename = file.replace(new RegExp(`\\${dirConfig.filePattern}$`), "");
3143
3138
  if (dirConfig.filePattern === ".instructions.md") {
3144
- const parsed = matter4(rawContent);
3145
- content = parsed.content.trim();
3146
- const parsedFrontmatter = parsed.data;
3139
+ const parsed = parseFrontmatter(rawContent);
3140
+ content = parsed.content;
3147
3141
  frontmatter = {
3148
3142
  root: false,
3149
3143
  targets: [config.tool],
3150
- description: parsedFrontmatter.description || `${dirConfig.description}: ${filename}`,
3151
- globs: Array.isArray(parsedFrontmatter.globs) ? parsedFrontmatter.globs : ["**/*"],
3152
- ...parsedFrontmatter.tags && { tags: parsedFrontmatter.tags }
3144
+ description: extractStringField(
3145
+ parsed.data,
3146
+ "description",
3147
+ `${dirConfig.description}: ${filename}`
3148
+ ),
3149
+ globs: extractArrayField(parsed.data, "globs", ["**/*"])
3153
3150
  };
3151
+ const tags = extractArrayField(parsed.data, "tags");
3152
+ if (tags.length > 0) {
3153
+ frontmatter.tags = tags;
3154
+ }
3154
3155
  } else {
3155
3156
  content = rawContent.trim();
3156
3157
  frontmatter = {
@@ -3275,14 +3276,14 @@ function parseMainFile(content, filepath, config) {
3275
3276
  async function parseMemoryFiles(memoryDir, config) {
3276
3277
  const rules = [];
3277
3278
  try {
3278
- const { readdir: readdir2 } = await import("fs/promises");
3279
- const files = await readdir2(memoryDir);
3279
+ const { readdir } = await import("fs/promises");
3280
+ const files = await readdir(memoryDir);
3280
3281
  for (const file of files) {
3281
3282
  if (file.endsWith(".md")) {
3282
- const filePath = join18(memoryDir, file);
3283
+ const filePath = join14(memoryDir, file);
3283
3284
  const content = await readFileContent(filePath);
3284
3285
  if (content.trim()) {
3285
- const filename = basename4(file, ".md");
3286
+ const filename = basename3(file, ".md");
3286
3287
  const frontmatter = {
3287
3288
  root: false,
3288
3289
  targets: [config.tool],
@@ -3305,24 +3306,23 @@ async function parseMemoryFiles(memoryDir, config) {
3305
3306
  async function parseCommandsFiles(commandsDir, config) {
3306
3307
  const rules = [];
3307
3308
  try {
3308
- const { readdir: readdir2 } = await import("fs/promises");
3309
- const files = await readdir2(commandsDir);
3309
+ const { readdir } = await import("fs/promises");
3310
+ const files = await readdir(commandsDir);
3310
3311
  for (const file of files) {
3311
3312
  if (file.endsWith(".md")) {
3312
- const filePath = join18(commandsDir, file);
3313
+ const filePath = join14(commandsDir, file);
3313
3314
  const content = await readFileContent(filePath);
3314
3315
  if (content.trim()) {
3315
- const filename = basename4(file, ".md");
3316
+ const filename = basename3(file, ".md");
3316
3317
  let frontmatter;
3317
3318
  let ruleContent;
3318
3319
  try {
3319
- const parsed = matter4(content);
3320
- ruleContent = parsed.content.trim();
3321
- const parsedFrontmatter = parsed.data;
3320
+ const parsed = parseFrontmatter(content);
3321
+ ruleContent = parsed.content;
3322
3322
  frontmatter = {
3323
3323
  root: false,
3324
3324
  targets: [config.tool],
3325
- description: parsedFrontmatter.description || `Command: ${filename}`,
3325
+ description: extractStringField(parsed.data, "description", `Command: ${filename}`),
3326
3326
  globs: ["**/*"]
3327
3327
  };
3328
3328
  } catch {
@@ -3388,6 +3388,170 @@ async function parseSettingsFile(settingsPath, tool) {
3388
3388
  };
3389
3389
  }
3390
3390
 
3391
+ // src/parsers/amazonqcli.ts
3392
+ async function parseAmazonqcliConfiguration(baseDir = process.cwd()) {
3393
+ return parseMemoryBasedConfiguration(baseDir, {
3394
+ tool: "amazonqcli",
3395
+ mainFileName: ".amazonq/rules/main.md",
3396
+ memoryDirPath: ".amazonq/rules",
3397
+ settingsPath: ".amazonq/mcp.json",
3398
+ mainDescription: "Main Amazon Q Developer CLI configuration",
3399
+ memoryDescription: "Amazon Q rule",
3400
+ filenamePrefix: "amazonq"
3401
+ });
3402
+ }
3403
+
3404
+ // src/parsers/augmentcode.ts
3405
+ import { basename as basename4, join as join15 } from "path";
3406
+
3407
+ // src/utils/parser-helpers.ts
3408
+ function createParseResult() {
3409
+ return { rules: [], errors: [] };
3410
+ }
3411
+ function addError(result, error) {
3412
+ result.errors.push(error);
3413
+ }
3414
+ function addRule(result, rule) {
3415
+ if (!result.rules) {
3416
+ result.rules = [];
3417
+ }
3418
+ result.rules.push(rule);
3419
+ }
3420
+ function addRules(result, rules) {
3421
+ if (!result.rules) {
3422
+ result.rules = [];
3423
+ }
3424
+ result.rules.push(...rules);
3425
+ }
3426
+ async function safeReadFile(operation, errorContext) {
3427
+ try {
3428
+ const result = await operation();
3429
+ return createSuccessResult(result);
3430
+ } catch (error) {
3431
+ return createErrorResult(error, errorContext);
3432
+ }
3433
+ }
3434
+
3435
+ // src/parsers/augmentcode.ts
3436
+ async function parseAugmentcodeConfiguration(baseDir = process.cwd()) {
3437
+ return parseUnifiedAugmentcode(baseDir, {
3438
+ rulesDir: ".augment/rules",
3439
+ targetName: "augmentcode",
3440
+ filenamePrefix: "augmentcode"
3441
+ });
3442
+ }
3443
+ async function parseAugmentcodeLegacyConfiguration(baseDir = process.cwd()) {
3444
+ return parseUnifiedAugmentcode(baseDir, {
3445
+ legacyFilePath: ".augment-guidelines",
3446
+ targetName: "augmentcode-legacy",
3447
+ filenamePrefix: "augmentcode-legacy"
3448
+ });
3449
+ }
3450
+ async function parseUnifiedAugmentcode(baseDir, config) {
3451
+ const result = createParseResult();
3452
+ if (config.rulesDir) {
3453
+ const rulesDir = join15(baseDir, config.rulesDir);
3454
+ if (await fileExists(rulesDir)) {
3455
+ const rulesResult = await parseAugmentRules(rulesDir, config);
3456
+ addRules(result, rulesResult.rules);
3457
+ result.errors.push(...rulesResult.errors);
3458
+ } else {
3459
+ addError(
3460
+ result,
3461
+ `No AugmentCode configuration found. Expected ${config.rulesDir} directory.`
3462
+ );
3463
+ }
3464
+ }
3465
+ if (config.legacyFilePath) {
3466
+ const legacyPath = join15(baseDir, config.legacyFilePath);
3467
+ if (await fileExists(legacyPath)) {
3468
+ const legacyResult = await parseAugmentGuidelines(legacyPath, config);
3469
+ if (legacyResult.rule) {
3470
+ addRule(result, legacyResult.rule);
3471
+ }
3472
+ result.errors.push(...legacyResult.errors);
3473
+ } else {
3474
+ addError(
3475
+ result,
3476
+ `No AugmentCode legacy configuration found. Expected ${config.legacyFilePath} file.`
3477
+ );
3478
+ }
3479
+ }
3480
+ return { rules: result.rules || [], errors: result.errors };
3481
+ }
3482
+ async function parseAugmentRules(rulesDir, config) {
3483
+ const rules = [];
3484
+ const errors = [];
3485
+ try {
3486
+ const { readdir } = await import("fs/promises");
3487
+ const files = await readdir(rulesDir);
3488
+ for (const file of files) {
3489
+ if (file.endsWith(".md") || file.endsWith(".mdc")) {
3490
+ const filePath = join15(rulesDir, file);
3491
+ try {
3492
+ const rawContent = await readFileContent(filePath);
3493
+ const parsed = parseFrontmatter(rawContent);
3494
+ const ruleType = extractStringField(parsed.data, "type", "manual");
3495
+ const description = extractStringField(parsed.data, "description", "");
3496
+ const tags = extractArrayField(parsed.data, "tags");
3497
+ const isRoot = ruleType === "always";
3498
+ const filename = basename4(file, file.endsWith(".mdc") ? ".mdc" : ".md");
3499
+ const frontmatter = {
3500
+ root: isRoot,
3501
+ targets: [config.targetName],
3502
+ description,
3503
+ globs: ["**/*"],
3504
+ // AugmentCode doesn't use specific globs in the same way
3505
+ ...tags.length > 0 && { tags }
3506
+ };
3507
+ rules.push({
3508
+ frontmatter,
3509
+ content: parsed.content.trim(),
3510
+ filename: `${config.filenamePrefix}-${ruleType}-${filename}`,
3511
+ filepath: filePath
3512
+ });
3513
+ } catch (error) {
3514
+ const errorMessage = error instanceof Error ? error.message : String(error);
3515
+ errors.push(`Failed to parse ${filePath}: ${errorMessage}`);
3516
+ }
3517
+ }
3518
+ }
3519
+ } catch (error) {
3520
+ const errorMessage = error instanceof Error ? error.message : String(error);
3521
+ errors.push(`Failed to read ${config.rulesDir || rulesDir} directory: ${errorMessage}`);
3522
+ }
3523
+ return { rules, errors };
3524
+ }
3525
+ async function parseAugmentGuidelines(guidelinesPath, config) {
3526
+ const parseResult = await safeReadFile(
3527
+ async () => {
3528
+ const content = await readFileContent(guidelinesPath);
3529
+ if (content.trim()) {
3530
+ const frontmatter = {
3531
+ root: true,
3532
+ // Legacy guidelines become root rules
3533
+ targets: [config.targetName],
3534
+ description: "Legacy AugmentCode guidelines",
3535
+ globs: ["**/*"]
3536
+ };
3537
+ return {
3538
+ frontmatter,
3539
+ content: content.trim(),
3540
+ filename: `${config.filenamePrefix}-guidelines`,
3541
+ filepath: guidelinesPath
3542
+ };
3543
+ }
3544
+ return null;
3545
+ },
3546
+ `Failed to parse ${config.legacyFilePath || guidelinesPath}`
3547
+ );
3548
+ if (parseResult.success) {
3549
+ return { rule: parseResult.result || null, errors: [] };
3550
+ } else {
3551
+ return { rule: null, errors: [parseResult.error || "Unknown error"] };
3552
+ }
3553
+ }
3554
+
3391
3555
  // src/parsers/claudecode.ts
3392
3556
  async function parseClaudeConfiguration(baseDir = process.cwd()) {
3393
3557
  return parseMemoryBasedConfiguration(baseDir, {
@@ -3423,7 +3587,7 @@ async function parseClineConfiguration(baseDir = process.cwd()) {
3423
3587
  }
3424
3588
 
3425
3589
  // src/parsers/codexcli.ts
3426
- import { join as join19 } from "path";
3590
+ import { join as join16 } from "path";
3427
3591
 
3428
3592
  // src/parsers/copilot.ts
3429
3593
  async function parseCopilotConfiguration(baseDir = process.cwd()) {
@@ -3446,8 +3610,7 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
3446
3610
  }
3447
3611
 
3448
3612
  // src/parsers/cursor.ts
3449
- import { basename as basename5, join as join20 } from "path";
3450
- import matter5 from "gray-matter";
3613
+ import { basename as basename5, join as join17 } from "path";
3451
3614
  import { DEFAULT_SCHEMA, FAILSAFE_SCHEMA, load } from "js-yaml";
3452
3615
  import { z as z7 } from "zod/mini";
3453
3616
  var customMatterOptions = {
@@ -3571,12 +3734,12 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3571
3734
  const rules = [];
3572
3735
  let ignorePatterns;
3573
3736
  let mcpServers;
3574
- const cursorFilePath = join20(baseDir, ".cursorrules");
3737
+ const cursorFilePath = join17(baseDir, ".cursorrules");
3575
3738
  if (await fileExists(cursorFilePath)) {
3576
3739
  try {
3577
3740
  const rawContent = await readFileContent(cursorFilePath);
3578
- const parsed = matter5(rawContent, customMatterOptions);
3579
- const content = parsed.content.trim();
3741
+ const parsed = parseFrontmatter(rawContent, { matterOptions: customMatterOptions });
3742
+ const content = parsed.content;
3580
3743
  if (content) {
3581
3744
  const frontmatter = convertCursorMdcFrontmatter(parsed.data, "cursorrules");
3582
3745
  frontmatter.targets = ["cursor"];
@@ -3592,18 +3755,18 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3592
3755
  errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
3593
3756
  }
3594
3757
  }
3595
- const cursorRulesDir = join20(baseDir, ".cursor", "rules");
3758
+ const cursorRulesDir = join17(baseDir, ".cursor", "rules");
3596
3759
  if (await fileExists(cursorRulesDir)) {
3597
3760
  try {
3598
- const { readdir: readdir2 } = await import("fs/promises");
3599
- const files = await readdir2(cursorRulesDir);
3761
+ const { readdir } = await import("fs/promises");
3762
+ const files = await readdir(cursorRulesDir);
3600
3763
  for (const file of files) {
3601
3764
  if (file.endsWith(".mdc")) {
3602
- const filePath = join20(cursorRulesDir, file);
3765
+ const filePath = join17(cursorRulesDir, file);
3603
3766
  try {
3604
3767
  const rawContent = await readFileContent(filePath);
3605
- const parsed = matter5(rawContent, customMatterOptions);
3606
- const content = parsed.content.trim();
3768
+ const parsed = parseFrontmatter(rawContent, { matterOptions: customMatterOptions });
3769
+ const content = parsed.content;
3607
3770
  if (content) {
3608
3771
  const filename = basename5(file, ".mdc");
3609
3772
  const frontmatter = convertCursorMdcFrontmatter(parsed.data, filename);
@@ -3628,7 +3791,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3628
3791
  if (rules.length === 0) {
3629
3792
  errors.push("No Cursor configuration files found (.cursorrules or .cursor/rules/*.mdc)");
3630
3793
  }
3631
- const cursorIgnorePath = join20(baseDir, ".cursorignore");
3794
+ const cursorIgnorePath = join17(baseDir, ".cursorignore");
3632
3795
  if (await fileExists(cursorIgnorePath)) {
3633
3796
  try {
3634
3797
  const content = await readFileContent(cursorIgnorePath);
@@ -3641,7 +3804,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3641
3804
  errors.push(`Failed to parse .cursorignore: ${errorMessage}`);
3642
3805
  }
3643
3806
  }
3644
- const cursorMcpPath = join20(baseDir, ".cursor", "mcp.json");
3807
+ const cursorMcpPath = join17(baseDir, ".cursor", "mcp.json");
3645
3808
  if (await fileExists(cursorMcpPath)) {
3646
3809
  try {
3647
3810
  const content = await readFileContent(cursorMcpPath);
@@ -3691,11 +3854,11 @@ async function parseGeminiConfiguration(baseDir = process.cwd()) {
3691
3854
  }
3692
3855
 
3693
3856
  // src/parsers/junie.ts
3694
- import { join as join21 } from "path";
3857
+ import { join as join18 } from "path";
3695
3858
  async function parseJunieConfiguration(baseDir = process.cwd()) {
3696
3859
  const errors = [];
3697
3860
  const rules = [];
3698
- const guidelinesPath = join21(baseDir, ".junie", "guidelines.md");
3861
+ const guidelinesPath = join18(baseDir, ".junie", "guidelines.md");
3699
3862
  if (!await fileExists(guidelinesPath)) {
3700
3863
  errors.push(".junie/guidelines.md file not found");
3701
3864
  return { rules, errors };
@@ -3726,6 +3889,32 @@ async function parseJunieConfiguration(baseDir = process.cwd()) {
3726
3889
  return { rules, errors };
3727
3890
  }
3728
3891
 
3892
+ // src/parsers/opencode.ts
3893
+ async function parseOpCodeIgnore(opcodeignorePath) {
3894
+ try {
3895
+ const content = await readFileContent(opcodeignorePath);
3896
+ const patterns = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
3897
+ return patterns;
3898
+ } catch {
3899
+ return [];
3900
+ }
3901
+ }
3902
+ async function parseOpenCodeConfiguration(baseDir = process.cwd()) {
3903
+ return parseMemoryBasedConfiguration(baseDir, {
3904
+ tool: "opencode",
3905
+ mainFileName: "AGENTS.md",
3906
+ memoryDirPath: ".opencode/memories",
3907
+ settingsPath: "opencode.json",
3908
+ mainDescription: "Main OpenCode configuration",
3909
+ memoryDescription: "Memory file",
3910
+ filenamePrefix: "opencode",
3911
+ additionalIgnoreFile: {
3912
+ path: ".opcodeignore",
3913
+ parser: parseOpCodeIgnore
3914
+ }
3915
+ });
3916
+ }
3917
+
3729
3918
  // src/parsers/roo.ts
3730
3919
  async function parseRooConfiguration(baseDir = process.cwd()) {
3731
3920
  return parseConfigurationFiles(baseDir, {
@@ -3747,9 +3936,8 @@ async function parseRooConfiguration(baseDir = process.cwd()) {
3747
3936
  }
3748
3937
 
3749
3938
  // src/parsers/windsurf.ts
3750
- import { readFile as readFile2 } from "fs/promises";
3751
- import { join as join22 } from "path";
3752
- import matter6 from "gray-matter";
3939
+ import { readFile } from "fs/promises";
3940
+ import { join as join19 } from "path";
3753
3941
 
3754
3942
  // src/core/importer.ts
3755
3943
  async function importConfiguration(options) {
@@ -3765,10 +3953,17 @@ async function importConfiguration(options) {
3765
3953
  let ignorePatterns;
3766
3954
  let mcpServers;
3767
3955
  if (verbose) {
3768
- console.log(`Importing ${tool} configuration from ${baseDir}...`);
3956
+ logger.log(`Importing ${tool} configuration from ${baseDir}...`);
3769
3957
  }
3770
3958
  try {
3771
3959
  switch (tool) {
3960
+ case "amazonqcli": {
3961
+ const amazonqResult = await parseAmazonqcliConfiguration(baseDir);
3962
+ rules = amazonqResult.rules;
3963
+ errors.push(...amazonqResult.errors);
3964
+ mcpServers = amazonqResult.mcpServers;
3965
+ break;
3966
+ }
3772
3967
  case "augmentcode": {
3773
3968
  const augmentResult = await parseAugmentcodeConfiguration(baseDir);
3774
3969
  rules = augmentResult.rules;
@@ -3829,6 +4024,14 @@ async function importConfiguration(options) {
3829
4024
  errors.push(...junieResult.errors);
3830
4025
  break;
3831
4026
  }
4027
+ case "opencode": {
4028
+ const opencodeResult = await parseOpenCodeConfiguration(baseDir);
4029
+ rules = opencodeResult.rules;
4030
+ errors.push(...opencodeResult.errors);
4031
+ ignorePatterns = opencodeResult.ignorePatterns;
4032
+ mcpServers = opencodeResult.mcpServers;
4033
+ break;
4034
+ }
3832
4035
  default:
3833
4036
  errors.push(`Unsupported tool: ${tool}`);
3834
4037
  return { success: false, rulesCreated: 0, errors };
@@ -3841,10 +4044,10 @@ async function importConfiguration(options) {
3841
4044
  if (rules.length === 0 && !ignorePatterns && !mcpServers) {
3842
4045
  return { success: false, rulesCreated: 0, errors };
3843
4046
  }
3844
- const rulesDirPath = join23(baseDir, rulesDir);
4047
+ const rulesDirPath = join20(baseDir, rulesDir);
3845
4048
  try {
3846
- const { mkdir: mkdir3 } = await import("fs/promises");
3847
- await mkdir3(rulesDirPath, { recursive: true });
4049
+ const { mkdir: mkdir2 } = await import("fs/promises");
4050
+ await mkdir2(rulesDirPath, { recursive: true });
3848
4051
  } catch (error) {
3849
4052
  const errorMessage = error instanceof Error ? error.message : String(error);
3850
4053
  errors.push(`Failed to create rules directory: ${errorMessage}`);
@@ -3856,22 +4059,22 @@ async function importConfiguration(options) {
3856
4059
  const baseFilename = rule.filename;
3857
4060
  let targetDir = rulesDirPath;
3858
4061
  if (rule.type === "command") {
3859
- targetDir = join23(rulesDirPath, "commands");
3860
- const { mkdir: mkdir3 } = await import("fs/promises");
3861
- await mkdir3(targetDir, { recursive: true });
4062
+ targetDir = join20(rulesDirPath, "commands");
4063
+ const { mkdir: mkdir2 } = await import("fs/promises");
4064
+ await mkdir2(targetDir, { recursive: true });
3862
4065
  } else {
3863
4066
  if (!useLegacyLocation) {
3864
- targetDir = join23(rulesDirPath, "rules");
3865
- const { mkdir: mkdir3 } = await import("fs/promises");
3866
- await mkdir3(targetDir, { recursive: true });
4067
+ targetDir = join20(rulesDirPath, "rules");
4068
+ const { mkdir: mkdir2 } = await import("fs/promises");
4069
+ await mkdir2(targetDir, { recursive: true });
3867
4070
  }
3868
4071
  }
3869
- const filePath = join23(targetDir, `${baseFilename}.md`);
4072
+ const filePath = join20(targetDir, `${baseFilename}.md`);
3870
4073
  const content = generateRuleFileContent(rule);
3871
4074
  await writeFileContent(filePath, content);
3872
4075
  rulesCreated++;
3873
4076
  if (verbose) {
3874
- console.log(`\u2705 Created rule file: ${filePath}`);
4077
+ logger.success(`Created rule file: ${filePath}`);
3875
4078
  }
3876
4079
  } catch (error) {
3877
4080
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -3881,13 +4084,13 @@ async function importConfiguration(options) {
3881
4084
  let ignoreFileCreated = false;
3882
4085
  if (ignorePatterns && ignorePatterns.length > 0) {
3883
4086
  try {
3884
- const rulesyncignorePath = join23(baseDir, ".rulesyncignore");
4087
+ const rulesyncignorePath = join20(baseDir, ".rulesyncignore");
3885
4088
  const ignoreContent = `${ignorePatterns.join("\n")}
3886
4089
  `;
3887
4090
  await writeFileContent(rulesyncignorePath, ignoreContent);
3888
4091
  ignoreFileCreated = true;
3889
4092
  if (verbose) {
3890
- console.log(`\u2705 Created .rulesyncignore with ${ignorePatterns.length} patterns`);
4093
+ logger.success(`Created .rulesyncignore with ${ignorePatterns.length} patterns`);
3891
4094
  }
3892
4095
  } catch (error) {
3893
4096
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -3897,13 +4100,13 @@ async function importConfiguration(options) {
3897
4100
  let mcpFileCreated = false;
3898
4101
  if (mcpServers && Object.keys(mcpServers).length > 0) {
3899
4102
  try {
3900
- const mcpPath = join23(baseDir, rulesDir, ".mcp.json");
4103
+ const mcpPath = join20(baseDir, rulesDir, ".mcp.json");
3901
4104
  const mcpContent = `${JSON.stringify({ mcpServers }, null, 2)}
3902
4105
  `;
3903
4106
  await writeFileContent(mcpPath, mcpContent);
3904
4107
  mcpFileCreated = true;
3905
4108
  if (verbose) {
3906
- console.log(`\u2705 Created .mcp.json with ${Object.keys(mcpServers).length} servers`);
4109
+ logger.success(`Created .mcp.json with ${Object.keys(mcpServers).length} servers`);
3907
4110
  }
3908
4111
  } catch (error) {
3909
4112
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -3924,10 +4127,10 @@ function generateRuleFileContent(rule) {
3924
4127
  description: rule.frontmatter.description,
3925
4128
  targets: rule.frontmatter.targets
3926
4129
  };
3927
- const frontmatter2 = matter7.stringify("", simplifiedFrontmatter);
4130
+ const frontmatter2 = matter2.stringify("", simplifiedFrontmatter);
3928
4131
  return frontmatter2 + rule.content;
3929
4132
  }
3930
- const frontmatter = matter7.stringify("", rule.frontmatter);
4133
+ const frontmatter = matter2.stringify("", rule.frontmatter);
3931
4134
  return frontmatter + rule.content;
3932
4135
  }
3933
4136
 
@@ -3935,6 +4138,7 @@ function generateRuleFileContent(rule) {
3935
4138
  async function importCommand(options = {}) {
3936
4139
  logger.setVerbose(options.verbose || false);
3937
4140
  const tools = [];
4141
+ if (options.amazonqcli) tools.push("amazonqcli");
3938
4142
  if (options.augmentcode) tools.push("augmentcode");
3939
4143
  if (options["augmentcode-legacy"]) tools.push("augmentcode-legacy");
3940
4144
  if (options.claudecode) tools.push("claudecode");
@@ -3943,9 +4147,10 @@ async function importCommand(options = {}) {
3943
4147
  if (options.cline) tools.push("cline");
3944
4148
  if (options.roo) tools.push("roo");
3945
4149
  if (options.geminicli) tools.push("geminicli");
4150
+ if (options.opencode) tools.push("opencode");
3946
4151
  if (tools.length === 0) {
3947
4152
  logger.error(
3948
- "\u274C Please specify one tool to import from (--augmentcode, --augmentcode-legacy, --claudecode, --cursor, --copilot, --cline, --roo, --geminicli)"
4153
+ "\u274C Please specify one tool to import from (--amazonqcli, --augmentcode, --augmentcode-legacy, --claudecode, --cursor, --copilot, --cline, --roo, --geminicli, --opencode)"
3949
4154
  );
3950
4155
  process.exit(1);
3951
4156
  }
@@ -3993,23 +4198,23 @@ async function importCommand(options = {}) {
3993
4198
  }
3994
4199
 
3995
4200
  // src/cli/commands/init.ts
3996
- import { join as join24 } from "path";
4201
+ import { join as join21 } from "path";
3997
4202
  async function initCommand(options = {}) {
3998
4203
  const configResult = await loadConfig();
3999
4204
  const config = configResult.config;
4000
4205
  const aiRulesDir = config.aiRulesDir;
4001
- console.log("Initializing rulesync...");
4206
+ logger.log("Initializing rulesync...");
4002
4207
  await ensureDir(aiRulesDir);
4003
4208
  const useLegacy = options.legacy ?? config.legacy ?? false;
4004
- const rulesDir = useLegacy ? aiRulesDir : join24(aiRulesDir, "rules");
4209
+ const rulesDir = useLegacy ? aiRulesDir : join21(aiRulesDir, "rules");
4005
4210
  if (!useLegacy) {
4006
4211
  await ensureDir(rulesDir);
4007
4212
  }
4008
4213
  await createSampleFiles(rulesDir);
4009
- console.log("\u2705 rulesync initialized successfully!");
4010
- console.log("\nNext steps:");
4011
- console.log(`1. Edit rule files in ${rulesDir}/`);
4012
- console.log("2. Run 'rulesync generate' to create configuration files");
4214
+ logger.success("rulesync initialized successfully!");
4215
+ logger.log("\nNext steps:");
4216
+ logger.log(`1. Edit rule files in ${rulesDir}/`);
4217
+ logger.log("2. Run 'rulesync generate' to create configuration files");
4013
4218
  }
4014
4219
  async function createSampleFiles(rulesDir) {
4015
4220
  const sampleFile = {
@@ -4047,36 +4252,36 @@ globs: ["**/*"]
4047
4252
  - Follow single responsibility principle
4048
4253
  `
4049
4254
  };
4050
- const filepath = join24(rulesDir, sampleFile.filename);
4255
+ const filepath = join21(rulesDir, sampleFile.filename);
4051
4256
  if (!await fileExists(filepath)) {
4052
4257
  await writeFileContent(filepath, sampleFile.content);
4053
- console.log(`Created ${filepath}`);
4258
+ logger.success(`Created ${filepath}`);
4054
4259
  } else {
4055
- console.log(`Skipped ${filepath} (already exists)`);
4260
+ logger.log(`Skipped ${filepath} (already exists)`);
4056
4261
  }
4057
4262
  }
4058
4263
 
4059
4264
  // src/cli/commands/status.ts
4060
4265
  async function statusCommand() {
4061
4266
  const config = getDefaultConfig();
4062
- console.log("rulesync Status");
4063
- console.log("===============");
4267
+ logger.log("rulesync Status");
4268
+ logger.log("===============");
4064
4269
  const rulesyncExists = await fileExists(config.aiRulesDir);
4065
- console.log(`
4270
+ logger.log(`
4066
4271
  \u{1F4C1} .rulesync directory: ${rulesyncExists ? "\u2705 Found" : "\u274C Not found"}`);
4067
4272
  if (!rulesyncExists) {
4068
- console.log("\n\u{1F4A1} Run 'rulesync init' to get started");
4273
+ logger.log("\n\u{1F4A1} Run 'rulesync init' to get started");
4069
4274
  return;
4070
4275
  }
4071
4276
  try {
4072
4277
  const rules = await parseRulesFromDirectory(config.aiRulesDir);
4073
- console.log(`
4278
+ logger.log(`
4074
4279
  \u{1F4CB} Rules: ${rules.length} total`);
4075
4280
  if (rules.length > 0) {
4076
4281
  const rootRules = rules.filter((r) => r.frontmatter.root).length;
4077
4282
  const nonRootRules = rules.length - rootRules;
4078
- console.log(` - Root rules: ${rootRules}`);
4079
- console.log(` - Non-root rules: ${nonRootRules}`);
4283
+ logger.log(` - Root rules: ${rootRules}`);
4284
+ logger.log(` - Non-root rules: ${nonRootRules}`);
4080
4285
  const targetCounts = { copilot: 0, cursor: 0, cline: 0, claudecode: 0, roo: 0 };
4081
4286
  for (const rule of rules) {
4082
4287
  const targets = rule.frontmatter.targets[0] === "*" ? config.defaultTargets : rule.frontmatter.targets;
@@ -4088,63 +4293,63 @@ async function statusCommand() {
4088
4293
  else if (target === "roo") targetCounts.roo++;
4089
4294
  }
4090
4295
  }
4091
- console.log("\n\u{1F3AF} Target tool coverage:");
4092
- console.log(` - Copilot: ${targetCounts.copilot} rules`);
4093
- console.log(` - Cursor: ${targetCounts.cursor} rules`);
4094
- console.log(` - Cline: ${targetCounts.cline} rules`);
4095
- console.log(` - Claude Code: ${targetCounts.claudecode} rules`);
4096
- console.log(` - Roo: ${targetCounts.roo} rules`);
4296
+ logger.log("\n\u{1F3AF} Target tool coverage:");
4297
+ logger.log(` - Copilot: ${targetCounts.copilot} rules`);
4298
+ logger.log(` - Cursor: ${targetCounts.cursor} rules`);
4299
+ logger.log(` - Cline: ${targetCounts.cline} rules`);
4300
+ logger.log(` - Claude Code: ${targetCounts.claudecode} rules`);
4301
+ logger.log(` - Roo: ${targetCounts.roo} rules`);
4097
4302
  }
4098
- console.log("\n\u{1F4E4} Generated files:");
4303
+ logger.log("\n\u{1F4E4} Generated files:");
4099
4304
  for (const [tool, outputPath] of Object.entries(config.outputPaths)) {
4100
4305
  const outputExists = await fileExists(outputPath);
4101
- console.log(` - ${tool}: ${outputExists ? "\u2705 Generated" : "\u274C Not found"}`);
4306
+ logger.log(` - ${tool}: ${outputExists ? "\u2705 Generated" : "\u274C Not found"}`);
4102
4307
  }
4103
4308
  if (rules.length > 0) {
4104
- console.log("\n\u{1F4A1} Run 'rulesync generate' to update configuration files");
4309
+ logger.log("\n\u{1F4A1} Run 'rulesync generate' to update configuration files");
4105
4310
  }
4106
4311
  } catch (error) {
4107
- console.error("\n\u274C Failed to get status:", error);
4312
+ logger.error("\nFailed to get status:", error);
4108
4313
  }
4109
4314
  }
4110
4315
 
4111
4316
  // src/cli/commands/validate.ts
4112
4317
  async function validateCommand() {
4113
4318
  const config = getDefaultConfig();
4114
- console.log("Validating rulesync configuration...");
4319
+ logger.log("Validating rulesync configuration...");
4115
4320
  if (!await fileExists(config.aiRulesDir)) {
4116
- console.error("\u274C .rulesync directory not found. Run 'rulesync init' first.");
4321
+ logger.error(".rulesync directory not found. Run 'rulesync init' first.");
4117
4322
  process.exit(1);
4118
4323
  }
4119
4324
  try {
4120
4325
  const rules = await parseRulesFromDirectory(config.aiRulesDir);
4121
4326
  if (rules.length === 0) {
4122
- console.warn("\u26A0\uFE0F No rules found in .rulesync directory");
4327
+ logger.warn("No rules found in .rulesync directory");
4123
4328
  return;
4124
4329
  }
4125
- console.log(`Found ${rules.length} rule(s), validating...`);
4330
+ logger.log(`Found ${rules.length} rule(s), validating...`);
4126
4331
  const validation = await validateRules(rules);
4127
4332
  if (validation.warnings.length > 0) {
4128
- console.log("\n\u26A0\uFE0F Warnings:");
4333
+ logger.log("\n\u26A0\uFE0F Warnings:");
4129
4334
  for (const warning of validation.warnings) {
4130
- console.log(` - ${warning}`);
4335
+ logger.log(` - ${warning}`);
4131
4336
  }
4132
4337
  }
4133
4338
  if (validation.errors.length > 0) {
4134
- console.log("\n\u274C Errors:");
4339
+ logger.log("\nErrors:");
4135
4340
  for (const error of validation.errors) {
4136
- console.log(` - ${error}`);
4341
+ logger.log(` - ${error}`);
4137
4342
  }
4138
4343
  }
4139
4344
  if (validation.isValid) {
4140
- console.log("\n\u2705 All rules are valid!");
4345
+ logger.success("\nAll rules are valid!");
4141
4346
  } else {
4142
- console.log(`
4143
- \u274C Validation failed with ${validation.errors.length} error(s)`);
4347
+ logger.log(`
4348
+ Validation failed with ${validation.errors.length} error(s)`);
4144
4349
  process.exit(1);
4145
4350
  }
4146
4351
  } catch (error) {
4147
- console.error("\u274C Failed to validate rules:", error);
4352
+ logger.error("Failed to validate rules:", error);
4148
4353
  process.exit(1);
4149
4354
  }
4150
4355
  }
@@ -4153,8 +4358,8 @@ async function validateCommand() {
4153
4358
  import { watch } from "chokidar";
4154
4359
  async function watchCommand() {
4155
4360
  const config = getDefaultConfig();
4156
- console.log("\u{1F440} Watching for changes in .rulesync directory...");
4157
- console.log("Press Ctrl+C to stop watching");
4361
+ logger.log("\u{1F440} Watching for changes in .rulesync directory...");
4362
+ logger.log("Press Ctrl+C to stop watching");
4158
4363
  await generateCommand({ verbose: false });
4159
4364
  const watcher = watch(`${config.aiRulesDir}/**/*.md`, {
4160
4365
  ignoreInitial: true,
@@ -4164,26 +4369,26 @@ async function watchCommand() {
4164
4369
  const handleChange = async (path5) => {
4165
4370
  if (isGenerating) return;
4166
4371
  isGenerating = true;
4167
- console.log(`
4372
+ logger.log(`
4168
4373
  \u{1F4DD} Detected change in ${path5}`);
4169
4374
  try {
4170
4375
  await generateCommand({ verbose: false });
4171
- console.log("\u2705 Regenerated configuration files");
4376
+ logger.success("Regenerated configuration files");
4172
4377
  } catch (error) {
4173
- console.error("\u274C Failed to regenerate:", error);
4378
+ logger.error("Failed to regenerate:", error);
4174
4379
  } finally {
4175
4380
  isGenerating = false;
4176
4381
  }
4177
4382
  };
4178
4383
  watcher.on("change", handleChange).on("add", handleChange).on("unlink", (path5) => {
4179
- console.log(`
4384
+ logger.log(`
4180
4385
  \u{1F5D1}\uFE0F Removed ${path5}`);
4181
4386
  handleChange(path5);
4182
4387
  }).on("error", (error) => {
4183
- console.error("\u274C Watcher error:", error);
4388
+ logger.error("Watcher error:", error);
4184
4389
  });
4185
4390
  process.on("SIGINT", () => {
4186
- console.log("\n\n\u{1F44B} Stopping watcher...");
4391
+ logger.log("\n\n\u{1F44B} Stopping watcher...");
4187
4392
  watcher.close();
4188
4393
  process.exit(0);
4189
4394
  });
@@ -4191,12 +4396,12 @@ async function watchCommand() {
4191
4396
 
4192
4397
  // src/cli/index.ts
4193
4398
  var program = new Command();
4194
- program.name("rulesync").description("Unified AI rules management CLI tool").version("0.62.0");
4399
+ program.name("rulesync").description("Unified AI rules management CLI tool").version("0.64.0");
4195
4400
  program.command("init").description("Initialize rulesync in current directory").option("--legacy", "Use legacy file location (.rulesync/*.md instead of .rulesync/rules/*.md)").action(initCommand);
4196
4401
  program.command("add <filename>").description("Add a new rule file").option("--legacy", "Use legacy file location (.rulesync/*.md instead of .rulesync/rules/*.md)").action(addCommand);
4197
4402
  program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
4198
- 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);
4199
- 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(
4403
+ 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("--opencode", "Import from OpenCode (AGENTS.md)").option("-v, --verbose", "Verbose output").option("--legacy", "Use legacy file location (.rulesync/*.md instead of .rulesync/rules/*.md)").action(importCommand);
4404
+ 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("--opencode", "Generate only for OpenCode").option("--windsurf", "Generate only for Windsurf").option("--delete", "Delete all existing files in output directories before generating").option(
4200
4405
  "-b, --base-dir <paths>",
4201
4406
  "Base directories to generate files (comma-separated for multiple paths)"
4202
4407
  ).option("-v, --verbose", "Verbose output").option("-c, --config <path>", "Path to configuration file").option("--no-config", "Disable configuration file loading").action(async (options) => {
@@ -4212,6 +4417,7 @@ program.command("generate").description("Generate configuration files for AI too
4212
4417
  if (options.geminicli) tools.push("geminicli");
4213
4418
  if (options.junie) tools.push("junie");
4214
4419
  if (options.kiro) tools.push("kiro");
4420
+ if (options.opencode) tools.push("opencode");
4215
4421
  if (options.windsurf) tools.push("windsurf");
4216
4422
  const generateOptions = {
4217
4423
  verbose: options.verbose,