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.
- package/README.md +87 -42
- package/dist/{amazonqcli-WVGYACHI.js → amazonqcli-PWXCSRAN.js} +2 -2
- package/dist/{augmentcode-DTHPPXWO.js → augmentcode-4AFYW4BU.js} +2 -2
- package/dist/{chunk-FVPZQEWP.js → chunk-35VMCHXQ.js} +1 -1
- package/dist/{chunk-EID75W45.js → chunk-3QGD3CH5.js} +1 -1
- package/dist/{chunk-LURFNGH4.js → chunk-5GKH5TQ4.js} +1 -1
- package/dist/{chunk-KKWJVA56.js → chunk-7BIZ5Y6F.js} +1 -1
- package/dist/{chunk-HHJIL3YZ.js → chunk-7QVQO6MQ.js} +1 -1
- package/dist/{chunk-TQOL7OKY.js → chunk-B2HD24KC.js} +1 -1
- package/dist/{chunk-LYVES5YR.js → chunk-CS7AV6JT.js} +2 -0
- package/dist/{chunk-DMTCLQ4T.js → chunk-KONQNQY3.js} +1 -1
- package/dist/{chunk-JX55DU6Y.js → chunk-OARJESSZ.js} +1 -1
- package/dist/{chunk-6LSN7HSJ.js → chunk-V36ICGOY.js} +14 -1
- package/dist/{chunk-TBXG53FV.js → chunk-WCON5BAI.js} +1 -1
- package/dist/{chunk-YPJW7Z5M.js → chunk-WFOWHPBC.js} +1 -1
- package/dist/{chunk-4NAQ5HL4.js → chunk-WYYQXVHC.js} +1 -1
- package/dist/{chunk-E2J3UBBK.js → chunk-YZUDL4GW.js} +1 -1
- package/dist/chunk-ZMGXHLYP.js +17 -0
- package/dist/{claudecode-SSYLLUXX.js → claudecode-OC7VHCF6.js} +3 -3
- package/dist/{cline-5EUGKNZ6.js → cline-A4KFSAQE.js} +3 -3
- package/dist/{codexcli-IGM2ADYK.js → codexcli-YP3X7FWB.js} +3 -3
- package/dist/{copilot-HSQO7ZCJ.js → copilot-3LIMXQ7O.js} +2 -2
- package/dist/{cursor-ZB3XNGBK.js → cursor-QV72CDJC.js} +3 -3
- package/dist/{geminicli-FNRKH5GX.js → geminicli-XPSJJS65.js} +3 -3
- package/dist/index.cjs +2048 -492
- package/dist/index.js +2006 -498
- package/dist/{junie-3YGOSOGF.js → junie-265XIW43.js} +3 -3
- package/dist/{kiro-B6WZNLY4.js → kiro-6OHFHN5X.js} +2 -2
- package/dist/{opencode-SZETJ62M.js → opencode-C5QAYVJ5.js} +2 -2
- package/dist/qwencode-5RR24UW6.js +10 -0
- package/dist/{roo-KLTWVAKE.js → roo-5IXVBUHD.js} +3 -3
- package/dist/{windsurf-IZEKUAID.js → windsurf-DVQUJJJ5.js} +3 -3
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./chunk-
|
|
3
|
-
import "./chunk-
|
|
4
|
-
import "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
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-
|
|
19
|
-
import "./chunk-
|
|
20
|
-
import "./chunk-
|
|
21
|
-
import "./chunk-
|
|
22
|
-
import "./chunk-
|
|
23
|
-
import "./chunk-
|
|
24
|
-
import "./chunk-
|
|
25
|
-
import "./chunk-
|
|
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-
|
|
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 =
|
|
48
|
-
permissions:
|
|
49
|
-
|
|
50
|
-
deny:
|
|
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
|
|
58
|
-
var OutputSchema =
|
|
123
|
+
import { z as z3 } from "zod/mini";
|
|
124
|
+
var OutputSchema = z3.object({
|
|
59
125
|
tool: ToolTargetSchema,
|
|
60
|
-
filepath:
|
|
61
|
-
content:
|
|
126
|
+
filepath: z3.string(),
|
|
127
|
+
content: z3.string()
|
|
62
128
|
});
|
|
63
|
-
var BaseFrontmatterSchema =
|
|
64
|
-
description:
|
|
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
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
|
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
|
|
2248
|
+
import { join as join7 } from "path";
|
|
1671
2249
|
|
|
1672
2250
|
// src/utils/ignore.ts
|
|
1673
|
-
import { join as
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2790
|
+
return join10(outputDir, ".windsurf-rules");
|
|
2190
2791
|
} else {
|
|
2191
|
-
const rulesDir =
|
|
2192
|
-
return
|
|
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:
|
|
2455
|
-
generateDetailContent:
|
|
2456
|
-
generateRootContent:
|
|
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
|
-
|
|
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
|
|
3113
|
+
function generateQwenRootMarkdown(rootRule, memoryRules, _baseDir) {
|
|
2466
3114
|
return generateRootMarkdownWithXmlDocs(rootRule, memoryRules, {
|
|
2467
|
-
memorySubDir: ".
|
|
2468
|
-
fallbackTitle: "
|
|
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-
|
|
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-
|
|
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
|
-
|
|
3404
|
+
claudecode: async (servers, dir) => (await import("./claudecode-OC7VHCF6.js")).generateClaudeMcpConfiguration(
|
|
2743
3405
|
servers,
|
|
2744
3406
|
dir
|
|
2745
3407
|
),
|
|
2746
|
-
|
|
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
|
-
|
|
2751
|
-
|
|
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
|
-
|
|
2759
|
-
|
|
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
|
-
|
|
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/
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
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 (
|
|
2816
|
-
logger.
|
|
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
|
-
|
|
2819
|
-
|
|
2820
|
-
logger.
|
|
3490
|
+
return outputs;
|
|
3491
|
+
} catch (error) {
|
|
3492
|
+
logger.error(`Error generating subagents for ${toolName}:`, error);
|
|
3493
|
+
return [];
|
|
2821
3494
|
}
|
|
2822
3495
|
}
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
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
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
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
|
-
|
|
2839
|
-
const
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
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
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
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
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
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
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
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
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
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
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
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
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
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
|
-
|
|
2923
|
-
|
|
2924
|
-
logger.
|
|
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
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
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
|
-
}
|
|
2956
|
-
logger.
|
|
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
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
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
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
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
|
|
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
|
|
4020
|
+
import { join as join14 } from "path";
|
|
2999
4021
|
var gitignoreCommand = async () => {
|
|
3000
|
-
const gitignorePath =
|
|
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
|
|
3072
|
-
import
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
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
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
4117
|
-
|
|
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 =
|
|
5463
|
+
const frontmatter2 = matter3.stringify("", simplifiedFrontmatter);
|
|
4131
5464
|
return frontmatter2 + rule.content;
|
|
4132
5465
|
}
|
|
4133
|
-
const 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
|
-
|
|
4141
|
-
|
|
4142
|
-
if (options.
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
if (
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
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
|
-
|
|
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("
|
|
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(
|
|
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("
|
|
5557
|
+
logger.info(" Detailed errors:");
|
|
4188
5558
|
for (const error of result.errors) {
|
|
4189
|
-
logger.info(`
|
|
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
|
|
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 :
|
|
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 =
|
|
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 (
|
|
5742
|
+
const handleChange = async (path8) => {
|
|
4370
5743
|
if (isGenerating) return;
|
|
4371
5744
|
isGenerating = true;
|
|
4372
5745
|
logger.log(`
|
|
4373
|
-
\u{1F4DD} Detected change in ${
|
|
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", (
|
|
5756
|
+
watcher.on("change", handleChange).on("add", handleChange).on("unlink", (path8) => {
|
|
4384
5757
|
logger.log(`
|
|
4385
|
-
\u{1F5D1}\uFE0F Removed ${
|
|
4386
|
-
handleChange(
|
|
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.
|
|
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("
|
|
4404
|
-
|
|
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
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
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);
|