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