rulesync 0.64.0 → 0.67.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 +87 -42
  2. package/dist/{amazonqcli-WVGYACHI.js → amazonqcli-PWXCSRAN.js} +2 -2
  3. package/dist/{augmentcode-DTHPPXWO.js → augmentcode-4AFYW4BU.js} +2 -2
  4. package/dist/{chunk-FVPZQEWP.js → chunk-35VMCHXQ.js} +1 -1
  5. package/dist/{chunk-EID75W45.js → chunk-3QGD3CH5.js} +1 -1
  6. package/dist/{chunk-LURFNGH4.js → chunk-5GKH5TQ4.js} +1 -1
  7. package/dist/{chunk-KKWJVA56.js → chunk-7BIZ5Y6F.js} +1 -1
  8. package/dist/{chunk-HHJIL3YZ.js → chunk-7QVQO6MQ.js} +1 -1
  9. package/dist/{chunk-TQOL7OKY.js → chunk-B2HD24KC.js} +1 -1
  10. package/dist/{chunk-LYVES5YR.js → chunk-CS7AV6JT.js} +2 -0
  11. package/dist/{chunk-DMTCLQ4T.js → chunk-KONQNQY3.js} +1 -1
  12. package/dist/{chunk-JX55DU6Y.js → chunk-OARJESSZ.js} +1 -1
  13. package/dist/{chunk-6LSN7HSJ.js → chunk-V36ICGOY.js} +14 -1
  14. package/dist/{chunk-TBXG53FV.js → chunk-WCON5BAI.js} +1 -1
  15. package/dist/{chunk-YPJW7Z5M.js → chunk-WFOWHPBC.js} +1 -1
  16. package/dist/{chunk-4NAQ5HL4.js → chunk-WYYQXVHC.js} +1 -1
  17. package/dist/{chunk-E2J3UBBK.js → chunk-YZUDL4GW.js} +1 -1
  18. package/dist/chunk-ZMGXHLYP.js +17 -0
  19. package/dist/{claudecode-SSYLLUXX.js → claudecode-OC7VHCF6.js} +3 -3
  20. package/dist/{cline-5EUGKNZ6.js → cline-A4KFSAQE.js} +3 -3
  21. package/dist/{codexcli-IGM2ADYK.js → codexcli-YP3X7FWB.js} +3 -3
  22. package/dist/{copilot-HSQO7ZCJ.js → copilot-3LIMXQ7O.js} +2 -2
  23. package/dist/{cursor-ZB3XNGBK.js → cursor-QV72CDJC.js} +3 -3
  24. package/dist/{geminicli-FNRKH5GX.js → geminicli-XPSJJS65.js} +3 -3
  25. package/dist/index.cjs +2048 -492
  26. package/dist/index.js +2006 -498
  27. package/dist/{junie-3YGOSOGF.js → junie-265XIW43.js} +3 -3
  28. package/dist/{kiro-B6WZNLY4.js → kiro-6OHFHN5X.js} +2 -2
  29. package/dist/{opencode-SZETJ62M.js → opencode-C5QAYVJ5.js} +2 -2
  30. package/dist/qwencode-5RR24UW6.js +10 -0
  31. package/dist/{roo-KLTWVAKE.js → roo-5IXVBUHD.js} +3 -3
  32. package/dist/{windsurf-IZEKUAID.js → windsurf-DVQUJJJ5.js} +3 -3
  33. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import "./chunk-DMTCLQ4T.js";
3
- import "./chunk-4NAQ5HL4.js";
4
- import "./chunk-EID75W45.js";
5
- import "./chunk-LURFNGH4.js";
6
- import "./chunk-HHJIL3YZ.js";
2
+ import "./chunk-KONQNQY3.js";
3
+ import "./chunk-WYYQXVHC.js";
4
+ import "./chunk-3QGD3CH5.js";
5
+ import "./chunk-ZMGXHLYP.js";
6
+ import "./chunk-5GKH5TQ4.js";
7
+ import "./chunk-7QVQO6MQ.js";
7
8
  import {
8
9
  ensureDir,
9
10
  fileExists,
@@ -15,25 +16,90 @@ import {
15
16
  removeDirectory,
16
17
  resolvePath,
17
18
  writeFileContent
18
- } from "./chunk-YPJW7Z5M.js";
19
- import "./chunk-E2J3UBBK.js";
20
- import "./chunk-FVPZQEWP.js";
21
- import "./chunk-JX55DU6Y.js";
22
- import "./chunk-TQOL7OKY.js";
23
- import "./chunk-KKWJVA56.js";
24
- import "./chunk-TBXG53FV.js";
25
- import "./chunk-6LSN7HSJ.js";
19
+ } from "./chunk-WFOWHPBC.js";
20
+ import "./chunk-YZUDL4GW.js";
21
+ import "./chunk-35VMCHXQ.js";
22
+ import "./chunk-OARJESSZ.js";
23
+ import "./chunk-B2HD24KC.js";
24
+ import "./chunk-7BIZ5Y6F.js";
25
+ import "./chunk-WCON5BAI.js";
26
+ import "./chunk-V36ICGOY.js";
26
27
  import {
27
28
  ALL_TOOL_TARGETS,
28
29
  RulesyncTargetsSchema,
29
30
  ToolTargetSchema,
30
31
  ToolTargetsSchema,
31
32
  isToolTarget
32
- } from "./chunk-LYVES5YR.js";
33
+ } from "./chunk-CS7AV6JT.js";
33
34
 
34
35
  // src/cli/index.ts
35
36
  import { Command } from "commander";
36
37
 
38
+ // src/types/config-options.ts
39
+ import { z } from "zod/mini";
40
+ var FEATURE_TYPES = ["rules", "commands", "mcp", "ignore", "subagents"];
41
+ var FeatureTypeSchema = z.enum(FEATURE_TYPES);
42
+ var FeaturesSchema = z.union([z.array(FeatureTypeSchema), z.literal("*")]);
43
+ var OutputPathsSchema = z.object({
44
+ agentsmd: z.optional(z.string()),
45
+ amazonqcli: z.optional(z.string()),
46
+ augmentcode: z.optional(z.string()),
47
+ "augmentcode-legacy": z.optional(z.string()),
48
+ copilot: z.optional(z.string()),
49
+ cursor: z.optional(z.string()),
50
+ cline: z.optional(z.string()),
51
+ claudecode: z.optional(z.string()),
52
+ codexcli: z.optional(z.string()),
53
+ opencode: z.optional(z.string()),
54
+ qwencode: z.optional(z.string()),
55
+ roo: z.optional(z.string()),
56
+ geminicli: z.optional(z.string()),
57
+ kiro: z.optional(z.string()),
58
+ junie: z.optional(z.string()),
59
+ windsurf: z.optional(z.string())
60
+ });
61
+ var ConfigOptionsSchema = z.object({
62
+ aiRulesDir: z.optional(z.string()),
63
+ outputPaths: z.optional(OutputPathsSchema),
64
+ watchEnabled: z.optional(z.boolean()),
65
+ defaultTargets: z.optional(ToolTargetsSchema),
66
+ targets: z.optional(z.array(ToolTargetSchema)),
67
+ exclude: z.optional(z.array(ToolTargetSchema)),
68
+ features: z.optional(FeaturesSchema),
69
+ verbose: z.optional(z.boolean()),
70
+ delete: z.optional(z.boolean()),
71
+ baseDir: z.optional(z.union([z.string(), z.array(z.string())])),
72
+ legacy: z.optional(z.boolean()),
73
+ watch: z.optional(
74
+ z.object({
75
+ enabled: z.optional(z.boolean()),
76
+ interval: z.optional(z.number()),
77
+ ignore: z.optional(z.array(z.string()))
78
+ })
79
+ )
80
+ });
81
+ var MergedConfigSchema = z.object({
82
+ aiRulesDir: z.string(),
83
+ outputPaths: z.record(ToolTargetSchema, z.string()),
84
+ watchEnabled: z.boolean(),
85
+ defaultTargets: ToolTargetsSchema,
86
+ targets: z.optional(z.array(ToolTargetSchema)),
87
+ exclude: z.optional(z.array(ToolTargetSchema)),
88
+ features: z.optional(FeaturesSchema),
89
+ verbose: z.optional(z.boolean()),
90
+ delete: z.optional(z.boolean()),
91
+ baseDir: z.optional(z.union([z.string(), z.array(z.string())])),
92
+ configPath: z.optional(z.string()),
93
+ legacy: z.optional(z.boolean()),
94
+ watch: z.optional(
95
+ z.object({
96
+ enabled: z.optional(z.boolean()),
97
+ interval: z.optional(z.number()),
98
+ ignore: z.optional(z.array(z.string()))
99
+ })
100
+ )
101
+ });
102
+
37
103
  // src/cli/commands/add.ts
38
104
  import { mkdir, writeFile } from "fs/promises";
39
105
  import * as path from "path";
@@ -43,98 +109,40 @@ import { loadConfig as loadC12Config } from "c12";
43
109
  import { $ZodError } from "zod/v4/core";
44
110
 
45
111
  // src/types/claudecode.ts
46
- import { z } from "zod/mini";
47
- var ClaudeSettingsSchema = z.looseObject({
48
- permissions: z._default(
49
- z.looseObject({
50
- deny: z._default(z.array(z.string()), [])
112
+ import { z as z2 } from "zod/mini";
113
+ var ClaudeSettingsSchema = z2.looseObject({
114
+ permissions: z2._default(
115
+ z2.looseObject({
116
+ deny: z2._default(z2.array(z2.string()), [])
51
117
  }),
52
118
  { deny: [] }
53
119
  )
54
120
  });
55
121
 
56
122
  // src/types/shared.ts
57
- import { z as z2 } from "zod/mini";
58
- var OutputSchema = z2.object({
123
+ import { z as z3 } from "zod/mini";
124
+ var OutputSchema = z3.object({
59
125
  tool: ToolTargetSchema,
60
- filepath: z2.string(),
61
- content: z2.string()
126
+ filepath: z3.string(),
127
+ content: z3.string()
62
128
  });
63
- var BaseFrontmatterSchema = z2.object({
64
- description: z2.optional(z2.string())
129
+ var BaseFrontmatterSchema = z3.object({
130
+ description: z3.optional(z3.string())
65
131
  });
66
132
 
67
133
  // src/types/commands.ts
68
134
  var CommandFrontmatterSchema = BaseFrontmatterSchema;
69
135
 
70
136
  // src/types/config.ts
71
- import { z as z3 } from "zod/mini";
72
- var ConfigSchema = z3.object({
73
- aiRulesDir: z3.string(),
74
- outputPaths: z3.record(ToolTargetSchema, z3.string()),
75
- watchEnabled: z3.boolean(),
76
- defaultTargets: ToolTargetsSchema,
77
- claudecodeCommands: z3.optional(z3.string()),
78
- geminicliCommands: z3.optional(z3.string()),
79
- legacy: z3.optional(z3.boolean())
80
- });
81
-
82
- // src/types/config-options.ts
83
137
  import { z as z4 } from "zod/mini";
84
- var OutputPathsSchema = z4.object({
85
- amazonqcli: z4.optional(z4.string()),
86
- augmentcode: z4.optional(z4.string()),
87
- "augmentcode-legacy": z4.optional(z4.string()),
88
- copilot: z4.optional(z4.string()),
89
- cursor: z4.optional(z4.string()),
90
- cline: z4.optional(z4.string()),
91
- claudecode: z4.optional(z4.string()),
92
- codexcli: z4.optional(z4.string()),
93
- opencode: z4.optional(z4.string()),
94
- roo: z4.optional(z4.string()),
95
- geminicli: z4.optional(z4.string()),
96
- kiro: z4.optional(z4.string()),
97
- junie: z4.optional(z4.string()),
98
- windsurf: z4.optional(z4.string())
99
- });
100
- var ConfigOptionsSchema = z4.object({
101
- aiRulesDir: z4.optional(z4.string()),
102
- outputPaths: z4.optional(OutputPathsSchema),
103
- watchEnabled: z4.optional(z4.boolean()),
104
- defaultTargets: z4.optional(ToolTargetsSchema),
105
- targets: z4.optional(z4.array(ToolTargetSchema)),
106
- exclude: z4.optional(z4.array(ToolTargetSchema)),
107
- verbose: z4.optional(z4.boolean()),
108
- delete: z4.optional(z4.boolean()),
109
- baseDir: z4.optional(z4.union([z4.string(), z4.array(z4.string())])),
110
- legacy: z4.optional(z4.boolean()),
111
- watch: z4.optional(
112
- z4.object({
113
- enabled: z4.optional(z4.boolean()),
114
- interval: z4.optional(z4.number()),
115
- ignore: z4.optional(z4.array(z4.string()))
116
- })
117
- )
118
- });
119
- var MergedConfigSchema = z4.object({
138
+ var ConfigSchema = z4.object({
120
139
  aiRulesDir: z4.string(),
121
140
  outputPaths: z4.record(ToolTargetSchema, z4.string()),
122
141
  watchEnabled: z4.boolean(),
123
142
  defaultTargets: ToolTargetsSchema,
124
- targets: z4.optional(z4.array(ToolTargetSchema)),
125
- exclude: z4.optional(z4.array(ToolTargetSchema)),
126
- verbose: z4.optional(z4.boolean()),
127
- delete: z4.optional(z4.boolean()),
128
- baseDir: z4.optional(z4.union([z4.string(), z4.array(z4.string())])),
129
- configPath: z4.optional(z4.string()),
130
- legacy: z4.optional(z4.boolean()),
131
- watch: z4.optional(
132
- z4.object({
133
- enabled: z4.optional(z4.boolean()),
134
- interval: z4.optional(z4.number()),
135
- ignore: z4.optional(z4.array(z4.string()))
136
- })
137
- )
143
+ claudecodeCommands: z4.optional(z4.string()),
144
+ geminicliCommands: z4.optional(z4.string()),
145
+ legacy: z4.optional(z4.boolean())
138
146
  });
139
147
 
140
148
  // src/types/mcp.ts
@@ -192,6 +200,7 @@ function getDefaultConfig() {
192
200
  return {
193
201
  aiRulesDir: ".rulesync",
194
202
  outputPaths: {
203
+ agentsmd: ".agents/memories",
195
204
  amazonqcli: ".amazonq/rules",
196
205
  augmentcode: ".",
197
206
  "augmentcode-legacy": ".",
@@ -201,6 +210,7 @@ function getDefaultConfig() {
201
210
  claudecode: ".",
202
211
  codexcli: ".",
203
212
  opencode: ".",
213
+ qwencode: ".qwen/memories",
204
214
  roo: ".roo/rules",
205
215
  geminicli: ".gemini/memories",
206
216
  kiro: ".kiro/steering",
@@ -308,6 +318,11 @@ function generateMinimalConfig(options) {
308
318
  if (comma) lines[lines.length - 1] += comma;
309
319
  lines.push(` "exclude": ${JSON.stringify(options.exclude)}`);
310
320
  }
321
+ if (options.features) {
322
+ const comma = lines.length > 1 ? "," : "";
323
+ if (comma) lines[lines.length - 1] += comma;
324
+ lines.push(` "features": ${JSON.stringify(options.features)}`);
325
+ }
311
326
  if (options.aiRulesDir) {
312
327
  const comma = lines.length > 1 ? "," : "";
313
328
  if (comma) lines[lines.length - 1] += comma;
@@ -348,6 +363,7 @@ function generateMinimalConfig(options) {
348
363
  function generateSampleConfig(options) {
349
364
  const targets = options?.targets || ALL_TOOL_TARGETS;
350
365
  const excludeValue = options?.exclude ? JSON.stringify(options.exclude) : null;
366
+ const featuresValue = options?.features || ["rules", "commands", "mcp", "ignore", "subagents"];
351
367
  const aiRulesDir = options?.aiRulesDir || null;
352
368
  const baseDir = options?.baseDir || null;
353
369
  const deleteFlag = options?.delete || false;
@@ -359,6 +375,10 @@ function generateSampleConfig(options) {
359
375
 
360
376
  // Tools to exclude from generation (overrides targets)
361
377
  ${excludeValue ? `"exclude": ${excludeValue},` : '// "exclude": ["roo"],'}
378
+
379
+ // Features to generate (rules, commands, mcp, ignore, subagents)
380
+ // Use "*" to generate all features, or specify an array of features
381
+ "features": ${JSON.stringify(featuresValue)},
362
382
  ${aiRulesDir ? `
363
383
  // Directory containing AI rule files
364
384
  "aiRulesDir": "${aiRulesDir}",` : ""}
@@ -389,23 +409,6 @@ function generateSampleConfig(options) {
389
409
  }
390
410
  `;
391
411
  }
392
- function mergeWithCliOptions(config, cliOptions) {
393
- const merged = { ...config };
394
- if (cliOptions.verbose !== void 0) {
395
- merged.verbose = cliOptions.verbose;
396
- }
397
- if (cliOptions.delete !== void 0) {
398
- merged.delete = cliOptions.delete;
399
- }
400
- if (cliOptions.baseDirs && cliOptions.baseDirs.length > 0) {
401
- merged.baseDir = cliOptions.baseDirs;
402
- }
403
- if (cliOptions.tools && cliOptions.tools.length > 0) {
404
- merged.defaultTargets = cliOptions.tools;
405
- merged.exclude = void 0;
406
- }
407
- return merged;
408
- }
409
412
 
410
413
  // src/cli/commands/add.ts
411
414
  function sanitizeFilename(filename) {
@@ -508,6 +511,11 @@ Default Targets: ${config.defaultTargets.join(", ")}`);
508
511
  if (config.exclude && config.exclude.length > 0) {
509
512
  logger.log(`Excluded Targets: ${config.exclude.join(", ")}`);
510
513
  }
514
+ if (config.features) {
515
+ const featuresDisplay = config.features === "*" ? "*" : Array.isArray(config.features) ? config.features.join(", ") : String(config.features);
516
+ logger.log(`
517
+ Features: ${featuresDisplay}`);
518
+ }
511
519
  logger.log("\nOutput Paths:");
512
520
  for (const [tool, outputPath] of Object.entries(config.outputPaths)) {
513
521
  logger.log(` ${tool}: ${outputPath}`);
@@ -638,6 +646,10 @@ const config: ConfigOptions = {
638
646
  // Available: ${ALL_TOOL_TARGETS.join(", ")}
639
647
  targets: ${JSON.stringify(ALL_TOOL_TARGETS)},
640
648
 
649
+ // Features to generate (rules, commands, mcp, ignore, subagents)
650
+ // Use "*" to generate all features, or specify an array of features
651
+ features: ["rules", "commands", "mcp", "ignore", "subagents"],
652
+
641
653
  // Custom output paths for specific tools
642
654
  // outputPaths: {
643
655
  // copilot: ".github/copilot-instructions.md",
@@ -659,6 +671,9 @@ export default config;`;
659
671
  if (options.exclude) {
660
672
  configLines.push(` exclude: ${JSON.stringify(options.exclude)}`);
661
673
  }
674
+ if (options.features) {
675
+ configLines.push(` features: ${JSON.stringify(options.features)}`);
676
+ }
662
677
  if (options.aiRulesDir) {
663
678
  configLines.push(` aiRulesDir: "${options.aiRulesDir}"`);
664
679
  }
@@ -691,7 +706,7 @@ export default config;
691
706
  }
692
707
 
693
708
  // src/cli/commands/generate.ts
694
- import { join as join12 } from "path";
709
+ import { join as join13 } from "path";
695
710
 
696
711
  // src/core/command-generator.ts
697
712
  import { join as join3 } from "path";
@@ -955,6 +970,473 @@ async function generateCommands(projectRoot, baseDir, targets) {
955
970
  return outputs;
956
971
  }
957
972
 
973
+ // src/utils/feature-validator.ts
974
+ function isFeatureType(value) {
975
+ return FEATURE_TYPES.some((type) => type === value);
976
+ }
977
+ function validateFeatures(features) {
978
+ if (features === void 0) {
979
+ return "*";
980
+ }
981
+ if (features === "*") {
982
+ return "*";
983
+ }
984
+ if (!Array.isArray(features)) {
985
+ throw new Error('Features must be an array of feature names or "*"');
986
+ }
987
+ if (features.length === 0) {
988
+ throw new Error('Features array cannot be empty. Use "*" to include all features');
989
+ }
990
+ const validFeatures = [];
991
+ const invalidFeatures = [];
992
+ for (const feature of features) {
993
+ if (isFeatureType(feature)) {
994
+ validFeatures.push(feature);
995
+ } else {
996
+ invalidFeatures.push(feature);
997
+ }
998
+ }
999
+ if (invalidFeatures.length > 0) {
1000
+ throw new Error(
1001
+ `Invalid feature types: ${invalidFeatures.join(", ")}. Valid features are: ${FEATURE_TYPES.join(", ")}`
1002
+ );
1003
+ }
1004
+ return [...new Set(validFeatures)];
1005
+ }
1006
+ function expandWildcard() {
1007
+ return [...FEATURE_TYPES];
1008
+ }
1009
+ function normalizeFeatures(features) {
1010
+ if (features === "*" || features === void 0) {
1011
+ return expandWildcard();
1012
+ }
1013
+ return features;
1014
+ }
1015
+
1016
+ // src/core/config/cli-parser.ts
1017
+ var CliParser = class {
1018
+ /**
1019
+ * CLI引数を解析する
1020
+ * @param cliOptions - 生のCLIオプション
1021
+ * @returns 解析されたCliOptions
1022
+ */
1023
+ parse(cliOptions) {
1024
+ const parsed = {};
1025
+ if (cliOptions.tools && Array.isArray(cliOptions.tools) && cliOptions.tools.length > 0) {
1026
+ parsed.tools = cliOptions.tools;
1027
+ }
1028
+ if (cliOptions.features !== void 0) {
1029
+ try {
1030
+ parsed.features = validateFeatures(cliOptions.features);
1031
+ } catch (error) {
1032
+ throw new Error(
1033
+ `Invalid features: ${error instanceof Error ? error.message : String(error)}`
1034
+ );
1035
+ }
1036
+ }
1037
+ if (typeof cliOptions.verbose === "boolean") {
1038
+ parsed.verbose = cliOptions.verbose;
1039
+ }
1040
+ if (typeof cliOptions.delete === "boolean") {
1041
+ parsed.delete = cliOptions.delete;
1042
+ }
1043
+ if (cliOptions.baseDirs && Array.isArray(cliOptions.baseDirs) && cliOptions.baseDirs.length > 0) {
1044
+ parsed.baseDirs = cliOptions.baseDirs;
1045
+ }
1046
+ if (typeof cliOptions.config === "string") {
1047
+ parsed.config = cliOptions.config;
1048
+ }
1049
+ if (typeof cliOptions.noConfig === "boolean") {
1050
+ parsed.noConfig = cliOptions.noConfig;
1051
+ }
1052
+ return parsed;
1053
+ }
1054
+ /**
1055
+ * CLI引数が有効かどうかを検証する
1056
+ * @param cliOptions - 検証するCLIオプション
1057
+ * @returns 検証結果
1058
+ */
1059
+ validate(cliOptions) {
1060
+ const errors = [];
1061
+ if (cliOptions.config && cliOptions.noConfig) {
1062
+ errors.push("--config and --no-config cannot be used together");
1063
+ }
1064
+ if (cliOptions.baseDirs && cliOptions.baseDirs.length === 0) {
1065
+ errors.push("--base-dirs cannot be empty");
1066
+ }
1067
+ if (cliOptions.tools && cliOptions.tools.length === 0) {
1068
+ errors.push("--tools cannot be empty");
1069
+ }
1070
+ return {
1071
+ valid: errors.length === 0,
1072
+ errors
1073
+ };
1074
+ }
1075
+ };
1076
+
1077
+ // src/core/config/config-file-loader.ts
1078
+ import { isAbsolute, resolve } from "path";
1079
+ var ConfigFileLoader = class {
1080
+ /**
1081
+ * 設定ファイルを読み込む
1082
+ * @param options - 設定ファイルの読み込みオプション
1083
+ * @returns 設定ファイルの読み込み結果
1084
+ */
1085
+ async load(options = {}) {
1086
+ try {
1087
+ return await loadConfig(options);
1088
+ } catch (error) {
1089
+ throw new Error(
1090
+ `Failed to load configuration file: ${error instanceof Error ? error.message : String(error)}`
1091
+ );
1092
+ }
1093
+ }
1094
+ /**
1095
+ * 設定ファイルが存在するかどうかを確認する
1096
+ * @param configPath - 設定ファイルのパス
1097
+ * @returns ファイルが存在するかどうか
1098
+ */
1099
+ async exists(configPath) {
1100
+ try {
1101
+ const { access } = await import("fs/promises");
1102
+ await access(configPath);
1103
+ return true;
1104
+ } catch {
1105
+ return false;
1106
+ }
1107
+ }
1108
+ /**
1109
+ * 設定ファイルのパスを解決する
1110
+ * @param configPath - 指定された設定ファイルのパス
1111
+ * @param workingDirectory - 作業ディレクトリ
1112
+ * @returns 解決された設定ファイルのパス
1113
+ */
1114
+ resolvePath(configPath, workingDirectory) {
1115
+ if (!configPath) {
1116
+ return void 0;
1117
+ }
1118
+ if (isAbsolute(configPath)) {
1119
+ return configPath;
1120
+ }
1121
+ const baseDir = workingDirectory || process.cwd();
1122
+ return resolve(baseDir, configPath);
1123
+ }
1124
+ };
1125
+
1126
+ // src/core/config/config-merger.ts
1127
+ var ConfigMerger = class {
1128
+ /**
1129
+ * 設定ファイルとCLI引数をマージする
1130
+ * @param fileConfig - 設定ファイルから読み込まれた設定
1131
+ * @param cliOptions - CLI引数から解析された設定
1132
+ * @returns マージされた設定
1133
+ */
1134
+ merge(fileConfig, cliOptions) {
1135
+ const merged = { ...fileConfig };
1136
+ if (cliOptions.verbose !== void 0) {
1137
+ merged.verbose = cliOptions.verbose;
1138
+ }
1139
+ if (cliOptions.delete !== void 0) {
1140
+ merged.delete = cliOptions.delete;
1141
+ }
1142
+ if (cliOptions.baseDirs && cliOptions.baseDirs.length > 0) {
1143
+ merged.baseDir = cliOptions.baseDirs;
1144
+ }
1145
+ if (cliOptions.tools && cliOptions.tools.length > 0) {
1146
+ merged.defaultTargets = cliOptions.tools;
1147
+ merged.exclude = void 0;
1148
+ }
1149
+ if (cliOptions.features !== void 0) {
1150
+ merged.features = cliOptions.features;
1151
+ }
1152
+ return merged;
1153
+ }
1154
+ /**
1155
+ * 設定値のマージメタデータを生成する
1156
+ * @param fileConfig - 設定ファイルの設定
1157
+ * @param cliOptions - CLI引数の設定
1158
+ * @param merged - マージされた設定
1159
+ * @returns マージメタデータ
1160
+ */
1161
+ generateMetadata(fileConfig, cliOptions, merged) {
1162
+ const metadata = {};
1163
+ if (merged.verbose !== void 0) {
1164
+ metadata.verbose = {
1165
+ source: cliOptions.verbose !== void 0 ? "cli_args" /* CLI_ARGS */ : "config_file" /* CONFIG_FILE */,
1166
+ value: merged.verbose
1167
+ };
1168
+ }
1169
+ if (merged.delete !== void 0) {
1170
+ metadata.delete = {
1171
+ source: cliOptions.delete !== void 0 ? "cli_args" /* CLI_ARGS */ : "config_file" /* CONFIG_FILE */,
1172
+ value: merged.delete
1173
+ };
1174
+ }
1175
+ if (merged.baseDir !== void 0) {
1176
+ metadata.baseDir = {
1177
+ source: cliOptions.baseDirs ? "cli_args" /* CLI_ARGS */ : "config_file" /* CONFIG_FILE */,
1178
+ value: merged.baseDir
1179
+ };
1180
+ }
1181
+ metadata.defaultTargets = {
1182
+ source: cliOptions.tools ? "cli_args" /* CLI_ARGS */ : "config_file" /* CONFIG_FILE */,
1183
+ value: merged.defaultTargets
1184
+ };
1185
+ if (merged.exclude !== void 0) {
1186
+ metadata.exclude = {
1187
+ source: "config_file" /* CONFIG_FILE */,
1188
+ // excludeはCLIから直接指定されない
1189
+ value: merged.exclude
1190
+ };
1191
+ }
1192
+ if (merged.features !== void 0) {
1193
+ metadata.features = {
1194
+ source: cliOptions.features !== void 0 ? "cli_args" /* CLI_ARGS */ : "config_file" /* CONFIG_FILE */,
1195
+ value: merged.features
1196
+ };
1197
+ }
1198
+ return metadata;
1199
+ }
1200
+ /**
1201
+ * マージされた設定を検証する
1202
+ * @param config - マージされた設定
1203
+ * @returns 検証結果
1204
+ */
1205
+ validate(config) {
1206
+ const errors = [];
1207
+ if (!config.defaultTargets || config.defaultTargets.length === 0) {
1208
+ errors.push("At least one tool must be specified in targets or CLI arguments");
1209
+ }
1210
+ if (!config.aiRulesDir) {
1211
+ errors.push("aiRulesDir must be specified");
1212
+ }
1213
+ if (!config.outputPaths) {
1214
+ errors.push("outputPaths must be specified");
1215
+ }
1216
+ return {
1217
+ valid: errors.length === 0,
1218
+ errors
1219
+ };
1220
+ }
1221
+ };
1222
+
1223
+ // src/core/config/validators.ts
1224
+ var ConfigValidationError = class extends Error {
1225
+ constructor(message, errors) {
1226
+ super(message);
1227
+ this.errors = errors;
1228
+ this.name = "ConfigValidationError";
1229
+ }
1230
+ };
1231
+ var ConfigValidator = class {
1232
+ /**
1233
+ * CLI引数を検証する
1234
+ * @param cliOptions - 検証するCLI引数
1235
+ * @throws {ConfigValidationError} バリデーションエラーが発生した場合
1236
+ */
1237
+ validateCliOptions(cliOptions) {
1238
+ const errors = [];
1239
+ if (cliOptions.config && cliOptions.noConfig) {
1240
+ errors.push("--config and --no-config cannot be used together");
1241
+ }
1242
+ if (cliOptions.baseDirs && cliOptions.baseDirs.length === 0) {
1243
+ errors.push("--base-dirs cannot be empty");
1244
+ }
1245
+ if (cliOptions.tools && cliOptions.tools.length === 0) {
1246
+ errors.push("--tools cannot be empty");
1247
+ }
1248
+ if (cliOptions.baseDirs) {
1249
+ for (const [index, dir] of cliOptions.baseDirs.entries()) {
1250
+ if (typeof dir !== "string" || dir.trim() === "") {
1251
+ errors.push(`Base directory at index ${index} must be a non-empty string`);
1252
+ }
1253
+ }
1254
+ }
1255
+ if (errors.length > 0) {
1256
+ throw new ConfigValidationError("CLI options validation failed", errors);
1257
+ }
1258
+ }
1259
+ /**
1260
+ * マージされた設定を検証する
1261
+ * @param config - 検証する設定
1262
+ * @throws {ConfigValidationError} バリデーションエラーが発生した場合
1263
+ */
1264
+ validateMergedConfig(config) {
1265
+ const errors = [];
1266
+ if (!config.aiRulesDir || config.aiRulesDir.trim() === "") {
1267
+ errors.push("aiRulesDir must be specified and non-empty");
1268
+ }
1269
+ if (!config.outputPaths) {
1270
+ errors.push("outputPaths must be specified");
1271
+ }
1272
+ if (!config.defaultTargets || config.defaultTargets.length === 0) {
1273
+ errors.push("At least one tool must be specified in defaultTargets");
1274
+ }
1275
+ if (config.watchEnabled === void 0) {
1276
+ errors.push("watchEnabled must be specified");
1277
+ }
1278
+ if (config.baseDir !== void 0) {
1279
+ const dirs = Array.isArray(config.baseDir) ? config.baseDir : [config.baseDir];
1280
+ for (const [index, dir] of dirs.entries()) {
1281
+ if (typeof dir !== "string" || dir.trim() === "") {
1282
+ errors.push(`Base directory at index ${index} must be a non-empty string`);
1283
+ }
1284
+ }
1285
+ }
1286
+ if (config.exclude && config.defaultTargets) {
1287
+ const invalidExcludes = config.exclude.filter(
1288
+ (excludeTool) => !config.defaultTargets.includes(excludeTool)
1289
+ );
1290
+ if (invalidExcludes.length > 0) {
1291
+ errors.push(`Exclude contains tools not in defaultTargets: ${invalidExcludes.join(", ")}`);
1292
+ }
1293
+ }
1294
+ if (errors.length > 0) {
1295
+ throw new ConfigValidationError("Merged configuration validation failed", errors);
1296
+ }
1297
+ }
1298
+ /**
1299
+ * 設定ファイルのパスが有効かどうかを検証する
1300
+ * @param configPath - 設定ファイルのパス
1301
+ * @throws {ConfigValidationError} パスが無効な場合
1302
+ */
1303
+ validateConfigPath(configPath) {
1304
+ if (configPath !== void 0 && typeof configPath !== "string") {
1305
+ throw new ConfigValidationError("Configuration validation failed", [
1306
+ "Config path must be a string"
1307
+ ]);
1308
+ }
1309
+ if (typeof configPath === "string" && configPath.trim() === "") {
1310
+ throw new ConfigValidationError("Configuration validation failed", [
1311
+ "Config path cannot be empty"
1312
+ ]);
1313
+ }
1314
+ }
1315
+ /**
1316
+ * 作業ディレクトリが有効かどうかを検証する
1317
+ * @param workingDirectory - 作業ディレクトリのパス
1318
+ * @throws {ConfigValidationError} パスが無効な場合
1319
+ */
1320
+ validateWorkingDirectory(workingDirectory) {
1321
+ if (workingDirectory !== void 0 && typeof workingDirectory !== "string") {
1322
+ throw new ConfigValidationError("Configuration validation failed", [
1323
+ "Working directory must be a string"
1324
+ ]);
1325
+ }
1326
+ if (typeof workingDirectory === "string" && workingDirectory.trim() === "") {
1327
+ throw new ConfigValidationError("Configuration validation failed", [
1328
+ "Working directory cannot be empty"
1329
+ ]);
1330
+ }
1331
+ }
1332
+ };
1333
+
1334
+ // src/core/config/config-resolver.ts
1335
+ var ConfigResolver = class {
1336
+ cliParser;
1337
+ configLoader;
1338
+ merger;
1339
+ validator;
1340
+ constructor(cliParser, configLoader, merger, validator) {
1341
+ this.cliParser = cliParser || new CliParser();
1342
+ this.configLoader = configLoader || new ConfigFileLoader();
1343
+ this.merger = merger || new ConfigMerger();
1344
+ this.validator = validator || new ConfigValidator();
1345
+ }
1346
+ /**
1347
+ * 設定を解決してマージされた設定を返す
1348
+ * @param options - 設定解決のオプション
1349
+ * @returns 解決された設定と詳細情報
1350
+ */
1351
+ async resolve(options) {
1352
+ try {
1353
+ this.validator.validateWorkingDirectory(options.workingDirectory);
1354
+ const parsedCliOptions = this.cliParser.parse(options.cliOptions);
1355
+ this.validator.validateCliOptions(parsedCliOptions);
1356
+ const configLoaderOptions = {
1357
+ cwd: options.workingDirectory || process.cwd(),
1358
+ ...parsedCliOptions.config && { configPath: parsedCliOptions.config },
1359
+ ...parsedCliOptions.noConfig && { noConfig: parsedCliOptions.noConfig }
1360
+ };
1361
+ this.validator.validateConfigPath(configLoaderOptions.configPath);
1362
+ const configResult = await this.configLoader.load(configLoaderOptions);
1363
+ const mergedConfig = this.merger.merge(configResult.config, parsedCliOptions);
1364
+ this.validator.validateMergedConfig(mergedConfig);
1365
+ const metadata = this.merger.generateMetadata(
1366
+ configResult.config,
1367
+ parsedCliOptions,
1368
+ mergedConfig
1369
+ );
1370
+ const configSource = this.determineConfigSource(configResult.isEmpty, parsedCliOptions);
1371
+ return {
1372
+ value: mergedConfig,
1373
+ source: configSource,
1374
+ metadata
1375
+ };
1376
+ } catch (error) {
1377
+ if (error instanceof ConfigValidationError) {
1378
+ throw error;
1379
+ }
1380
+ throw new Error(
1381
+ `Failed to resolve configuration: ${error instanceof Error ? error.message : String(error)}`
1382
+ );
1383
+ }
1384
+ }
1385
+ /**
1386
+ * 設定の主要なソースを判定する
1387
+ * @param configFileEmpty - 設定ファイルが空かどうか
1388
+ * @param cliOptions - CLI引数
1389
+ * @returns 主要な設定ソース
1390
+ */
1391
+ determineConfigSource(configFileEmpty, cliOptions) {
1392
+ if (cliOptions.tools || cliOptions.verbose !== void 0 || cliOptions.delete !== void 0) {
1393
+ return "cli_args" /* CLI_ARGS */;
1394
+ }
1395
+ if (!configFileEmpty) {
1396
+ return "config_file" /* CONFIG_FILE */;
1397
+ }
1398
+ return "default" /* DEFAULT */;
1399
+ }
1400
+ /**
1401
+ * 設定解決の詳細情報を取得する(デバッグ用)
1402
+ * @param options - 設定解決のオプション
1403
+ * @returns 詳細な解決情報
1404
+ */
1405
+ async resolveWithDetails(options) {
1406
+ const parsedCliOptions = this.cliParser.parse(options.cliOptions);
1407
+ const configLoaderOptions = {
1408
+ cwd: options.workingDirectory || process.cwd(),
1409
+ ...parsedCliOptions.config && { configPath: parsedCliOptions.config },
1410
+ ...parsedCliOptions.noConfig && { noConfig: parsedCliOptions.noConfig }
1411
+ };
1412
+ const configFileResult = await this.configLoader.load(configLoaderOptions);
1413
+ try {
1414
+ const result = await this.resolve(options);
1415
+ return {
1416
+ result,
1417
+ details: {
1418
+ parsedCliOptions,
1419
+ configFileResult
1420
+ }
1421
+ };
1422
+ } catch (error) {
1423
+ const validationErrors = error instanceof ConfigValidationError ? error.errors : [error instanceof Error ? error.message : String(error)];
1424
+ const partialConfig = this.merger.merge(configFileResult.config, parsedCliOptions);
1425
+ return {
1426
+ result: {
1427
+ value: partialConfig,
1428
+ source: this.determineConfigSource(configFileResult.isEmpty, parsedCliOptions)
1429
+ },
1430
+ details: {
1431
+ parsedCliOptions,
1432
+ configFileResult,
1433
+ validationErrors
1434
+ }
1435
+ };
1436
+ }
1437
+ }
1438
+ };
1439
+
958
1440
  // src/generators/ignore/shared-factory.ts
959
1441
  import { join as join4 } from "path";
960
1442
 
@@ -1661,23 +2143,119 @@ async function generateKiroIgnoreFiles(rules, config, baseDir) {
1661
2143
  return generateIgnoreFile(rules, config, ignoreConfigs.kiro, baseDir);
1662
2144
  }
1663
2145
 
2146
+ // src/generators/ignore/qwencode.ts
2147
+ import { join as join5 } from "path";
2148
+ function extractQwenCodeFileFilteringPatterns(content) {
2149
+ const filtering = {};
2150
+ const configBlocks = content.match(/```(?:json|javascript)\s*\n([\s\S]*?)\n```/g);
2151
+ if (configBlocks) {
2152
+ for (const block of configBlocks) {
2153
+ try {
2154
+ const jsonContent = block.replace(/```(?:json|javascript)\s*\n/, "").replace(/\n```$/, "");
2155
+ const parsed = JSON.parse(jsonContent);
2156
+ if (parsed.fileFiltering) {
2157
+ Object.assign(filtering, parsed.fileFiltering);
2158
+ }
2159
+ } catch {
2160
+ }
2161
+ }
2162
+ }
2163
+ if (content.includes("respectGitIgnore")) {
2164
+ if (content.includes("respectGitIgnore: false") || content.includes('"respectGitIgnore": false')) {
2165
+ filtering.respectGitIgnore = false;
2166
+ } else {
2167
+ filtering.respectGitIgnore = true;
2168
+ }
2169
+ }
2170
+ if (content.includes("enableRecursiveFileSearch")) {
2171
+ if (content.includes("enableRecursiveFileSearch: false") || content.includes('"enableRecursiveFileSearch": false')) {
2172
+ filtering.enableRecursiveFileSearch = false;
2173
+ } else {
2174
+ filtering.enableRecursiveFileSearch = true;
2175
+ }
2176
+ }
2177
+ return Object.keys(filtering).length > 0 ? filtering : void 0;
2178
+ }
2179
+ function generateQwenCodeConfiguration(rules) {
2180
+ const config = {};
2181
+ config.fileFiltering = {
2182
+ respectGitIgnore: true,
2183
+ enableRecursiveFileSearch: true
2184
+ };
2185
+ for (const rule of rules) {
2186
+ const ruleFiltering = extractQwenCodeFileFilteringPatterns(rule.content);
2187
+ if (ruleFiltering) {
2188
+ Object.assign(config.fileFiltering, ruleFiltering);
2189
+ }
2190
+ }
2191
+ return config;
2192
+ }
2193
+ async function generateQwenCodeIgnoreFiles(rules, config, baseDir) {
2194
+ const outputs = [];
2195
+ const outputPath = baseDir || process.cwd();
2196
+ const qwenConfig = generateQwenCodeConfiguration(rules);
2197
+ const settingsPath = join5(outputPath, ".qwen", "settings.json");
2198
+ outputs.push({
2199
+ tool: "qwencode",
2200
+ filepath: settingsPath,
2201
+ content: `${JSON.stringify(qwenConfig, null, 2)}
2202
+ `
2203
+ });
2204
+ return outputs;
2205
+ }
2206
+
1664
2207
  // src/generators/ignore/windsurf.ts
1665
2208
  function generateWindsurfIgnore(rules, config, baseDir) {
1666
2209
  return generateIgnoreFile(rules, config, ignoreConfigs.windsurf, baseDir);
1667
2210
  }
1668
2211
 
2212
+ // src/generators/rules/agentsmd.ts
2213
+ async function generateAgentsMdConfig(rules, config, baseDir) {
2214
+ const outputs = [];
2215
+ const nonEmptyRules = rules.filter((rule) => rule.content.trim().length > 0);
2216
+ if (nonEmptyRules.length > 0) {
2217
+ const rootRule = nonEmptyRules.find((rule) => rule.frontmatter.root);
2218
+ const detailRules = nonEmptyRules.filter((rule) => !rule.frontmatter.root);
2219
+ if (rootRule) {
2220
+ const agentsPath = resolvePath("AGENTS.md", baseDir);
2221
+ const agentsContent = generateAgentsMarkdown(rootRule);
2222
+ outputs.push({
2223
+ tool: "agentsmd",
2224
+ filepath: agentsPath,
2225
+ content: agentsContent
2226
+ });
2227
+ }
2228
+ for (const rule of detailRules) {
2229
+ const memoryPath = resolvePath(`.agents/memories/${rule.filename}.md`, baseDir);
2230
+ const memoryContent = generateMemoryMarkdown(rule);
2231
+ outputs.push({
2232
+ tool: "agentsmd",
2233
+ filepath: memoryPath,
2234
+ content: memoryContent
2235
+ });
2236
+ }
2237
+ }
2238
+ return outputs;
2239
+ }
2240
+ function generateAgentsMarkdown(rootRule) {
2241
+ return rootRule.content.trim();
2242
+ }
2243
+ function generateMemoryMarkdown(rule) {
2244
+ return rule.content.trim();
2245
+ }
2246
+
1669
2247
  // src/generators/rules/shared-helpers.ts
1670
- import { join as join6 } from "path";
2248
+ import { join as join7 } from "path";
1671
2249
 
1672
2250
  // src/utils/ignore.ts
1673
- import { join as join5 } from "path";
2251
+ import { join as join6 } from "path";
1674
2252
  import micromatch from "micromatch";
1675
2253
  var cachedIgnorePatterns = null;
1676
2254
  async function loadIgnorePatterns(baseDir = process.cwd()) {
1677
2255
  if (cachedIgnorePatterns) {
1678
2256
  return cachedIgnorePatterns;
1679
2257
  }
1680
- const ignorePath = join5(baseDir, ".rulesyncignore");
2258
+ const ignorePath = join6(baseDir, ".rulesyncignore");
1681
2259
  if (!await fileExists(ignorePath)) {
1682
2260
  cachedIgnorePatterns = { patterns: [] };
1683
2261
  return cachedIgnorePatterns;
@@ -1731,21 +2309,35 @@ function addOutput(outputs, tool, config, baseDir, relativePath, content) {
1731
2309
  const outputDir = resolveOutputDir(config, tool, baseDir);
1732
2310
  outputs.push({
1733
2311
  tool,
1734
- filepath: join6(outputDir, relativePath),
2312
+ filepath: join7(outputDir, relativePath),
1735
2313
  content
1736
2314
  });
1737
2315
  }
1738
2316
  async function generateRulesConfig(rules, config, generatorConfig, baseDir) {
1739
2317
  const outputs = [];
1740
- for (const rule of rules) {
1741
- const content = generatorConfig.generateContent(rule);
1742
- const outputDir = resolveOutputDir(config, generatorConfig.tool, baseDir);
1743
- const filepath = generatorConfig.pathResolver ? generatorConfig.pathResolver(rule, outputDir) : join6(outputDir, `${rule.filename}${generatorConfig.fileExtension}`);
1744
- outputs.push({
2318
+ const nonEmptyRules = rules.filter((rule) => rule.content.trim().length > 0);
2319
+ if (nonEmptyRules.length === 0) {
2320
+ return outputs;
2321
+ }
2322
+ if (generatorConfig.singleFileMode && generatorConfig.fileName && generatorConfig.generateCombinedContent) {
2323
+ const filepath = resolvePath(generatorConfig.fileName, baseDir);
2324
+ const content = generatorConfig.generateCombinedContent(nonEmptyRules);
2325
+ outputs.push({
1745
2326
  tool: generatorConfig.tool,
1746
2327
  filepath,
1747
2328
  content
1748
2329
  });
2330
+ } else if (generatorConfig.fileExtension) {
2331
+ for (const rule of nonEmptyRules) {
2332
+ const content = generatorConfig.generateContent(rule);
2333
+ const outputDir = resolveOutputDir(config, generatorConfig.tool, baseDir);
2334
+ const filepath = generatorConfig.pathResolver ? generatorConfig.pathResolver(rule, outputDir) : join7(outputDir, `${rule.filename}${generatorConfig.fileExtension}`);
2335
+ outputs.push({
2336
+ tool: generatorConfig.tool,
2337
+ filepath,
2338
+ content
2339
+ });
2340
+ }
1749
2341
  }
1750
2342
  const ignorePatterns = await loadIgnorePatterns(baseDir);
1751
2343
  if (ignorePatterns.patterns.length > 0 && generatorConfig.ignoreFileName) {
@@ -1768,7 +2360,7 @@ async function generateComplexRules(rules, config, generatorConfig, baseDir) {
1768
2360
  for (const rule of detailRules) {
1769
2361
  const content = generatorConfig.generateDetailContent(rule);
1770
2362
  const filepath = resolvePath(
1771
- join6(generatorConfig.detailSubDir, `${rule.filename}.md`),
2363
+ join7(generatorConfig.detailSubDir, `${rule.filename}.md`),
1772
2364
  baseDir
1773
2365
  );
1774
2366
  outputs.push({
@@ -1876,7 +2468,7 @@ function generateRuleFile(rule) {
1876
2468
  }
1877
2469
 
1878
2470
  // src/generators/rules/augmentcode.ts
1879
- import { join as join7 } from "path";
2471
+ import { join as join8 } from "path";
1880
2472
  async function generateAugmentcodeConfig(rules, config, baseDir) {
1881
2473
  const outputs = createOutputsArray();
1882
2474
  rules.forEach((rule) => {
@@ -1885,7 +2477,7 @@ async function generateAugmentcodeConfig(rules, config, baseDir) {
1885
2477
  "augmentcode",
1886
2478
  config,
1887
2479
  baseDir,
1888
- join7(".augment", "rules", `${rule.filename}.md`),
2480
+ join8(".augment", "rules", `${rule.filename}.md`),
1889
2481
  generateRuleFile2(rule)
1890
2482
  );
1891
2483
  });
@@ -1938,7 +2530,7 @@ function generateLegacyGuidelinesFile(allRules) {
1938
2530
  }
1939
2531
 
1940
2532
  // src/generators/rules/claudecode.ts
1941
- import { join as join8 } from "path";
2533
+ import { join as join9 } from "path";
1942
2534
  async function generateClaudecodeConfig(rules, config, baseDir) {
1943
2535
  const generatorConfig = {
1944
2536
  tool: "claudecode",
@@ -1950,7 +2542,7 @@ async function generateClaudecodeConfig(rules, config, baseDir) {
1950
2542
  generateDetailContent: generateMemoryFile,
1951
2543
  detailSubDir: ".claude/memories",
1952
2544
  updateAdditionalConfig: async (ignorePatterns, baseDir2) => {
1953
- const settingsPath = resolvePath(join8(".claude", "settings.json"), baseDir2);
2545
+ const settingsPath = resolvePath(join9(".claude", "settings.json"), baseDir2);
1954
2546
  await updateClaudeSettings(settingsPath, ignorePatterns);
1955
2547
  return [];
1956
2548
  }
@@ -2014,7 +2606,7 @@ async function updateClaudeSettings(settingsPath, ignorePatterns) {
2014
2606
  }
2015
2607
 
2016
2608
  // src/generators/rules/generator-registry.ts
2017
- import { join as join9 } from "path";
2609
+ import { join as join10 } from "path";
2018
2610
  function determineCursorRuleType(frontmatter) {
2019
2611
  if (frontmatter.cursorRuleType) {
2020
2612
  return frontmatter.cursorRuleType;
@@ -2038,6 +2630,15 @@ function determineCursorRuleType(frontmatter) {
2038
2630
  }
2039
2631
  var GENERATOR_REGISTRY = {
2040
2632
  // Simple generators - generate one file per rule
2633
+ agentsmd: {
2634
+ type: "complex",
2635
+ tool: "agentsmd",
2636
+ fileExtension: ".md",
2637
+ ignoreFileName: ".agentsignore",
2638
+ generateContent: (rule) => rule.content.trim()
2639
+ // NOTE: AGENTS.md specific logic is handled in the actual generator file
2640
+ // Root rule goes to AGENTS.md, detail rules go to .agents/memories/
2641
+ },
2041
2642
  amazonqcli: {
2042
2643
  type: "complex",
2043
2644
  tool: "amazonqcli",
@@ -2110,7 +2711,7 @@ var GENERATOR_REGISTRY = {
2110
2711
  },
2111
2712
  pathResolver: (rule, outputDir) => {
2112
2713
  const baseFilename = rule.filename.replace(/\.md$/, "");
2113
- return join9(outputDir, `${baseFilename}.instructions.md`);
2714
+ return join10(outputDir, `${baseFilename}.instructions.md`);
2114
2715
  }
2115
2716
  },
2116
2717
  cursor: {
@@ -2150,7 +2751,7 @@ var GENERATOR_REGISTRY = {
2150
2751
  return lines.join("\n");
2151
2752
  },
2152
2753
  pathResolver: (rule, outputDir) => {
2153
- return join9(outputDir, `${rule.filename}.mdc`);
2754
+ return join10(outputDir, `${rule.filename}.mdc`);
2154
2755
  }
2155
2756
  },
2156
2757
  codexcli: {
@@ -2186,10 +2787,10 @@ var GENERATOR_REGISTRY = {
2186
2787
  pathResolver: (rule, outputDir) => {
2187
2788
  const outputFormat = rule.frontmatter.windsurfOutputFormat || "directory";
2188
2789
  if (outputFormat === "single-file") {
2189
- return join9(outputDir, ".windsurf-rules");
2790
+ return join10(outputDir, ".windsurf-rules");
2190
2791
  } else {
2191
- const rulesDir = join9(outputDir, ".windsurf", "rules");
2192
- return join9(rulesDir, `${rule.filename}.md`);
2792
+ const rulesDir = join10(outputDir, ".windsurf", "rules");
2793
+ return join10(rulesDir, `${rule.filename}.md`);
2193
2794
  }
2194
2795
  }
2195
2796
  },
@@ -2252,6 +2853,22 @@ var GENERATOR_REGISTRY = {
2252
2853
  const lines = [];
2253
2854
  if (rule.frontmatter.description) {
2254
2855
  lines.push(`# ${rule.frontmatter.description}
2856
+ `);
2857
+ }
2858
+ lines.push(rule.content.trim());
2859
+ return lines.join("\n");
2860
+ }
2861
+ // Complex generation handled by existing generator
2862
+ },
2863
+ qwencode: {
2864
+ type: "complex",
2865
+ tool: "qwencode",
2866
+ fileExtension: ".md",
2867
+ // ignoreFileName omitted - Qwen Code uses git-aware filtering instead of dedicated ignore files
2868
+ generateContent: (rule) => {
2869
+ const lines = [];
2870
+ if (rule.frontmatter.description) {
2871
+ lines.push(`# ${rule.frontmatter.description}
2255
2872
  `);
2256
2873
  }
2257
2874
  lines.push(rule.content.trim());
@@ -2308,6 +2925,60 @@ var generateCopilotConfig = createSimpleGenerator("copilot");
2308
2925
  var generateWindsurfConfig = createSimpleGenerator("windsurf");
2309
2926
  var generateKiroConfig = createSimpleGenerator("kiro");
2310
2927
  var generateRooConfig = createSimpleGenerator("roo");
2928
+ var generateQwencodeConfig = createSimpleGenerator("qwencode");
2929
+
2930
+ // src/generators/rules/base-generator.ts
2931
+ async function generateBaseRulesConfig(rules, config, generatorConfig, baseDir) {
2932
+ const unifiedConfig = {
2933
+ tool: generatorConfig.tool,
2934
+ fileName: generatorConfig.fileName,
2935
+ ...generatorConfig.ignoreFileName ? { ignoreFileName: generatorConfig.ignoreFileName } : {},
2936
+ singleFileMode: true,
2937
+ generateCombinedContent: generatorConfig.generateContent,
2938
+ generateContent: () => ""
2939
+ // Not used in single file mode
2940
+ };
2941
+ return generateRulesConfig(rules, config, unifiedConfig, baseDir);
2942
+ }
2943
+ function categorizeRules(rules) {
2944
+ return rules.reduce(
2945
+ (acc, rule) => {
2946
+ if (rule.frontmatter.root) {
2947
+ acc.root.push(rule);
2948
+ } else {
2949
+ acc.detail.push(rule);
2950
+ }
2951
+ return acc;
2952
+ },
2953
+ { root: [], detail: [] }
2954
+ );
2955
+ }
2956
+ function generateMarkdownContent(rules) {
2957
+ const sections = [];
2958
+ const categorized = categorizeRules(rules);
2959
+ if (categorized.root.length > 0) {
2960
+ sections.push(...categorized.root.map((rule) => rule.content.trim()));
2961
+ }
2962
+ if (categorized.detail.length > 0) {
2963
+ sections.push(...categorized.detail.map((rule) => rule.content.trim()));
2964
+ }
2965
+ return sections.join("\n\n").trim();
2966
+ }
2967
+
2968
+ // src/generators/rules/codexcli.ts
2969
+ async function generateCodexConfig(rules, config, baseDir) {
2970
+ return generateBaseRulesConfig(
2971
+ rules,
2972
+ config,
2973
+ {
2974
+ fileName: "AGENTS.md",
2975
+ ignoreFileName: ".codexignore",
2976
+ generateContent: generateMarkdownContent,
2977
+ tool: "codexcli"
2978
+ },
2979
+ baseDir
2980
+ );
2981
+ }
2311
2982
 
2312
2983
  // src/utils/xml-document-generator.ts
2313
2984
  import { XMLBuilder } from "fast-xml-parser";
@@ -2353,47 +3024,6 @@ function generateRootMarkdownWithXmlDocs(rootRule, memoryRules, config) {
2353
3024
  return lines.join("\n");
2354
3025
  }
2355
3026
 
2356
- // src/generators/rules/codexcli.ts
2357
- async function generateCodexConfig(rules, config, baseDir) {
2358
- const outputs = [];
2359
- const nonEmptyRules = rules.filter((rule) => rule.content.trim().length > 0);
2360
- if (nonEmptyRules.length > 0) {
2361
- const generatorConfig = {
2362
- tool: "codexcli",
2363
- fileExtension: ".md",
2364
- ignoreFileName: ".codexignore",
2365
- generateContent: generateCodexMemoryMarkdown,
2366
- generateDetailContent: generateCodexMemoryMarkdown,
2367
- generateRootContent: generateCodexRootMarkdown,
2368
- rootFilePath: "AGENTS.md",
2369
- detailSubDir: ".codex/memories"
2370
- };
2371
- const ruleOutputs = await generateComplexRules(nonEmptyRules, config, generatorConfig, baseDir);
2372
- outputs.push(...ruleOutputs);
2373
- } else {
2374
- const ignorePatterns = await loadIgnorePatterns(baseDir);
2375
- if (ignorePatterns.patterns.length > 0) {
2376
- const ignorePath = resolvePath(".codexignore", baseDir);
2377
- const ignoreContent = generateIgnoreFile2(ignorePatterns.patterns, "codexcli");
2378
- outputs.push({
2379
- tool: "codexcli",
2380
- filepath: ignorePath,
2381
- content: ignoreContent
2382
- });
2383
- }
2384
- }
2385
- return outputs;
2386
- }
2387
- function generateCodexMemoryMarkdown(rule) {
2388
- return rule.content.trim();
2389
- }
2390
- function generateCodexRootMarkdown(rootRule, memoryRules, _baseDir) {
2391
- return generateRootMarkdownWithXmlDocs(rootRule, memoryRules, {
2392
- memorySubDir: ".codex/memories",
2393
- fallbackTitle: "OpenAI Codex CLI Configuration"
2394
- });
2395
- }
2396
-
2397
3027
  // src/generators/rules/geminicli.ts
2398
3028
  async function generateGeminiConfig(rules, config, baseDir) {
2399
3029
  const generatorConfig = {
@@ -2451,21 +3081,39 @@ async function generateOpenCodeConfig(rules, config, baseDir) {
2451
3081
  tool: "opencode",
2452
3082
  fileExtension: ".md",
2453
3083
  // ignoreFileName omitted - OpenCode doesn't use dedicated ignore files
2454
- generateContent: generateOpenCodeMarkdown,
2455
- generateDetailContent: generateOpenCodeMarkdown,
2456
- generateRootContent: generateOpenCodeRootMarkdown,
3084
+ generateContent: (rule) => rule.content.trim(),
3085
+ generateDetailContent: (rule) => rule.content.trim(),
3086
+ generateRootContent: (rootRule, memoryRules) => generateRootMarkdownWithXmlDocs(rootRule, memoryRules, {
3087
+ memorySubDir: ".opencode/memories",
3088
+ fallbackTitle: "OpenCode Configuration"
3089
+ }),
2457
3090
  rootFilePath: "AGENTS.md",
2458
3091
  detailSubDir: ".opencode/memories"
2459
3092
  };
2460
3093
  return generateComplexRules(rules, config, generatorConfig, baseDir);
2461
3094
  }
2462
- function generateOpenCodeMarkdown(rule) {
3095
+
3096
+ // src/generators/rules/qwencode.ts
3097
+ async function generateQwencodeConfig2(rules, config, baseDir) {
3098
+ const generatorConfig = {
3099
+ tool: "qwencode",
3100
+ fileExtension: ".md",
3101
+ // ignoreFileName omitted - Qwen Code uses git-aware filtering instead of dedicated ignore files
3102
+ generateContent: generateQwenMemoryMarkdown,
3103
+ generateDetailContent: generateQwenMemoryMarkdown,
3104
+ generateRootContent: generateQwenRootMarkdown,
3105
+ rootFilePath: "QWEN.md",
3106
+ detailSubDir: ".qwen/memories"
3107
+ };
3108
+ return generateComplexRules(rules, config, generatorConfig, baseDir);
3109
+ }
3110
+ function generateQwenMemoryMarkdown(rule) {
2463
3111
  return rule.content.trim();
2464
3112
  }
2465
- function generateOpenCodeRootMarkdown(rootRule, memoryRules, _baseDir) {
3113
+ function generateQwenRootMarkdown(rootRule, memoryRules, _baseDir) {
2466
3114
  return generateRootMarkdownWithXmlDocs(rootRule, memoryRules, {
2467
- memorySubDir: ".opencode/memories",
2468
- fallbackTitle: "OpenCode Configuration"
3115
+ memorySubDir: ".qwen/memories",
3116
+ fallbackTitle: "Qwen Code Configuration"
2469
3117
  });
2470
3118
  }
2471
3119
 
@@ -2501,6 +3149,8 @@ function filterRulesForTool(rules, tool, config) {
2501
3149
  }
2502
3150
  async function generateForTool(tool, rules, config, baseDir) {
2503
3151
  switch (tool) {
3152
+ case "agentsmd":
3153
+ return await generateAgentsMdConfig(rules, config, baseDir);
2504
3154
  case "amazonqcli":
2505
3155
  return await generateAmazonqcliConfig(rules, config, baseDir);
2506
3156
  case "augmentcode": {
@@ -2543,6 +3193,11 @@ async function generateForTool(tool, rules, config, baseDir) {
2543
3193
  }
2544
3194
  case "opencode":
2545
3195
  return generateOpenCodeConfig(rules, config, baseDir);
3196
+ case "qwencode": {
3197
+ const qwenRulesOutputs = await generateQwencodeConfig2(rules, config, baseDir);
3198
+ const qwenIgnoreOutputs = await generateQwenCodeIgnoreFiles(rules, config, baseDir);
3199
+ return [...qwenRulesOutputs, ...qwenIgnoreOutputs];
3200
+ }
2546
3201
  case "windsurf": {
2547
3202
  const windsurfRulesOutputs = await generateWindsurfConfig(rules, config, baseDir);
2548
3203
  const windsurfIgnoreOutputs = await generateWindsurfIgnore(rules, config, baseDir);
@@ -2706,10 +3361,12 @@ function parseMcpConfig(projectRoot) {
2706
3361
  async function generateMcpConfigurations(mcpConfig, baseDir, targetTools) {
2707
3362
  const outputs = [];
2708
3363
  const toolMap = {
3364
+ agentsmd: async () => [],
2709
3365
  amazonqcli: async (servers, dir) => {
2710
3366
  const config = {
2711
3367
  aiRulesDir: ".rulesync",
2712
3368
  outputPaths: {
3369
+ agentsmd: ".agents/memories",
2713
3370
  amazonqcli: ".amazonq/rules",
2714
3371
  augmentcode: ".",
2715
3372
  "augmentcode-legacy": ".",
@@ -2719,6 +3376,7 @@ async function generateMcpConfigurations(mcpConfig, baseDir, targetTools) {
2719
3376
  claudecode: ".",
2720
3377
  codexcli: ".",
2721
3378
  opencode: ".",
3379
+ qwencode: ".qwen/memories",
2722
3380
  roo: ".roo/rules",
2723
3381
  geminicli: ".gemini/memories",
2724
3382
  kiro: ".kiro/steering",
@@ -2728,41 +3386,45 @@ async function generateMcpConfigurations(mcpConfig, baseDir, targetTools) {
2728
3386
  watchEnabled: false,
2729
3387
  defaultTargets: []
2730
3388
  };
2731
- const results = await (await import("./amazonqcli-WVGYACHI.js")).generateAmazonqcliMcp(
3389
+ const results = await (await import("./amazonqcli-PWXCSRAN.js")).generateAmazonqcliMcp(
2732
3390
  servers,
2733
3391
  config,
2734
3392
  dir
2735
3393
  );
2736
3394
  return results.map((result) => ({ filepath: result.filepath, content: result.content }));
2737
3395
  },
2738
- augmentcode: async (servers, dir) => (await import("./augmentcode-DTHPPXWO.js")).generateAugmentcodeMcpConfiguration(
3396
+ augmentcode: async (servers, dir) => (await import("./augmentcode-4AFYW4BU.js")).generateAugmentcodeMcpConfiguration(
3397
+ servers,
3398
+ dir
3399
+ ),
3400
+ "augmentcode-legacy": async (servers, dir) => (await import("./augmentcode-4AFYW4BU.js")).generateAugmentcodeMcpConfiguration(
2739
3401
  servers,
2740
3402
  dir
2741
3403
  ),
2742
- "augmentcode-legacy": async (servers, dir) => (await import("./augmentcode-DTHPPXWO.js")).generateAugmentcodeMcpConfiguration(
3404
+ claudecode: async (servers, dir) => (await import("./claudecode-OC7VHCF6.js")).generateClaudeMcpConfiguration(
2743
3405
  servers,
2744
3406
  dir
2745
3407
  ),
2746
- claudecode: async (servers, dir) => (await import("./claudecode-SSYLLUXX.js")).generateClaudeMcpConfiguration(
3408
+ copilot: async (servers, dir) => (await import("./copilot-3LIMXQ7O.js")).generateCopilotMcpConfiguration(servers, dir),
3409
+ cursor: async (servers, dir) => (await import("./cursor-QV72CDJC.js")).generateCursorMcpConfiguration(servers, dir),
3410
+ cline: async (servers, dir) => (await import("./cline-A4KFSAQE.js")).generateClineMcpConfiguration(servers, dir),
3411
+ codexcli: async (servers, dir) => (await import("./codexcli-YP3X7FWB.js")).generateCodexMcpConfiguration(servers, dir),
3412
+ opencode: async (servers, dir) => (await import("./opencode-C5QAYVJ5.js")).generateOpenCodeMcpConfiguration(
2747
3413
  servers,
2748
3414
  dir
2749
3415
  ),
2750
- copilot: async (servers, dir) => (await import("./copilot-HSQO7ZCJ.js")).generateCopilotMcpConfiguration(servers, dir),
2751
- cursor: async (servers, dir) => (await import("./cursor-ZB3XNGBK.js")).generateCursorMcpConfiguration(servers, dir),
2752
- cline: async (servers, dir) => (await import("./cline-5EUGKNZ6.js")).generateClineMcpConfiguration(servers, dir),
2753
- codexcli: async (servers, dir) => (await import("./codexcli-IGM2ADYK.js")).generateCodexMcpConfiguration(servers, dir),
2754
- opencode: async (servers, dir) => (await import("./opencode-SZETJ62M.js")).generateOpenCodeMcpConfiguration(
3416
+ roo: async (servers, dir) => (await import("./roo-5IXVBUHD.js")).generateRooMcpConfiguration(servers, dir),
3417
+ geminicli: async (servers, dir) => (await import("./geminicli-XPSJJS65.js")).generateGeminiCliMcpConfiguration(
2755
3418
  servers,
2756
3419
  dir
2757
3420
  ),
2758
- roo: async (servers, dir) => (await import("./roo-KLTWVAKE.js")).generateRooMcpConfiguration(servers, dir),
2759
- geminicli: async (servers, dir) => (await import("./geminicli-FNRKH5GX.js")).generateGeminiCliMcpConfiguration(
3421
+ kiro: async (servers, dir) => (await import("./kiro-6OHFHN5X.js")).generateKiroMcpConfiguration(servers, dir),
3422
+ junie: async (servers, dir) => (await import("./junie-265XIW43.js")).generateJunieMcpConfiguration(servers, dir),
3423
+ qwencode: async (servers, dir) => (await import("./qwencode-5RR24UW6.js")).generateQwenCodeMcpConfiguration(
2760
3424
  servers,
2761
3425
  dir
2762
3426
  ),
2763
- kiro: async (servers, dir) => (await import("./kiro-B6WZNLY4.js")).generateKiroMcpConfiguration(servers, dir),
2764
- junie: async (servers, dir) => (await import("./junie-3YGOSOGF.js")).generateJunieMcpConfiguration(servers, dir),
2765
- windsurf: async (servers, dir) => (await import("./windsurf-IZEKUAID.js")).generateWindsurfMcpConfiguration(
3427
+ windsurf: async (servers, dir) => (await import("./windsurf-DVQUJJJ5.js")).generateWindsurfMcpConfiguration(
2766
3428
  servers,
2767
3429
  dir
2768
3430
  )
@@ -2783,221 +3445,581 @@ async function generateMcpConfigurations(mcpConfig, baseDir, targetTools) {
2783
3445
  return outputs;
2784
3446
  }
2785
3447
 
2786
- // src/cli/commands/generate.ts
2787
- async function generateCommand(options = {}) {
2788
- const configLoaderOptions = {
2789
- ...options.config !== void 0 && { configPath: options.config },
2790
- ...options.noConfig !== void 0 && { noConfig: options.noConfig }
2791
- };
2792
- const configResult = await loadConfig(configLoaderOptions);
2793
- const cliOptions = {
2794
- ...options.tools !== void 0 && { tools: options.tools },
2795
- ...options.verbose !== void 0 && { verbose: options.verbose },
2796
- ...options.delete !== void 0 && { delete: options.delete },
2797
- ...options.baseDirs !== void 0 && { baseDirs: options.baseDirs }
2798
- };
2799
- const config = mergeWithCliOptions(configResult.config, cliOptions);
2800
- logger.setVerbose(config.verbose || false);
2801
- if (options.tools && options.tools.length > 0) {
2802
- const configTargets = config.defaultTargets;
2803
- const cliTools = options.tools;
2804
- const cliToolsSet = new Set(cliTools);
2805
- const configTargetsSet = new Set(configTargets);
2806
- const notInConfig = cliTools.filter((tool) => !configTargetsSet.has(tool));
2807
- const notInCli = configTargets.filter((tool) => !cliToolsSet.has(tool));
2808
- if (notInConfig.length > 0 || notInCli.length > 0) {
2809
- logger.warn("\u26A0\uFE0F Warning: CLI tool selection differs from configuration!");
2810
- logger.warn(` Config targets: ${configTargets.join(", ")}`);
2811
- logger.warn(` CLI specified: ${cliTools.join(", ")}`);
2812
- if (notInConfig.length > 0) {
2813
- logger.warn(` Tools specified but not in config: ${notInConfig.join(", ")}`);
3448
+ // src/core/subagent-generator.ts
3449
+ import path7 from "path";
3450
+
3451
+ // src/generators/subagents/base.ts
3452
+ import path5 from "path";
3453
+ var BaseSubagentGenerator = class {
3454
+ /**
3455
+ * Generate subagent files for the tool
3456
+ */
3457
+ async generate(rules, config, baseDir, parsedSubagents) {
3458
+ const toolName = this.getToolName();
3459
+ const agentsDir = this.getAgentsDirectory();
3460
+ const outputs = [];
3461
+ try {
3462
+ const outputDir = resolvePath(
3463
+ path5.join(config.outputPaths[toolName] ?? "", agentsDir),
3464
+ baseDir
3465
+ );
3466
+ await ensureDir(outputDir);
3467
+ let subagentOutputs;
3468
+ if (parsedSubagents && parsedSubagents.length > 0) {
3469
+ logger.debug(
3470
+ `Generating ${parsedSubagents.length} subagents from parsed data for ${toolName}`
3471
+ );
3472
+ subagentOutputs = this.generateFromParsedSubagents(parsedSubagents);
3473
+ } else {
3474
+ logger.debug(`Generating subagents from rules for ${toolName}`);
3475
+ subagentOutputs = this.generateFromRules(rules);
3476
+ }
3477
+ for (const subagent of subagentOutputs) {
3478
+ const filePath = path5.join(outputDir, subagent.filename);
3479
+ outputs.push({
3480
+ tool: toolName,
3481
+ filepath: filePath,
3482
+ content: subagent.content
3483
+ });
2814
3484
  }
2815
- if (notInCli.length > 0) {
2816
- logger.warn(` Tools in config but not specified: ${notInCli.join(", ")}`);
3485
+ if (outputs.length > 0) {
3486
+ logger.info(`Generated ${outputs.length} subagent files for ${toolName}`);
3487
+ } else {
3488
+ logger.debug(`No subagents generated for ${toolName}`);
2817
3489
  }
2818
- logger.warn("\n The configuration file targets will be used.");
2819
- logger.warn(" To change targets, update your rulesync config file.");
2820
- logger.warn("");
3490
+ return outputs;
3491
+ } catch (error) {
3492
+ logger.error(`Error generating subagents for ${toolName}:`, error);
3493
+ return [];
2821
3494
  }
2822
3495
  }
2823
- let baseDirs;
2824
- if (config.baseDir) {
2825
- baseDirs = Array.isArray(config.baseDir) ? config.baseDir : [config.baseDir];
2826
- } else if (options.baseDirs) {
2827
- baseDirs = options.baseDirs;
2828
- } else {
2829
- baseDirs = [process.cwd()];
3496
+ };
3497
+
3498
+ // src/generators/subagents/claudecode.ts
3499
+ var ClaudeCodeSubagentGenerator = class extends BaseSubagentGenerator {
3500
+ getToolName() {
3501
+ return "claudecode";
2830
3502
  }
2831
- logger.info(`Loaded configuration from: ${configResult.filepath}`);
2832
- logger.log("Generating configuration files...");
2833
- if (!await fileExists(config.aiRulesDir)) {
2834
- logger.error("\u274C .rulesync directory not found. Run 'rulesync init' first.");
2835
- process.exit(1);
3503
+ getAgentsDirectory() {
3504
+ return ".claude/agents";
3505
+ }
3506
+ generateFromRules(_rules) {
3507
+ logger.debug("Skipping rule-to-subagent conversion (deprecated behavior)");
3508
+ return [];
2836
3509
  }
3510
+ generateFromParsedSubagents(subagents) {
3511
+ return subagents.map((subagent) => {
3512
+ const frontmatterLines = ["---"];
3513
+ frontmatterLines.push(`name: ${subagent.frontmatter.name}`);
3514
+ frontmatterLines.push(`description: ${subagent.frontmatter.description}`);
3515
+ if (subagent.frontmatter.claudecode?.model) {
3516
+ frontmatterLines.push(`model: ${subagent.frontmatter.claudecode.model}`);
3517
+ }
3518
+ frontmatterLines.push("---");
3519
+ const content = `${frontmatterLines.join("\n")}
3520
+
3521
+ ${subagent.content}`;
3522
+ return {
3523
+ filename: `${subagent.filename}.md`,
3524
+ content
3525
+ };
3526
+ });
3527
+ }
3528
+ processContent(subagent) {
3529
+ const frontmatterLines = ["---"];
3530
+ frontmatterLines.push(`name: ${subagent.frontmatter.name}`);
3531
+ frontmatterLines.push(`description: ${subagent.frontmatter.description}`);
3532
+ if (subagent.frontmatter.claudecode?.model) {
3533
+ frontmatterLines.push(`model: ${subagent.frontmatter.claudecode.model}`);
3534
+ }
3535
+ frontmatterLines.push("---");
3536
+ return `${frontmatterLines.join("\n")}
3537
+
3538
+ ${subagent.content}`;
3539
+ }
3540
+ };
3541
+
3542
+ // src/parsers/subagent-parser.ts
3543
+ import { readdir, readFile } from "fs/promises";
3544
+ import path6 from "path";
3545
+ import matter2 from "gray-matter";
3546
+
3547
+ // src/types/subagent.ts
3548
+ import { z as z7 } from "zod/mini";
3549
+ var ClaudeCodeConfigSchema = z7.object({
3550
+ model: z7.optional(z7.string())
3551
+ });
3552
+ var SubagentFrontmatterSchema = z7.object({
3553
+ name: z7.string(),
3554
+ description: z7.string(),
3555
+ targets: z7.optional(z7.array(z7.string())),
3556
+ // Tool-specific configurations
3557
+ claudecode: z7.optional(ClaudeCodeConfigSchema)
3558
+ });
3559
+
3560
+ // src/parsers/subagent-parser.ts
3561
+ async function parseSubagentFile(filepath) {
2837
3562
  try {
2838
- logger.info(`Parsing rules from ${config.aiRulesDir}...`);
2839
- const rules = await parseRulesFromDirectory(config.aiRulesDir);
2840
- if (rules.length === 0) {
2841
- logger.warn("\u26A0\uFE0F No rules found in .rulesync directory");
2842
- return;
3563
+ const fileContent = await readFile(filepath, "utf-8");
3564
+ const { data: frontmatter, content } = matter2(fileContent);
3565
+ const result = SubagentFrontmatterSchema.safeParse(frontmatter);
3566
+ if (!result.success) {
3567
+ logger.warn(`Invalid frontmatter in ${filepath}: ${result.error.message}`);
3568
+ return null;
2843
3569
  }
2844
- logger.info(`Found ${rules.length} rule(s)`);
2845
- logger.info(`Base directories: ${baseDirs.join(", ")}`);
2846
- if (config.delete) {
2847
- logger.info("Deleting existing output directories...");
2848
- const targetTools = config.defaultTargets;
2849
- const deleteTasks = [];
2850
- const commandsDir = join12(config.aiRulesDir, "commands");
2851
- const hasCommands = await fileExists(commandsDir);
2852
- let hasCommandFiles = false;
2853
- if (hasCommands) {
2854
- const { readdir } = await import("fs/promises");
2855
- try {
2856
- const files = await readdir(commandsDir);
2857
- hasCommandFiles = files.some((file) => file.endsWith(".md"));
2858
- } catch {
2859
- hasCommandFiles = false;
3570
+ const parsedFrontmatter = result.data;
3571
+ if (!parsedFrontmatter.targets) {
3572
+ parsedFrontmatter.targets = ["*"];
3573
+ }
3574
+ const filename = path6.basename(filepath, path6.extname(filepath));
3575
+ return {
3576
+ frontmatter: parsedFrontmatter,
3577
+ content: content.trim(),
3578
+ filename,
3579
+ filepath
3580
+ };
3581
+ } catch (error) {
3582
+ logger.error(`Error parsing subagent file ${filepath}:`, error);
3583
+ return null;
3584
+ }
3585
+ }
3586
+ async function parseSubagentsFromDirectory(agentsDir) {
3587
+ try {
3588
+ if (!await fileExists(agentsDir)) {
3589
+ logger.debug(`Agents directory does not exist: ${agentsDir}`);
3590
+ return [];
3591
+ }
3592
+ const entries = await readdir(agentsDir);
3593
+ const mdFiles = entries.filter((file) => file.endsWith(".md"));
3594
+ const files = mdFiles.map((file) => path6.join(agentsDir, file));
3595
+ if (files.length === 0) {
3596
+ logger.debug(`No subagent files found in ${agentsDir}`);
3597
+ return [];
3598
+ }
3599
+ logger.debug(`Found ${files.length} subagent files in ${agentsDir}`);
3600
+ const parsePromises = files.map((file) => parseSubagentFile(file));
3601
+ const results = await Promise.all(parsePromises);
3602
+ const subagents = results.filter((result) => result !== null);
3603
+ logger.info(`Successfully parsed ${subagents.length} subagents`);
3604
+ return subagents;
3605
+ } catch (error) {
3606
+ logger.error(`Error parsing subagents from directory ${agentsDir}:`, error);
3607
+ return [];
3608
+ }
3609
+ }
3610
+
3611
+ // src/core/subagent-generator.ts
3612
+ async function generateSubagents(rulesyncDir, outputDir, tools, rules) {
3613
+ const outputs = [];
3614
+ const subagentsDir = path7.join(rulesyncDir, "subagents");
3615
+ let parsedSubagents = [];
3616
+ try {
3617
+ parsedSubagents = await parseSubagentsFromDirectory(subagentsDir);
3618
+ if (parsedSubagents.length > 0) {
3619
+ logger.debug(`Found ${parsedSubagents.length} subagent files in ${subagentsDir}`);
3620
+ }
3621
+ } catch (error) {
3622
+ logger.debug(`No subagents directory found or error reading: ${error}`);
3623
+ }
3624
+ if (!tools || tools.length === 0) {
3625
+ return outputs;
3626
+ }
3627
+ const config = {
3628
+ aiRulesDir: rulesyncDir,
3629
+ outputPaths: {
3630
+ agentsmd: ".",
3631
+ amazonqcli: ".",
3632
+ augmentcode: ".augment",
3633
+ "augmentcode-legacy": ".",
3634
+ copilot: ".github",
3635
+ cursor: ".cursor",
3636
+ cline: ".",
3637
+ claudecode: ".",
3638
+ codexcli: ".",
3639
+ opencode: ".",
3640
+ qwencode: ".",
3641
+ roo: ".roo",
3642
+ geminicli: ".gemini",
3643
+ kiro: ".kiro",
3644
+ junie: ".junie",
3645
+ windsurf: ".windsurf"
3646
+ },
3647
+ watchEnabled: false,
3648
+ defaultTargets: tools
3649
+ };
3650
+ for (const tool of tools) {
3651
+ try {
3652
+ let generator;
3653
+ switch (tool) {
3654
+ case "claudecode":
3655
+ generator = new ClaudeCodeSubagentGenerator();
3656
+ break;
3657
+ // Add other tool generators here as they are implemented
3658
+ default:
3659
+ logger.debug(`Subagent generation not yet implemented for ${tool}`);
3660
+ continue;
3661
+ }
3662
+ const generatedOutputs = await generator.generate(
3663
+ parsedSubagents.length > 0 ? [] : rules || [],
3664
+ config,
3665
+ outputDir,
3666
+ parsedSubagents
3667
+ );
3668
+ for (const output of generatedOutputs) {
3669
+ outputs.push({
3670
+ tool,
3671
+ filepath: output.filepath,
3672
+ content: output.content
3673
+ });
3674
+ }
3675
+ } catch (error) {
3676
+ logger.error(`Failed to generate subagents for ${tool}:`, error);
3677
+ }
3678
+ }
3679
+ return outputs;
3680
+ }
3681
+
3682
+ // src/cli/commands/shared-utils.ts
3683
+ function showBackwardCompatibilityWarning(commandName, exampleCommand) {
3684
+ const yellow = "\x1B[33m";
3685
+ const gray = "\x1B[90m";
3686
+ const cyan = "\x1B[36m";
3687
+ const reset = "\x1B[0m";
3688
+ logger.warn(
3689
+ `
3690
+ ${yellow}\u26A0\uFE0F Warning: No --features option specified.${reset}
3691
+ ${gray}Currently ${commandName} all features for backward compatibility.${reset}
3692
+ ${gray}In future versions, this behavior may change.${reset}
3693
+ ${gray}Please specify --features explicitly:${reset}
3694
+ ${cyan} ${exampleCommand}${reset}
3695
+ ${gray}Or use --features * to ${commandName.replace("ing", "")} all features.${reset}
3696
+ `
3697
+ );
3698
+ }
3699
+
3700
+ // src/cli/commands/generate.ts
3701
+ async function generateCommand(options = {}) {
3702
+ try {
3703
+ const cliParser = new CliParser();
3704
+ const cliInputs = {};
3705
+ if (options.tools !== void 0) cliInputs.tools = options.tools;
3706
+ if (options.features !== void 0) cliInputs.features = options.features;
3707
+ if (options.verbose !== void 0) cliInputs.verbose = options.verbose;
3708
+ if (options.delete !== void 0) cliInputs.delete = options.delete;
3709
+ if (options.baseDirs !== void 0) cliInputs.baseDirs = options.baseDirs;
3710
+ if (options.config !== void 0) cliInputs.config = options.config;
3711
+ if (options.noConfig !== void 0) cliInputs.noConfig = options.noConfig;
3712
+ const cliOptions = cliParser.parse(cliInputs);
3713
+ const configResolver = new ConfigResolver();
3714
+ const resolutionResult = await configResolver.resolve({
3715
+ cliOptions
3716
+ });
3717
+ const config = resolutionResult.value;
3718
+ let resolvedFeatures;
3719
+ let showWarning = false;
3720
+ if (cliOptions.features !== void 0) {
3721
+ resolvedFeatures = cliOptions.features;
3722
+ } else if (config.features !== void 0) {
3723
+ resolvedFeatures = config.features;
3724
+ } else {
3725
+ resolvedFeatures = "*";
3726
+ showWarning = true;
3727
+ }
3728
+ if (showWarning) {
3729
+ showBackwardCompatibilityWarning(
3730
+ "generating",
3731
+ "rulesync generate --features rules,commands,mcp,ignore"
3732
+ );
3733
+ }
3734
+ const normalizedFeatures = normalizeFeatures(resolvedFeatures);
3735
+ if (!config.defaultTargets || config.defaultTargets.length === 0) {
3736
+ const errorMessage = `\u274C Error: At least one tool must be specified.
3737
+
3738
+ You can specify tools in three ways:
3739
+
3740
+ 1. Use the --targets flag:
3741
+ rulesync generate --targets copilot,cursor
3742
+
3743
+ 2. Use the --all flag to generate for all tools:
3744
+ rulesync generate --all
3745
+
3746
+ 3. Set targets in rulesync.jsonc:
3747
+ {
3748
+ "targets": ["copilot", "cursor"]
3749
+ }
3750
+
3751
+ Available tools:
3752
+ agentsmd, amazonqcli, augmentcode, augmentcode-legacy, copilot, cursor, cline,
3753
+ claudecode, codexcli, opencode, qwencode, roo, geminicli, kiro, junie, windsurf`;
3754
+ logger.error(errorMessage);
3755
+ process.exit(1);
3756
+ }
3757
+ logger.setVerbose(config.verbose || false);
3758
+ let baseDirs;
3759
+ if (config.baseDir) {
3760
+ baseDirs = Array.isArray(config.baseDir) ? config.baseDir : [config.baseDir];
3761
+ } else if (options.baseDirs) {
3762
+ baseDirs = options.baseDirs;
3763
+ } else {
3764
+ baseDirs = [process.cwd()];
3765
+ }
3766
+ logger.info(`Configuration resolved from: ${resolutionResult.source}`);
3767
+ logger.log("Generating configuration files...");
3768
+ if (!await fileExists(config.aiRulesDir)) {
3769
+ logger.error("\u274C .rulesync directory not found. Run 'rulesync init' first.");
3770
+ process.exit(1);
3771
+ }
3772
+ try {
3773
+ logger.info(`Parsing rules from ${config.aiRulesDir}...`);
3774
+ const rules = await parseRulesFromDirectory(config.aiRulesDir);
3775
+ if (rules.length === 0) {
3776
+ logger.warn("\u26A0\uFE0F No rules found in .rulesync directory");
3777
+ return;
3778
+ }
3779
+ logger.info(`Found ${rules.length} rule(s)`);
3780
+ logger.info(`Base directories: ${baseDirs.join(", ")}`);
3781
+ if (config.delete) {
3782
+ logger.info("Deleting existing output directories...");
3783
+ const targetTools = config.defaultTargets;
3784
+ const deleteTasks = [];
3785
+ const commandsDir = join13(config.aiRulesDir, "commands");
3786
+ const hasCommands = await fileExists(commandsDir);
3787
+ let hasCommandFiles = false;
3788
+ if (hasCommands) {
3789
+ const { readdir: readdir2 } = await import("fs/promises");
3790
+ try {
3791
+ const files = await readdir2(commandsDir);
3792
+ hasCommandFiles = files.some((file) => file.endsWith(".md"));
3793
+ } catch {
3794
+ hasCommandFiles = false;
3795
+ }
3796
+ }
3797
+ for (const tool of targetTools) {
3798
+ switch (tool) {
3799
+ case "augmentcode":
3800
+ if (normalizedFeatures.includes("rules")) {
3801
+ deleteTasks.push(removeDirectory(join13(".augment", "rules")));
3802
+ }
3803
+ if (normalizedFeatures.includes("ignore")) {
3804
+ deleteTasks.push(removeDirectory(join13(".augment", "ignore")));
3805
+ }
3806
+ break;
3807
+ case "augmentcode-legacy":
3808
+ if (normalizedFeatures.includes("rules")) {
3809
+ deleteTasks.push(removeClaudeGeneratedFiles());
3810
+ }
3811
+ if (normalizedFeatures.includes("ignore")) {
3812
+ deleteTasks.push(removeDirectory(join13(".augment", "ignore")));
3813
+ }
3814
+ break;
3815
+ case "copilot":
3816
+ if (normalizedFeatures.includes("rules")) {
3817
+ deleteTasks.push(removeDirectory(config.outputPaths.copilot));
3818
+ }
3819
+ break;
3820
+ case "cursor":
3821
+ if (normalizedFeatures.includes("rules")) {
3822
+ deleteTasks.push(removeDirectory(config.outputPaths.cursor));
3823
+ }
3824
+ break;
3825
+ case "cline":
3826
+ if (normalizedFeatures.includes("rules")) {
3827
+ deleteTasks.push(removeDirectory(config.outputPaths.cline));
3828
+ }
3829
+ break;
3830
+ case "claudecode":
3831
+ if (normalizedFeatures.includes("rules")) {
3832
+ deleteTasks.push(removeClaudeGeneratedFiles());
3833
+ }
3834
+ if (normalizedFeatures.includes("commands") && hasCommandFiles) {
3835
+ deleteTasks.push(removeDirectory(join13(".claude", "commands")));
3836
+ }
3837
+ if (normalizedFeatures.includes("subagents")) {
3838
+ deleteTasks.push(removeDirectory(join13(".claude", "agents")));
3839
+ }
3840
+ break;
3841
+ case "roo":
3842
+ if (normalizedFeatures.includes("rules")) {
3843
+ deleteTasks.push(removeDirectory(config.outputPaths.roo));
3844
+ }
3845
+ if (normalizedFeatures.includes("commands") && hasCommandFiles) {
3846
+ deleteTasks.push(removeDirectory(join13(".roo", "commands")));
3847
+ }
3848
+ break;
3849
+ case "geminicli":
3850
+ if (normalizedFeatures.includes("rules")) {
3851
+ deleteTasks.push(removeDirectory(config.outputPaths.geminicli));
3852
+ }
3853
+ if (normalizedFeatures.includes("commands") && hasCommandFiles) {
3854
+ deleteTasks.push(removeDirectory(join13(".gemini", "commands")));
3855
+ }
3856
+ break;
3857
+ case "kiro":
3858
+ if (normalizedFeatures.includes("rules")) {
3859
+ deleteTasks.push(removeDirectory(config.outputPaths.kiro));
3860
+ }
3861
+ break;
3862
+ case "opencode":
3863
+ if (normalizedFeatures.includes("rules")) {
3864
+ deleteTasks.push(removeDirectory(config.outputPaths.opencode));
3865
+ }
3866
+ break;
3867
+ case "qwencode":
3868
+ if (normalizedFeatures.includes("rules")) {
3869
+ deleteTasks.push(removeDirectory(config.outputPaths.qwencode));
3870
+ }
3871
+ break;
3872
+ case "windsurf":
3873
+ if (normalizedFeatures.includes("rules")) {
3874
+ deleteTasks.push(removeDirectory(config.outputPaths.windsurf));
3875
+ }
3876
+ break;
3877
+ }
3878
+ }
3879
+ await Promise.all(deleteTasks);
3880
+ logger.info("Deleted existing output directories");
3881
+ }
3882
+ let totalOutputs = 0;
3883
+ if (normalizedFeatures.includes("rules")) {
3884
+ for (const baseDir of baseDirs) {
3885
+ logger.info(`
3886
+ Generating rule configurations for base directory: ${baseDir}`);
3887
+ const outputs = await generateConfigurations(
3888
+ rules,
3889
+ config,
3890
+ config.defaultTargets,
3891
+ baseDir
3892
+ );
3893
+ if (outputs.length === 0) {
3894
+ logger.warn(`\u26A0\uFE0F No rule configurations generated for ${baseDir}`);
3895
+ continue;
3896
+ }
3897
+ for (const output of outputs) {
3898
+ await writeFileContent(output.filepath, output.content);
3899
+ logger.success(`Generated ${output.tool} rule configuration: ${output.filepath}`);
3900
+ }
3901
+ totalOutputs += outputs.length;
2860
3902
  }
3903
+ } else {
3904
+ logger.info("\nSkipping rule generation (not in --features)");
2861
3905
  }
2862
- for (const tool of targetTools) {
2863
- switch (tool) {
2864
- case "augmentcode":
2865
- deleteTasks.push(removeDirectory(join12(".augment", "rules")));
2866
- deleteTasks.push(removeDirectory(join12(".augment", "ignore")));
2867
- break;
2868
- case "augmentcode-legacy":
2869
- deleteTasks.push(removeClaudeGeneratedFiles());
2870
- deleteTasks.push(removeDirectory(join12(".augment", "ignore")));
2871
- break;
2872
- case "copilot":
2873
- deleteTasks.push(removeDirectory(config.outputPaths.copilot));
2874
- break;
2875
- case "cursor":
2876
- deleteTasks.push(removeDirectory(config.outputPaths.cursor));
2877
- break;
2878
- case "cline":
2879
- deleteTasks.push(removeDirectory(config.outputPaths.cline));
2880
- break;
2881
- case "claudecode":
2882
- deleteTasks.push(removeClaudeGeneratedFiles());
2883
- if (hasCommandFiles) {
2884
- deleteTasks.push(removeDirectory(join12(".claude", "commands")));
3906
+ let totalMcpOutputs = 0;
3907
+ if (normalizedFeatures.includes("mcp")) {
3908
+ logger.info("\nGenerating MCP configurations...");
3909
+ for (const baseDir of baseDirs) {
3910
+ try {
3911
+ const mcpConfig = parseMcpConfig(process.cwd());
3912
+ if (!mcpConfig || !mcpConfig.mcpServers || Object.keys(mcpConfig.mcpServers).length === 0) {
3913
+ logger.info(`No MCP configuration found for ${baseDir}`);
3914
+ continue;
2885
3915
  }
2886
- break;
2887
- case "roo":
2888
- deleteTasks.push(removeDirectory(config.outputPaths.roo));
2889
- if (hasCommandFiles) {
2890
- deleteTasks.push(removeDirectory(join12(".roo", "commands")));
3916
+ const mcpResults = await generateMcpConfigurations(
3917
+ mcpConfig,
3918
+ baseDir === process.cwd() ? "." : baseDir,
3919
+ config.defaultTargets
3920
+ );
3921
+ if (mcpResults.length === 0) {
3922
+ logger.info(`No MCP configurations generated for ${baseDir}`);
3923
+ continue;
2891
3924
  }
2892
- break;
2893
- case "geminicli":
2894
- deleteTasks.push(removeDirectory(config.outputPaths.geminicli));
2895
- if (hasCommandFiles) {
2896
- deleteTasks.push(removeDirectory(join12(".gemini", "commands")));
3925
+ for (const result of mcpResults) {
3926
+ await writeFileContent(result.filepath, result.content);
3927
+ logger.success(`Generated ${result.tool} MCP configuration: ${result.filepath}`);
3928
+ totalMcpOutputs++;
2897
3929
  }
2898
- break;
2899
- case "kiro":
2900
- deleteTasks.push(removeDirectory(config.outputPaths.kiro));
2901
- break;
2902
- case "opencode":
2903
- deleteTasks.push(removeDirectory(config.outputPaths.opencode));
2904
- break;
2905
- case "windsurf":
2906
- deleteTasks.push(removeDirectory(config.outputPaths.windsurf));
2907
- break;
3930
+ } catch (error) {
3931
+ logger.error(
3932
+ `\u274C Failed to generate MCP configurations: ${error instanceof Error ? error.message : String(error)}`
3933
+ );
3934
+ }
2908
3935
  }
3936
+ } else {
3937
+ logger.info("\nSkipping MCP configuration generation (not in --features)");
2909
3938
  }
2910
- await Promise.all(deleteTasks);
2911
- logger.info("Deleted existing output directories");
2912
- }
2913
- let totalOutputs = 0;
2914
- for (const baseDir of baseDirs) {
2915
- logger.info(`
2916
- Generating configurations for base directory: ${baseDir}`);
2917
- const outputs = await generateConfigurations(rules, config, config.defaultTargets, baseDir);
2918
- if (outputs.length === 0) {
2919
- logger.warn(`\u26A0\uFE0F No configurations generated for ${baseDir}`);
2920
- continue;
3939
+ let totalCommandOutputs = 0;
3940
+ if (normalizedFeatures.includes("commands")) {
3941
+ logger.info("\nGenerating command files...");
3942
+ for (const baseDir of baseDirs) {
3943
+ const commandResults = await generateCommands(
3944
+ process.cwd(),
3945
+ baseDir === process.cwd() ? void 0 : baseDir,
3946
+ config.defaultTargets
3947
+ );
3948
+ if (commandResults.length === 0) {
3949
+ logger.info(`No commands found for ${baseDir}`);
3950
+ continue;
3951
+ }
3952
+ for (const result of commandResults) {
3953
+ await writeFileContent(result.filepath, result.content);
3954
+ logger.success(`Generated ${result.tool} command: ${result.filepath}`);
3955
+ totalCommandOutputs++;
3956
+ }
3957
+ }
3958
+ } else {
3959
+ logger.info("\nSkipping command file generation (not in --features)");
2921
3960
  }
2922
- for (const output of outputs) {
2923
- await writeFileContent(output.filepath, output.content);
2924
- logger.success(`Generated ${output.tool} configuration: ${output.filepath}`);
3961
+ const totalIgnoreOutputs = 0;
3962
+ if (normalizedFeatures.includes("ignore")) {
3963
+ logger.info("\nGenerating ignore files...");
3964
+ logger.info("Ignore file generation is not yet implemented");
3965
+ } else {
3966
+ logger.info("\nSkipping ignore file generation (not in --features)");
2925
3967
  }
2926
- totalOutputs += outputs.length;
2927
- }
2928
- if (totalOutputs === 0) {
2929
- logger.warn("\u26A0\uFE0F No configurations generated");
2930
- return;
2931
- }
2932
- logger.info("\nGenerating MCP configurations...");
2933
- let totalMcpOutputs = 0;
2934
- for (const baseDir of baseDirs) {
2935
- try {
2936
- const mcpConfig = parseMcpConfig(process.cwd());
2937
- if (!mcpConfig || !mcpConfig.mcpServers || Object.keys(mcpConfig.mcpServers).length === 0) {
2938
- logger.info(`No MCP configuration found for ${baseDir}`);
2939
- continue;
2940
- }
2941
- const mcpResults = await generateMcpConfigurations(
2942
- mcpConfig,
2943
- baseDir === process.cwd() ? "." : baseDir,
2944
- config.defaultTargets
2945
- );
2946
- if (mcpResults.length === 0) {
2947
- logger.info(`No MCP configurations generated for ${baseDir}`);
2948
- continue;
2949
- }
2950
- for (const result of mcpResults) {
2951
- await writeFileContent(result.filepath, result.content);
2952
- logger.success(`Generated ${result.tool} MCP configuration: ${result.filepath}`);
2953
- totalMcpOutputs++;
3968
+ let totalSubagentOutputs = 0;
3969
+ if (normalizedFeatures.includes("subagents")) {
3970
+ logger.info("\nGenerating subagent files...");
3971
+ for (const baseDir of baseDirs) {
3972
+ const subagentResults = await generateSubagents(
3973
+ config.aiRulesDir,
3974
+ baseDir === process.cwd() ? void 0 : baseDir,
3975
+ config.defaultTargets,
3976
+ rules
3977
+ );
3978
+ if (subagentResults.length === 0) {
3979
+ logger.info(`No subagents generated for ${baseDir}`);
3980
+ continue;
3981
+ }
3982
+ for (const result of subagentResults) {
3983
+ await writeFileContent(result.filepath, result.content);
3984
+ logger.success(`Generated ${result.tool} subagent: ${result.filepath}`);
3985
+ totalSubagentOutputs++;
3986
+ }
2954
3987
  }
2955
- } catch (error) {
2956
- logger.error(
2957
- `\u274C Failed to generate MCP configurations: ${error instanceof Error ? error.message : String(error)}`
2958
- );
2959
- }
2960
- }
2961
- logger.info("\nGenerating command files...");
2962
- let totalCommandOutputs = 0;
2963
- for (const baseDir of baseDirs) {
2964
- const commandResults = await generateCommands(
2965
- process.cwd(),
2966
- baseDir === process.cwd() ? void 0 : baseDir,
2967
- config.defaultTargets
2968
- );
2969
- if (commandResults.length === 0) {
2970
- logger.info(`No commands found for ${baseDir}`);
2971
- continue;
3988
+ } else {
3989
+ logger.info("\nSkipping subagent file generation (not in --features)");
2972
3990
  }
2973
- for (const result of commandResults) {
2974
- await writeFileContent(result.filepath, result.content);
2975
- logger.success(`Generated ${result.tool} command: ${result.filepath}`);
2976
- totalCommandOutputs++;
3991
+ const totalGenerated = totalOutputs + totalMcpOutputs + totalCommandOutputs + totalIgnoreOutputs + totalSubagentOutputs;
3992
+ if (totalGenerated === 0) {
3993
+ const enabledFeatures = normalizedFeatures.join(", ");
3994
+ logger.warn(`\u26A0\uFE0F No files generated for enabled features: ${enabledFeatures}`);
3995
+ return;
2977
3996
  }
2978
- }
2979
- const totalGenerated = totalOutputs + totalMcpOutputs + totalCommandOutputs;
2980
- if (totalGenerated > 0) {
2981
- const parts = [];
2982
- if (totalOutputs > 0) parts.push(`${totalOutputs} configurations`);
2983
- if (totalMcpOutputs > 0) parts.push(`${totalMcpOutputs} MCP configurations`);
2984
- if (totalCommandOutputs > 0) parts.push(`${totalCommandOutputs} commands`);
2985
- logger.success(
2986
- `
3997
+ if (totalGenerated > 0) {
3998
+ const parts = [];
3999
+ if (totalOutputs > 0) parts.push(`${totalOutputs} configurations`);
4000
+ if (totalMcpOutputs > 0) parts.push(`${totalMcpOutputs} MCP configurations`);
4001
+ if (totalCommandOutputs > 0) parts.push(`${totalCommandOutputs} commands`);
4002
+ if (totalSubagentOutputs > 0) parts.push(`${totalSubagentOutputs} subagents`);
4003
+ logger.success(
4004
+ `
2987
4005
  \u{1F389} All done! Generated ${totalGenerated} file(s) total (${parts.join(" + ")})`
2988
- );
4006
+ );
4007
+ }
4008
+ } catch (error) {
4009
+ logger.error("\u274C Failed to generate configurations:", error);
4010
+ process.exit(1);
2989
4011
  }
2990
4012
  } catch (error) {
2991
- logger.error("\u274C Failed to generate configurations:", error);
4013
+ logger.error("\u274C Failed to resolve configuration:", error);
2992
4014
  process.exit(1);
2993
4015
  }
2994
4016
  }
2995
4017
 
2996
4018
  // src/cli/commands/gitignore.ts
2997
4019
  import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
2998
- import { join as join13 } from "path";
4020
+ import { join as join14 } from "path";
2999
4021
  var gitignoreCommand = async () => {
3000
- const gitignorePath = join13(process.cwd(), ".gitignore");
4022
+ const gitignorePath = join14(process.cwd(), ".gitignore");
3001
4023
  const rulesFilesToIgnore = [
3002
4024
  "# Generated by rulesync - AI tool configuration files",
3003
4025
  "**/.amazonq/rules/",
@@ -3011,7 +4033,9 @@ var gitignoreCommand = async () => {
3011
4033
  "**/CLAUDE.md",
3012
4034
  "**/.claude/memories/",
3013
4035
  "**/.claude/commands/",
4036
+ "**/.claude/agents/",
3014
4037
  "**/AGENTS.md",
4038
+ "**/.agents/",
3015
4039
  "**/.codexignore",
3016
4040
  "**/.roo/rules/",
3017
4041
  "**/.rooignore",
@@ -3019,6 +4043,8 @@ var gitignoreCommand = async () => {
3019
4043
  "**/GEMINI.md",
3020
4044
  "**/.gemini/memories/",
3021
4045
  "**/.gemini/commands/",
4046
+ "**/QWEN.md",
4047
+ "**/.qwen/memories/",
3022
4048
  "**/.aiexclude",
3023
4049
  "**/.aiignore",
3024
4050
  "**/.augmentignore",
@@ -3037,6 +4063,7 @@ var gitignoreCommand = async () => {
3037
4063
  "**/.vscode/mcp.json",
3038
4064
  "**/.codex/mcp-config.json",
3039
4065
  "**/.gemini/settings.json",
4066
+ "**/.qwen/settings.json",
3040
4067
  "**/.roo/mcp.json"
3041
4068
  ];
3042
4069
  let gitignoreContent = "";
@@ -3068,11 +4095,155 @@ ${linesToAdd.join("\n")}
3068
4095
  };
3069
4096
 
3070
4097
  // src/core/importer.ts
3071
- import { join as join20 } from "path";
3072
- import matter2 from "gray-matter";
4098
+ import { join as join23 } from "path";
4099
+ import matter3 from "gray-matter";
4100
+
4101
+ // src/parsers/agentsmd.ts
4102
+ import { join as join15 } from "path";
4103
+ async function parseAgentsMdConfiguration(baseDir = process.cwd()) {
4104
+ const errors = [];
4105
+ const rules = [];
4106
+ const projectAgentsPath = join15(baseDir, "AGENTS.md");
4107
+ if (await fileExists(projectAgentsPath)) {
4108
+ try {
4109
+ const content = await readFileContent(projectAgentsPath);
4110
+ if (content.trim()) {
4111
+ const frontmatter = {
4112
+ root: true,
4113
+ targets: ["agentsmd"],
4114
+ description: "Project-level AGENTS.md instructions",
4115
+ globs: ["**/*"]
4116
+ };
4117
+ rules.push({
4118
+ frontmatter,
4119
+ content: content.trim(),
4120
+ filename: "project-instructions",
4121
+ filepath: projectAgentsPath
4122
+ });
4123
+ }
4124
+ } catch (error) {
4125
+ const errorMessage = error instanceof Error ? error.message : String(error);
4126
+ errors.push(`Failed to parse AGENTS.md: ${errorMessage}`);
4127
+ }
4128
+ }
4129
+ const memoriesDir = join15(baseDir, ".agents", "memories");
4130
+ if (await fileExists(memoriesDir)) {
4131
+ try {
4132
+ const { readdir: readdir2, stat } = await import("fs/promises");
4133
+ const memoriesPath = memoriesDir;
4134
+ const memoriesStat = await stat(memoriesPath);
4135
+ if (memoriesStat.isDirectory()) {
4136
+ const files = await readdir2(memoriesPath);
4137
+ for (const file of files) {
4138
+ if (file.endsWith(".md")) {
4139
+ const filePath = join15(memoriesPath, file);
4140
+ try {
4141
+ const content = await readFileContent(filePath);
4142
+ if (content.trim()) {
4143
+ const filename = file.replace(/\.md$/, "");
4144
+ const frontmatter = {
4145
+ root: false,
4146
+ targets: ["agentsmd"],
4147
+ description: `AGENTS.md memory: ${filename}`,
4148
+ globs: ["**/*"]
4149
+ };
4150
+ rules.push({
4151
+ frontmatter,
4152
+ content: content.trim(),
4153
+ filename,
4154
+ filepath: filePath
4155
+ });
4156
+ }
4157
+ } catch (error) {
4158
+ const errorMessage = error instanceof Error ? error.message : String(error);
4159
+ errors.push(`Failed to parse memory file ${file}: ${errorMessage}`);
4160
+ }
4161
+ }
4162
+ }
4163
+ }
4164
+ } catch {
4165
+ }
4166
+ }
4167
+ try {
4168
+ const { readdir: readdir2 } = await import("fs/promises");
4169
+ const files = await readdir2(baseDir);
4170
+ for (const file of files) {
4171
+ if (file === "AGENTS.md") continue;
4172
+ if (file.endsWith(".md") && (file.includes("agents") || file.includes("instructions") || file.includes("guidelines") || file.includes("rules"))) {
4173
+ const filePath = join15(baseDir, file);
4174
+ try {
4175
+ const content = await readFileContent(filePath);
4176
+ if (content.trim()) {
4177
+ const filename = file.replace(/\.md$/, "");
4178
+ const frontmatter = {
4179
+ root: false,
4180
+ targets: ["agentsmd"],
4181
+ description: `AGENTS.md instructions: ${filename}`,
4182
+ globs: ["**/*"]
4183
+ };
4184
+ rules.push({
4185
+ frontmatter,
4186
+ content: content.trim(),
4187
+ filename,
4188
+ filepath: filePath
4189
+ });
4190
+ }
4191
+ } catch (error) {
4192
+ const errorMessage = error instanceof Error ? error.message : String(error);
4193
+ errors.push(`Failed to parse ${file}: ${errorMessage}`);
4194
+ }
4195
+ }
4196
+ }
4197
+ for (const file of files) {
4198
+ const filePath = join15(baseDir, file);
4199
+ try {
4200
+ const { stat } = await import("fs/promises");
4201
+ const stats = await stat(filePath);
4202
+ if (stats.isDirectory() && !file.startsWith(".") && file !== "node_modules") {
4203
+ const subAgentsPath = join15(filePath, "AGENTS.md");
4204
+ if (await fileExists(subAgentsPath)) {
4205
+ try {
4206
+ const content = await readFileContent(subAgentsPath);
4207
+ if (content.trim()) {
4208
+ const frontmatter = {
4209
+ root: false,
4210
+ targets: ["agentsmd"],
4211
+ description: `Directory-specific AGENTS.md instructions: ${file}`,
4212
+ globs: [`${file}/**/*`]
4213
+ };
4214
+ rules.push({
4215
+ frontmatter,
4216
+ content: content.trim(),
4217
+ filename: `${file}-agents`,
4218
+ filepath: subAgentsPath
4219
+ });
4220
+ }
4221
+ } catch (error) {
4222
+ const errorMessage = error instanceof Error ? error.message : String(error);
4223
+ errors.push(`Failed to parse ${subAgentsPath}: ${errorMessage}`);
4224
+ }
4225
+ }
4226
+ }
4227
+ } catch {
4228
+ }
4229
+ }
4230
+ } catch (error) {
4231
+ const errorMessage = error instanceof Error ? error.message : String(error);
4232
+ errors.push(`Failed to scan directory for AGENTS.md files: ${errorMessage}`);
4233
+ }
4234
+ if (rules.length === 0) {
4235
+ errors.push(
4236
+ "No AGENTS.md configuration files found. Expected to find AGENTS.md in the project root or memory files in .agents/memories/."
4237
+ );
4238
+ }
4239
+ return {
4240
+ rules,
4241
+ errors
4242
+ };
4243
+ }
3073
4244
 
3074
4245
  // src/parsers/shared-helpers.ts
3075
- import { basename as basename3, join as join14 } from "path";
4246
+ import { basename as basename3, join as join16 } from "path";
3076
4247
  async function parseConfigurationFiles(baseDir = process.cwd(), config) {
3077
4248
  const errors = [];
3078
4249
  const rules = [];
@@ -3125,11 +4296,11 @@ async function parseConfigurationFiles(baseDir = process.cwd(), config) {
3125
4296
  const dirPath = resolvePath(dirConfig.directory, baseDir);
3126
4297
  if (await fileExists(dirPath)) {
3127
4298
  const result = await safeAsyncOperation(async () => {
3128
- const { readdir } = await import("fs/promises");
3129
- const files = await readdir(dirPath);
4299
+ const { readdir: readdir2 } = await import("fs/promises");
4300
+ const files = await readdir2(dirPath);
3130
4301
  for (const file of files) {
3131
4302
  if (file.endsWith(dirConfig.filePattern)) {
3132
- const filePath = join14(dirPath, file);
4303
+ const filePath = join16(dirPath, file);
3133
4304
  const fileResult = await safeAsyncOperation(async () => {
3134
4305
  const rawContent = await readFileContent(filePath);
3135
4306
  let content;
@@ -3276,11 +4447,11 @@ function parseMainFile(content, filepath, config) {
3276
4447
  async function parseMemoryFiles(memoryDir, config) {
3277
4448
  const rules = [];
3278
4449
  try {
3279
- const { readdir } = await import("fs/promises");
3280
- const files = await readdir(memoryDir);
4450
+ const { readdir: readdir2 } = await import("fs/promises");
4451
+ const files = await readdir2(memoryDir);
3281
4452
  for (const file of files) {
3282
4453
  if (file.endsWith(".md")) {
3283
- const filePath = join14(memoryDir, file);
4454
+ const filePath = join16(memoryDir, file);
3284
4455
  const content = await readFileContent(filePath);
3285
4456
  if (content.trim()) {
3286
4457
  const filename = basename3(file, ".md");
@@ -3306,11 +4477,11 @@ async function parseMemoryFiles(memoryDir, config) {
3306
4477
  async function parseCommandsFiles(commandsDir, config) {
3307
4478
  const rules = [];
3308
4479
  try {
3309
- const { readdir } = await import("fs/promises");
3310
- const files = await readdir(commandsDir);
4480
+ const { readdir: readdir2 } = await import("fs/promises");
4481
+ const files = await readdir2(commandsDir);
3311
4482
  for (const file of files) {
3312
4483
  if (file.endsWith(".md")) {
3313
- const filePath = join14(commandsDir, file);
4484
+ const filePath = join16(commandsDir, file);
3314
4485
  const content = await readFileContent(filePath);
3315
4486
  if (content.trim()) {
3316
4487
  const filename = basename3(file, ".md");
@@ -3402,7 +4573,7 @@ async function parseAmazonqcliConfiguration(baseDir = process.cwd()) {
3402
4573
  }
3403
4574
 
3404
4575
  // src/parsers/augmentcode.ts
3405
- import { basename as basename4, join as join15 } from "path";
4576
+ import { basename as basename4, join as join17 } from "path";
3406
4577
 
3407
4578
  // src/utils/parser-helpers.ts
3408
4579
  function createParseResult() {
@@ -3450,7 +4621,7 @@ async function parseAugmentcodeLegacyConfiguration(baseDir = process.cwd()) {
3450
4621
  async function parseUnifiedAugmentcode(baseDir, config) {
3451
4622
  const result = createParseResult();
3452
4623
  if (config.rulesDir) {
3453
- const rulesDir = join15(baseDir, config.rulesDir);
4624
+ const rulesDir = join17(baseDir, config.rulesDir);
3454
4625
  if (await fileExists(rulesDir)) {
3455
4626
  const rulesResult = await parseAugmentRules(rulesDir, config);
3456
4627
  addRules(result, rulesResult.rules);
@@ -3463,7 +4634,7 @@ async function parseUnifiedAugmentcode(baseDir, config) {
3463
4634
  }
3464
4635
  }
3465
4636
  if (config.legacyFilePath) {
3466
- const legacyPath = join15(baseDir, config.legacyFilePath);
4637
+ const legacyPath = join17(baseDir, config.legacyFilePath);
3467
4638
  if (await fileExists(legacyPath)) {
3468
4639
  const legacyResult = await parseAugmentGuidelines(legacyPath, config);
3469
4640
  if (legacyResult.rule) {
@@ -3483,11 +4654,11 @@ async function parseAugmentRules(rulesDir, config) {
3483
4654
  const rules = [];
3484
4655
  const errors = [];
3485
4656
  try {
3486
- const { readdir } = await import("fs/promises");
3487
- const files = await readdir(rulesDir);
4657
+ const { readdir: readdir2 } = await import("fs/promises");
4658
+ const files = await readdir2(rulesDir);
3488
4659
  for (const file of files) {
3489
4660
  if (file.endsWith(".md") || file.endsWith(".mdc")) {
3490
- const filePath = join15(rulesDir, file);
4661
+ const filePath = join17(rulesDir, file);
3491
4662
  try {
3492
4663
  const rawContent = await readFileContent(filePath);
3493
4664
  const parsed = parseFrontmatter(rawContent);
@@ -3554,7 +4725,7 @@ async function parseAugmentGuidelines(guidelinesPath, config) {
3554
4725
 
3555
4726
  // src/parsers/claudecode.ts
3556
4727
  async function parseClaudeConfiguration(baseDir = process.cwd()) {
3557
- return parseMemoryBasedConfiguration(baseDir, {
4728
+ const memoryResult = await parseMemoryBasedConfiguration(baseDir, {
3558
4729
  tool: "claudecode",
3559
4730
  mainFileName: "CLAUDE.md",
3560
4731
  memoryDirPath: ".claude/memories",
@@ -3564,6 +4735,24 @@ async function parseClaudeConfiguration(baseDir = process.cwd()) {
3564
4735
  filenamePrefix: "claude",
3565
4736
  commandsDirPath: ".claude/commands"
3566
4737
  });
4738
+ const result = {
4739
+ rules: memoryResult.rules,
4740
+ errors: memoryResult.errors
4741
+ };
4742
+ if (memoryResult.ignorePatterns) {
4743
+ result.ignorePatterns = memoryResult.ignorePatterns;
4744
+ }
4745
+ if (memoryResult.mcpServers) {
4746
+ result.mcpServers = memoryResult.mcpServers;
4747
+ }
4748
+ const agentsDir = resolvePath(".claude/agents", baseDir);
4749
+ if (await fileExists(agentsDir)) {
4750
+ const subagents = await parseSubagentsFromDirectory(agentsDir);
4751
+ if (subagents.length > 0) {
4752
+ result.subagents = subagents;
4753
+ }
4754
+ }
4755
+ return result;
3567
4756
  }
3568
4757
 
3569
4758
  // src/parsers/cline.ts
@@ -3587,7 +4776,7 @@ async function parseClineConfiguration(baseDir = process.cwd()) {
3587
4776
  }
3588
4777
 
3589
4778
  // src/parsers/codexcli.ts
3590
- import { join as join16 } from "path";
4779
+ import { join as join18 } from "path";
3591
4780
 
3592
4781
  // src/parsers/copilot.ts
3593
4782
  async function parseCopilotConfiguration(baseDir = process.cwd()) {
@@ -3610,9 +4799,9 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
3610
4799
  }
3611
4800
 
3612
4801
  // src/parsers/cursor.ts
3613
- import { basename as basename5, join as join17 } from "path";
4802
+ import { basename as basename5, join as join19 } from "path";
3614
4803
  import { DEFAULT_SCHEMA, FAILSAFE_SCHEMA, load } from "js-yaml";
3615
- import { z as z7 } from "zod/mini";
4804
+ import { z as z8 } from "zod/mini";
3616
4805
  var customMatterOptions = {
3617
4806
  engines: {
3618
4807
  yaml: {
@@ -3640,7 +4829,7 @@ var customMatterOptions = {
3640
4829
  }
3641
4830
  };
3642
4831
  function convertCursorMdcFrontmatter(cursorFrontmatter, _filename) {
3643
- const FrontmatterSchema = z7.record(z7.string(), z7.unknown());
4832
+ const FrontmatterSchema = z8.record(z8.string(), z8.unknown());
3644
4833
  const parseResult = FrontmatterSchema.safeParse(cursorFrontmatter);
3645
4834
  if (!parseResult.success) {
3646
4835
  return {
@@ -3734,7 +4923,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3734
4923
  const rules = [];
3735
4924
  let ignorePatterns;
3736
4925
  let mcpServers;
3737
- const cursorFilePath = join17(baseDir, ".cursorrules");
4926
+ const cursorFilePath = join19(baseDir, ".cursorrules");
3738
4927
  if (await fileExists(cursorFilePath)) {
3739
4928
  try {
3740
4929
  const rawContent = await readFileContent(cursorFilePath);
@@ -3755,14 +4944,14 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3755
4944
  errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
3756
4945
  }
3757
4946
  }
3758
- const cursorRulesDir = join17(baseDir, ".cursor", "rules");
4947
+ const cursorRulesDir = join19(baseDir, ".cursor", "rules");
3759
4948
  if (await fileExists(cursorRulesDir)) {
3760
4949
  try {
3761
- const { readdir } = await import("fs/promises");
3762
- const files = await readdir(cursorRulesDir);
4950
+ const { readdir: readdir2 } = await import("fs/promises");
4951
+ const files = await readdir2(cursorRulesDir);
3763
4952
  for (const file of files) {
3764
4953
  if (file.endsWith(".mdc")) {
3765
- const filePath = join17(cursorRulesDir, file);
4954
+ const filePath = join19(cursorRulesDir, file);
3766
4955
  try {
3767
4956
  const rawContent = await readFileContent(filePath);
3768
4957
  const parsed = parseFrontmatter(rawContent, { matterOptions: customMatterOptions });
@@ -3791,7 +4980,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3791
4980
  if (rules.length === 0) {
3792
4981
  errors.push("No Cursor configuration files found (.cursorrules or .cursor/rules/*.mdc)");
3793
4982
  }
3794
- const cursorIgnorePath = join17(baseDir, ".cursorignore");
4983
+ const cursorIgnorePath = join19(baseDir, ".cursorignore");
3795
4984
  if (await fileExists(cursorIgnorePath)) {
3796
4985
  try {
3797
4986
  const content = await readFileContent(cursorIgnorePath);
@@ -3804,7 +4993,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3804
4993
  errors.push(`Failed to parse .cursorignore: ${errorMessage}`);
3805
4994
  }
3806
4995
  }
3807
- const cursorMcpPath = join17(baseDir, ".cursor", "mcp.json");
4996
+ const cursorMcpPath = join19(baseDir, ".cursor", "mcp.json");
3808
4997
  if (await fileExists(cursorMcpPath)) {
3809
4998
  try {
3810
4999
  const content = await readFileContent(cursorMcpPath);
@@ -3827,6 +5016,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
3827
5016
  }
3828
5017
 
3829
5018
  // src/parsers/geminicli.ts
5019
+ import { basename as basename6, join as join20 } from "path";
3830
5020
  async function parseAiexclude(aiexcludePath) {
3831
5021
  try {
3832
5022
  const content = await readFileContent(aiexcludePath);
@@ -3836,8 +5026,51 @@ async function parseAiexclude(aiexcludePath) {
3836
5026
  return [];
3837
5027
  }
3838
5028
  }
5029
+ async function parseGeminiCommands(commandsDir) {
5030
+ const rules = [];
5031
+ try {
5032
+ const { readdir: readdir2 } = await import("fs/promises");
5033
+ const { parse } = await import("smol-toml");
5034
+ const files = await readdir2(commandsDir);
5035
+ for (const file of files) {
5036
+ if (file.endsWith(".toml")) {
5037
+ const filePath = join20(commandsDir, file);
5038
+ const content = await readFileContent(filePath);
5039
+ if (content.trim()) {
5040
+ const filename = basename6(file, ".toml");
5041
+ try {
5042
+ const parsed = parse(content);
5043
+ if (typeof parsed !== "object" || parsed === null) {
5044
+ continue;
5045
+ }
5046
+ const commandConfig = parsed;
5047
+ if (typeof commandConfig.prompt === "string") {
5048
+ const description = typeof commandConfig.description === "string" ? commandConfig.description : `Command: ${filename}`;
5049
+ const frontmatter = {
5050
+ root: false,
5051
+ targets: ["geminicli"],
5052
+ description,
5053
+ globs: ["**/*"]
5054
+ };
5055
+ rules.push({
5056
+ frontmatter,
5057
+ content: commandConfig.prompt,
5058
+ filename,
5059
+ filepath: filePath,
5060
+ type: "command"
5061
+ });
5062
+ }
5063
+ } catch {
5064
+ }
5065
+ }
5066
+ }
5067
+ }
5068
+ } catch {
5069
+ }
5070
+ return rules;
5071
+ }
3839
5072
  async function parseGeminiConfiguration(baseDir = process.cwd()) {
3840
- return parseMemoryBasedConfiguration(baseDir, {
5073
+ const result = await parseMemoryBasedConfiguration(baseDir, {
3841
5074
  tool: "geminicli",
3842
5075
  mainFileName: "GEMINI.md",
3843
5076
  memoryDirPath: ".gemini/memories",
@@ -3848,17 +5081,23 @@ async function parseGeminiConfiguration(baseDir = process.cwd()) {
3848
5081
  additionalIgnoreFile: {
3849
5082
  path: ".aiexclude",
3850
5083
  parser: parseAiexclude
3851
- },
3852
- commandsDirPath: ".gemini/commands"
5084
+ }
5085
+ // commandsDirPath is removed - Gemini uses .toml files which need special handling
3853
5086
  });
5087
+ const commandsDir = resolvePath(".gemini/commands", baseDir);
5088
+ if (await fileExists(commandsDir)) {
5089
+ const commandsRules = await parseGeminiCommands(commandsDir);
5090
+ result.rules.push(...commandsRules);
5091
+ }
5092
+ return result;
3854
5093
  }
3855
5094
 
3856
5095
  // src/parsers/junie.ts
3857
- import { join as join18 } from "path";
5096
+ import { join as join21 } from "path";
3858
5097
  async function parseJunieConfiguration(baseDir = process.cwd()) {
3859
5098
  const errors = [];
3860
5099
  const rules = [];
3861
- const guidelinesPath = join18(baseDir, ".junie", "guidelines.md");
5100
+ const guidelinesPath = join21(baseDir, ".junie", "guidelines.md");
3862
5101
  if (!await fileExists(guidelinesPath)) {
3863
5102
  errors.push(".junie/guidelines.md file not found");
3864
5103
  return { rules, errors };
@@ -3915,6 +5154,22 @@ async function parseOpenCodeConfiguration(baseDir = process.cwd()) {
3915
5154
  });
3916
5155
  }
3917
5156
 
5157
+ // src/parsers/qwencode.ts
5158
+ async function parseQwenConfiguration(baseDir = process.cwd()) {
5159
+ return parseMemoryBasedConfiguration(baseDir, {
5160
+ tool: "qwencode",
5161
+ mainFileName: "QWEN.md",
5162
+ memoryDirPath: ".qwen/memories",
5163
+ settingsPath: ".qwen/settings.json",
5164
+ mainDescription: "Main Qwen Code configuration",
5165
+ memoryDescription: "Memory file",
5166
+ filenamePrefix: "qwen",
5167
+ // Qwen Code uses git-aware filtering instead of dedicated ignore files
5168
+ // additionalIgnoreFile is omitted
5169
+ commandsDirPath: ".qwen/commands"
5170
+ });
5171
+ }
5172
+
3918
5173
  // src/parsers/roo.ts
3919
5174
  async function parseRooConfiguration(baseDir = process.cwd()) {
3920
5175
  return parseConfigurationFiles(baseDir, {
@@ -3936,13 +5191,15 @@ async function parseRooConfiguration(baseDir = process.cwd()) {
3936
5191
  }
3937
5192
 
3938
5193
  // src/parsers/windsurf.ts
3939
- import { readFile } from "fs/promises";
3940
- import { join as join19 } from "path";
5194
+ import { readFile as readFile2 } from "fs/promises";
5195
+ import { join as join22 } from "path";
3941
5196
 
3942
5197
  // src/core/importer.ts
3943
5198
  async function importConfiguration(options) {
3944
5199
  const {
3945
5200
  tool,
5201
+ features = ["rules", "commands", "mcp", "ignore", "subagents"],
5202
+ // Default to all features for backward compatibility
3946
5203
  baseDir = process.cwd(),
3947
5204
  rulesDir = ".rulesync",
3948
5205
  verbose = false,
@@ -3952,11 +5209,18 @@ async function importConfiguration(options) {
3952
5209
  let rules = [];
3953
5210
  let ignorePatterns;
3954
5211
  let mcpServers;
5212
+ let subagents;
3955
5213
  if (verbose) {
3956
5214
  logger.log(`Importing ${tool} configuration from ${baseDir}...`);
3957
5215
  }
3958
5216
  try {
3959
5217
  switch (tool) {
5218
+ case "agentsmd": {
5219
+ const agentsmdResult = await parseAgentsMdConfiguration(baseDir);
5220
+ rules = agentsmdResult.rules;
5221
+ errors.push(...agentsmdResult.errors);
5222
+ break;
5223
+ }
3960
5224
  case "amazonqcli": {
3961
5225
  const amazonqResult = await parseAmazonqcliConfiguration(baseDir);
3962
5226
  rules = amazonqResult.rules;
@@ -3982,6 +5246,7 @@ async function importConfiguration(options) {
3982
5246
  errors.push(...claudeResult.errors);
3983
5247
  ignorePatterns = claudeResult.ignorePatterns;
3984
5248
  mcpServers = claudeResult.mcpServers;
5249
+ subagents = claudeResult.subagents;
3985
5250
  break;
3986
5251
  }
3987
5252
  case "cursor": {
@@ -4032,6 +5297,13 @@ async function importConfiguration(options) {
4032
5297
  mcpServers = opencodeResult.mcpServers;
4033
5298
  break;
4034
5299
  }
5300
+ case "qwencode": {
5301
+ const qwenResult = await parseQwenConfiguration(baseDir);
5302
+ rules = qwenResult.rules;
5303
+ errors.push(...qwenResult.errors);
5304
+ mcpServers = qwenResult.mcpServers;
5305
+ break;
5306
+ }
4035
5307
  default:
4036
5308
  errors.push(`Unsupported tool: ${tool}`);
4037
5309
  return { success: false, rulesCreated: 0, errors };
@@ -4041,10 +5313,20 @@ async function importConfiguration(options) {
4041
5313
  errors.push(`Failed to parse ${tool} configuration: ${errorMessage}`);
4042
5314
  return { success: false, rulesCreated: 0, errors };
4043
5315
  }
4044
- if (rules.length === 0 && !ignorePatterns && !mcpServers) {
5316
+ if (rules.length === 0 && !ignorePatterns && !mcpServers && !subagents) {
4045
5317
  return { success: false, rulesCreated: 0, errors };
4046
5318
  }
4047
- const rulesDirPath = join20(baseDir, rulesDir);
5319
+ const rulesEnabled = features.includes("rules") || features.includes("commands");
5320
+ const ignoreEnabled = features.includes("ignore");
5321
+ const mcpEnabled = features.includes("mcp");
5322
+ const subagentsEnabled = features.includes("subagents");
5323
+ if (!rulesEnabled && !ignoreEnabled && !mcpEnabled && !subagentsEnabled) {
5324
+ if (verbose) {
5325
+ logger.log("No relevant features enabled for import");
5326
+ }
5327
+ return { success: false, rulesCreated: 0, errors: ["No features enabled for import"] };
5328
+ }
5329
+ const rulesDirPath = join23(baseDir, rulesDir);
4048
5330
  try {
4049
5331
  const { mkdir: mkdir2 } = await import("fs/promises");
4050
5332
  await mkdir2(rulesDirPath, { recursive: true });
@@ -4054,37 +5336,49 @@ async function importConfiguration(options) {
4054
5336
  return { success: false, rulesCreated: 0, errors };
4055
5337
  }
4056
5338
  let rulesCreated = 0;
4057
- for (const rule of rules) {
4058
- try {
4059
- const baseFilename = rule.filename;
4060
- let targetDir = rulesDirPath;
4061
- if (rule.type === "command") {
4062
- targetDir = join20(rulesDirPath, "commands");
4063
- const { mkdir: mkdir2 } = await import("fs/promises");
4064
- await mkdir2(targetDir, { recursive: true });
4065
- } else {
4066
- if (!useLegacyLocation) {
4067
- targetDir = join20(rulesDirPath, "rules");
5339
+ if (rulesEnabled) {
5340
+ for (const rule of rules) {
5341
+ try {
5342
+ const baseFilename = rule.filename;
5343
+ let targetDir = rulesDirPath;
5344
+ if (rule.type === "command") {
5345
+ if (!features.includes("commands")) {
5346
+ continue;
5347
+ }
5348
+ targetDir = join23(rulesDirPath, "commands");
4068
5349
  const { mkdir: mkdir2 } = await import("fs/promises");
4069
5350
  await mkdir2(targetDir, { recursive: true });
5351
+ } else {
5352
+ if (!features.includes("rules")) {
5353
+ continue;
5354
+ }
5355
+ if (!useLegacyLocation) {
5356
+ targetDir = join23(rulesDirPath, "rules");
5357
+ const { mkdir: mkdir2 } = await import("fs/promises");
5358
+ await mkdir2(targetDir, { recursive: true });
5359
+ }
4070
5360
  }
5361
+ const filePath = join23(targetDir, `${baseFilename}.md`);
5362
+ const content = generateRuleFileContent(rule);
5363
+ await writeFileContent(filePath, content);
5364
+ rulesCreated++;
5365
+ if (verbose) {
5366
+ logger.success(`Created rule file: ${filePath}`);
5367
+ }
5368
+ } catch (error) {
5369
+ const errorMessage = error instanceof Error ? error.message : String(error);
5370
+ errors.push(`Failed to create rule file for ${rule.filename}: ${errorMessage}`);
4071
5371
  }
4072
- const filePath = join20(targetDir, `${baseFilename}.md`);
4073
- const content = generateRuleFileContent(rule);
4074
- await writeFileContent(filePath, content);
4075
- rulesCreated++;
4076
- if (verbose) {
4077
- logger.success(`Created rule file: ${filePath}`);
4078
- }
4079
- } catch (error) {
4080
- const errorMessage = error instanceof Error ? error.message : String(error);
4081
- errors.push(`Failed to create rule file for ${rule.filename}: ${errorMessage}`);
5372
+ }
5373
+ } else {
5374
+ if (verbose && rules.length > 0) {
5375
+ logger.log(`Skipping ${rules.length} rule(s) (rules/commands features not enabled)`);
4082
5376
  }
4083
5377
  }
4084
5378
  let ignoreFileCreated = false;
4085
- if (ignorePatterns && ignorePatterns.length > 0) {
5379
+ if (ignoreEnabled && ignorePatterns && ignorePatterns.length > 0) {
4086
5380
  try {
4087
- const rulesyncignorePath = join20(baseDir, ".rulesyncignore");
5381
+ const rulesyncignorePath = join23(baseDir, ".rulesyncignore");
4088
5382
  const ignoreContent = `${ignorePatterns.join("\n")}
4089
5383
  `;
4090
5384
  await writeFileContent(rulesyncignorePath, ignoreContent);
@@ -4096,11 +5390,13 @@ async function importConfiguration(options) {
4096
5390
  const errorMessage = error instanceof Error ? error.message : String(error);
4097
5391
  errors.push(`Failed to create .rulesyncignore: ${errorMessage}`);
4098
5392
  }
5393
+ } else if (verbose && ignorePatterns && ignorePatterns.length > 0 && !ignoreEnabled) {
5394
+ logger.log(`Skipping ignore patterns (ignore feature not enabled)`);
4099
5395
  }
4100
5396
  let mcpFileCreated = false;
4101
- if (mcpServers && Object.keys(mcpServers).length > 0) {
5397
+ if (mcpEnabled && mcpServers && Object.keys(mcpServers).length > 0) {
4102
5398
  try {
4103
- const mcpPath = join20(baseDir, rulesDir, ".mcp.json");
5399
+ const mcpPath = join23(baseDir, rulesDir, ".mcp.json");
4104
5400
  const mcpContent = `${JSON.stringify({ mcpServers }, null, 2)}
4105
5401
  `;
4106
5402
  await writeFileContent(mcpPath, mcpContent);
@@ -4112,14 +5408,51 @@ async function importConfiguration(options) {
4112
5408
  const errorMessage = error instanceof Error ? error.message : String(error);
4113
5409
  errors.push(`Failed to create .mcp.json: ${errorMessage}`);
4114
5410
  }
5411
+ } else if (verbose && mcpServers && Object.keys(mcpServers).length > 0 && !mcpEnabled) {
5412
+ logger.log(`Skipping MCP configuration (mcp feature not enabled)`);
4115
5413
  }
4116
- return {
4117
- success: errors.length === 0 && (rulesCreated > 0 || ignoreFileCreated || mcpFileCreated),
5414
+ let subagentsCreated = 0;
5415
+ if (subagentsEnabled && subagents && subagents.length > 0) {
5416
+ try {
5417
+ const { mkdir: mkdir2 } = await import("fs/promises");
5418
+ const subagentsDir = join23(baseDir, rulesDir, "subagents");
5419
+ await mkdir2(subagentsDir, { recursive: true });
5420
+ for (const subagent of subagents) {
5421
+ try {
5422
+ const filename = `${subagent.filename}.md`;
5423
+ const filepath = join23(subagentsDir, filename);
5424
+ const content = generateSubagentFileContent(subagent);
5425
+ await writeFileContent(filepath, content);
5426
+ subagentsCreated++;
5427
+ if (verbose) {
5428
+ logger.success(`Created subagent: ${filename}`);
5429
+ }
5430
+ } catch (error) {
5431
+ const errorMessage = error instanceof Error ? error.message : String(error);
5432
+ errors.push(`Failed to create subagent ${subagent.filename}: ${errorMessage}`);
5433
+ }
5434
+ }
5435
+ if (verbose && subagentsCreated > 0) {
5436
+ logger.success(`Created ${subagentsCreated} subagent files`);
5437
+ }
5438
+ } catch (error) {
5439
+ const errorMessage = error instanceof Error ? error.message : String(error);
5440
+ errors.push(`Failed to create subagents directory: ${errorMessage}`);
5441
+ }
5442
+ } else if (verbose && subagents && subagents.length > 0 && !subagentsEnabled) {
5443
+ logger.log(`Skipping subagents (subagents feature not enabled)`);
5444
+ }
5445
+ const result = {
5446
+ success: errors.length === 0 && (rulesCreated > 0 || ignoreFileCreated || mcpFileCreated || subagentsCreated > 0),
4118
5447
  rulesCreated,
4119
5448
  errors,
4120
5449
  ignoreFileCreated,
4121
5450
  mcpFileCreated
4122
5451
  };
5452
+ if (subagentsCreated > 0) {
5453
+ result.subagentsCreated = subagentsCreated;
5454
+ }
5455
+ return result;
4123
5456
  }
4124
5457
  function generateRuleFileContent(rule) {
4125
5458
  if (rule.type === "command") {
@@ -4127,68 +5460,108 @@ function generateRuleFileContent(rule) {
4127
5460
  description: rule.frontmatter.description,
4128
5461
  targets: rule.frontmatter.targets
4129
5462
  };
4130
- const frontmatter2 = matter2.stringify("", simplifiedFrontmatter);
5463
+ const frontmatter2 = matter3.stringify("", simplifiedFrontmatter);
4131
5464
  return frontmatter2 + rule.content;
4132
5465
  }
4133
- const frontmatter = matter2.stringify("", rule.frontmatter);
5466
+ const frontmatter = matter3.stringify("", rule.frontmatter);
4134
5467
  return frontmatter + rule.content;
4135
5468
  }
5469
+ function generateSubagentFileContent(subagent) {
5470
+ const frontmatter = matter3.stringify("", subagent.frontmatter);
5471
+ return frontmatter + subagent.content;
5472
+ }
4136
5473
 
4137
5474
  // src/cli/commands/import.ts
4138
5475
  async function importCommand(options = {}) {
4139
5476
  logger.setVerbose(options.verbose || false);
4140
- const tools = [];
4141
- if (options.amazonqcli) tools.push("amazonqcli");
4142
- if (options.augmentcode) tools.push("augmentcode");
4143
- if (options["augmentcode-legacy"]) tools.push("augmentcode-legacy");
4144
- if (options.claudecode) tools.push("claudecode");
4145
- if (options.cursor) tools.push("cursor");
4146
- if (options.copilot) tools.push("copilot");
4147
- if (options.cline) tools.push("cline");
4148
- if (options.roo) tools.push("roo");
4149
- if (options.geminicli) tools.push("geminicli");
4150
- if (options.opencode) tools.push("opencode");
4151
- if (tools.length === 0) {
4152
- logger.error(
4153
- "\u274C Please specify one tool to import from (--amazonqcli, --augmentcode, --augmentcode-legacy, --claudecode, --cursor, --copilot, --cline, --roo, --geminicli, --opencode)"
5477
+ let resolvedFeatures;
5478
+ let showWarning = false;
5479
+ if (options.features !== void 0) {
5480
+ resolvedFeatures = options.features;
5481
+ } else {
5482
+ resolvedFeatures = "*";
5483
+ showWarning = true;
5484
+ }
5485
+ if (showWarning) {
5486
+ showBackwardCompatibilityWarning(
5487
+ "importing",
5488
+ "rulesync import --targets cursor,copilot --features rules,mcp,ignore"
4154
5489
  );
5490
+ }
5491
+ const normalizedFeatures = normalizeFeatures(resolvedFeatures);
5492
+ let tools = [];
5493
+ if (options.targets && options.targets.length > 0) {
5494
+ tools = options.targets;
5495
+ } else {
5496
+ if (options.agentsmd) tools.push("agentsmd");
5497
+ if (options.amazonqcli) tools.push("amazonqcli");
5498
+ if (options.augmentcode) tools.push("augmentcode");
5499
+ if (options["augmentcode-legacy"]) tools.push("augmentcode-legacy");
5500
+ if (options.claudecode) tools.push("claudecode");
5501
+ if (options.cursor) tools.push("cursor");
5502
+ if (options.copilot) tools.push("copilot");
5503
+ if (options.cline) tools.push("cline");
5504
+ if (options.roo) tools.push("roo");
5505
+ if (options.geminicli) tools.push("geminicli");
5506
+ if (options.junie) tools.push("junie");
5507
+ if (options.qwencode) tools.push("qwencode");
5508
+ if (options.opencode) tools.push("opencode");
5509
+ }
5510
+ if (tools.length === 0) {
5511
+ logger.error("\u274C Please specify a tool to import from using --targets <tool>.");
5512
+ logger.info("Example: rulesync import --targets cursor");
4155
5513
  process.exit(1);
4156
5514
  }
4157
5515
  if (tools.length > 1) {
4158
5516
  logger.error(
4159
- "\u274C Only one tool can be specified at a time. Please run the import command separately for each tool."
5517
+ `\u274C Import command only supports a single target.
5518
+ You specified: ${tools.join(", ")}
5519
+
5520
+ Please run the command separately for each tool:`
4160
5521
  );
5522
+ for (const tool2 of tools) {
5523
+ logger.info(` rulesync import --targets ${tool2}`);
5524
+ }
4161
5525
  process.exit(1);
4162
5526
  }
4163
5527
  const tool = tools[0];
4164
5528
  if (!tool) {
4165
- logger.error("Error: No tool specified");
5529
+ logger.error("\u274C Unexpected error: No tool selected");
4166
5530
  process.exit(1);
4167
5531
  }
4168
5532
  logger.log(`Importing configuration files from ${tool}...`);
4169
5533
  try {
4170
5534
  const result = await importConfiguration({
4171
5535
  tool,
5536
+ features: normalizedFeatures,
4172
5537
  verbose: options.verbose ?? false,
4173
5538
  useLegacyLocation: options.legacy ?? false
4174
5539
  });
4175
5540
  if (result.success) {
4176
- logger.success(`Imported ${result.rulesCreated} rule(s) from ${tool}`);
5541
+ logger.success(`\u2705 Imported ${result.rulesCreated} rule(s) from ${tool}`);
4177
5542
  if (result.ignoreFileCreated) {
4178
- logger.success("Created .rulesyncignore file from ignore patterns");
5543
+ logger.success(" Created .rulesyncignore file from ignore patterns");
4179
5544
  }
4180
5545
  if (result.mcpFileCreated) {
4181
- logger.success("Created .rulesync/.mcp.json file from MCP configuration");
5546
+ logger.success(" Created .rulesync/.mcp.json file from MCP configuration");
5547
+ }
5548
+ if (result.subagentsCreated) {
5549
+ logger.success(` Created ${result.subagentsCreated} subagent files`);
4182
5550
  }
5551
+ logger.success(`
5552
+ \u{1F389} Successfully imported from ${tool}`);
4183
5553
  logger.log("You can now run 'rulesync generate' to create tool-specific configurations.");
4184
5554
  } else if (result.errors.length > 0) {
4185
5555
  logger.warn(`\u26A0\uFE0F Failed to import from ${tool}: ${result.errors[0]}`);
4186
5556
  if (result.errors.length > 1) {
4187
- logger.info("\nDetailed errors:");
5557
+ logger.info(" Detailed errors:");
4188
5558
  for (const error of result.errors) {
4189
- logger.info(` - ${error}`);
5559
+ logger.info(` - ${error}`);
4190
5560
  }
4191
5561
  }
5562
+ logger.error(`
5563
+ \u274C Failed to import from ${tool}.`);
5564
+ process.exit(1);
4192
5565
  }
4193
5566
  } catch (error) {
4194
5567
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -4198,7 +5571,7 @@ async function importCommand(options = {}) {
4198
5571
  }
4199
5572
 
4200
5573
  // src/cli/commands/init.ts
4201
- import { join as join21 } from "path";
5574
+ import { join as join24 } from "path";
4202
5575
  async function initCommand(options = {}) {
4203
5576
  const configResult = await loadConfig();
4204
5577
  const config = configResult.config;
@@ -4206,7 +5579,7 @@ async function initCommand(options = {}) {
4206
5579
  logger.log("Initializing rulesync...");
4207
5580
  await ensureDir(aiRulesDir);
4208
5581
  const useLegacy = options.legacy ?? config.legacy ?? false;
4209
- const rulesDir = useLegacy ? aiRulesDir : join21(aiRulesDir, "rules");
5582
+ const rulesDir = useLegacy ? aiRulesDir : join24(aiRulesDir, "rules");
4210
5583
  if (!useLegacy) {
4211
5584
  await ensureDir(rulesDir);
4212
5585
  }
@@ -4252,7 +5625,7 @@ globs: ["**/*"]
4252
5625
  - Follow single responsibility principle
4253
5626
  `
4254
5627
  };
4255
- const filepath = join21(rulesDir, sampleFile.filename);
5628
+ const filepath = join24(rulesDir, sampleFile.filename);
4256
5629
  if (!await fileExists(filepath)) {
4257
5630
  await writeFileContent(filepath, sampleFile.content);
4258
5631
  logger.success(`Created ${filepath}`);
@@ -4366,11 +5739,11 @@ async function watchCommand() {
4366
5739
  persistent: true
4367
5740
  });
4368
5741
  let isGenerating = false;
4369
- const handleChange = async (path5) => {
5742
+ const handleChange = async (path8) => {
4370
5743
  if (isGenerating) return;
4371
5744
  isGenerating = true;
4372
5745
  logger.log(`
4373
- \u{1F4DD} Detected change in ${path5}`);
5746
+ \u{1F4DD} Detected change in ${path8}`);
4374
5747
  try {
4375
5748
  await generateCommand({ verbose: false });
4376
5749
  logger.success("Regenerated configuration files");
@@ -4380,10 +5753,10 @@ async function watchCommand() {
4380
5753
  isGenerating = false;
4381
5754
  }
4382
5755
  };
4383
- watcher.on("change", handleChange).on("add", handleChange).on("unlink", (path5) => {
5756
+ watcher.on("change", handleChange).on("add", handleChange).on("unlink", (path8) => {
4384
5757
  logger.log(`
4385
- \u{1F5D1}\uFE0F Removed ${path5}`);
4386
- handleChange(path5);
5758
+ \u{1F5D1}\uFE0F Removed ${path8}`);
5759
+ handleChange(path8);
4387
5760
  }).on("error", (error) => {
4388
5761
  logger.error("Watcher error:", error);
4389
5762
  });
@@ -4394,44 +5767,179 @@ async function watchCommand() {
4394
5767
  });
4395
5768
  }
4396
5769
 
5770
+ // src/cli/utils/targets-parser.ts
5771
+ function parseTargets(targetsInput) {
5772
+ if (!targetsInput) {
5773
+ return [];
5774
+ }
5775
+ let targetStrings;
5776
+ if (Array.isArray(targetsInput)) {
5777
+ targetStrings = targetsInput;
5778
+ } else {
5779
+ targetStrings = targetsInput.split(",").map((target) => target.trim()).filter((target) => target.length > 0);
5780
+ }
5781
+ const results = [];
5782
+ const errors = [];
5783
+ let hasWildcard = false;
5784
+ for (const targetString of targetStrings) {
5785
+ if (targetString === "*" || targetString === "all") {
5786
+ hasWildcard = true;
5787
+ results.push(...ALL_TOOL_TARGETS);
5788
+ } else if (isValidToolTarget(targetString)) {
5789
+ results.push(targetString);
5790
+ } else {
5791
+ errors.push(targetString);
5792
+ }
5793
+ }
5794
+ if (hasWildcard && targetStrings.length > 1) {
5795
+ throw new Error(
5796
+ "Cannot use '*' (all tools) with specific tool targets. Use either '--targets *' for all tools, or specify individual tools."
5797
+ );
5798
+ }
5799
+ if (errors.length > 0) {
5800
+ const validTargets = ALL_TOOL_TARGETS.join(", ");
5801
+ throw new Error(
5802
+ `Invalid tool targets: ${errors.join(", ")}. Valid targets are: ${validTargets}, *, all`
5803
+ );
5804
+ }
5805
+ return [...new Set(results)];
5806
+ }
5807
+ function isValidToolTarget(target) {
5808
+ return ALL_TOOL_TARGETS.includes(target);
5809
+ }
5810
+ function checkDeprecatedFlags(options) {
5811
+ const deprecatedTools = [];
5812
+ const flagToToolMap = {
5813
+ agentsmd: "agentsmd",
5814
+ amazonqcli: "amazonqcli",
5815
+ augmentcode: "augmentcode",
5816
+ "augmentcode-legacy": "augmentcode-legacy",
5817
+ copilot: "copilot",
5818
+ cursor: "cursor",
5819
+ cline: "cline",
5820
+ codexcli: "codexcli",
5821
+ claudecode: "claudecode",
5822
+ roo: "roo",
5823
+ geminicli: "geminicli",
5824
+ junie: "junie",
5825
+ qwencode: "qwencode",
5826
+ kiro: "kiro",
5827
+ opencode: "opencode",
5828
+ windsurf: "windsurf"
5829
+ };
5830
+ for (const [flag, tool] of Object.entries(flagToToolMap)) {
5831
+ if (options[flag]) {
5832
+ deprecatedTools.push(tool);
5833
+ }
5834
+ }
5835
+ return deprecatedTools;
5836
+ }
5837
+ function getDeprecationWarning(deprecatedTools, command = "generate") {
5838
+ const toolsStr = deprecatedTools.join(",");
5839
+ return [
5840
+ "\u26A0\uFE0F DEPRECATED: Individual tool flags are deprecated and will be removed in a future version.",
5841
+ ` Current: rulesync ${command} ${deprecatedTools.map((t) => `--${t}`).join(" ")}`,
5842
+ ` New: rulesync ${command} --targets ${toolsStr}`,
5843
+ " Please update your scripts to use the new --targets flag."
5844
+ ].join("\n");
5845
+ }
5846
+ function mergeAndDeduplicateTools(targetsTools, deprecatedTools, allFlag) {
5847
+ if (allFlag) {
5848
+ logger.warn(
5849
+ [
5850
+ "\u26A0\uFE0F DEPRECATED: The --all flag is deprecated and will be removed in a future version.",
5851
+ " Current: rulesync generate --all",
5852
+ " New: rulesync generate --targets *",
5853
+ " Please update your scripts to use the new --targets flag."
5854
+ ].join("\n")
5855
+ );
5856
+ return [...ALL_TOOL_TARGETS];
5857
+ }
5858
+ const allTools = [...targetsTools, ...deprecatedTools];
5859
+ return [...new Set(allTools)];
5860
+ }
5861
+
4397
5862
  // src/cli/index.ts
4398
5863
  var program = new Command();
4399
- program.name("rulesync").description("Unified AI rules management CLI tool").version("0.64.0");
5864
+ program.name("rulesync").description("Unified AI rules management CLI tool").version("0.67.0");
4400
5865
  program.command("init").description("Initialize rulesync in current directory").option("--legacy", "Use legacy file location (.rulesync/*.md instead of .rulesync/rules/*.md)").action(initCommand);
4401
5866
  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);
4402
5867
  program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
4403
- program.command("import").description("Import configurations from AI tools to rulesync format").option("--augmentcode", "Import from AugmentCode (.augment/rules/)").option("--augmentcode-legacy", "Import from AugmentCode legacy format (.augment-guidelines)").option("--claudecode", "Import from Claude Code (CLAUDE.md)").option("--cursor", "Import from Cursor (.cursorrules)").option("--copilot", "Import from GitHub Copilot (.github/copilot-instructions.md)").option("--cline", "Import from Cline (.cline/instructions.md)").option("--roo", "Import from Roo Code (.roo/instructions.md)").option("--geminicli", "Import from Gemini CLI (GEMINI.md)").option("--junie", "Import from JetBrains Junie (.junie/guidelines.md)").option("--opencode", "Import from OpenCode (AGENTS.md)").option("-v, --verbose", "Verbose output").option("--legacy", "Use legacy file location (.rulesync/*.md instead of .rulesync/rules/*.md)").action(importCommand);
4404
- program.command("generate").description("Generate configuration files for AI tools").option("--augmentcode", "Generate only for AugmentCode").option("--augmentcode-legacy", "Generate only for AugmentCode legacy format").option("--copilot", "Generate only for GitHub Copilot").option("--cursor", "Generate only for Cursor").option("--cline", "Generate only for Cline").option("--codexcli", "Generate only for OpenAI Codex CLI").option("--claudecode", "Generate only for Claude Code").option("--roo", "Generate only for Roo Code").option("--geminicli", "Generate only for Gemini CLI").option("--junie", "Generate only for JetBrains Junie").option("--kiro", "Generate only for Kiro IDE").option("--opencode", "Generate only for OpenCode").option("--windsurf", "Generate only for Windsurf").option("--delete", "Delete all existing files in output directories before generating").option(
5868
+ program.command("import").description("Import configurations from AI tools to rulesync format").option("-t, --targets <tool>", "Tool to import from (e.g., 'copilot', 'cursor', 'cline')").option(
5869
+ "--features <features>",
5870
+ `Comma-separated list of features to import (${FEATURE_TYPES.join(",")}) or '*' for all`,
5871
+ (value) => {
5872
+ if (value === "*") return "*";
5873
+ return value.split(",").map((f) => f.trim()).filter(Boolean);
5874
+ }
5875
+ ).option("--agentsmd", "[DEPRECATED] Import from AGENTS.md (use --targets agentsmd)").option("--augmentcode", "[DEPRECATED] Import from AugmentCode (use --targets augmentcode)").option(
5876
+ "--augmentcode-legacy",
5877
+ "[DEPRECATED] Import from AugmentCode legacy format (use --targets augmentcode-legacy)"
5878
+ ).option("--claudecode", "[DEPRECATED] Import from Claude Code (use --targets claudecode)").option("--cursor", "[DEPRECATED] Import from Cursor (use --targets cursor)").option("--copilot", "[DEPRECATED] Import from GitHub Copilot (use --targets copilot)").option("--cline", "[DEPRECATED] Import from Cline (use --targets cline)").option("--roo", "[DEPRECATED] Import from Roo Code (use --targets roo)").option("--geminicli", "[DEPRECATED] Import from Gemini CLI (use --targets geminicli)").option("--junie", "[DEPRECATED] Import from JetBrains Junie (use --targets junie)").option("--qwencode", "[DEPRECATED] Import from Qwen Code (use --targets qwencode)").option("--opencode", "[DEPRECATED] Import from OpenCode (use --targets opencode)").option("-v, --verbose", "Verbose output").option("--legacy", "Use legacy file location (.rulesync/*.md instead of .rulesync/rules/*.md)").action(async (options) => {
5879
+ try {
5880
+ let tools = [];
5881
+ const targetsTools = options.targets ? parseTargets(options.targets) : [];
5882
+ const deprecatedTools = checkDeprecatedFlags(options);
5883
+ if (deprecatedTools.length > 0) {
5884
+ logger.warn(getDeprecationWarning(deprecatedTools, "import"));
5885
+ }
5886
+ tools = mergeAndDeduplicateTools(targetsTools, deprecatedTools, false);
5887
+ const importOptions = {
5888
+ ...tools.length > 0 && { targets: tools },
5889
+ ...options.features && { features: options.features },
5890
+ verbose: options.verbose,
5891
+ legacy: options.legacy
5892
+ };
5893
+ await importCommand(importOptions);
5894
+ } catch (error) {
5895
+ logger.error(error instanceof Error ? error.message : String(error));
5896
+ process.exit(1);
5897
+ }
5898
+ });
5899
+ program.command("generate").description("Generate configuration files for AI tools").option("--all", "[DEPRECATED] Generate for all supported AI tools (use --targets * instead)").option(
5900
+ "-t, --targets <tools>",
5901
+ "Comma-separated list of tools to generate for (e.g., 'copilot,cursor,cline' or '*' for all)"
5902
+ ).option(
5903
+ "--features <features>",
5904
+ `Comma-separated list of features to generate (${FEATURE_TYPES.join(",")}) or '*' for all`,
5905
+ (value) => {
5906
+ if (value === "*") return "*";
5907
+ return value.split(",").map((f) => f.trim()).filter(Boolean);
5908
+ }
5909
+ ).option("--agentsmd", "[DEPRECATED] Generate only for AGENTS.md (use --targets agentsmd)").option(
5910
+ "--amazonqcli",
5911
+ "[DEPRECATED] Generate only for Amazon Q Developer CLI (use --targets amazonqcli)"
5912
+ ).option("--augmentcode", "[DEPRECATED] Generate only for AugmentCode (use --targets augmentcode)").option(
5913
+ "--augmentcode-legacy",
5914
+ "[DEPRECATED] Generate only for AugmentCode legacy format (use --targets augmentcode-legacy)"
5915
+ ).option("--copilot", "[DEPRECATED] Generate only for GitHub Copilot (use --targets copilot)").option("--cursor", "[DEPRECATED] Generate only for Cursor (use --targets cursor)").option("--cline", "[DEPRECATED] Generate only for Cline (use --targets cline)").option("--codexcli", "[DEPRECATED] Generate only for OpenAI Codex CLI (use --targets codexcli)").option("--claudecode", "[DEPRECATED] Generate only for Claude Code (use --targets claudecode)").option("--roo", "[DEPRECATED] Generate only for Roo Code (use --targets roo)").option("--geminicli", "[DEPRECATED] Generate only for Gemini CLI (use --targets geminicli)").option("--junie", "[DEPRECATED] Generate only for JetBrains Junie (use --targets junie)").option("--qwencode", "[DEPRECATED] Generate only for Qwen Code (use --targets qwencode)").option("--kiro", "[DEPRECATED] Generate only for Kiro IDE (use --targets kiro)").option("--opencode", "[DEPRECATED] Generate only for OpenCode (use --targets opencode)").option("--windsurf", "[DEPRECATED] Generate only for Windsurf (use --targets windsurf)").option("--delete", "Delete all existing files in output directories before generating").option(
4405
5916
  "-b, --base-dir <paths>",
4406
5917
  "Base directories to generate files (comma-separated for multiple paths)"
4407
5918
  ).option("-v, --verbose", "Verbose output").option("-c, --config <path>", "Path to configuration file").option("--no-config", "Disable configuration file loading").action(async (options) => {
4408
- const tools = [];
4409
- if (options.augmentcode) tools.push("augmentcode");
4410
- if (options["augmentcode-legacy"]) tools.push("augmentcode-legacy");
4411
- if (options.copilot) tools.push("copilot");
4412
- if (options.cursor) tools.push("cursor");
4413
- if (options.cline) tools.push("cline");
4414
- if (options.codexcli) tools.push("codexcli");
4415
- if (options.claudecode) tools.push("claudecode");
4416
- if (options.roo) tools.push("roo");
4417
- if (options.geminicli) tools.push("geminicli");
4418
- if (options.junie) tools.push("junie");
4419
- if (options.kiro) tools.push("kiro");
4420
- if (options.opencode) tools.push("opencode");
4421
- if (options.windsurf) tools.push("windsurf");
4422
- const generateOptions = {
4423
- verbose: options.verbose,
4424
- delete: options.delete,
4425
- config: options.config,
4426
- noConfig: options.noConfig
4427
- };
4428
- if (tools.length > 0) {
4429
- generateOptions.tools = tools;
4430
- }
4431
- if (options.baseDir) {
4432
- generateOptions.baseDirs = options.baseDir.split(",").map((dir) => dir.trim()).filter((dir) => dir.length > 0);
5919
+ try {
5920
+ let tools = [];
5921
+ const targetsTools = options.targets ? parseTargets(options.targets) : [];
5922
+ const deprecatedTools = checkDeprecatedFlags(options);
5923
+ if (deprecatedTools.length > 0) {
5924
+ logger.warn(getDeprecationWarning(deprecatedTools, "generate"));
5925
+ }
5926
+ tools = mergeAndDeduplicateTools(targetsTools, deprecatedTools, options.all === true);
5927
+ const generateOptions = {
5928
+ verbose: options.verbose,
5929
+ tools: tools.length > 0 ? tools : void 0,
5930
+ features: options.features,
5931
+ delete: options.delete,
5932
+ config: options.config,
5933
+ noConfig: options.noConfig
5934
+ };
5935
+ if (options.baseDir) {
5936
+ generateOptions.baseDirs = options.baseDir.split(",").map((dir) => dir.trim()).filter((dir) => dir.length > 0);
5937
+ }
5938
+ await generateCommand(generateOptions);
5939
+ } catch (error) {
5940
+ logger.error(error instanceof Error ? error.message : String(error));
5941
+ process.exit(1);
4433
5942
  }
4434
- await generateCommand(generateOptions);
4435
5943
  });
4436
5944
  program.command("validate").description("Validate rulesync configuration").action(validateCommand);
4437
5945
  program.command("status").description("Show current status of rulesync").action(statusCommand);