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