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.cjs CHANGED
@@ -35,12 +35,13 @@ function isToolTarget(target) {
35
35
  if (!target) return false;
36
36
  return ALL_TOOL_TARGETS.some((validTarget) => validTarget === target);
37
37
  }
38
- var import_mini3, ALL_TOOL_TARGETS, ToolTargetSchema, ToolTargetsSchema, WildcardTargetSchema, RulesyncTargetsSchema;
38
+ var import_mini2, ALL_TOOL_TARGETS, ToolTargetSchema, ToolTargetsSchema, WildcardTargetSchema, RulesyncTargetsSchema;
39
39
  var init_tool_targets = __esm({
40
40
  "src/types/tool-targets.ts"() {
41
41
  "use strict";
42
- import_mini3 = require("zod/mini");
42
+ import_mini2 = require("zod/mini");
43
43
  ALL_TOOL_TARGETS = [
44
+ "amazonqcli",
44
45
  "augmentcode",
45
46
  "augmentcode-legacy",
46
47
  "copilot",
@@ -48,16 +49,163 @@ var init_tool_targets = __esm({
48
49
  "cline",
49
50
  "claudecode",
50
51
  "codexcli",
52
+ "opencode",
51
53
  "roo",
52
54
  "geminicli",
53
55
  "kiro",
54
56
  "junie",
55
57
  "windsurf"
56
58
  ];
57
- ToolTargetSchema = import_mini3.z.enum(ALL_TOOL_TARGETS);
58
- ToolTargetsSchema = import_mini3.z.array(ToolTargetSchema);
59
- WildcardTargetSchema = import_mini3.z.tuple([import_mini3.z.literal("*")]);
60
- RulesyncTargetsSchema = import_mini3.z.union([ToolTargetsSchema, WildcardTargetSchema]);
59
+ ToolTargetSchema = import_mini2.z.enum(ALL_TOOL_TARGETS);
60
+ ToolTargetsSchema = import_mini2.z.array(ToolTargetSchema);
61
+ WildcardTargetSchema = import_mini2.z.tuple([import_mini2.z.literal("*")]);
62
+ RulesyncTargetsSchema = import_mini2.z.union([ToolTargetsSchema, WildcardTargetSchema]);
63
+ }
64
+ });
65
+
66
+ // src/utils/logger.ts
67
+ var import_consola, Logger, logger;
68
+ var init_logger = __esm({
69
+ "src/utils/logger.ts"() {
70
+ "use strict";
71
+ import_consola = require("consola");
72
+ Logger = class {
73
+ _verbose = false;
74
+ console = import_consola.consola.withDefaults({
75
+ tag: "rulesync"
76
+ });
77
+ setVerbose(verbose) {
78
+ this._verbose = verbose;
79
+ }
80
+ get verbose() {
81
+ return this._verbose;
82
+ }
83
+ // Regular log (always shown, regardless of verbose)
84
+ log(message, ...args) {
85
+ this.console.log(message, ...args);
86
+ }
87
+ // Info level (shown only in verbose mode)
88
+ info(message, ...args) {
89
+ if (this._verbose) {
90
+ this.console.info(message, ...args);
91
+ }
92
+ }
93
+ // Success (always shown)
94
+ success(message, ...args) {
95
+ this.console.success(message, ...args);
96
+ }
97
+ // Warning (always shown)
98
+ warn(message, ...args) {
99
+ this.console.warn(message, ...args);
100
+ }
101
+ // Error (always shown)
102
+ error(message, ...args) {
103
+ this.console.error(message, ...args);
104
+ }
105
+ // Debug level (shown only in verbose mode)
106
+ debug(message, ...args) {
107
+ if (this._verbose) {
108
+ this.console.debug(message, ...args);
109
+ }
110
+ }
111
+ };
112
+ logger = new Logger();
113
+ }
114
+ });
115
+
116
+ // src/utils/file.ts
117
+ async function ensureDir(dirPath) {
118
+ try {
119
+ await (0, import_promises2.stat)(dirPath);
120
+ } catch {
121
+ await (0, import_promises2.mkdir)(dirPath, { recursive: true });
122
+ }
123
+ }
124
+ function resolvePath(relativePath, baseDir) {
125
+ if (!baseDir) return relativePath;
126
+ const resolved = (0, import_node_path.resolve)(baseDir, relativePath);
127
+ const rel = (0, import_node_path.relative)(baseDir, resolved);
128
+ if (rel.startsWith("..") || (0, import_node_path.resolve)(resolved) !== resolved) {
129
+ throw new Error(`Path traversal detected: ${relativePath}`);
130
+ }
131
+ return resolved;
132
+ }
133
+ async function readFileContent(filepath) {
134
+ return (0, import_promises2.readFile)(filepath, "utf-8");
135
+ }
136
+ async function writeFileContent(filepath, content) {
137
+ await ensureDir((0, import_node_path.dirname)(filepath));
138
+ await (0, import_promises2.writeFile)(filepath, content, "utf-8");
139
+ }
140
+ async function fileExists(filepath) {
141
+ try {
142
+ await (0, import_promises2.stat)(filepath);
143
+ return true;
144
+ } catch {
145
+ return false;
146
+ }
147
+ }
148
+ async function findFiles(dir, extension = ".md") {
149
+ try {
150
+ const files = await (0, import_promises2.readdir)(dir);
151
+ return files.filter((file) => file.endsWith(extension)).map((file) => (0, import_node_path.join)(dir, file));
152
+ } catch {
153
+ return [];
154
+ }
155
+ }
156
+ async function findRuleFiles(aiRulesDir) {
157
+ const rulesDir = (0, import_node_path.join)(aiRulesDir, "rules");
158
+ const newLocationFiles = await findFiles(rulesDir, ".md");
159
+ const legacyLocationFiles = await findFiles(aiRulesDir, ".md");
160
+ const newLocationBasenames = new Set(
161
+ newLocationFiles.map((file) => file.split("/").pop()?.replace(/\.md$/, ""))
162
+ );
163
+ const filteredLegacyFiles = legacyLocationFiles.filter((file) => {
164
+ const basename6 = file.split("/").pop()?.replace(/\.md$/, "");
165
+ return !newLocationBasenames.has(basename6);
166
+ });
167
+ return [...newLocationFiles, ...filteredLegacyFiles];
168
+ }
169
+ async function removeDirectory(dirPath) {
170
+ const dangerousPaths = [".", "/", "~", "src", "node_modules"];
171
+ if (dangerousPaths.includes(dirPath) || dirPath === "") {
172
+ logger.warn(`Skipping deletion of dangerous path: ${dirPath}`);
173
+ return;
174
+ }
175
+ try {
176
+ if (await fileExists(dirPath)) {
177
+ await (0, import_promises2.rm)(dirPath, { recursive: true, force: true });
178
+ }
179
+ } catch (error) {
180
+ logger.warn(`Failed to remove directory ${dirPath}:`, error);
181
+ }
182
+ }
183
+ async function removeFile(filepath) {
184
+ try {
185
+ if (await fileExists(filepath)) {
186
+ await (0, import_promises2.rm)(filepath);
187
+ }
188
+ } catch (error) {
189
+ logger.warn(`Failed to remove file ${filepath}:`, error);
190
+ }
191
+ }
192
+ async function removeClaudeGeneratedFiles() {
193
+ const filesToRemove = ["CLAUDE.md", ".claude/memories"];
194
+ for (const fileOrDir of filesToRemove) {
195
+ if (fileOrDir.endsWith("/memories")) {
196
+ await removeDirectory(fileOrDir);
197
+ } else {
198
+ await removeFile(fileOrDir);
199
+ }
200
+ }
201
+ }
202
+ var import_promises2, import_node_path;
203
+ var init_file = __esm({
204
+ "src/utils/file.ts"() {
205
+ "use strict";
206
+ import_promises2 = require("fs/promises");
207
+ import_node_path = require("path");
208
+ init_logger();
61
209
  }
62
210
  });
63
211
 
@@ -85,6 +233,75 @@ var init_mcp_helpers = __esm({
85
233
  }
86
234
  });
87
235
 
236
+ // src/generators/mcp/amazonqcli.ts
237
+ var amazonqcli_exports = {};
238
+ __export(amazonqcli_exports, {
239
+ generateAmazonqcliMcp: () => generateAmazonqcliMcp,
240
+ generateAmazonqcliMcpString: () => generateAmazonqcliMcpString
241
+ });
242
+ async function generateAmazonqcliMcp(mcpServers, config, baseDir) {
243
+ const outputs = [];
244
+ const configPaths = [
245
+ ".amazonq/mcp.json"
246
+ // Workspace configuration
247
+ // Note: Global configuration is ~/.aws/amazonq/mcp.json but is user-specific
248
+ // According to precautions.md, we should not create user-level files
249
+ ];
250
+ for (const configPath of configPaths) {
251
+ const filepath = resolvePath(configPath, baseDir);
252
+ const content = generateAmazonqcliMcpConfig({ mcpServers });
253
+ outputs.push({
254
+ tool: "amazonqcli",
255
+ filepath,
256
+ content
257
+ });
258
+ }
259
+ return outputs;
260
+ }
261
+ function generateAmazonqcliMcpString(config) {
262
+ return generateAmazonqcliMcpConfig(config);
263
+ }
264
+ function generateAmazonqcliMcpConfig(config) {
265
+ const servers = {};
266
+ for (const [serverName, server] of Object.entries(config.mcpServers)) {
267
+ if (!shouldIncludeServer(server, "amazonqcli")) {
268
+ continue;
269
+ }
270
+ const amazonqServer = {};
271
+ if (server.command) {
272
+ amazonqServer.command = server.command;
273
+ if (server.args) {
274
+ amazonqServer.args = server.args;
275
+ }
276
+ }
277
+ if (server.env) {
278
+ amazonqServer.env = server.env;
279
+ }
280
+ if (server.timeout !== void 0) {
281
+ amazonqServer.timeout = server.timeout;
282
+ }
283
+ if (server.disabled !== void 0) {
284
+ amazonqServer.disabled = server.disabled;
285
+ }
286
+ if (server.alwaysAllow) {
287
+ amazonqServer.autoApprove = server.alwaysAllow;
288
+ }
289
+ servers[serverName] = amazonqServer;
290
+ }
291
+ const finalConfig = {
292
+ mcpServers: servers
293
+ };
294
+ return `${JSON.stringify(finalConfig, null, 2)}
295
+ `;
296
+ }
297
+ var init_amazonqcli = __esm({
298
+ "src/generators/mcp/amazonqcli.ts"() {
299
+ "use strict";
300
+ init_file();
301
+ init_mcp_helpers();
302
+ }
303
+ });
304
+
88
305
  // src/generators/mcp/augmentcode.ts
89
306
  var augmentcode_exports = {};
90
307
  __export(augmentcode_exports, {
@@ -104,7 +321,10 @@ function generateAugmentcodeMcp(config) {
104
321
  name: serverName
105
322
  };
106
323
  if (server.command) {
107
- augmentServer.command = server.command;
324
+ const command = Array.isArray(server.command) ? server.command[0] : server.command;
325
+ if (command) {
326
+ augmentServer.command = command;
327
+ }
108
328
  if (server.args) {
109
329
  augmentServer.args = server.args;
110
330
  }
@@ -152,7 +372,10 @@ function generateAugmentcodeMcpConfiguration(mcpServers, baseDir = "") {
152
372
  const { targets: _, ...serverConfig } = server;
153
373
  const augmentServer = {};
154
374
  if (serverConfig.command) {
155
- augmentServer.command = serverConfig.command;
375
+ const command = Array.isArray(serverConfig.command) ? serverConfig.command[0] : serverConfig.command;
376
+ if (command) {
377
+ augmentServer.command = command;
378
+ }
156
379
  if (serverConfig.args) {
157
380
  augmentServer.args = serverConfig.args;
158
381
  }
@@ -199,6 +422,17 @@ var init_augmentcode = __esm({
199
422
  }
200
423
  });
201
424
 
425
+ // src/constants/schemas.ts
426
+ var SCHEMA_URLS;
427
+ var init_schemas = __esm({
428
+ "src/constants/schemas.ts"() {
429
+ "use strict";
430
+ SCHEMA_URLS = {
431
+ OPENCODE: "https://opencode.ai/config.json"
432
+ };
433
+ }
434
+ });
435
+
202
436
  // src/generators/mcp/shared-factory.ts
203
437
  function generateMcpConfig(config, toolConfig) {
204
438
  const servers = {};
@@ -248,7 +482,7 @@ function generateMcpConfigurationFilesFromRegistry(tool, mcpServers, baseDir = "
248
482
  if (tool === "junie") {
249
483
  return generateJunieMcpConfigurationFiles(mcpServers, baseDir);
250
484
  }
251
- const customTools = ["copilot", "augmentcode", "roo", "codexcli", "kiro", "geminicli"];
485
+ const customTools = ["copilot", "augmentcode", "codexcli", "kiro"];
252
486
  if (customTools.includes(tool)) {
253
487
  throw new Error(
254
488
  `Tool ${tool} uses custom configuration logic - use its specific generator function instead`
@@ -297,6 +531,7 @@ var serverTransforms, configWrappers, MCP_GENERATOR_REGISTRY, cursorMcpGenerator
297
531
  var init_shared_factory = __esm({
298
532
  "src/generators/mcp/shared-factory.ts"() {
299
533
  "use strict";
534
+ init_schemas();
300
535
  init_mcp_helpers();
301
536
  serverTransforms = {
302
537
  /**
@@ -307,7 +542,8 @@ var init_shared_factory = __esm({
307
542
  if (server.command) {
308
543
  result.command = server.command;
309
544
  if (server.args) result.args = server.args;
310
- } else if (server.url || server.httpUrl) {
545
+ }
546
+ if (server.url || server.httpUrl) {
311
547
  const url = server.httpUrl || server.url;
312
548
  if (url) result.url = url;
313
549
  }
@@ -316,6 +552,25 @@ var init_shared_factory = __esm({
316
552
  }
317
553
  return result;
318
554
  },
555
+ /**
556
+ * Roo-specific server transformation (preserves httpUrl, transport, type, etc.)
557
+ */
558
+ roo: (server) => {
559
+ const result = serverTransforms.extended(server);
560
+ if (server.httpUrl) {
561
+ if (!server.url) {
562
+ result.httpUrl = server.httpUrl;
563
+ delete result.url;
564
+ }
565
+ }
566
+ if (server.transport) {
567
+ result.transport = server.transport;
568
+ }
569
+ if (server.type) {
570
+ result.type = server.type;
571
+ }
572
+ return result;
573
+ },
319
574
  /**
320
575
  * Extended server transformation (includes disabled, alwaysAllow, etc.)
321
576
  */
@@ -367,6 +622,12 @@ var init_shared_factory = __esm({
367
622
  })
368
623
  };
369
624
  MCP_GENERATOR_REGISTRY = {
625
+ roo: {
626
+ target: "roo",
627
+ configPaths: [".roo/mcp.json"],
628
+ serverTransform: serverTransforms.roo,
629
+ configWrapper: configWrappers.mcpServers
630
+ },
370
631
  claudecode: {
371
632
  target: "claudecode",
372
633
  configPaths: [".mcp.json"],
@@ -493,6 +754,48 @@ var init_shared_factory = __esm({
493
754
  configPaths: [".cline/mcp.json"],
494
755
  serverTransform: serverTransforms.extended,
495
756
  configWrapper: configWrappers.mcpServers
757
+ },
758
+ geminicli: {
759
+ target: "geminicli",
760
+ configPaths: [".gemini/settings.json"],
761
+ serverTransform: (server) => {
762
+ const { targets: _, ...serverConfig } = server;
763
+ const geminiServer = { ...serverConfig };
764
+ if (server.env) {
765
+ geminiServer.env = server.env;
766
+ }
767
+ return geminiServer;
768
+ },
769
+ configWrapper: configWrappers.mcpServers
770
+ },
771
+ opencode: {
772
+ target: "opencode",
773
+ configPaths: ["opencode.json"],
774
+ serverTransform: (server) => {
775
+ const opencodeServer = {};
776
+ if (server.command) {
777
+ opencodeServer.type = "local";
778
+ opencodeServer.command = Array.isArray(server.command) ? server.command : [server.command];
779
+ if (server.args) opencodeServer.args = server.args;
780
+ if (server.env) opencodeServer.environment = server.env;
781
+ if (server.cwd) opencodeServer.cwd = server.cwd;
782
+ } else if (server.url || server.httpUrl) {
783
+ opencodeServer.type = "remote";
784
+ const url = server.httpUrl || server.url;
785
+ if (url) opencodeServer.url = url;
786
+ if (server.headers) opencodeServer.headers = server.headers;
787
+ }
788
+ if (server.disabled !== void 0) {
789
+ opencodeServer.enabled = !server.disabled;
790
+ } else {
791
+ opencodeServer.enabled = true;
792
+ }
793
+ return opencodeServer;
794
+ },
795
+ configWrapper: (servers) => ({
796
+ $schema: SCHEMA_URLS.OPENCODE,
797
+ mcp: servers
798
+ })
496
799
  }
497
800
  };
498
801
  cursorMcpGenerator = createMcpGenerator("cursor");
@@ -696,7 +999,10 @@ function generateCopilotMcp(config, target) {
696
999
  if (!shouldIncludeServer(server, "copilot")) continue;
697
1000
  const copilotServer = {};
698
1001
  if (server.command) {
699
- copilotServer.command = server.command;
1002
+ const command = Array.isArray(server.command) ? server.command[0] : server.command;
1003
+ if (command) {
1004
+ copilotServer.command = command;
1005
+ }
700
1006
  if (server.args) copilotServer.args = server.args;
701
1007
  } else if (server.url || server.httpUrl) {
702
1008
  const url = server.httpUrl || server.url;
@@ -786,53 +1092,10 @@ __export(geminicli_exports, {
786
1092
  generateGeminiCliMcpConfiguration: () => generateGeminiCliMcpConfiguration
787
1093
  });
788
1094
  function generateGeminiCliMcp(config) {
789
- return generateMcpConfig(config, {
790
- target: "geminicli",
791
- configPaths: [".gemini/settings.json"],
792
- serverTransform: (server) => {
793
- const geminiServer = {};
794
- if (server.command) {
795
- geminiServer.command = server.command;
796
- if (server.args) geminiServer.args = server.args;
797
- } else if (server.url || server.httpUrl) {
798
- if (server.httpUrl) {
799
- geminiServer.httpUrl = server.httpUrl;
800
- } else if (server.url) {
801
- geminiServer.url = server.url;
802
- }
803
- }
804
- if (server.env) {
805
- geminiServer.env = server.env;
806
- }
807
- if (server.timeout !== void 0) {
808
- geminiServer.timeout = server.timeout;
809
- }
810
- if (server.trust !== void 0) {
811
- geminiServer.trust = server.trust;
812
- }
813
- return geminiServer;
814
- },
815
- configWrapper: configWrappers.mcpServers
816
- });
1095
+ return generateMcpFromRegistry("geminicli", config);
817
1096
  }
818
1097
  function generateGeminiCliMcpConfiguration(mcpServers, baseDir = "") {
819
- return generateMcpConfigurationFiles(
820
- mcpServers,
821
- {
822
- target: "geminicli",
823
- configPaths: [".gemini/settings.json"],
824
- serverTransform: (server) => {
825
- const { targets: _, ...serverConfig } = server;
826
- const geminiServer = { ...serverConfig };
827
- if (server.env) {
828
- geminiServer.env = server.env;
829
- }
830
- return geminiServer;
831
- },
832
- configWrapper: configWrappers.mcpServers
833
- },
834
- baseDir
835
- );
1098
+ return generateMcpConfigurationFilesFromRegistry("geminicli", mcpServers, baseDir);
836
1099
  }
837
1100
  var init_geminicli = __esm({
838
1101
  "src/generators/mcp/geminicli.ts"() {
@@ -956,77 +1219,15 @@ __export(roo_exports, {
956
1219
  generateRooMcpConfiguration: () => generateRooMcpConfiguration
957
1220
  });
958
1221
  function generateRooMcp(config) {
959
- const rooConfig = {
960
- mcpServers: {}
961
- };
962
- for (const [serverName, server] of Object.entries(config.mcpServers)) {
963
- if (!shouldIncludeServer(server, "roo")) continue;
964
- const rooServer = {};
965
- if (server.command) {
966
- rooServer.command = server.command;
967
- if (server.args) rooServer.args = server.args;
968
- } else if (server.url || server.httpUrl) {
969
- const url = server.httpUrl || server.url;
970
- if (url) {
971
- rooServer.url = url;
972
- }
973
- if (server.httpUrl || server.transport === "http") {
974
- rooServer.type = "streamable-http";
975
- } else if (server.transport === "sse" || server.type === "sse") {
976
- rooServer.type = "sse";
977
- }
978
- }
979
- if (server.env) {
980
- rooServer.env = {};
981
- for (const [key, value] of Object.entries(server.env)) {
982
- if (value.startsWith("${env:") && value.endsWith("}")) {
983
- rooServer.env[key] = value;
984
- } else {
985
- rooServer.env[key] = `\${env:${value}}`;
986
- }
987
- }
988
- }
989
- if (server.disabled !== void 0) {
990
- rooServer.disabled = server.disabled;
991
- }
992
- if (server.alwaysAllow) {
993
- rooServer.alwaysAllow = server.alwaysAllow;
994
- }
995
- if (server.networkTimeout !== void 0) {
996
- rooServer.networkTimeout = Math.max(3e4, Math.min(3e5, server.networkTimeout));
997
- }
998
- rooConfig.mcpServers[serverName] = rooServer;
999
- }
1000
- return JSON.stringify(rooConfig, null, 2);
1222
+ return generateMcpFromRegistry("roo", config);
1001
1223
  }
1002
1224
  function generateRooMcpConfiguration(mcpServers, baseDir = "") {
1003
- const filepath = baseDir ? `${baseDir}/.roo/mcp.json` : ".roo/mcp.json";
1004
- const config = {
1005
- mcpServers: {}
1006
- };
1007
- for (const [serverName, server] of Object.entries(mcpServers)) {
1008
- if (!shouldIncludeServer(server, "roo")) {
1009
- continue;
1010
- }
1011
- const { targets: _targets, ...serverConfig } = server;
1012
- const rooServer = { ...serverConfig };
1013
- if (serverConfig.httpUrl !== void 0 && serverConfig.url !== void 0) {
1014
- rooServer.url = serverConfig.httpUrl;
1015
- }
1016
- config.mcpServers[serverName] = rooServer;
1017
- }
1018
- return [
1019
- {
1020
- filepath,
1021
- content: `${JSON.stringify(config, null, 2)}
1022
- `
1023
- }
1024
- ];
1225
+ return generateMcpConfigurationFilesFromRegistry("roo", mcpServers, baseDir);
1025
1226
  }
1026
1227
  var init_roo = __esm({
1027
1228
  "src/generators/mcp/roo.ts"() {
1028
1229
  "use strict";
1029
- init_mcp_helpers();
1230
+ init_shared_factory();
1030
1231
  }
1031
1232
  });
1032
1233
 
@@ -1049,6 +1250,25 @@ var init_windsurf = __esm({
1049
1250
  }
1050
1251
  });
1051
1252
 
1253
+ // src/generators/mcp/opencode.ts
1254
+ var opencode_exports = {};
1255
+ __export(opencode_exports, {
1256
+ generateOpenCodeMcp: () => generateOpenCodeMcp,
1257
+ generateOpenCodeMcpConfiguration: () => generateOpenCodeMcpConfiguration
1258
+ });
1259
+ function generateOpenCodeMcp(config) {
1260
+ return generateMcpFromRegistry("opencode", config);
1261
+ }
1262
+ function generateOpenCodeMcpConfiguration(mcpServers, baseDir = "") {
1263
+ return generateMcpConfigurationFilesFromRegistry("opencode", mcpServers, baseDir);
1264
+ }
1265
+ var init_opencode = __esm({
1266
+ "src/generators/mcp/opencode.ts"() {
1267
+ "use strict";
1268
+ init_shared_factory();
1269
+ }
1270
+ });
1271
+
1052
1272
  // src/cli/index.ts
1053
1273
  var import_commander = require("commander");
1054
1274
 
@@ -1071,12 +1291,21 @@ var ClaudeSettingsSchema = import_mini.z.looseObject({
1071
1291
  )
1072
1292
  });
1073
1293
 
1074
- // src/types/commands.ts
1075
- var import_mini2 = require("zod/mini");
1076
- var CommandFrontmatterSchema = import_mini2.z.object({
1077
- description: import_mini2.z.optional(import_mini2.z.string())
1294
+ // src/types/shared.ts
1295
+ var import_mini3 = require("zod/mini");
1296
+ init_tool_targets();
1297
+ var OutputSchema = import_mini3.z.object({
1298
+ tool: ToolTargetSchema,
1299
+ filepath: import_mini3.z.string(),
1300
+ content: import_mini3.z.string()
1301
+ });
1302
+ var BaseFrontmatterSchema = import_mini3.z.object({
1303
+ description: import_mini3.z.optional(import_mini3.z.string())
1078
1304
  });
1079
1305
 
1306
+ // src/types/commands.ts
1307
+ var CommandFrontmatterSchema = BaseFrontmatterSchema;
1308
+
1080
1309
  // src/types/config.ts
1081
1310
  var import_mini4 = require("zod/mini");
1082
1311
  init_tool_targets();
@@ -1094,6 +1323,7 @@ var ConfigSchema = import_mini4.z.object({
1094
1323
  var import_mini5 = require("zod/mini");
1095
1324
  init_tool_targets();
1096
1325
  var OutputPathsSchema = import_mini5.z.object({
1326
+ amazonqcli: import_mini5.z.optional(import_mini5.z.string()),
1097
1327
  augmentcode: import_mini5.z.optional(import_mini5.z.string()),
1098
1328
  "augmentcode-legacy": import_mini5.z.optional(import_mini5.z.string()),
1099
1329
  copilot: import_mini5.z.optional(import_mini5.z.string()),
@@ -1101,6 +1331,7 @@ var OutputPathsSchema = import_mini5.z.object({
1101
1331
  cline: import_mini5.z.optional(import_mini5.z.string()),
1102
1332
  claudecode: import_mini5.z.optional(import_mini5.z.string()),
1103
1333
  codexcli: import_mini5.z.optional(import_mini5.z.string()),
1334
+ opencode: import_mini5.z.optional(import_mini5.z.string()),
1104
1335
  roo: import_mini5.z.optional(import_mini5.z.string()),
1105
1336
  geminicli: import_mini5.z.optional(import_mini5.z.string()),
1106
1337
  kiro: import_mini5.z.optional(import_mini5.z.string()),
@@ -1152,7 +1383,7 @@ var import_mini6 = require("zod/mini");
1152
1383
  init_tool_targets();
1153
1384
  var McpTransportTypeSchema = import_mini6.z.enum(["stdio", "sse", "http"]);
1154
1385
  var McpServerBaseSchema = import_mini6.z.object({
1155
- command: import_mini6.z.optional(import_mini6.z.string()),
1386
+ command: import_mini6.z.optional(import_mini6.z.union([import_mini6.z.string(), import_mini6.z.array(import_mini6.z.string())])),
1156
1387
  args: import_mini6.z.optional(import_mini6.z.array(import_mini6.z.string())),
1157
1388
  url: import_mini6.z.optional(import_mini6.z.string()),
1158
1389
  httpUrl: import_mini6.z.optional(import_mini6.z.string()),
@@ -1193,11 +1424,6 @@ var RuleFrontmatterSchema = import_mini7.z.object({
1193
1424
  windsurfOutputFormat: import_mini7.z.optional(import_mini7.z.enum(["single-file", "directory"])),
1194
1425
  tags: import_mini7.z.optional(import_mini7.z.array(import_mini7.z.string()))
1195
1426
  });
1196
- var GeneratedOutputSchema = import_mini7.z.object({
1197
- tool: ToolTargetSchema,
1198
- filepath: import_mini7.z.string(),
1199
- content: import_mini7.z.string()
1200
- });
1201
1427
  var GenerateOptionsSchema = import_mini7.z.object({
1202
1428
  targetTools: import_mini7.z.optional(ToolTargetsSchema),
1203
1429
  outputDir: import_mini7.z.optional(import_mini7.z.string()),
@@ -1213,6 +1439,7 @@ function getDefaultConfig() {
1213
1439
  return {
1214
1440
  aiRulesDir: ".rulesync",
1215
1441
  outputPaths: {
1442
+ amazonqcli: ".amazonq/rules",
1216
1443
  augmentcode: ".",
1217
1444
  "augmentcode-legacy": ".",
1218
1445
  copilot: ".github/instructions",
@@ -1220,6 +1447,7 @@ function getDefaultConfig() {
1220
1447
  cline: ".clinerules",
1221
1448
  claudecode: ".",
1222
1449
  codexcli: ".",
1450
+ opencode: ".",
1223
1451
  roo: ".roo/rules",
1224
1452
  geminicli: ".gemini/memories",
1225
1453
  kiro: ".kiro/steering",
@@ -1427,6 +1655,7 @@ function mergeWithCliOptions(config, cliOptions) {
1427
1655
  }
1428
1656
 
1429
1657
  // src/cli/commands/add.ts
1658
+ init_logger();
1430
1659
  function sanitizeFilename(filename) {
1431
1660
  return filename.endsWith(".md") ? filename.slice(0, -3) : filename;
1432
1661
  }
@@ -1455,11 +1684,11 @@ async function addCommand(filename, options = {}) {
1455
1684
  await (0, import_promises.mkdir)(rulesDir, { recursive: true });
1456
1685
  const template = generateRuleTemplate(sanitizedFilename);
1457
1686
  await (0, import_promises.writeFile)(filePath, template, "utf8");
1458
- console.log(`\u2705 Created rule file: ${filePath}`);
1459
- console.log(`\u{1F4DD} Edit the file to customize your rules.`);
1687
+ logger.success(`Created rule file: ${filePath}`);
1688
+ logger.log(`\u{1F4DD} Edit the file to customize your rules.`);
1460
1689
  } catch (error) {
1461
- console.error(
1462
- `\u274C Failed to create rule file: ${error instanceof Error ? error.message : String(error)}`
1690
+ logger.error(
1691
+ `Failed to create rule file: ${error instanceof Error ? error.message : String(error)}`
1463
1692
  );
1464
1693
  process.exit(3);
1465
1694
  }
@@ -1470,6 +1699,7 @@ var import_node_fs = require("fs");
1470
1699
  var import_node_path2 = __toESM(require("path"), 1);
1471
1700
 
1472
1701
  // src/utils/error.ts
1702
+ init_logger();
1473
1703
  function getErrorMessage(error) {
1474
1704
  return error instanceof Error ? error.message : String(error);
1475
1705
  }
@@ -1499,134 +1729,11 @@ async function safeAsyncOperation(operation, errorContext) {
1499
1729
  }
1500
1730
  }
1501
1731
 
1502
- // src/utils/file.ts
1503
- var import_promises2 = require("fs/promises");
1504
- var import_node_path = require("path");
1505
- async function ensureDir(dirPath) {
1506
- try {
1507
- await (0, import_promises2.stat)(dirPath);
1508
- } catch {
1509
- await (0, import_promises2.mkdir)(dirPath, { recursive: true });
1510
- }
1511
- }
1512
- function resolvePath(relativePath, baseDir) {
1513
- return baseDir ? (0, import_node_path.join)(baseDir, relativePath) : relativePath;
1514
- }
1515
- async function readFileContent(filepath) {
1516
- return (0, import_promises2.readFile)(filepath, "utf-8");
1517
- }
1518
- async function writeFileContent(filepath, content) {
1519
- await ensureDir((0, import_node_path.dirname)(filepath));
1520
- await (0, import_promises2.writeFile)(filepath, content, "utf-8");
1521
- }
1522
- async function fileExists(filepath) {
1523
- try {
1524
- await (0, import_promises2.stat)(filepath);
1525
- return true;
1526
- } catch {
1527
- return false;
1528
- }
1529
- }
1530
- async function findFiles(dir, extension = ".md") {
1531
- try {
1532
- const files = await (0, import_promises2.readdir)(dir);
1533
- return files.filter((file) => file.endsWith(extension)).map((file) => (0, import_node_path.join)(dir, file));
1534
- } catch {
1535
- return [];
1536
- }
1537
- }
1538
- async function findRuleFiles(aiRulesDir) {
1539
- const rulesDir = (0, import_node_path.join)(aiRulesDir, "rules");
1540
- const newLocationFiles = await findFiles(rulesDir, ".md");
1541
- const legacyLocationFiles = await findFiles(aiRulesDir, ".md");
1542
- const newLocationBasenames = new Set(
1543
- newLocationFiles.map((file) => file.split("/").pop()?.replace(/\.md$/, ""))
1544
- );
1545
- const filteredLegacyFiles = legacyLocationFiles.filter((file) => {
1546
- const basename6 = file.split("/").pop()?.replace(/\.md$/, "");
1547
- return !newLocationBasenames.has(basename6);
1548
- });
1549
- return [...newLocationFiles, ...filteredLegacyFiles];
1550
- }
1551
- async function removeDirectory(dirPath) {
1552
- const dangerousPaths = [".", "/", "~", "src", "node_modules"];
1553
- if (dangerousPaths.includes(dirPath) || dirPath === "") {
1554
- console.warn(`Skipping deletion of dangerous path: ${dirPath}`);
1555
- return;
1556
- }
1557
- try {
1558
- if (await fileExists(dirPath)) {
1559
- await (0, import_promises2.rm)(dirPath, { recursive: true, force: true });
1560
- }
1561
- } catch (error) {
1562
- console.warn(`Failed to remove directory ${dirPath}:`, error);
1563
- }
1564
- }
1565
- async function removeFile(filepath) {
1566
- try {
1567
- if (await fileExists(filepath)) {
1568
- await (0, import_promises2.rm)(filepath);
1569
- }
1570
- } catch (error) {
1571
- console.warn(`Failed to remove file ${filepath}:`, error);
1572
- }
1573
- }
1574
- async function removeClaudeGeneratedFiles() {
1575
- const filesToRemove = ["CLAUDE.md", ".claude/memories"];
1576
- for (const fileOrDir of filesToRemove) {
1577
- if (fileOrDir.endsWith("/memories")) {
1578
- await removeDirectory(fileOrDir);
1579
- } else {
1580
- await removeFile(fileOrDir);
1581
- }
1582
- }
1583
- }
1584
-
1585
- // src/utils/logger.ts
1586
- var import_consola = require("consola");
1587
- var Logger = class {
1588
- _verbose = false;
1589
- console = import_consola.consola.withDefaults({
1590
- tag: "rulesync"
1591
- });
1592
- setVerbose(verbose) {
1593
- this._verbose = verbose;
1594
- }
1595
- get verbose() {
1596
- return this._verbose;
1597
- }
1598
- // Regular log (always shown, regardless of verbose)
1599
- log(message, ...args) {
1600
- this.console.log(message, ...args);
1601
- }
1602
- // Info level (shown only in verbose mode)
1603
- info(message, ...args) {
1604
- if (this._verbose) {
1605
- this.console.info(message, ...args);
1606
- }
1607
- }
1608
- // Success (always shown)
1609
- success(message, ...args) {
1610
- this.console.success(message, ...args);
1611
- }
1612
- // Warning (always shown)
1613
- warn(message, ...args) {
1614
- this.console.warn(message, ...args);
1615
- }
1616
- // Error (always shown)
1617
- error(message, ...args) {
1618
- this.console.error(message, ...args);
1619
- }
1620
- // Debug level (shown only in verbose mode)
1621
- debug(message, ...args) {
1622
- if (this._verbose) {
1623
- this.console.debug(message, ...args);
1624
- }
1625
- }
1626
- };
1627
- var logger = new Logger();
1732
+ // src/utils/index.ts
1733
+ init_file();
1628
1734
 
1629
1735
  // src/cli/commands/config.ts
1736
+ init_logger();
1630
1737
  async function configCommand(options = {}) {
1631
1738
  if (options.init) {
1632
1739
  await initConfig(options);
@@ -1837,96 +1944,161 @@ export default config;
1837
1944
  }
1838
1945
 
1839
1946
  // src/cli/commands/generate.ts
1840
- var import_node_path15 = require("path");
1947
+ var import_node_path13 = require("path");
1841
1948
 
1842
1949
  // src/core/command-generator.ts
1843
- var import_node_path7 = require("path");
1950
+ var import_node_path5 = require("path");
1844
1951
 
1845
- // src/generators/commands/claudecode.ts
1952
+ // src/utils/command-generators.ts
1846
1953
  var import_node_path3 = require("path");
1847
- var ClaudeCodeCommandGenerator = class {
1848
- generate(command, outputDir) {
1849
- const filepath = this.getOutputPath(command.filename, outputDir);
1850
- const frontmatter = ["---"];
1851
- if (command.frontmatter.description) {
1852
- frontmatter.push(`description: ${command.frontmatter.description}`);
1954
+ function generateYamlFrontmatter(command, options) {
1955
+ const frontmatterLines = ["---"];
1956
+ if (options?.includeDescription && command.frontmatter.description) {
1957
+ frontmatterLines.push(`description: ${command.frontmatter.description}`);
1958
+ }
1959
+ if (options?.additionalFields) {
1960
+ for (const field of options.additionalFields) {
1961
+ frontmatterLines.push(`${field.key}: ${field.value}`);
1853
1962
  }
1854
- frontmatter.push("---");
1855
- const content = `${frontmatter.join("\n")}
1963
+ }
1964
+ frontmatterLines.push("---");
1965
+ return frontmatterLines;
1966
+ }
1967
+ function buildCommandContent(command, frontmatterOptions) {
1968
+ const frontmatter = generateYamlFrontmatter(command, frontmatterOptions);
1969
+ return `${frontmatter.join("\n")}
1856
1970
 
1857
1971
  ${command.content.trim()}
1858
1972
  `;
1973
+ }
1974
+ function getFlattenedCommandPath(filename, baseDir, subdir) {
1975
+ const flattenedName = filename.replace(/\//g, "-");
1976
+ return (0, import_node_path3.join)(baseDir, subdir, `${flattenedName}.md`);
1977
+ }
1978
+ function getHierarchicalCommandPath(filename, baseDir, subdir, extension = "md") {
1979
+ const nameWithoutExt = filename.replace(/\.[^/.]+$/, "");
1980
+ const fileWithExt = `${nameWithoutExt}.${extension}`;
1981
+ return (0, import_node_path3.join)(baseDir, subdir, fileWithExt);
1982
+ }
1983
+ function escapeTomlString(str) {
1984
+ return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
1985
+ }
1986
+ var syntaxConverters = {
1987
+ /**
1988
+ * Convert Claude Code syntax to Gemini CLI syntax
1989
+ */
1990
+ toGeminiCli(content) {
1991
+ let converted = content;
1992
+ converted = converted.replace(/\$ARGUMENTS/g, "{{args}}");
1993
+ converted = converted.replace(/!`([^`]+)`/g, "!{$1}");
1994
+ return converted.trim();
1995
+ },
1996
+ /**
1997
+ * Convert to Roo Code syntax (currently identical to Claude Code)
1998
+ */
1999
+ toRooCode(content) {
2000
+ return content.trim();
2001
+ }
2002
+ };
2003
+
2004
+ // src/generators/commands/base.ts
2005
+ var BaseCommandGenerator = class {
2006
+ /**
2007
+ * Generate command output for the specified tool
2008
+ */
2009
+ generate(command, outputDir) {
2010
+ const filepath = this.getOutputPath(command.filename, outputDir);
2011
+ const content = this.processContent(command);
1859
2012
  return {
1860
- tool: "claudecode",
2013
+ tool: this.getToolName(),
1861
2014
  filepath,
1862
2015
  content
1863
2016
  };
1864
2017
  }
2018
+ /**
2019
+ * Get the output path for the command file
2020
+ * Override this method if custom path logic is needed
2021
+ */
1865
2022
  getOutputPath(filename, baseDir) {
1866
- const flattenedName = filename.replace(/\//g, "-");
1867
- return (0, import_node_path3.join)(baseDir, ".claude", "commands", `${flattenedName}.md`);
2023
+ if (this.supportsHierarchy()) {
2024
+ return getHierarchicalCommandPath(
2025
+ filename,
2026
+ baseDir,
2027
+ this.getCommandsDirectory(),
2028
+ this.getFileExtension()
2029
+ );
2030
+ } else {
2031
+ return getFlattenedCommandPath(filename, baseDir, this.getCommandsDirectory());
2032
+ }
2033
+ }
2034
+ /**
2035
+ * Whether this tool supports hierarchical directory structure
2036
+ * Override to return true for tools that support nested commands
2037
+ */
2038
+ supportsHierarchy() {
2039
+ return false;
2040
+ }
2041
+ /**
2042
+ * Get file extension for the target tool
2043
+ * Override if tool uses different extension than .md
2044
+ */
2045
+ getFileExtension() {
2046
+ return "md";
2047
+ }
2048
+ };
2049
+
2050
+ // src/generators/commands/claudecode.ts
2051
+ var ClaudeCodeCommandGenerator = class extends BaseCommandGenerator {
2052
+ getToolName() {
2053
+ return "claudecode";
2054
+ }
2055
+ getCommandsDirectory() {
2056
+ return ".claude/commands";
2057
+ }
2058
+ processContent(command) {
2059
+ return buildCommandContent(command, { includeDescription: true });
1868
2060
  }
2061
+ // Uses flattened structure by default (supportsHierarchy returns false)
1869
2062
  };
1870
2063
 
1871
2064
  // src/generators/commands/geminicli.ts
1872
- var import_node_path4 = require("path");
1873
- var GeminiCliCommandGenerator = class {
1874
- generate(command, outputDir) {
1875
- const filepath = this.getOutputPath(command.filename, outputDir);
1876
- const convertedContent = this.convertSyntax(command.content);
2065
+ var GeminiCliCommandGenerator = class extends BaseCommandGenerator {
2066
+ getToolName() {
2067
+ return "geminicli";
2068
+ }
2069
+ getCommandsDirectory() {
2070
+ return ".gemini/commands";
2071
+ }
2072
+ processContent(command) {
2073
+ const convertedContent = syntaxConverters.toGeminiCli(command.content);
1877
2074
  const tomlLines = [];
1878
2075
  if (command.frontmatter.description) {
1879
- tomlLines.push(`description = "${this.escapeTomlString(command.frontmatter.description)}"`);
2076
+ tomlLines.push(`description = "${escapeTomlString(command.frontmatter.description)}"`);
1880
2077
  tomlLines.push("");
1881
2078
  }
1882
2079
  tomlLines.push(`prompt = """${convertedContent}"""`);
1883
- const content = tomlLines.join("\n") + "\n";
1884
- return {
1885
- tool: "geminicli",
1886
- filepath,
1887
- content
1888
- };
1889
- }
1890
- getOutputPath(filename, baseDir) {
1891
- const tomlFilename = filename.replace(/\.md$/, ".toml");
1892
- const filenameWithExt = tomlFilename.endsWith(".toml") ? tomlFilename : `${tomlFilename}.toml`;
1893
- return (0, import_node_path4.join)(baseDir, ".gemini", "commands", filenameWithExt);
2080
+ return tomlLines.join("\n") + "\n";
1894
2081
  }
1895
- convertSyntax(content) {
1896
- let converted = content;
1897
- converted = converted.replace(/\$ARGUMENTS/g, "{{args}}");
1898
- converted = converted.replace(/!`([^`]+)`/g, "!{$1}");
1899
- return converted.trim();
2082
+ supportsHierarchy() {
2083
+ return true;
1900
2084
  }
1901
- escapeTomlString(str) {
1902
- return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
2085
+ getFileExtension() {
2086
+ return "toml";
1903
2087
  }
1904
2088
  };
1905
2089
 
1906
2090
  // src/generators/commands/roo.ts
1907
- var import_node_path5 = require("path");
1908
- var RooCommandGenerator = class {
1909
- generate(command, outputDir) {
1910
- const filepath = this.getOutputPath(command.filename, outputDir);
1911
- const frontmatter = ["---"];
1912
- if (command.frontmatter.description) {
1913
- frontmatter.push(`description: ${command.frontmatter.description}`);
1914
- }
1915
- frontmatter.push("---");
1916
- const content = `${frontmatter.join("\n")}
1917
-
1918
- ${command.content.trim()}
1919
- `;
1920
- return {
1921
- tool: "roo",
1922
- filepath,
1923
- content
1924
- };
2091
+ var RooCommandGenerator = class extends BaseCommandGenerator {
2092
+ getToolName() {
2093
+ return "roo";
1925
2094
  }
1926
- getOutputPath(filename, baseDir) {
1927
- const flattenedName = filename.replace(/\//g, "-");
1928
- return (0, import_node_path5.join)(baseDir, ".roo", "commands", `${flattenedName}.md`);
2095
+ getCommandsDirectory() {
2096
+ return ".roo/commands";
2097
+ }
2098
+ processContent(command) {
2099
+ return buildCommandContent(command, { includeDescription: true });
1929
2100
  }
2101
+ // Uses flattened structure by default (supportsHierarchy returns false)
1930
2102
  };
1931
2103
 
1932
2104
  // src/generators/commands/index.ts
@@ -1939,9 +2111,33 @@ function getCommandGenerator(tool) {
1939
2111
  return commandGenerators[tool];
1940
2112
  }
1941
2113
 
2114
+ // src/core/command-generator.ts
2115
+ init_file();
2116
+ init_logger();
2117
+
1942
2118
  // src/core/command-parser.ts
1943
- var import_node_path6 = require("path");
2119
+ var import_node_path4 = require("path");
2120
+
2121
+ // src/utils/frontmatter.ts
1944
2122
  var import_gray_matter = __toESM(require("gray-matter"), 1);
2123
+ function parseFrontmatter(content, options) {
2124
+ const parsed = (0, import_gray_matter.default)(content, options?.matterOptions);
2125
+ return {
2126
+ content: parsed.content.trim(),
2127
+ data: parsed.data || {}
2128
+ };
2129
+ }
2130
+ function extractArrayField(data, key, defaultValue = []) {
2131
+ const value = data[key];
2132
+ return Array.isArray(value) ? value : defaultValue;
2133
+ }
2134
+ function extractStringField(data, key, defaultValue) {
2135
+ const value = data[key];
2136
+ return typeof value === "string" ? value : defaultValue;
2137
+ }
2138
+
2139
+ // src/core/command-parser.ts
2140
+ init_logger();
1945
2141
  async function parseCommandsFromDirectory(commandsDir) {
1946
2142
  const commandFiles = await findFiles(commandsDir, ".md");
1947
2143
  const commands = [];
@@ -1956,17 +2152,17 @@ async function parseCommandsFromDirectory(commandsDir) {
1956
2152
  }
1957
2153
  }
1958
2154
  if (errors.length > 0) {
1959
- console.warn(`\u26A0\uFE0F Command parsing errors:
2155
+ logger.warn(`Command parsing errors:
1960
2156
  ${errors.join("\n")}`);
1961
2157
  }
1962
2158
  return commands;
1963
2159
  }
1964
2160
  async function parseCommandFile(filepath) {
1965
2161
  const content = await readFileContent(filepath);
1966
- const parsed = (0, import_gray_matter.default)(content);
2162
+ const parsed = parseFrontmatter(content);
1967
2163
  try {
1968
2164
  const validatedData = CommandFrontmatterSchema.parse(parsed.data);
1969
- const filename = (0, import_node_path6.basename)(filepath, ".md");
2165
+ const filename = (0, import_node_path4.basename)(filepath, ".md");
1970
2166
  return {
1971
2167
  frontmatter: {
1972
2168
  description: validatedData.description
@@ -1984,7 +2180,7 @@ async function parseCommandFile(filepath) {
1984
2180
 
1985
2181
  // src/core/command-generator.ts
1986
2182
  async function generateCommands(projectRoot, baseDir, targets) {
1987
- const commandsDir = (0, import_node_path7.join)(projectRoot, ".rulesync", "commands");
2183
+ const commandsDir = (0, import_node_path5.join)(projectRoot, ".rulesync", "commands");
1988
2184
  if (!await fileExists(commandsDir)) {
1989
2185
  return [];
1990
2186
  }
@@ -2008,8 +2204,8 @@ async function generateCommands(projectRoot, baseDir, targets) {
2008
2204
  outputs.push(output);
2009
2205
  } catch (error) {
2010
2206
  const errorMessage = error instanceof Error ? error.message : String(error);
2011
- console.error(
2012
- `\u274C Failed to generate ${target} command for ${command.filename}: ${errorMessage}`
2207
+ logger.error(
2208
+ `Failed to generate ${target} command for ${command.filename}: ${errorMessage}`
2013
2209
  );
2014
2210
  }
2015
2211
  }
@@ -2018,7 +2214,7 @@ async function generateCommands(projectRoot, baseDir, targets) {
2018
2214
  }
2019
2215
 
2020
2216
  // src/generators/ignore/shared-factory.ts
2021
- var import_node_path8 = require("path");
2217
+ var import_node_path6 = require("path");
2022
2218
 
2023
2219
  // src/generators/ignore/shared-helpers.ts
2024
2220
  function extractIgnorePatternsFromRules(rules) {
@@ -2141,7 +2337,7 @@ function generateIgnoreFile(rules, config, ignoreConfig, baseDir) {
2141
2337
  const outputs = [];
2142
2338
  const content = generateIgnoreContent(rules, ignoreConfig);
2143
2339
  const outputPath = baseDir || process.cwd();
2144
- const filepath = (0, import_node_path8.join)(outputPath, ignoreConfig.filename);
2340
+ const filepath = (0, import_node_path6.join)(outputPath, ignoreConfig.filename);
2145
2341
  outputs.push({
2146
2342
  tool: ignoreConfig.tool,
2147
2343
  filepath,
@@ -2728,21 +2924,21 @@ function generateWindsurfIgnore(rules, config, baseDir) {
2728
2924
  return generateIgnoreFile(rules, config, ignoreConfigs.windsurf, baseDir);
2729
2925
  }
2730
2926
 
2731
- // src/generators/rules/augmentcode.ts
2732
- var import_node_path11 = require("path");
2733
-
2734
2927
  // src/generators/rules/shared-helpers.ts
2735
- var import_node_path10 = require("path");
2928
+ var import_node_path8 = require("path");
2929
+ init_file();
2736
2930
 
2737
2931
  // src/utils/ignore.ts
2738
- var import_node_path9 = require("path");
2932
+ var import_node_path7 = require("path");
2739
2933
  var import_micromatch = __toESM(require("micromatch"), 1);
2934
+ init_file();
2935
+ init_logger();
2740
2936
  var cachedIgnorePatterns = null;
2741
2937
  async function loadIgnorePatterns(baseDir = process.cwd()) {
2742
2938
  if (cachedIgnorePatterns) {
2743
2939
  return cachedIgnorePatterns;
2744
2940
  }
2745
- const ignorePath = (0, import_node_path9.join)(baseDir, ".rulesyncignore");
2941
+ const ignorePath = (0, import_node_path7.join)(baseDir, ".rulesyncignore");
2746
2942
  if (!await fileExists(ignorePath)) {
2747
2943
  cachedIgnorePatterns = { patterns: [] };
2748
2944
  return cachedIgnorePatterns;
@@ -2753,7 +2949,7 @@ async function loadIgnorePatterns(baseDir = process.cwd()) {
2753
2949
  cachedIgnorePatterns = { patterns };
2754
2950
  return cachedIgnorePatterns;
2755
2951
  } catch (error) {
2756
- console.warn(`Failed to read .rulesyncignore: ${error}`);
2952
+ logger.warn(`Failed to read .rulesyncignore: ${error}`);
2757
2953
  cachedIgnorePatterns = { patterns: [] };
2758
2954
  return cachedIgnorePatterns;
2759
2955
  }
@@ -2796,7 +2992,7 @@ function addOutput(outputs, tool, config, baseDir, relativePath, content) {
2796
2992
  const outputDir = resolveOutputDir(config, tool, baseDir);
2797
2993
  outputs.push({
2798
2994
  tool,
2799
- filepath: (0, import_node_path10.join)(outputDir, relativePath),
2995
+ filepath: (0, import_node_path8.join)(outputDir, relativePath),
2800
2996
  content
2801
2997
  });
2802
2998
  }
@@ -2805,7 +3001,7 @@ async function generateRulesConfig(rules, config, generatorConfig, baseDir) {
2805
3001
  for (const rule of rules) {
2806
3002
  const content = generatorConfig.generateContent(rule);
2807
3003
  const outputDir = resolveOutputDir(config, generatorConfig.tool, baseDir);
2808
- const filepath = generatorConfig.pathResolver ? generatorConfig.pathResolver(rule, outputDir) : (0, import_node_path10.join)(outputDir, `${rule.filename}${generatorConfig.fileExtension}`);
3004
+ const filepath = generatorConfig.pathResolver ? generatorConfig.pathResolver(rule, outputDir) : (0, import_node_path8.join)(outputDir, `${rule.filename}${generatorConfig.fileExtension}`);
2809
3005
  outputs.push({
2810
3006
  tool: generatorConfig.tool,
2811
3007
  filepath,
@@ -2833,7 +3029,7 @@ async function generateComplexRules(rules, config, generatorConfig, baseDir) {
2833
3029
  for (const rule of detailRules) {
2834
3030
  const content = generatorConfig.generateDetailContent(rule);
2835
3031
  const filepath = resolvePath(
2836
- (0, import_node_path10.join)(generatorConfig.detailSubDir, `${rule.filename}.md`),
3032
+ (0, import_node_path8.join)(generatorConfig.detailSubDir, `${rule.filename}.md`),
2837
3033
  baseDir
2838
3034
  );
2839
3035
  outputs.push({
@@ -2887,7 +3083,61 @@ function generateIgnoreFile2(patterns, tool) {
2887
3083
  return lines.join("\n");
2888
3084
  }
2889
3085
 
3086
+ // src/generators/rules/amazonqcli.ts
3087
+ async function generateAmazonqcliConfig(rules, config, baseDir) {
3088
+ const generatorConfig = {
3089
+ tool: "amazonqcli",
3090
+ fileExtension: ".md",
3091
+ generateContent: generateRuleFile,
3092
+ generateRootContent: generateMainRulesFile,
3093
+ rootFilePath: ".amazonq/rules/main.md",
3094
+ generateDetailContent: generateRuleFile,
3095
+ detailSubDir: ".amazonq/rules"
3096
+ };
3097
+ return generateComplexRules(rules, config, generatorConfig, baseDir);
3098
+ }
3099
+ function generateMainRulesFile(rootRule, detailRules) {
3100
+ const lines = [];
3101
+ if (detailRules.length > 0) {
3102
+ lines.push("# Amazon Q Developer CLI Project Rules");
3103
+ lines.push("");
3104
+ lines.push("This file contains the main project rules. See also:");
3105
+ lines.push("");
3106
+ for (const rule of detailRules) {
3107
+ lines.push(`- ${rule.filename}.md: ${rule.frontmatter.description}`);
3108
+ }
3109
+ lines.push("");
3110
+ }
3111
+ if (rootRule) {
3112
+ if (detailRules.length > 0) {
3113
+ lines.push("## Overview");
3114
+ lines.push("");
3115
+ }
3116
+ lines.push(rootRule.content);
3117
+ lines.push("");
3118
+ } else if (detailRules.length === 0) {
3119
+ lines.push("# Amazon Q Developer CLI Project Rules");
3120
+ lines.push("");
3121
+ lines.push("This file contains project-specific rules and context for Amazon Q Developer CLI.");
3122
+ lines.push("");
3123
+ lines.push("## Development Standards");
3124
+ lines.push("");
3125
+ lines.push("Add your project-specific development standards here.");
3126
+ lines.push("");
3127
+ }
3128
+ return lines.join("\n").trim() + "\n";
3129
+ }
3130
+ function generateRuleFile(rule) {
3131
+ const lines = [];
3132
+ lines.push(`# ${rule.frontmatter.description || rule.filename}`);
3133
+ lines.push("");
3134
+ lines.push(rule.content.trim());
3135
+ lines.push("");
3136
+ return lines.join("\n");
3137
+ }
3138
+
2890
3139
  // src/generators/rules/augmentcode.ts
3140
+ var import_node_path9 = require("path");
2891
3141
  async function generateAugmentcodeConfig(rules, config, baseDir) {
2892
3142
  const outputs = createOutputsArray();
2893
3143
  rules.forEach((rule) => {
@@ -2896,13 +3146,13 @@ async function generateAugmentcodeConfig(rules, config, baseDir) {
2896
3146
  "augmentcode",
2897
3147
  config,
2898
3148
  baseDir,
2899
- (0, import_node_path11.join)(".augment", "rules", `${rule.filename}.md`),
2900
- generateRuleFile(rule)
3149
+ (0, import_node_path9.join)(".augment", "rules", `${rule.filename}.md`),
3150
+ generateRuleFile2(rule)
2901
3151
  );
2902
3152
  });
2903
3153
  return outputs;
2904
3154
  }
2905
- function generateRuleFile(rule) {
3155
+ function generateRuleFile2(rule) {
2906
3156
  const lines = [];
2907
3157
  lines.push("---");
2908
3158
  let ruleType = "manual";
@@ -2949,7 +3199,9 @@ function generateLegacyGuidelinesFile(allRules) {
2949
3199
  }
2950
3200
 
2951
3201
  // src/generators/rules/claudecode.ts
2952
- var import_node_path12 = require("path");
3202
+ var import_node_path10 = require("path");
3203
+ init_file();
3204
+ init_logger();
2953
3205
  async function generateClaudecodeConfig(rules, config, baseDir) {
2954
3206
  const generatorConfig = {
2955
3207
  tool: "claudecode",
@@ -2961,7 +3213,7 @@ async function generateClaudecodeConfig(rules, config, baseDir) {
2961
3213
  generateDetailContent: generateMemoryFile,
2962
3214
  detailSubDir: ".claude/memories",
2963
3215
  updateAdditionalConfig: async (ignorePatterns, baseDir2) => {
2964
- const settingsPath = resolvePath((0, import_node_path12.join)(".claude", "settings.json"), baseDir2);
3216
+ const settingsPath = resolvePath((0, import_node_path10.join)(".claude", "settings.json"), baseDir2);
2965
3217
  await updateClaudeSettings(settingsPath, ignorePatterns);
2966
3218
  return [];
2967
3219
  }
@@ -2998,7 +3250,7 @@ async function updateClaudeSettings(settingsPath, ignorePatterns) {
2998
3250
  const content = await readFileContent(settingsPath);
2999
3251
  rawSettings = JSON.parse(content);
3000
3252
  } catch {
3001
- console.warn(`Failed to parse existing ${settingsPath}, creating new settings`);
3253
+ logger.warn(`Failed to parse existing ${settingsPath}, creating new settings`);
3002
3254
  rawSettings = {};
3003
3255
  }
3004
3256
  }
@@ -3021,11 +3273,11 @@ async function updateClaudeSettings(settingsPath, ignorePatterns) {
3021
3273
  settings.permissions.deny = Array.from(new Set(filteredDeny));
3022
3274
  const jsonContent = JSON.stringify(settings, null, 2);
3023
3275
  await writeFileContent(settingsPath, jsonContent);
3024
- console.log(`\u2705 Updated Claude Code settings: ${settingsPath}`);
3276
+ logger.success(`Updated Claude Code settings: ${settingsPath}`);
3025
3277
  }
3026
3278
 
3027
3279
  // src/generators/rules/generator-registry.ts
3028
- var import_node_path13 = require("path");
3280
+ var import_node_path11 = require("path");
3029
3281
  function determineCursorRuleType(frontmatter) {
3030
3282
  if (frontmatter.cursorRuleType) {
3031
3283
  return frontmatter.cursorRuleType;
@@ -3049,6 +3301,22 @@ function determineCursorRuleType(frontmatter) {
3049
3301
  }
3050
3302
  var GENERATOR_REGISTRY = {
3051
3303
  // Simple generators - generate one file per rule
3304
+ amazonqcli: {
3305
+ type: "complex",
3306
+ tool: "amazonqcli",
3307
+ fileExtension: ".md",
3308
+ // ignoreFileName omitted - Amazon Q CLI doesn't have native ignore file support yet
3309
+ generateContent: (rule) => {
3310
+ const lines = [];
3311
+ if (rule.frontmatter.description) {
3312
+ lines.push(`# ${rule.frontmatter.description}
3313
+ `);
3314
+ }
3315
+ lines.push(rule.content.trim());
3316
+ return lines.join("\n");
3317
+ }
3318
+ // Complex generation handled by existing generator
3319
+ },
3052
3320
  cline: {
3053
3321
  type: "simple",
3054
3322
  tool: "cline",
@@ -3105,7 +3373,7 @@ var GENERATOR_REGISTRY = {
3105
3373
  },
3106
3374
  pathResolver: (rule, outputDir) => {
3107
3375
  const baseFilename = rule.filename.replace(/\.md$/, "");
3108
- return (0, import_node_path13.join)(outputDir, `${baseFilename}.instructions.md`);
3376
+ return (0, import_node_path11.join)(outputDir, `${baseFilename}.instructions.md`);
3109
3377
  }
3110
3378
  },
3111
3379
  cursor: {
@@ -3145,7 +3413,7 @@ var GENERATOR_REGISTRY = {
3145
3413
  return lines.join("\n");
3146
3414
  },
3147
3415
  pathResolver: (rule, outputDir) => {
3148
- return (0, import_node_path13.join)(outputDir, `${rule.filename}.mdc`);
3416
+ return (0, import_node_path11.join)(outputDir, `${rule.filename}.mdc`);
3149
3417
  }
3150
3418
  },
3151
3419
  codexcli: {
@@ -3181,10 +3449,10 @@ var GENERATOR_REGISTRY = {
3181
3449
  pathResolver: (rule, outputDir) => {
3182
3450
  const outputFormat = rule.frontmatter.windsurfOutputFormat || "directory";
3183
3451
  if (outputFormat === "single-file") {
3184
- return (0, import_node_path13.join)(outputDir, ".windsurf-rules");
3452
+ return (0, import_node_path11.join)(outputDir, ".windsurf-rules");
3185
3453
  } else {
3186
- const rulesDir = (0, import_node_path13.join)(outputDir, ".windsurf", "rules");
3187
- return (0, import_node_path13.join)(rulesDir, `${rule.filename}.md`);
3454
+ const rulesDir = (0, import_node_path11.join)(outputDir, ".windsurf", "rules");
3455
+ return (0, import_node_path11.join)(rulesDir, `${rule.filename}.md`);
3188
3456
  }
3189
3457
  }
3190
3458
  },
@@ -3215,6 +3483,22 @@ var GENERATOR_REGISTRY = {
3215
3483
  const lines = [];
3216
3484
  if (rule.frontmatter.description) {
3217
3485
  lines.push(`# ${rule.frontmatter.description}
3486
+ `);
3487
+ }
3488
+ lines.push(rule.content.trim());
3489
+ return lines.join("\n");
3490
+ }
3491
+ // Complex generation handled by existing generator
3492
+ },
3493
+ opencode: {
3494
+ type: "complex",
3495
+ tool: "opencode",
3496
+ fileExtension: ".md",
3497
+ // ignoreFileName omitted - OpenCode doesn't use dedicated ignore files
3498
+ generateContent: (rule) => {
3499
+ const lines = [];
3500
+ if (rule.frontmatter.description) {
3501
+ lines.push(`# ${rule.frontmatter.description}
3218
3502
  `);
3219
3503
  }
3220
3504
  lines.push(rule.content.trim());
@@ -3257,8 +3541,8 @@ async function generateFromRegistry(tool, rules, config, baseDir) {
3257
3541
  const enhancedConfig = {
3258
3542
  tool: generatorConfig.tool,
3259
3543
  fileExtension: generatorConfig.fileExtension,
3260
- ignoreFileName: generatorConfig.ignoreFileName,
3261
3544
  generateContent: generatorConfig.generateContent,
3545
+ ...generatorConfig.ignoreFileName && { ignoreFileName: generatorConfig.ignoreFileName },
3262
3546
  ...generatorConfig.generateRootContent && {
3263
3547
  generateRootContent: generatorConfig.generateRootContent
3264
3548
  },
@@ -3289,48 +3573,91 @@ var generateKiroConfig = createSimpleGenerator("kiro");
3289
3573
  var generateRooConfig = createSimpleGenerator("roo");
3290
3574
 
3291
3575
  // src/generators/rules/codexcli.ts
3292
- async function generateCodexConfig(rules, config, baseDir) {
3293
- const outputs = [];
3294
- if (rules.length === 0) {
3295
- return outputs;
3296
- }
3297
- const sortedRules = [...rules].sort((a, b) => {
3298
- if (a.frontmatter.root === true && b.frontmatter.root !== true) return -1;
3299
- if (a.frontmatter.root !== true && b.frontmatter.root === true) return 1;
3300
- return 0;
3301
- });
3302
- const concatenatedContent = generateConcatenatedCodexContent(sortedRules);
3303
- if (concatenatedContent.trim()) {
3304
- const outputDir = resolveOutputDir(config, "codexcli", baseDir);
3305
- const filepath = `${outputDir}/codex.md`;
3306
- outputs.push({
3307
- tool: "codexcli",
3308
- filepath,
3309
- content: concatenatedContent
3310
- });
3311
- }
3312
- const ignorePatterns = await loadIgnorePatterns(baseDir);
3313
- if (ignorePatterns.patterns.length > 0) {
3314
- const ignorePath = resolvePath(".codexignore", baseDir);
3315
- const ignoreContent = generateIgnoreFile2(ignorePatterns.patterns, "codexcli");
3316
- outputs.push({
3317
- tool: "codexcli",
3318
- filepath: ignorePath,
3319
- content: ignoreContent
3576
+ init_file();
3577
+
3578
+ // src/utils/xml-document-generator.ts
3579
+ var import_fast_xml_parser = require("fast-xml-parser");
3580
+ function generateRootMarkdownWithXmlDocs(rootRule, memoryRules, config) {
3581
+ const lines = [];
3582
+ if (memoryRules.length > 0) {
3583
+ lines.push(
3584
+ "Please also reference the following documents as needed. In this case, `@` stands for the project root directory."
3585
+ );
3586
+ lines.push("");
3587
+ const documentsData = {
3588
+ Documents: {
3589
+ Document: memoryRules.map((rule) => {
3590
+ const relativePath = `@${config.memorySubDir}/${rule.filename}.md`;
3591
+ const document = {
3592
+ Path: relativePath,
3593
+ Description: rule.frontmatter.description
3594
+ };
3595
+ if (rule.frontmatter.globs.length > 0) {
3596
+ document.FilePatterns = rule.frontmatter.globs.join(", ");
3597
+ }
3598
+ return document;
3599
+ })
3600
+ }
3601
+ };
3602
+ const builder = new import_fast_xml_parser.XMLBuilder({
3603
+ format: true,
3604
+ ignoreAttributes: false,
3605
+ suppressEmptyNode: false
3320
3606
  });
3607
+ const xmlContent = builder.build(documentsData);
3608
+ lines.push(xmlContent);
3609
+ lines.push("");
3610
+ lines.push("");
3321
3611
  }
3322
- return outputs;
3612
+ if (rootRule) {
3613
+ lines.push(rootRule.content.trim());
3614
+ } else if (memoryRules.length === 0) {
3615
+ lines.push(`# ${config.fallbackTitle}`);
3616
+ lines.push("");
3617
+ lines.push("No configuration rules have been defined yet.");
3618
+ }
3619
+ return lines.join("\n");
3323
3620
  }
3324
- function generateConcatenatedCodexContent(rules) {
3325
- const sections = [];
3326
- for (const rule of rules) {
3327
- const content = rule.content.trim();
3328
- if (!content) {
3329
- continue;
3621
+
3622
+ // src/generators/rules/codexcli.ts
3623
+ async function generateCodexConfig(rules, config, baseDir) {
3624
+ const outputs = [];
3625
+ const nonEmptyRules = rules.filter((rule) => rule.content.trim().length > 0);
3626
+ if (nonEmptyRules.length > 0) {
3627
+ const generatorConfig = {
3628
+ tool: "codexcli",
3629
+ fileExtension: ".md",
3630
+ ignoreFileName: ".codexignore",
3631
+ generateContent: generateCodexMemoryMarkdown,
3632
+ generateDetailContent: generateCodexMemoryMarkdown,
3633
+ generateRootContent: generateCodexRootMarkdown,
3634
+ rootFilePath: "AGENTS.md",
3635
+ detailSubDir: ".codex/memories"
3636
+ };
3637
+ const ruleOutputs = await generateComplexRules(nonEmptyRules, config, generatorConfig, baseDir);
3638
+ outputs.push(...ruleOutputs);
3639
+ } else {
3640
+ const ignorePatterns = await loadIgnorePatterns(baseDir);
3641
+ if (ignorePatterns.patterns.length > 0) {
3642
+ const ignorePath = resolvePath(".codexignore", baseDir);
3643
+ const ignoreContent = generateIgnoreFile2(ignorePatterns.patterns, "codexcli");
3644
+ outputs.push({
3645
+ tool: "codexcli",
3646
+ filepath: ignorePath,
3647
+ content: ignoreContent
3648
+ });
3330
3649
  }
3331
- sections.push(content);
3332
3650
  }
3333
- return sections.join("\n\n---\n\n");
3651
+ return outputs;
3652
+ }
3653
+ function generateCodexMemoryMarkdown(rule) {
3654
+ return rule.content.trim();
3655
+ }
3656
+ function generateCodexRootMarkdown(rootRule, memoryRules, _baseDir) {
3657
+ return generateRootMarkdownWithXmlDocs(rootRule, memoryRules, {
3658
+ memorySubDir: ".codex/memories",
3659
+ fallbackTitle: "OpenAI Codex CLI Configuration"
3660
+ });
3334
3661
  }
3335
3662
 
3336
3663
  // src/generators/rules/geminicli.ts
@@ -3351,28 +3678,10 @@ function generateGeminiMemoryMarkdown(rule) {
3351
3678
  return rule.content.trim();
3352
3679
  }
3353
3680
  function generateGeminiRootMarkdown(rootRule, memoryRules, _baseDir) {
3354
- const lines = [];
3355
- if (memoryRules.length > 0) {
3356
- lines.push("Please also reference the following documents as needed:");
3357
- lines.push("");
3358
- lines.push("| Document | Description | File Patterns |");
3359
- lines.push("|----------|-------------|---------------|");
3360
- for (const rule of memoryRules) {
3361
- const relativePath = `@.gemini/memories/${rule.filename}.md`;
3362
- const filePatterns = rule.frontmatter.globs.length > 0 ? rule.frontmatter.globs.join(", ") : "-";
3363
- lines.push(`| ${relativePath} | ${rule.frontmatter.description} | ${filePatterns} |`);
3364
- }
3365
- lines.push("");
3366
- lines.push("");
3367
- }
3368
- if (rootRule) {
3369
- lines.push(rootRule.content.trim());
3370
- } else if (memoryRules.length === 0) {
3371
- lines.push("# Gemini CLI Configuration");
3372
- lines.push("");
3373
- lines.push("No configuration rules have been defined yet.");
3374
- }
3375
- return lines.join("\n");
3681
+ return generateRootMarkdownWithXmlDocs(rootRule, memoryRules, {
3682
+ memorySubDir: ".gemini/memories",
3683
+ fallbackTitle: "Gemini CLI Configuration"
3684
+ });
3376
3685
  }
3377
3686
 
3378
3687
  // src/generators/rules/junie.ts
@@ -3402,20 +3711,43 @@ function generateGuidelinesMarkdown(rootRule, detailRules) {
3402
3711
  return lines.join("\n").trim();
3403
3712
  }
3404
3713
 
3714
+ // src/generators/rules/opencode.ts
3715
+ async function generateOpenCodeConfig(rules, config, baseDir) {
3716
+ const generatorConfig = {
3717
+ tool: "opencode",
3718
+ fileExtension: ".md",
3719
+ // ignoreFileName omitted - OpenCode doesn't use dedicated ignore files
3720
+ generateContent: generateOpenCodeMarkdown,
3721
+ generateDetailContent: generateOpenCodeMarkdown,
3722
+ generateRootContent: generateOpenCodeRootMarkdown,
3723
+ rootFilePath: "AGENTS.md",
3724
+ detailSubDir: ".opencode/memories"
3725
+ };
3726
+ return generateComplexRules(rules, config, generatorConfig, baseDir);
3727
+ }
3728
+ function generateOpenCodeMarkdown(rule) {
3729
+ return rule.content.trim();
3730
+ }
3731
+ function generateOpenCodeRootMarkdown(rootRule, memoryRules, _baseDir) {
3732
+ return generateRootMarkdownWithXmlDocs(rootRule, memoryRules, {
3733
+ memorySubDir: ".opencode/memories",
3734
+ fallbackTitle: "OpenCode Configuration"
3735
+ });
3736
+ }
3737
+
3405
3738
  // src/core/generator.ts
3739
+ init_logger();
3406
3740
  async function generateConfigurations(rules, config, targetTools, baseDir) {
3407
3741
  const outputs = createOutputsArray();
3408
3742
  const toolsToGenerate = targetTools || config.defaultTargets;
3409
3743
  const rootFiles = rules.filter((rule) => rule.frontmatter.root === true);
3410
3744
  if (rootFiles.length === 0) {
3411
- console.warn(
3412
- "\u26A0\uFE0F Warning: No files with 'root: true' found. This may result in incomplete configurations."
3413
- );
3745
+ logger.warn("No files with 'root: true' found. This may result in incomplete configurations.");
3414
3746
  }
3415
3747
  for (const tool of toolsToGenerate) {
3416
3748
  const relevantRules = filterRulesForTool(rules, tool, config);
3417
3749
  if (relevantRules.length === 0) {
3418
- console.warn(`No rules found for tool: ${tool}`);
3750
+ logger.warn(`No rules found for tool: ${tool}`);
3419
3751
  continue;
3420
3752
  }
3421
3753
  const toolOutputs = await generateForTool(tool, relevantRules, config, baseDir);
@@ -3436,6 +3768,8 @@ function filterRulesForTool(rules, tool, config) {
3436
3768
  }
3437
3769
  async function generateForTool(tool, rules, config, baseDir) {
3438
3770
  switch (tool) {
3771
+ case "amazonqcli":
3772
+ return await generateAmazonqcliConfig(rules, config, baseDir);
3439
3773
  case "augmentcode": {
3440
3774
  const augmentRulesOutputs = await generateAugmentcodeConfig(rules, config, baseDir);
3441
3775
  const augmentIgnoreOutputs = await generateAugmentCodeIgnoreFiles(rules, config, baseDir);
@@ -3474,20 +3808,22 @@ async function generateForTool(tool, rules, config, baseDir) {
3474
3808
  const kiroIgnoreOutputs = await generateKiroIgnoreFiles(rules, config, baseDir);
3475
3809
  return [...kiroRulesOutputs, ...kiroIgnoreOutputs];
3476
3810
  }
3811
+ case "opencode":
3812
+ return generateOpenCodeConfig(rules, config, baseDir);
3477
3813
  case "windsurf": {
3478
3814
  const windsurfRulesOutputs = await generateWindsurfConfig(rules, config, baseDir);
3479
3815
  const windsurfIgnoreOutputs = await generateWindsurfIgnore(rules, config, baseDir);
3480
3816
  return [...windsurfRulesOutputs, ...windsurfIgnoreOutputs];
3481
3817
  }
3482
3818
  default:
3483
- console.warn(`Unknown tool: ${tool}`);
3819
+ logger.warn(`Unknown tool: ${tool}`);
3484
3820
  return null;
3485
3821
  }
3486
3822
  }
3487
3823
 
3488
3824
  // src/core/parser.ts
3489
- var import_node_path14 = require("path");
3490
- var import_gray_matter2 = __toESM(require("gray-matter"), 1);
3825
+ var import_node_path12 = require("path");
3826
+ init_logger();
3491
3827
  async function parseRulesFromDirectory(aiRulesDir) {
3492
3828
  const ignorePatterns = await loadIgnorePatterns();
3493
3829
  const allRuleFiles = await findRuleFiles(aiRulesDir);
@@ -3495,7 +3831,7 @@ async function parseRulesFromDirectory(aiRulesDir) {
3495
3831
  const rules = [];
3496
3832
  const errors = [];
3497
3833
  if (ignorePatterns.patterns.length > 0) {
3498
- console.log(`Loaded ${ignorePatterns.patterns.length} ignore patterns from .rulesyncignore`);
3834
+ logger.info(`Loaded ${ignorePatterns.patterns.length} ignore patterns from .rulesyncignore`);
3499
3835
  }
3500
3836
  for (const filepath of ruleFiles) {
3501
3837
  try {
@@ -3521,7 +3857,7 @@ ${errors.join("\n")}`);
3521
3857
  }
3522
3858
  async function parseRuleFile(filepath) {
3523
3859
  const content = await readFileContent(filepath);
3524
- const parsed = (0, import_gray_matter2.default)(content);
3860
+ const parsed = parseFrontmatter(content);
3525
3861
  try {
3526
3862
  const validatedData = RuleFrontmatterSchema.parse(parsed.data);
3527
3863
  const frontmatter = {
@@ -3540,7 +3876,7 @@ async function parseRuleFile(filepath) {
3540
3876
  },
3541
3877
  ...validatedData.tags !== void 0 && { tags: validatedData.tags }
3542
3878
  };
3543
- const filename = (0, import_node_path14.basename)(filepath, ".md");
3879
+ const filename = (0, import_node_path12.basename)(filepath, ".md");
3544
3880
  return {
3545
3881
  frontmatter,
3546
3882
  content: parsed.content,
@@ -3608,6 +3944,7 @@ async function validateRule(rule) {
3608
3944
  var path4 = __toESM(require("path"), 1);
3609
3945
 
3610
3946
  // src/generators/mcp/index.ts
3947
+ init_amazonqcli();
3611
3948
  init_augmentcode();
3612
3949
  init_claudecode();
3613
3950
  init_cline();
@@ -3620,6 +3957,9 @@ init_kiro();
3620
3957
  init_roo();
3621
3958
  init_windsurf();
3622
3959
 
3960
+ // src/core/mcp-generator.ts
3961
+ init_file();
3962
+
3623
3963
  // src/core/mcp-parser.ts
3624
3964
  var fs = __toESM(require("fs"), 1);
3625
3965
  var path3 = __toESM(require("path"), 1);
@@ -3651,6 +3991,35 @@ function parseMcpConfig(projectRoot) {
3651
3991
  async function generateMcpConfigurations(mcpConfig, baseDir, targetTools) {
3652
3992
  const outputs = [];
3653
3993
  const toolMap = {
3994
+ amazonqcli: async (servers, dir) => {
3995
+ const config = {
3996
+ aiRulesDir: ".rulesync",
3997
+ outputPaths: {
3998
+ amazonqcli: ".amazonq/rules",
3999
+ augmentcode: ".",
4000
+ "augmentcode-legacy": ".",
4001
+ copilot: ".github/instructions",
4002
+ cursor: ".cursor/rules",
4003
+ cline: ".clinerules",
4004
+ claudecode: ".",
4005
+ codexcli: ".",
4006
+ opencode: ".",
4007
+ roo: ".roo/rules",
4008
+ geminicli: ".gemini/memories",
4009
+ kiro: ".kiro/steering",
4010
+ junie: ".",
4011
+ windsurf: "."
4012
+ },
4013
+ watchEnabled: false,
4014
+ defaultTargets: []
4015
+ };
4016
+ const results = await (await Promise.resolve().then(() => (init_amazonqcli(), amazonqcli_exports))).generateAmazonqcliMcp(
4017
+ servers,
4018
+ config,
4019
+ dir
4020
+ );
4021
+ return results.map((result) => ({ filepath: result.filepath, content: result.content }));
4022
+ },
3654
4023
  augmentcode: async (servers, dir) => (await Promise.resolve().then(() => (init_augmentcode(), augmentcode_exports))).generateAugmentcodeMcpConfiguration(
3655
4024
  servers,
3656
4025
  dir
@@ -3667,6 +4036,10 @@ async function generateMcpConfigurations(mcpConfig, baseDir, targetTools) {
3667
4036
  cursor: async (servers, dir) => (await Promise.resolve().then(() => (init_cursor(), cursor_exports))).generateCursorMcpConfiguration(servers, dir),
3668
4037
  cline: async (servers, dir) => (await Promise.resolve().then(() => (init_cline(), cline_exports))).generateClineMcpConfiguration(servers, dir),
3669
4038
  codexcli: async (servers, dir) => (await Promise.resolve().then(() => (init_codexcli(), codexcli_exports))).generateCodexMcpConfiguration(servers, dir),
4039
+ opencode: async (servers, dir) => (await Promise.resolve().then(() => (init_opencode(), opencode_exports))).generateOpenCodeMcpConfiguration(
4040
+ servers,
4041
+ dir
4042
+ ),
3670
4043
  roo: async (servers, dir) => (await Promise.resolve().then(() => (init_roo(), roo_exports))).generateRooMcpConfiguration(servers, dir),
3671
4044
  geminicli: async (servers, dir) => (await Promise.resolve().then(() => (init_geminicli(), geminicli_exports))).generateGeminiCliMcpConfiguration(
3672
4045
  servers,
@@ -3696,6 +4069,7 @@ async function generateMcpConfigurations(mcpConfig, baseDir, targetTools) {
3696
4069
  }
3697
4070
 
3698
4071
  // src/cli/commands/generate.ts
4072
+ init_logger();
3699
4073
  async function generateCommand(options = {}) {
3700
4074
  const configLoaderOptions = {
3701
4075
  ...options.config !== void 0 && { configPath: options.config },
@@ -3759,7 +4133,7 @@ async function generateCommand(options = {}) {
3759
4133
  logger.info("Deleting existing output directories...");
3760
4134
  const targetTools = config.defaultTargets;
3761
4135
  const deleteTasks = [];
3762
- const commandsDir = (0, import_node_path15.join)(config.aiRulesDir, "commands");
4136
+ const commandsDir = (0, import_node_path13.join)(config.aiRulesDir, "commands");
3763
4137
  const hasCommands = await fileExists(commandsDir);
3764
4138
  let hasCommandFiles = false;
3765
4139
  if (hasCommands) {
@@ -3774,12 +4148,12 @@ async function generateCommand(options = {}) {
3774
4148
  for (const tool of targetTools) {
3775
4149
  switch (tool) {
3776
4150
  case "augmentcode":
3777
- deleteTasks.push(removeDirectory((0, import_node_path15.join)(".augment", "rules")));
3778
- deleteTasks.push(removeDirectory((0, import_node_path15.join)(".augment", "ignore")));
4151
+ deleteTasks.push(removeDirectory((0, import_node_path13.join)(".augment", "rules")));
4152
+ deleteTasks.push(removeDirectory((0, import_node_path13.join)(".augment", "ignore")));
3779
4153
  break;
3780
4154
  case "augmentcode-legacy":
3781
4155
  deleteTasks.push(removeClaudeGeneratedFiles());
3782
- deleteTasks.push(removeDirectory((0, import_node_path15.join)(".augment", "ignore")));
4156
+ deleteTasks.push(removeDirectory((0, import_node_path13.join)(".augment", "ignore")));
3783
4157
  break;
3784
4158
  case "copilot":
3785
4159
  deleteTasks.push(removeDirectory(config.outputPaths.copilot));
@@ -3793,24 +4167,27 @@ async function generateCommand(options = {}) {
3793
4167
  case "claudecode":
3794
4168
  deleteTasks.push(removeClaudeGeneratedFiles());
3795
4169
  if (hasCommandFiles) {
3796
- deleteTasks.push(removeDirectory((0, import_node_path15.join)(".claude", "commands")));
4170
+ deleteTasks.push(removeDirectory((0, import_node_path13.join)(".claude", "commands")));
3797
4171
  }
3798
4172
  break;
3799
4173
  case "roo":
3800
4174
  deleteTasks.push(removeDirectory(config.outputPaths.roo));
3801
4175
  if (hasCommandFiles) {
3802
- deleteTasks.push(removeDirectory((0, import_node_path15.join)(".roo", "commands")));
4176
+ deleteTasks.push(removeDirectory((0, import_node_path13.join)(".roo", "commands")));
3803
4177
  }
3804
4178
  break;
3805
4179
  case "geminicli":
3806
4180
  deleteTasks.push(removeDirectory(config.outputPaths.geminicli));
3807
4181
  if (hasCommandFiles) {
3808
- deleteTasks.push(removeDirectory((0, import_node_path15.join)(".gemini", "commands")));
4182
+ deleteTasks.push(removeDirectory((0, import_node_path13.join)(".gemini", "commands")));
3809
4183
  }
3810
4184
  break;
3811
4185
  case "kiro":
3812
4186
  deleteTasks.push(removeDirectory(config.outputPaths.kiro));
3813
4187
  break;
4188
+ case "opencode":
4189
+ deleteTasks.push(removeDirectory(config.outputPaths.opencode));
4190
+ break;
3814
4191
  case "windsurf":
3815
4192
  deleteTasks.push(removeDirectory(config.outputPaths.windsurf));
3816
4193
  break;
@@ -3904,11 +4281,14 @@ Generating configurations for base directory: ${baseDir}`);
3904
4281
 
3905
4282
  // src/cli/commands/gitignore.ts
3906
4283
  var import_node_fs2 = require("fs");
3907
- var import_node_path16 = require("path");
4284
+ var import_node_path14 = require("path");
4285
+ init_logger();
3908
4286
  var gitignoreCommand = async () => {
3909
- const gitignorePath = (0, import_node_path16.join)(process.cwd(), ".gitignore");
4287
+ const gitignorePath = (0, import_node_path14.join)(process.cwd(), ".gitignore");
3910
4288
  const rulesFilesToIgnore = [
3911
4289
  "# Generated by rulesync - AI tool configuration files",
4290
+ "**/.amazonq/rules/",
4291
+ "**/.amazonq/mcp.json",
3912
4292
  "**/.github/copilot-instructions.md",
3913
4293
  "**/.github/instructions/",
3914
4294
  "**/.cursor/rules/",
@@ -3918,7 +4298,7 @@ var gitignoreCommand = async () => {
3918
4298
  "**/CLAUDE.md",
3919
4299
  "**/.claude/memories/",
3920
4300
  "**/.claude/commands/",
3921
- "**/codex.md",
4301
+ "**/AGENTS.md",
3922
4302
  "**/.codexignore",
3923
4303
  "**/.roo/rules/",
3924
4304
  "**/.rooignore",
@@ -3934,6 +4314,9 @@ var gitignoreCommand = async () => {
3934
4314
  "**/.augment-guidelines",
3935
4315
  "**/.junie/guidelines.md",
3936
4316
  "**/.noai",
4317
+ "**/.opencode/memories/",
4318
+ "**/.opencode/commands/",
4319
+ "**/opencode.json",
3937
4320
  "**/.mcp.json",
3938
4321
  "!.rulesync/.mcp.json",
3939
4322
  "**/.cursor/mcp.json",
@@ -3954,7 +4337,7 @@ var gitignoreCommand = async () => {
3954
4337
  }
3955
4338
  }
3956
4339
  if (linesToAdd.length === 0) {
3957
- console.log("\u2705 .gitignore is already up to date");
4340
+ logger.success(".gitignore is already up to date");
3958
4341
  return;
3959
4342
  }
3960
4343
  const newContent = gitignoreContent ? `${gitignoreContent.trimEnd()}
@@ -3963,174 +4346,21 @@ ${linesToAdd.join("\n")}
3963
4346
  ` : `${linesToAdd.join("\n")}
3964
4347
  `;
3965
4348
  (0, import_node_fs2.writeFileSync)(gitignorePath, newContent);
3966
- console.log(`\u2705 Added ${linesToAdd.length} rules to .gitignore:`);
4349
+ logger.success(`Added ${linesToAdd.length} rules to .gitignore:`);
3967
4350
  for (const line of linesToAdd) {
3968
4351
  if (!line.startsWith("#")) {
3969
- console.log(` ${line}`);
4352
+ logger.log(` ${line}`);
3970
4353
  }
3971
4354
  }
3972
4355
  };
3973
4356
 
3974
4357
  // src/core/importer.ts
3975
- var import_node_path23 = require("path");
3976
- var import_gray_matter7 = __toESM(require("gray-matter"), 1);
3977
-
3978
- // src/parsers/augmentcode.ts
3979
- var import_node_path17 = require("path");
3980
- var import_gray_matter3 = __toESM(require("gray-matter"), 1);
3981
-
3982
- // src/utils/parser-helpers.ts
3983
- function createParseResult() {
3984
- return { rules: [], errors: [] };
3985
- }
3986
- function addError(result, error) {
3987
- result.errors.push(error);
3988
- }
3989
- function addRule(result, rule) {
3990
- if (!result.rules) {
3991
- result.rules = [];
3992
- }
3993
- result.rules.push(rule);
3994
- }
3995
- function addRules(result, rules) {
3996
- if (!result.rules) {
3997
- result.rules = [];
3998
- }
3999
- result.rules.push(...rules);
4000
- }
4001
- async function safeReadFile(operation, errorContext) {
4002
- try {
4003
- const result = await operation();
4004
- return createSuccessResult(result);
4005
- } catch (error) {
4006
- return createErrorResult(error, errorContext);
4007
- }
4008
- }
4009
-
4010
- // src/parsers/augmentcode.ts
4011
- async function parseAugmentcodeConfiguration(baseDir = process.cwd()) {
4012
- return parseUnifiedAugmentcode(baseDir, {
4013
- rulesDir: ".augment/rules",
4014
- targetName: "augmentcode",
4015
- filenamePrefix: "augmentcode"
4016
- });
4017
- }
4018
- async function parseAugmentcodeLegacyConfiguration(baseDir = process.cwd()) {
4019
- return parseUnifiedAugmentcode(baseDir, {
4020
- legacyFilePath: ".augment-guidelines",
4021
- targetName: "augmentcode-legacy",
4022
- filenamePrefix: "augmentcode-legacy"
4023
- });
4024
- }
4025
- async function parseUnifiedAugmentcode(baseDir, config) {
4026
- const result = createParseResult();
4027
- if (config.rulesDir) {
4028
- const rulesDir = (0, import_node_path17.join)(baseDir, config.rulesDir);
4029
- if (await fileExists(rulesDir)) {
4030
- const rulesResult = await parseAugmentRules(rulesDir, config);
4031
- addRules(result, rulesResult.rules);
4032
- result.errors.push(...rulesResult.errors);
4033
- } else {
4034
- addError(
4035
- result,
4036
- `No AugmentCode configuration found. Expected ${config.rulesDir} directory.`
4037
- );
4038
- }
4039
- }
4040
- if (config.legacyFilePath) {
4041
- const legacyPath = (0, import_node_path17.join)(baseDir, config.legacyFilePath);
4042
- if (await fileExists(legacyPath)) {
4043
- const legacyResult = await parseAugmentGuidelines(legacyPath, config);
4044
- if (legacyResult.rule) {
4045
- addRule(result, legacyResult.rule);
4046
- }
4047
- result.errors.push(...legacyResult.errors);
4048
- } else {
4049
- addError(
4050
- result,
4051
- `No AugmentCode legacy configuration found. Expected ${config.legacyFilePath} file.`
4052
- );
4053
- }
4054
- }
4055
- return { rules: result.rules || [], errors: result.errors };
4056
- }
4057
- async function parseAugmentRules(rulesDir, config) {
4058
- const rules = [];
4059
- const errors = [];
4060
- try {
4061
- const { readdir: readdir2 } = await import("fs/promises");
4062
- const files = await readdir2(rulesDir);
4063
- for (const file of files) {
4064
- if (file.endsWith(".md") || file.endsWith(".mdc")) {
4065
- const filePath = (0, import_node_path17.join)(rulesDir, file);
4066
- try {
4067
- const rawContent = await readFileContent(filePath);
4068
- const parsed = (0, import_gray_matter3.default)(rawContent);
4069
- const frontmatterData = parsed.data;
4070
- const ruleType = frontmatterData.type || "manual";
4071
- const description = frontmatterData.description || "";
4072
- const tags = Array.isArray(frontmatterData.tags) ? frontmatterData.tags : void 0;
4073
- const isRoot = ruleType === "always";
4074
- const filename = (0, import_node_path17.basename)(file, file.endsWith(".mdc") ? ".mdc" : ".md");
4075
- const frontmatter = {
4076
- root: isRoot,
4077
- targets: [config.targetName],
4078
- description,
4079
- globs: ["**/*"],
4080
- // AugmentCode doesn't use specific globs in the same way
4081
- ...tags && { tags }
4082
- };
4083
- rules.push({
4084
- frontmatter,
4085
- content: parsed.content.trim(),
4086
- filename: `${config.filenamePrefix}-${ruleType}-${filename}`,
4087
- filepath: filePath
4088
- });
4089
- } catch (error) {
4090
- const errorMessage = error instanceof Error ? error.message : String(error);
4091
- errors.push(`Failed to parse ${filePath}: ${errorMessage}`);
4092
- }
4093
- }
4094
- }
4095
- } catch (error) {
4096
- const errorMessage = error instanceof Error ? error.message : String(error);
4097
- errors.push(`Failed to read ${config.rulesDir || rulesDir} directory: ${errorMessage}`);
4098
- }
4099
- return { rules, errors };
4100
- }
4101
- async function parseAugmentGuidelines(guidelinesPath, config) {
4102
- const parseResult = await safeReadFile(
4103
- async () => {
4104
- const content = await readFileContent(guidelinesPath);
4105
- if (content.trim()) {
4106
- const frontmatter = {
4107
- root: true,
4108
- // Legacy guidelines become root rules
4109
- targets: [config.targetName],
4110
- description: "Legacy AugmentCode guidelines",
4111
- globs: ["**/*"]
4112
- };
4113
- return {
4114
- frontmatter,
4115
- content: content.trim(),
4116
- filename: `${config.filenamePrefix}-guidelines`,
4117
- filepath: guidelinesPath
4118
- };
4119
- }
4120
- return null;
4121
- },
4122
- `Failed to parse ${config.legacyFilePath || guidelinesPath}`
4123
- );
4124
- if (parseResult.success) {
4125
- return { rule: parseResult.result || null, errors: [] };
4126
- } else {
4127
- return { rule: null, errors: [parseResult.error || "Unknown error"] };
4128
- }
4129
- }
4358
+ var import_node_path21 = require("path");
4359
+ var import_gray_matter2 = __toESM(require("gray-matter"), 1);
4130
4360
 
4131
4361
  // src/parsers/shared-helpers.ts
4132
- var import_node_path18 = require("path");
4133
- var import_gray_matter4 = __toESM(require("gray-matter"), 1);
4362
+ var import_node_path15 = require("path");
4363
+ init_file();
4134
4364
  async function parseConfigurationFiles(baseDir = process.cwd(), config) {
4135
4365
  const errors = [];
4136
4366
  const rules = [];
@@ -4143,16 +4373,18 @@ async function parseConfigurationFiles(baseDir = process.cwd(), config) {
4143
4373
  let content;
4144
4374
  let frontmatter;
4145
4375
  if (mainFile.useFrontmatter) {
4146
- const parsed = (0, import_gray_matter4.default)(rawContent);
4147
- content = parsed.content.trim();
4148
- const parsedFrontmatter = parsed.data;
4376
+ const parsed = parseFrontmatter(rawContent);
4377
+ content = parsed.content;
4149
4378
  frontmatter = {
4150
4379
  root: mainFile.isRoot ?? false,
4151
4380
  targets: [config.tool],
4152
- description: parsedFrontmatter.description || mainFile.description,
4153
- globs: Array.isArray(parsedFrontmatter.globs) ? parsedFrontmatter.globs : ["**/*"],
4154
- ...parsedFrontmatter.tags && { tags: parsedFrontmatter.tags }
4381
+ description: extractStringField(parsed.data, "description", mainFile.description),
4382
+ globs: extractArrayField(parsed.data, "globs", ["**/*"])
4155
4383
  };
4384
+ const tags = extractArrayField(parsed.data, "tags");
4385
+ if (tags.length > 0) {
4386
+ frontmatter.tags = tags;
4387
+ }
4156
4388
  } else {
4157
4389
  content = rawContent.trim();
4158
4390
  frontmatter = {
@@ -4185,23 +4417,29 @@ async function parseConfigurationFiles(baseDir = process.cwd(), config) {
4185
4417
  const files = await readdir2(dirPath);
4186
4418
  for (const file of files) {
4187
4419
  if (file.endsWith(dirConfig.filePattern)) {
4188
- const filePath = (0, import_node_path18.join)(dirPath, file);
4420
+ const filePath = (0, import_node_path15.join)(dirPath, file);
4189
4421
  const fileResult = await safeAsyncOperation(async () => {
4190
4422
  const rawContent = await readFileContent(filePath);
4191
4423
  let content;
4192
4424
  let frontmatter;
4193
4425
  const filename = file.replace(new RegExp(`\\${dirConfig.filePattern}$`), "");
4194
4426
  if (dirConfig.filePattern === ".instructions.md") {
4195
- const parsed = (0, import_gray_matter4.default)(rawContent);
4196
- content = parsed.content.trim();
4197
- const parsedFrontmatter = parsed.data;
4427
+ const parsed = parseFrontmatter(rawContent);
4428
+ content = parsed.content;
4198
4429
  frontmatter = {
4199
4430
  root: false,
4200
4431
  targets: [config.tool],
4201
- description: parsedFrontmatter.description || `${dirConfig.description}: ${filename}`,
4202
- globs: Array.isArray(parsedFrontmatter.globs) ? parsedFrontmatter.globs : ["**/*"],
4203
- ...parsedFrontmatter.tags && { tags: parsedFrontmatter.tags }
4432
+ description: extractStringField(
4433
+ parsed.data,
4434
+ "description",
4435
+ `${dirConfig.description}: ${filename}`
4436
+ ),
4437
+ globs: extractArrayField(parsed.data, "globs", ["**/*"])
4204
4438
  };
4439
+ const tags = extractArrayField(parsed.data, "tags");
4440
+ if (tags.length > 0) {
4441
+ frontmatter.tags = tags;
4442
+ }
4205
4443
  } else {
4206
4444
  content = rawContent.trim();
4207
4445
  frontmatter = {
@@ -4330,10 +4568,10 @@ async function parseMemoryFiles(memoryDir, config) {
4330
4568
  const files = await readdir2(memoryDir);
4331
4569
  for (const file of files) {
4332
4570
  if (file.endsWith(".md")) {
4333
- const filePath = (0, import_node_path18.join)(memoryDir, file);
4571
+ const filePath = (0, import_node_path15.join)(memoryDir, file);
4334
4572
  const content = await readFileContent(filePath);
4335
4573
  if (content.trim()) {
4336
- const filename = (0, import_node_path18.basename)(file, ".md");
4574
+ const filename = (0, import_node_path15.basename)(file, ".md");
4337
4575
  const frontmatter = {
4338
4576
  root: false,
4339
4577
  targets: [config.tool],
@@ -4360,20 +4598,19 @@ async function parseCommandsFiles(commandsDir, config) {
4360
4598
  const files = await readdir2(commandsDir);
4361
4599
  for (const file of files) {
4362
4600
  if (file.endsWith(".md")) {
4363
- const filePath = (0, import_node_path18.join)(commandsDir, file);
4601
+ const filePath = (0, import_node_path15.join)(commandsDir, file);
4364
4602
  const content = await readFileContent(filePath);
4365
4603
  if (content.trim()) {
4366
- const filename = (0, import_node_path18.basename)(file, ".md");
4604
+ const filename = (0, import_node_path15.basename)(file, ".md");
4367
4605
  let frontmatter;
4368
4606
  let ruleContent;
4369
4607
  try {
4370
- const parsed = (0, import_gray_matter4.default)(content);
4371
- ruleContent = parsed.content.trim();
4372
- const parsedFrontmatter = parsed.data;
4608
+ const parsed = parseFrontmatter(content);
4609
+ ruleContent = parsed.content;
4373
4610
  frontmatter = {
4374
4611
  root: false,
4375
4612
  targets: [config.tool],
4376
- description: parsedFrontmatter.description || `Command: ${filename}`,
4613
+ description: extractStringField(parsed.data, "description", `Command: ${filename}`),
4377
4614
  globs: ["**/*"]
4378
4615
  };
4379
4616
  } catch {
@@ -4439,6 +4676,170 @@ async function parseSettingsFile(settingsPath, tool) {
4439
4676
  };
4440
4677
  }
4441
4678
 
4679
+ // src/parsers/amazonqcli.ts
4680
+ async function parseAmazonqcliConfiguration(baseDir = process.cwd()) {
4681
+ return parseMemoryBasedConfiguration(baseDir, {
4682
+ tool: "amazonqcli",
4683
+ mainFileName: ".amazonq/rules/main.md",
4684
+ memoryDirPath: ".amazonq/rules",
4685
+ settingsPath: ".amazonq/mcp.json",
4686
+ mainDescription: "Main Amazon Q Developer CLI configuration",
4687
+ memoryDescription: "Amazon Q rule",
4688
+ filenamePrefix: "amazonq"
4689
+ });
4690
+ }
4691
+
4692
+ // src/parsers/augmentcode.ts
4693
+ var import_node_path16 = require("path");
4694
+
4695
+ // src/utils/parser-helpers.ts
4696
+ function createParseResult() {
4697
+ return { rules: [], errors: [] };
4698
+ }
4699
+ function addError(result, error) {
4700
+ result.errors.push(error);
4701
+ }
4702
+ function addRule(result, rule) {
4703
+ if (!result.rules) {
4704
+ result.rules = [];
4705
+ }
4706
+ result.rules.push(rule);
4707
+ }
4708
+ function addRules(result, rules) {
4709
+ if (!result.rules) {
4710
+ result.rules = [];
4711
+ }
4712
+ result.rules.push(...rules);
4713
+ }
4714
+ async function safeReadFile(operation, errorContext) {
4715
+ try {
4716
+ const result = await operation();
4717
+ return createSuccessResult(result);
4718
+ } catch (error) {
4719
+ return createErrorResult(error, errorContext);
4720
+ }
4721
+ }
4722
+
4723
+ // src/parsers/augmentcode.ts
4724
+ async function parseAugmentcodeConfiguration(baseDir = process.cwd()) {
4725
+ return parseUnifiedAugmentcode(baseDir, {
4726
+ rulesDir: ".augment/rules",
4727
+ targetName: "augmentcode",
4728
+ filenamePrefix: "augmentcode"
4729
+ });
4730
+ }
4731
+ async function parseAugmentcodeLegacyConfiguration(baseDir = process.cwd()) {
4732
+ return parseUnifiedAugmentcode(baseDir, {
4733
+ legacyFilePath: ".augment-guidelines",
4734
+ targetName: "augmentcode-legacy",
4735
+ filenamePrefix: "augmentcode-legacy"
4736
+ });
4737
+ }
4738
+ async function parseUnifiedAugmentcode(baseDir, config) {
4739
+ const result = createParseResult();
4740
+ if (config.rulesDir) {
4741
+ const rulesDir = (0, import_node_path16.join)(baseDir, config.rulesDir);
4742
+ if (await fileExists(rulesDir)) {
4743
+ const rulesResult = await parseAugmentRules(rulesDir, config);
4744
+ addRules(result, rulesResult.rules);
4745
+ result.errors.push(...rulesResult.errors);
4746
+ } else {
4747
+ addError(
4748
+ result,
4749
+ `No AugmentCode configuration found. Expected ${config.rulesDir} directory.`
4750
+ );
4751
+ }
4752
+ }
4753
+ if (config.legacyFilePath) {
4754
+ const legacyPath = (0, import_node_path16.join)(baseDir, config.legacyFilePath);
4755
+ if (await fileExists(legacyPath)) {
4756
+ const legacyResult = await parseAugmentGuidelines(legacyPath, config);
4757
+ if (legacyResult.rule) {
4758
+ addRule(result, legacyResult.rule);
4759
+ }
4760
+ result.errors.push(...legacyResult.errors);
4761
+ } else {
4762
+ addError(
4763
+ result,
4764
+ `No AugmentCode legacy configuration found. Expected ${config.legacyFilePath} file.`
4765
+ );
4766
+ }
4767
+ }
4768
+ return { rules: result.rules || [], errors: result.errors };
4769
+ }
4770
+ async function parseAugmentRules(rulesDir, config) {
4771
+ const rules = [];
4772
+ const errors = [];
4773
+ try {
4774
+ const { readdir: readdir2 } = await import("fs/promises");
4775
+ const files = await readdir2(rulesDir);
4776
+ for (const file of files) {
4777
+ if (file.endsWith(".md") || file.endsWith(".mdc")) {
4778
+ const filePath = (0, import_node_path16.join)(rulesDir, file);
4779
+ try {
4780
+ const rawContent = await readFileContent(filePath);
4781
+ const parsed = parseFrontmatter(rawContent);
4782
+ const ruleType = extractStringField(parsed.data, "type", "manual");
4783
+ const description = extractStringField(parsed.data, "description", "");
4784
+ const tags = extractArrayField(parsed.data, "tags");
4785
+ const isRoot = ruleType === "always";
4786
+ const filename = (0, import_node_path16.basename)(file, file.endsWith(".mdc") ? ".mdc" : ".md");
4787
+ const frontmatter = {
4788
+ root: isRoot,
4789
+ targets: [config.targetName],
4790
+ description,
4791
+ globs: ["**/*"],
4792
+ // AugmentCode doesn't use specific globs in the same way
4793
+ ...tags.length > 0 && { tags }
4794
+ };
4795
+ rules.push({
4796
+ frontmatter,
4797
+ content: parsed.content.trim(),
4798
+ filename: `${config.filenamePrefix}-${ruleType}-${filename}`,
4799
+ filepath: filePath
4800
+ });
4801
+ } catch (error) {
4802
+ const errorMessage = error instanceof Error ? error.message : String(error);
4803
+ errors.push(`Failed to parse ${filePath}: ${errorMessage}`);
4804
+ }
4805
+ }
4806
+ }
4807
+ } catch (error) {
4808
+ const errorMessage = error instanceof Error ? error.message : String(error);
4809
+ errors.push(`Failed to read ${config.rulesDir || rulesDir} directory: ${errorMessage}`);
4810
+ }
4811
+ return { rules, errors };
4812
+ }
4813
+ async function parseAugmentGuidelines(guidelinesPath, config) {
4814
+ const parseResult = await safeReadFile(
4815
+ async () => {
4816
+ const content = await readFileContent(guidelinesPath);
4817
+ if (content.trim()) {
4818
+ const frontmatter = {
4819
+ root: true,
4820
+ // Legacy guidelines become root rules
4821
+ targets: [config.targetName],
4822
+ description: "Legacy AugmentCode guidelines",
4823
+ globs: ["**/*"]
4824
+ };
4825
+ return {
4826
+ frontmatter,
4827
+ content: content.trim(),
4828
+ filename: `${config.filenamePrefix}-guidelines`,
4829
+ filepath: guidelinesPath
4830
+ };
4831
+ }
4832
+ return null;
4833
+ },
4834
+ `Failed to parse ${config.legacyFilePath || guidelinesPath}`
4835
+ );
4836
+ if (parseResult.success) {
4837
+ return { rule: parseResult.result || null, errors: [] };
4838
+ } else {
4839
+ return { rule: null, errors: [parseResult.error || "Unknown error"] };
4840
+ }
4841
+ }
4842
+
4442
4843
  // src/parsers/claudecode.ts
4443
4844
  async function parseClaudeConfiguration(baseDir = process.cwd()) {
4444
4845
  return parseMemoryBasedConfiguration(baseDir, {
@@ -4474,7 +4875,7 @@ async function parseClineConfiguration(baseDir = process.cwd()) {
4474
4875
  }
4475
4876
 
4476
4877
  // src/parsers/codexcli.ts
4477
- var import_node_path19 = require("path");
4878
+ var import_node_path17 = require("path");
4478
4879
 
4479
4880
  // src/parsers/copilot.ts
4480
4881
  async function parseCopilotConfiguration(baseDir = process.cwd()) {
@@ -4497,8 +4898,7 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
4497
4898
  }
4498
4899
 
4499
4900
  // src/parsers/cursor.ts
4500
- var import_node_path20 = require("path");
4501
- var import_gray_matter5 = __toESM(require("gray-matter"), 1);
4901
+ var import_node_path18 = require("path");
4502
4902
  var import_js_yaml = require("js-yaml");
4503
4903
  var import_mini8 = require("zod/mini");
4504
4904
  var customMatterOptions = {
@@ -4622,12 +5022,12 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
4622
5022
  const rules = [];
4623
5023
  let ignorePatterns;
4624
5024
  let mcpServers;
4625
- const cursorFilePath = (0, import_node_path20.join)(baseDir, ".cursorrules");
5025
+ const cursorFilePath = (0, import_node_path18.join)(baseDir, ".cursorrules");
4626
5026
  if (await fileExists(cursorFilePath)) {
4627
5027
  try {
4628
5028
  const rawContent = await readFileContent(cursorFilePath);
4629
- const parsed = (0, import_gray_matter5.default)(rawContent, customMatterOptions);
4630
- const content = parsed.content.trim();
5029
+ const parsed = parseFrontmatter(rawContent, { matterOptions: customMatterOptions });
5030
+ const content = parsed.content;
4631
5031
  if (content) {
4632
5032
  const frontmatter = convertCursorMdcFrontmatter(parsed.data, "cursorrules");
4633
5033
  frontmatter.targets = ["cursor"];
@@ -4643,20 +5043,20 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
4643
5043
  errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
4644
5044
  }
4645
5045
  }
4646
- const cursorRulesDir = (0, import_node_path20.join)(baseDir, ".cursor", "rules");
5046
+ const cursorRulesDir = (0, import_node_path18.join)(baseDir, ".cursor", "rules");
4647
5047
  if (await fileExists(cursorRulesDir)) {
4648
5048
  try {
4649
5049
  const { readdir: readdir2 } = await import("fs/promises");
4650
5050
  const files = await readdir2(cursorRulesDir);
4651
5051
  for (const file of files) {
4652
5052
  if (file.endsWith(".mdc")) {
4653
- const filePath = (0, import_node_path20.join)(cursorRulesDir, file);
5053
+ const filePath = (0, import_node_path18.join)(cursorRulesDir, file);
4654
5054
  try {
4655
5055
  const rawContent = await readFileContent(filePath);
4656
- const parsed = (0, import_gray_matter5.default)(rawContent, customMatterOptions);
4657
- const content = parsed.content.trim();
5056
+ const parsed = parseFrontmatter(rawContent, { matterOptions: customMatterOptions });
5057
+ const content = parsed.content;
4658
5058
  if (content) {
4659
- const filename = (0, import_node_path20.basename)(file, ".mdc");
5059
+ const filename = (0, import_node_path18.basename)(file, ".mdc");
4660
5060
  const frontmatter = convertCursorMdcFrontmatter(parsed.data, filename);
4661
5061
  rules.push({
4662
5062
  frontmatter,
@@ -4679,7 +5079,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
4679
5079
  if (rules.length === 0) {
4680
5080
  errors.push("No Cursor configuration files found (.cursorrules or .cursor/rules/*.mdc)");
4681
5081
  }
4682
- const cursorIgnorePath = (0, import_node_path20.join)(baseDir, ".cursorignore");
5082
+ const cursorIgnorePath = (0, import_node_path18.join)(baseDir, ".cursorignore");
4683
5083
  if (await fileExists(cursorIgnorePath)) {
4684
5084
  try {
4685
5085
  const content = await readFileContent(cursorIgnorePath);
@@ -4692,7 +5092,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
4692
5092
  errors.push(`Failed to parse .cursorignore: ${errorMessage}`);
4693
5093
  }
4694
5094
  }
4695
- const cursorMcpPath = (0, import_node_path20.join)(baseDir, ".cursor", "mcp.json");
5095
+ const cursorMcpPath = (0, import_node_path18.join)(baseDir, ".cursor", "mcp.json");
4696
5096
  if (await fileExists(cursorMcpPath)) {
4697
5097
  try {
4698
5098
  const content = await readFileContent(cursorMcpPath);
@@ -4742,11 +5142,11 @@ async function parseGeminiConfiguration(baseDir = process.cwd()) {
4742
5142
  }
4743
5143
 
4744
5144
  // src/parsers/junie.ts
4745
- var import_node_path21 = require("path");
5145
+ var import_node_path19 = require("path");
4746
5146
  async function parseJunieConfiguration(baseDir = process.cwd()) {
4747
5147
  const errors = [];
4748
5148
  const rules = [];
4749
- const guidelinesPath = (0, import_node_path21.join)(baseDir, ".junie", "guidelines.md");
5149
+ const guidelinesPath = (0, import_node_path19.join)(baseDir, ".junie", "guidelines.md");
4750
5150
  if (!await fileExists(guidelinesPath)) {
4751
5151
  errors.push(".junie/guidelines.md file not found");
4752
5152
  return { rules, errors };
@@ -4777,6 +5177,32 @@ async function parseJunieConfiguration(baseDir = process.cwd()) {
4777
5177
  return { rules, errors };
4778
5178
  }
4779
5179
 
5180
+ // src/parsers/opencode.ts
5181
+ async function parseOpCodeIgnore(opcodeignorePath) {
5182
+ try {
5183
+ const content = await readFileContent(opcodeignorePath);
5184
+ const patterns = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
5185
+ return patterns;
5186
+ } catch {
5187
+ return [];
5188
+ }
5189
+ }
5190
+ async function parseOpenCodeConfiguration(baseDir = process.cwd()) {
5191
+ return parseMemoryBasedConfiguration(baseDir, {
5192
+ tool: "opencode",
5193
+ mainFileName: "AGENTS.md",
5194
+ memoryDirPath: ".opencode/memories",
5195
+ settingsPath: "opencode.json",
5196
+ mainDescription: "Main OpenCode configuration",
5197
+ memoryDescription: "Memory file",
5198
+ filenamePrefix: "opencode",
5199
+ additionalIgnoreFile: {
5200
+ path: ".opcodeignore",
5201
+ parser: parseOpCodeIgnore
5202
+ }
5203
+ });
5204
+ }
5205
+
4780
5206
  // src/parsers/roo.ts
4781
5207
  async function parseRooConfiguration(baseDir = process.cwd()) {
4782
5208
  return parseConfigurationFiles(baseDir, {
@@ -4799,10 +5225,11 @@ async function parseRooConfiguration(baseDir = process.cwd()) {
4799
5225
 
4800
5226
  // src/parsers/windsurf.ts
4801
5227
  var import_promises3 = require("fs/promises");
4802
- var import_node_path22 = require("path");
4803
- var import_gray_matter6 = __toESM(require("gray-matter"), 1);
5228
+ var import_node_path20 = require("path");
5229
+ init_logger();
4804
5230
 
4805
5231
  // src/core/importer.ts
5232
+ init_logger();
4806
5233
  async function importConfiguration(options) {
4807
5234
  const {
4808
5235
  tool,
@@ -4816,10 +5243,17 @@ async function importConfiguration(options) {
4816
5243
  let ignorePatterns;
4817
5244
  let mcpServers;
4818
5245
  if (verbose) {
4819
- console.log(`Importing ${tool} configuration from ${baseDir}...`);
5246
+ logger.log(`Importing ${tool} configuration from ${baseDir}...`);
4820
5247
  }
4821
5248
  try {
4822
5249
  switch (tool) {
5250
+ case "amazonqcli": {
5251
+ const amazonqResult = await parseAmazonqcliConfiguration(baseDir);
5252
+ rules = amazonqResult.rules;
5253
+ errors.push(...amazonqResult.errors);
5254
+ mcpServers = amazonqResult.mcpServers;
5255
+ break;
5256
+ }
4823
5257
  case "augmentcode": {
4824
5258
  const augmentResult = await parseAugmentcodeConfiguration(baseDir);
4825
5259
  rules = augmentResult.rules;
@@ -4880,6 +5314,14 @@ async function importConfiguration(options) {
4880
5314
  errors.push(...junieResult.errors);
4881
5315
  break;
4882
5316
  }
5317
+ case "opencode": {
5318
+ const opencodeResult = await parseOpenCodeConfiguration(baseDir);
5319
+ rules = opencodeResult.rules;
5320
+ errors.push(...opencodeResult.errors);
5321
+ ignorePatterns = opencodeResult.ignorePatterns;
5322
+ mcpServers = opencodeResult.mcpServers;
5323
+ break;
5324
+ }
4883
5325
  default:
4884
5326
  errors.push(`Unsupported tool: ${tool}`);
4885
5327
  return { success: false, rulesCreated: 0, errors };
@@ -4892,7 +5334,7 @@ async function importConfiguration(options) {
4892
5334
  if (rules.length === 0 && !ignorePatterns && !mcpServers) {
4893
5335
  return { success: false, rulesCreated: 0, errors };
4894
5336
  }
4895
- const rulesDirPath = (0, import_node_path23.join)(baseDir, rulesDir);
5337
+ const rulesDirPath = (0, import_node_path21.join)(baseDir, rulesDir);
4896
5338
  try {
4897
5339
  const { mkdir: mkdir3 } = await import("fs/promises");
4898
5340
  await mkdir3(rulesDirPath, { recursive: true });
@@ -4907,22 +5349,22 @@ async function importConfiguration(options) {
4907
5349
  const baseFilename = rule.filename;
4908
5350
  let targetDir = rulesDirPath;
4909
5351
  if (rule.type === "command") {
4910
- targetDir = (0, import_node_path23.join)(rulesDirPath, "commands");
5352
+ targetDir = (0, import_node_path21.join)(rulesDirPath, "commands");
4911
5353
  const { mkdir: mkdir3 } = await import("fs/promises");
4912
5354
  await mkdir3(targetDir, { recursive: true });
4913
5355
  } else {
4914
5356
  if (!useLegacyLocation) {
4915
- targetDir = (0, import_node_path23.join)(rulesDirPath, "rules");
5357
+ targetDir = (0, import_node_path21.join)(rulesDirPath, "rules");
4916
5358
  const { mkdir: mkdir3 } = await import("fs/promises");
4917
5359
  await mkdir3(targetDir, { recursive: true });
4918
5360
  }
4919
5361
  }
4920
- const filePath = (0, import_node_path23.join)(targetDir, `${baseFilename}.md`);
5362
+ const filePath = (0, import_node_path21.join)(targetDir, `${baseFilename}.md`);
4921
5363
  const content = generateRuleFileContent(rule);
4922
5364
  await writeFileContent(filePath, content);
4923
5365
  rulesCreated++;
4924
5366
  if (verbose) {
4925
- console.log(`\u2705 Created rule file: ${filePath}`);
5367
+ logger.success(`Created rule file: ${filePath}`);
4926
5368
  }
4927
5369
  } catch (error) {
4928
5370
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -4932,13 +5374,13 @@ async function importConfiguration(options) {
4932
5374
  let ignoreFileCreated = false;
4933
5375
  if (ignorePatterns && ignorePatterns.length > 0) {
4934
5376
  try {
4935
- const rulesyncignorePath = (0, import_node_path23.join)(baseDir, ".rulesyncignore");
5377
+ const rulesyncignorePath = (0, import_node_path21.join)(baseDir, ".rulesyncignore");
4936
5378
  const ignoreContent = `${ignorePatterns.join("\n")}
4937
5379
  `;
4938
5380
  await writeFileContent(rulesyncignorePath, ignoreContent);
4939
5381
  ignoreFileCreated = true;
4940
5382
  if (verbose) {
4941
- console.log(`\u2705 Created .rulesyncignore with ${ignorePatterns.length} patterns`);
5383
+ logger.success(`Created .rulesyncignore with ${ignorePatterns.length} patterns`);
4942
5384
  }
4943
5385
  } catch (error) {
4944
5386
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -4948,13 +5390,13 @@ async function importConfiguration(options) {
4948
5390
  let mcpFileCreated = false;
4949
5391
  if (mcpServers && Object.keys(mcpServers).length > 0) {
4950
5392
  try {
4951
- const mcpPath = (0, import_node_path23.join)(baseDir, rulesDir, ".mcp.json");
5393
+ const mcpPath = (0, import_node_path21.join)(baseDir, rulesDir, ".mcp.json");
4952
5394
  const mcpContent = `${JSON.stringify({ mcpServers }, null, 2)}
4953
5395
  `;
4954
5396
  await writeFileContent(mcpPath, mcpContent);
4955
5397
  mcpFileCreated = true;
4956
5398
  if (verbose) {
4957
- console.log(`\u2705 Created .mcp.json with ${Object.keys(mcpServers).length} servers`);
5399
+ logger.success(`Created .mcp.json with ${Object.keys(mcpServers).length} servers`);
4958
5400
  }
4959
5401
  } catch (error) {
4960
5402
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -4975,17 +5417,19 @@ function generateRuleFileContent(rule) {
4975
5417
  description: rule.frontmatter.description,
4976
5418
  targets: rule.frontmatter.targets
4977
5419
  };
4978
- const frontmatter2 = import_gray_matter7.default.stringify("", simplifiedFrontmatter);
5420
+ const frontmatter2 = import_gray_matter2.default.stringify("", simplifiedFrontmatter);
4979
5421
  return frontmatter2 + rule.content;
4980
5422
  }
4981
- const frontmatter = import_gray_matter7.default.stringify("", rule.frontmatter);
5423
+ const frontmatter = import_gray_matter2.default.stringify("", rule.frontmatter);
4982
5424
  return frontmatter + rule.content;
4983
5425
  }
4984
5426
 
4985
5427
  // src/cli/commands/import.ts
5428
+ init_logger();
4986
5429
  async function importCommand(options = {}) {
4987
5430
  logger.setVerbose(options.verbose || false);
4988
5431
  const tools = [];
5432
+ if (options.amazonqcli) tools.push("amazonqcli");
4989
5433
  if (options.augmentcode) tools.push("augmentcode");
4990
5434
  if (options["augmentcode-legacy"]) tools.push("augmentcode-legacy");
4991
5435
  if (options.claudecode) tools.push("claudecode");
@@ -4994,9 +5438,10 @@ async function importCommand(options = {}) {
4994
5438
  if (options.cline) tools.push("cline");
4995
5439
  if (options.roo) tools.push("roo");
4996
5440
  if (options.geminicli) tools.push("geminicli");
5441
+ if (options.opencode) tools.push("opencode");
4997
5442
  if (tools.length === 0) {
4998
5443
  logger.error(
4999
- "\u274C Please specify one tool to import from (--augmentcode, --augmentcode-legacy, --claudecode, --cursor, --copilot, --cline, --roo, --geminicli)"
5444
+ "\u274C Please specify one tool to import from (--amazonqcli, --augmentcode, --augmentcode-legacy, --claudecode, --cursor, --copilot, --cline, --roo, --geminicli, --opencode)"
5000
5445
  );
5001
5446
  process.exit(1);
5002
5447
  }
@@ -5044,23 +5489,24 @@ async function importCommand(options = {}) {
5044
5489
  }
5045
5490
 
5046
5491
  // src/cli/commands/init.ts
5047
- var import_node_path24 = require("path");
5492
+ var import_node_path22 = require("path");
5493
+ init_logger();
5048
5494
  async function initCommand(options = {}) {
5049
5495
  const configResult = await loadConfig();
5050
5496
  const config = configResult.config;
5051
5497
  const aiRulesDir = config.aiRulesDir;
5052
- console.log("Initializing rulesync...");
5498
+ logger.log("Initializing rulesync...");
5053
5499
  await ensureDir(aiRulesDir);
5054
5500
  const useLegacy = options.legacy ?? config.legacy ?? false;
5055
- const rulesDir = useLegacy ? aiRulesDir : (0, import_node_path24.join)(aiRulesDir, "rules");
5501
+ const rulesDir = useLegacy ? aiRulesDir : (0, import_node_path22.join)(aiRulesDir, "rules");
5056
5502
  if (!useLegacy) {
5057
5503
  await ensureDir(rulesDir);
5058
5504
  }
5059
5505
  await createSampleFiles(rulesDir);
5060
- console.log("\u2705 rulesync initialized successfully!");
5061
- console.log("\nNext steps:");
5062
- console.log(`1. Edit rule files in ${rulesDir}/`);
5063
- console.log("2. Run 'rulesync generate' to create configuration files");
5506
+ logger.success("rulesync initialized successfully!");
5507
+ logger.log("\nNext steps:");
5508
+ logger.log(`1. Edit rule files in ${rulesDir}/`);
5509
+ logger.log("2. Run 'rulesync generate' to create configuration files");
5064
5510
  }
5065
5511
  async function createSampleFiles(rulesDir) {
5066
5512
  const sampleFile = {
@@ -5098,36 +5544,37 @@ globs: ["**/*"]
5098
5544
  - Follow single responsibility principle
5099
5545
  `
5100
5546
  };
5101
- const filepath = (0, import_node_path24.join)(rulesDir, sampleFile.filename);
5547
+ const filepath = (0, import_node_path22.join)(rulesDir, sampleFile.filename);
5102
5548
  if (!await fileExists(filepath)) {
5103
5549
  await writeFileContent(filepath, sampleFile.content);
5104
- console.log(`Created ${filepath}`);
5550
+ logger.success(`Created ${filepath}`);
5105
5551
  } else {
5106
- console.log(`Skipped ${filepath} (already exists)`);
5552
+ logger.log(`Skipped ${filepath} (already exists)`);
5107
5553
  }
5108
5554
  }
5109
5555
 
5110
5556
  // src/cli/commands/status.ts
5557
+ init_logger();
5111
5558
  async function statusCommand() {
5112
5559
  const config = getDefaultConfig();
5113
- console.log("rulesync Status");
5114
- console.log("===============");
5560
+ logger.log("rulesync Status");
5561
+ logger.log("===============");
5115
5562
  const rulesyncExists = await fileExists(config.aiRulesDir);
5116
- console.log(`
5563
+ logger.log(`
5117
5564
  \u{1F4C1} .rulesync directory: ${rulesyncExists ? "\u2705 Found" : "\u274C Not found"}`);
5118
5565
  if (!rulesyncExists) {
5119
- console.log("\n\u{1F4A1} Run 'rulesync init' to get started");
5566
+ logger.log("\n\u{1F4A1} Run 'rulesync init' to get started");
5120
5567
  return;
5121
5568
  }
5122
5569
  try {
5123
5570
  const rules = await parseRulesFromDirectory(config.aiRulesDir);
5124
- console.log(`
5571
+ logger.log(`
5125
5572
  \u{1F4CB} Rules: ${rules.length} total`);
5126
5573
  if (rules.length > 0) {
5127
5574
  const rootRules = rules.filter((r) => r.frontmatter.root).length;
5128
5575
  const nonRootRules = rules.length - rootRules;
5129
- console.log(` - Root rules: ${rootRules}`);
5130
- console.log(` - Non-root rules: ${nonRootRules}`);
5576
+ logger.log(` - Root rules: ${rootRules}`);
5577
+ logger.log(` - Non-root rules: ${nonRootRules}`);
5131
5578
  const targetCounts = { copilot: 0, cursor: 0, cline: 0, claudecode: 0, roo: 0 };
5132
5579
  for (const rule of rules) {
5133
5580
  const targets = rule.frontmatter.targets[0] === "*" ? config.defaultTargets : rule.frontmatter.targets;
@@ -5139,73 +5586,75 @@ async function statusCommand() {
5139
5586
  else if (target === "roo") targetCounts.roo++;
5140
5587
  }
5141
5588
  }
5142
- console.log("\n\u{1F3AF} Target tool coverage:");
5143
- console.log(` - Copilot: ${targetCounts.copilot} rules`);
5144
- console.log(` - Cursor: ${targetCounts.cursor} rules`);
5145
- console.log(` - Cline: ${targetCounts.cline} rules`);
5146
- console.log(` - Claude Code: ${targetCounts.claudecode} rules`);
5147
- console.log(` - Roo: ${targetCounts.roo} rules`);
5148
- }
5149
- console.log("\n\u{1F4E4} Generated files:");
5589
+ logger.log("\n\u{1F3AF} Target tool coverage:");
5590
+ logger.log(` - Copilot: ${targetCounts.copilot} rules`);
5591
+ logger.log(` - Cursor: ${targetCounts.cursor} rules`);
5592
+ logger.log(` - Cline: ${targetCounts.cline} rules`);
5593
+ logger.log(` - Claude Code: ${targetCounts.claudecode} rules`);
5594
+ logger.log(` - Roo: ${targetCounts.roo} rules`);
5595
+ }
5596
+ logger.log("\n\u{1F4E4} Generated files:");
5150
5597
  for (const [tool, outputPath] of Object.entries(config.outputPaths)) {
5151
5598
  const outputExists = await fileExists(outputPath);
5152
- console.log(` - ${tool}: ${outputExists ? "\u2705 Generated" : "\u274C Not found"}`);
5599
+ logger.log(` - ${tool}: ${outputExists ? "\u2705 Generated" : "\u274C Not found"}`);
5153
5600
  }
5154
5601
  if (rules.length > 0) {
5155
- console.log("\n\u{1F4A1} Run 'rulesync generate' to update configuration files");
5602
+ logger.log("\n\u{1F4A1} Run 'rulesync generate' to update configuration files");
5156
5603
  }
5157
5604
  } catch (error) {
5158
- console.error("\n\u274C Failed to get status:", error);
5605
+ logger.error("\nFailed to get status:", error);
5159
5606
  }
5160
5607
  }
5161
5608
 
5162
5609
  // src/cli/commands/validate.ts
5610
+ init_logger();
5163
5611
  async function validateCommand() {
5164
5612
  const config = getDefaultConfig();
5165
- console.log("Validating rulesync configuration...");
5613
+ logger.log("Validating rulesync configuration...");
5166
5614
  if (!await fileExists(config.aiRulesDir)) {
5167
- console.error("\u274C .rulesync directory not found. Run 'rulesync init' first.");
5615
+ logger.error(".rulesync directory not found. Run 'rulesync init' first.");
5168
5616
  process.exit(1);
5169
5617
  }
5170
5618
  try {
5171
5619
  const rules = await parseRulesFromDirectory(config.aiRulesDir);
5172
5620
  if (rules.length === 0) {
5173
- console.warn("\u26A0\uFE0F No rules found in .rulesync directory");
5621
+ logger.warn("No rules found in .rulesync directory");
5174
5622
  return;
5175
5623
  }
5176
- console.log(`Found ${rules.length} rule(s), validating...`);
5624
+ logger.log(`Found ${rules.length} rule(s), validating...`);
5177
5625
  const validation = await validateRules(rules);
5178
5626
  if (validation.warnings.length > 0) {
5179
- console.log("\n\u26A0\uFE0F Warnings:");
5627
+ logger.log("\n\u26A0\uFE0F Warnings:");
5180
5628
  for (const warning of validation.warnings) {
5181
- console.log(` - ${warning}`);
5629
+ logger.log(` - ${warning}`);
5182
5630
  }
5183
5631
  }
5184
5632
  if (validation.errors.length > 0) {
5185
- console.log("\n\u274C Errors:");
5633
+ logger.log("\nErrors:");
5186
5634
  for (const error of validation.errors) {
5187
- console.log(` - ${error}`);
5635
+ logger.log(` - ${error}`);
5188
5636
  }
5189
5637
  }
5190
5638
  if (validation.isValid) {
5191
- console.log("\n\u2705 All rules are valid!");
5639
+ logger.success("\nAll rules are valid!");
5192
5640
  } else {
5193
- console.log(`
5194
- \u274C Validation failed with ${validation.errors.length} error(s)`);
5641
+ logger.log(`
5642
+ Validation failed with ${validation.errors.length} error(s)`);
5195
5643
  process.exit(1);
5196
5644
  }
5197
5645
  } catch (error) {
5198
- console.error("\u274C Failed to validate rules:", error);
5646
+ logger.error("Failed to validate rules:", error);
5199
5647
  process.exit(1);
5200
5648
  }
5201
5649
  }
5202
5650
 
5203
5651
  // src/cli/commands/watch.ts
5204
5652
  var import_chokidar = require("chokidar");
5653
+ init_logger();
5205
5654
  async function watchCommand() {
5206
5655
  const config = getDefaultConfig();
5207
- console.log("\u{1F440} Watching for changes in .rulesync directory...");
5208
- console.log("Press Ctrl+C to stop watching");
5656
+ logger.log("\u{1F440} Watching for changes in .rulesync directory...");
5657
+ logger.log("Press Ctrl+C to stop watching");
5209
5658
  await generateCommand({ verbose: false });
5210
5659
  const watcher = (0, import_chokidar.watch)(`${config.aiRulesDir}/**/*.md`, {
5211
5660
  ignoreInitial: true,
@@ -5215,26 +5664,26 @@ async function watchCommand() {
5215
5664
  const handleChange = async (path5) => {
5216
5665
  if (isGenerating) return;
5217
5666
  isGenerating = true;
5218
- console.log(`
5667
+ logger.log(`
5219
5668
  \u{1F4DD} Detected change in ${path5}`);
5220
5669
  try {
5221
5670
  await generateCommand({ verbose: false });
5222
- console.log("\u2705 Regenerated configuration files");
5671
+ logger.success("Regenerated configuration files");
5223
5672
  } catch (error) {
5224
- console.error("\u274C Failed to regenerate:", error);
5673
+ logger.error("Failed to regenerate:", error);
5225
5674
  } finally {
5226
5675
  isGenerating = false;
5227
5676
  }
5228
5677
  };
5229
5678
  watcher.on("change", handleChange).on("add", handleChange).on("unlink", (path5) => {
5230
- console.log(`
5679
+ logger.log(`
5231
5680
  \u{1F5D1}\uFE0F Removed ${path5}`);
5232
5681
  handleChange(path5);
5233
5682
  }).on("error", (error) => {
5234
- console.error("\u274C Watcher error:", error);
5683
+ logger.error("Watcher error:", error);
5235
5684
  });
5236
5685
  process.on("SIGINT", () => {
5237
- console.log("\n\n\u{1F44B} Stopping watcher...");
5686
+ logger.log("\n\n\u{1F44B} Stopping watcher...");
5238
5687
  watcher.close();
5239
5688
  process.exit(0);
5240
5689
  });
@@ -5242,12 +5691,12 @@ async function watchCommand() {
5242
5691
 
5243
5692
  // src/cli/index.ts
5244
5693
  var program = new import_commander.Command();
5245
- program.name("rulesync").description("Unified AI rules management CLI tool").version("0.62.0");
5694
+ program.name("rulesync").description("Unified AI rules management CLI tool").version("0.64.0");
5246
5695
  program.command("init").description("Initialize rulesync in current directory").option("--legacy", "Use legacy file location (.rulesync/*.md instead of .rulesync/rules/*.md)").action(initCommand);
5247
5696
  program.command("add <filename>").description("Add a new rule file").option("--legacy", "Use legacy file location (.rulesync/*.md instead of .rulesync/rules/*.md)").action(addCommand);
5248
5697
  program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
5249
- 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);
5250
- 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(
5698
+ 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);
5699
+ 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(
5251
5700
  "-b, --base-dir <paths>",
5252
5701
  "Base directories to generate files (comma-separated for multiple paths)"
5253
5702
  ).option("-v, --verbose", "Verbose output").option("-c, --config <path>", "Path to configuration file").option("--no-config", "Disable configuration file loading").action(async (options) => {
@@ -5263,6 +5712,7 @@ program.command("generate").description("Generate configuration files for AI too
5263
5712
  if (options.geminicli) tools.push("geminicli");
5264
5713
  if (options.junie) tools.push("junie");
5265
5714
  if (options.kiro) tools.push("kiro");
5715
+ if (options.opencode) tools.push("opencode");
5266
5716
  if (options.windsurf) tools.push("windsurf");
5267
5717
  const generateOptions = {
5268
5718
  verbose: options.verbose,