rulesync 0.19.0 → 0.21.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.ja.md +45 -1
- package/README.md +45 -1
- package/dist/index.js +411 -28
- package/dist/index.mjs +411 -28
- package/package.json +38 -32
package/README.ja.md
CHANGED
|
@@ -27,6 +27,8 @@ yarn global add rulesync
|
|
|
27
27
|
|
|
28
28
|
## 使い始める
|
|
29
29
|
|
|
30
|
+
### 新しいプロジェクト
|
|
31
|
+
|
|
30
32
|
1. **プロジェクトを初期化:**
|
|
31
33
|
```bash
|
|
32
34
|
npx rulesync init
|
|
@@ -49,6 +51,22 @@ yarn global add rulesync
|
|
|
49
51
|
npx rulesync gitignore
|
|
50
52
|
```
|
|
51
53
|
|
|
54
|
+
### AIツール設定を持つ既存プロジェクト
|
|
55
|
+
|
|
56
|
+
既にAIツールの設定がある場合、それらをインポートできます:
|
|
57
|
+
|
|
58
|
+
1. **既存設定をインポート:**
|
|
59
|
+
```bash
|
|
60
|
+
npx rulesync import --claude --cursor --copilot
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
2. **`.rulesync/`ディレクトリのインポートされたルールを確認・編集**
|
|
64
|
+
|
|
65
|
+
3. **統合された設定を生成:**
|
|
66
|
+
```bash
|
|
67
|
+
npx rulesync generate
|
|
68
|
+
```
|
|
69
|
+
|
|
52
70
|
以上です!AIコーディングアシスタントが生成された設定ファイルを自動的に使用するようになります。
|
|
53
71
|
|
|
54
72
|
## rulesyncを使う理由
|
|
@@ -180,7 +198,33 @@ npx rulesync generate --base-dir ./apps/web,./apps/api,./packages/shared
|
|
|
180
198
|
- `--copilot`, `--cursor`, `--cline`, `--claude`, `--roo`: 指定されたツールのみ生成
|
|
181
199
|
- `--base-dir <paths>`: 指定されたベースディレクトリに設定ファイルを生成(複数パスの場合はカンマ区切り)。異なるプロジェクトディレクトリにツール固有の設定を生成したいmonorepoセットアップに便利。
|
|
182
200
|
|
|
183
|
-
### 4.
|
|
201
|
+
### 4. 既存設定のインポート
|
|
202
|
+
|
|
203
|
+
プロジェクトに既存のAIツール設定がある場合、rulesync形式にインポートできます:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
# 既存のAIツール設定からインポート
|
|
207
|
+
npx rulesync import --claude # CLAUDE.mdからインポート
|
|
208
|
+
npx rulesync import --cursor # .cursorrulesからインポート
|
|
209
|
+
npx rulesync import --copilot # .github/copilot-instructions.mdからインポート
|
|
210
|
+
npx rulesync import --cline # .cline/instructions.mdからインポート
|
|
211
|
+
npx rulesync import --roo # .roo/instructions.mdからインポート
|
|
212
|
+
|
|
213
|
+
# 複数のツールからインポート
|
|
214
|
+
npx rulesync import --claude --cursor --copilot
|
|
215
|
+
|
|
216
|
+
# インポート時の詳細出力
|
|
217
|
+
npx rulesync import --claude --verbose
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
importコマンドの動作:
|
|
221
|
+
- 各AIツールの既存設定ファイルをパース
|
|
222
|
+
- 適切なフロントマターを付けてrulesync形式に変換
|
|
223
|
+
- インポートしたコンテンツで新しい`.rulesync/*.md`ファイルを作成
|
|
224
|
+
- ファイル名の競合を避けるためツール固有のプレフィックスを使用(例:`claude__overview.md`)
|
|
225
|
+
- 競合が発生した場合はユニークなファイル名を生成
|
|
226
|
+
|
|
227
|
+
### 5. その他のコマンド
|
|
184
228
|
|
|
185
229
|
```bash
|
|
186
230
|
# サンプルファイルでプロジェクトを初期化
|
package/README.md
CHANGED
|
@@ -27,6 +27,8 @@ yarn global add rulesync
|
|
|
27
27
|
|
|
28
28
|
## Getting Started
|
|
29
29
|
|
|
30
|
+
### New Project
|
|
31
|
+
|
|
30
32
|
1. **Initialize your project:**
|
|
31
33
|
```bash
|
|
32
34
|
npx rulesync init
|
|
@@ -49,6 +51,22 @@ yarn global add rulesync
|
|
|
49
51
|
npx rulesync gitignore
|
|
50
52
|
```
|
|
51
53
|
|
|
54
|
+
### Existing Project with AI Tool Configurations
|
|
55
|
+
|
|
56
|
+
If you already have AI tool configurations, you can import them:
|
|
57
|
+
|
|
58
|
+
1. **Import existing configurations:**
|
|
59
|
+
```bash
|
|
60
|
+
npx rulesync import --claude --cursor --copilot
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
2. **Review and edit** the imported rules in `.rulesync/` directory
|
|
64
|
+
|
|
65
|
+
3. **Generate unified configurations:**
|
|
66
|
+
```bash
|
|
67
|
+
npx rulesync generate
|
|
68
|
+
```
|
|
69
|
+
|
|
52
70
|
That's it! Your AI coding assistants will now use the generated configuration files automatically.
|
|
53
71
|
|
|
54
72
|
## Why rulesync?
|
|
@@ -180,7 +198,33 @@ npx rulesync generate --base-dir ./apps/web,./apps/api,./packages/shared
|
|
|
180
198
|
- `--copilot`, `--cursor`, `--cline`, `--claude`, `--roo`: Generate only for specified tools
|
|
181
199
|
- `--base-dir <paths>`: Generate configuration files in specified base directories (comma-separated for multiple paths). Useful for monorepo setups where you want to generate tool-specific configurations in different project directories.
|
|
182
200
|
|
|
183
|
-
### 4.
|
|
201
|
+
### 4. Import Existing Configurations
|
|
202
|
+
|
|
203
|
+
If you already have AI tool configurations in your project, you can import them to rulesync format:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
# Import from existing AI tool configurations
|
|
207
|
+
npx rulesync import --claude # Import from CLAUDE.md
|
|
208
|
+
npx rulesync import --cursor # Import from .cursorrules
|
|
209
|
+
npx rulesync import --copilot # Import from .github/copilot-instructions.md
|
|
210
|
+
npx rulesync import --cline # Import from .cline/instructions.md
|
|
211
|
+
npx rulesync import --roo # Import from .roo/instructions.md
|
|
212
|
+
|
|
213
|
+
# Import from multiple tools
|
|
214
|
+
npx rulesync import --claude --cursor --copilot
|
|
215
|
+
|
|
216
|
+
# Verbose output during import
|
|
217
|
+
npx rulesync import --claude --verbose
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
The import command will:
|
|
221
|
+
- Parse existing configuration files from each AI tool
|
|
222
|
+
- Convert them to rulesync format with appropriate frontmatter
|
|
223
|
+
- Create new `.rulesync/*.md` files with imported content
|
|
224
|
+
- Use tool-specific prefixes to avoid filename conflicts (e.g., `claude__overview.md`)
|
|
225
|
+
- Generate unique filenames if conflicts occur
|
|
226
|
+
|
|
227
|
+
### 5. Other Commands
|
|
184
228
|
|
|
185
229
|
```bash
|
|
186
230
|
# Initialize project with sample files
|
package/dist/index.js
CHANGED
|
@@ -46,7 +46,7 @@ function getDefaultConfig() {
|
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
function resolveTargets(targets, config) {
|
|
49
|
-
if (targets
|
|
49
|
+
if (targets[0] === "*") {
|
|
50
50
|
return config.defaultTargets;
|
|
51
51
|
}
|
|
52
52
|
return targets;
|
|
@@ -116,32 +116,24 @@ function generateClaudeMarkdown(rootRules, detailRules) {
|
|
|
116
116
|
if (detailRules.length > 0) {
|
|
117
117
|
lines.push("Please also reference the following documents as needed:");
|
|
118
118
|
lines.push("");
|
|
119
|
+
lines.push("| Document | Description | File Patterns |");
|
|
120
|
+
lines.push("|----------|-------------|---------------|");
|
|
119
121
|
for (const rule of detailRules) {
|
|
120
|
-
|
|
122
|
+
const globsText = rule.frontmatter.globs.length > 0 ? rule.frontmatter.globs.join(", ") : "-";
|
|
123
|
+
lines.push(
|
|
124
|
+
`| @.claude/memories/${rule.filename}.md | ${rule.frontmatter.description} | ${globsText} |`
|
|
125
|
+
);
|
|
121
126
|
}
|
|
122
127
|
lines.push("");
|
|
123
128
|
}
|
|
124
|
-
lines.push("# Claude Code Memory - Project Instructions");
|
|
125
|
-
lines.push("");
|
|
126
|
-
lines.push(
|
|
127
|
-
"Generated from rulesync configuration. These instructions guide Claude Code's behavior for this project."
|
|
128
|
-
);
|
|
129
|
-
lines.push("");
|
|
130
129
|
if (rootRules.length > 0) {
|
|
131
130
|
for (const rule of rootRules) {
|
|
132
|
-
lines.push(
|
|
131
|
+
lines.push(rule.content);
|
|
132
|
+
lines.push("");
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
return lines.join("\n");
|
|
136
136
|
}
|
|
137
|
-
function formatRuleForClaude(rule) {
|
|
138
|
-
const lines = [];
|
|
139
|
-
lines.push(`### ${rule.frontmatter.description}`);
|
|
140
|
-
lines.push("");
|
|
141
|
-
lines.push(rule.content);
|
|
142
|
-
lines.push("");
|
|
143
|
-
return lines;
|
|
144
|
-
}
|
|
145
137
|
function generateMemoryFile(rule) {
|
|
146
138
|
return rule.content.trim();
|
|
147
139
|
}
|
|
@@ -325,6 +317,12 @@ async function removeClaudeGeneratedFiles() {
|
|
|
325
317
|
async function generateConfigurations(rules, config, targetTools, baseDir) {
|
|
326
318
|
const outputs = [];
|
|
327
319
|
const toolsToGenerate = targetTools || config.defaultTargets;
|
|
320
|
+
const rootFiles = rules.filter((rule) => rule.frontmatter.root === true);
|
|
321
|
+
if (rootFiles.length === 0) {
|
|
322
|
+
console.warn(
|
|
323
|
+
"\u26A0\uFE0F Warning: No files with 'root: true' found. This may result in incomplete configurations."
|
|
324
|
+
);
|
|
325
|
+
}
|
|
328
326
|
for (const tool of toolsToGenerate) {
|
|
329
327
|
const relevantRules = filterRulesForTool(rules, tool, config);
|
|
330
328
|
if (relevantRules.length === 0) {
|
|
@@ -385,7 +383,9 @@ ${errors.join("\n")}`);
|
|
|
385
383
|
const rootRules = rules.filter((rule) => rule.frontmatter.root);
|
|
386
384
|
if (rootRules.length > 1) {
|
|
387
385
|
const rootRuleFiles = rootRules.map((rule) => rule.filepath).join(", ");
|
|
388
|
-
throw new Error(
|
|
386
|
+
throw new Error(
|
|
387
|
+
`Multiple root rules found: ${rootRuleFiles}. Only one rule can have root: true.`
|
|
388
|
+
);
|
|
389
389
|
}
|
|
390
390
|
return rules;
|
|
391
391
|
}
|
|
@@ -656,8 +656,387 @@ ${linesToAdd.join("\n")}
|
|
|
656
656
|
}
|
|
657
657
|
};
|
|
658
658
|
|
|
659
|
-
// src/
|
|
659
|
+
// src/core/importer.ts
|
|
660
|
+
var import_node_path15 = require("path");
|
|
661
|
+
var import_gray_matter2 = __toESM(require("gray-matter"));
|
|
662
|
+
|
|
663
|
+
// src/parsers/claude.ts
|
|
660
664
|
var import_node_path10 = require("path");
|
|
665
|
+
async function parseClaudeConfiguration(baseDir = process.cwd()) {
|
|
666
|
+
const errors = [];
|
|
667
|
+
const rules = [];
|
|
668
|
+
const claudeFilePath = (0, import_node_path10.join)(baseDir, "CLAUDE.md");
|
|
669
|
+
if (!await fileExists(claudeFilePath)) {
|
|
670
|
+
errors.push("CLAUDE.md file not found");
|
|
671
|
+
return { rules, errors };
|
|
672
|
+
}
|
|
673
|
+
try {
|
|
674
|
+
const claudeContent = await readFileContent(claudeFilePath);
|
|
675
|
+
const mainRule = parseClaudeMainFile(claudeContent, claudeFilePath);
|
|
676
|
+
if (mainRule) {
|
|
677
|
+
rules.push(mainRule);
|
|
678
|
+
}
|
|
679
|
+
const memoryDir = (0, import_node_path10.join)(baseDir, ".claude", "memories");
|
|
680
|
+
if (await fileExists(memoryDir)) {
|
|
681
|
+
const memoryRules = await parseClaudeMemoryFiles(memoryDir);
|
|
682
|
+
rules.push(...memoryRules);
|
|
683
|
+
}
|
|
684
|
+
} catch (error) {
|
|
685
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
686
|
+
errors.push(`Failed to parse Claude configuration: ${errorMessage}`);
|
|
687
|
+
}
|
|
688
|
+
return { rules, errors };
|
|
689
|
+
}
|
|
690
|
+
function parseClaudeMainFile(content, filepath) {
|
|
691
|
+
const lines = content.split("\n");
|
|
692
|
+
let contentStartIndex = 0;
|
|
693
|
+
if (lines.some((line) => line.includes("| Document | Description | File Patterns |"))) {
|
|
694
|
+
const tableEndIndex = lines.findIndex(
|
|
695
|
+
(line, index) => index > 0 && line.trim() === "" && lines[index - 1]?.includes("|") && !lines[index + 1]?.includes("|")
|
|
696
|
+
);
|
|
697
|
+
if (tableEndIndex !== -1) {
|
|
698
|
+
contentStartIndex = tableEndIndex + 1;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
const mainContent = lines.slice(contentStartIndex).join("\n").trim();
|
|
702
|
+
if (!mainContent) {
|
|
703
|
+
return null;
|
|
704
|
+
}
|
|
705
|
+
const frontmatter = {
|
|
706
|
+
root: false,
|
|
707
|
+
targets: ["claude"],
|
|
708
|
+
description: "Main Claude Code configuration",
|
|
709
|
+
globs: ["**/*"]
|
|
710
|
+
};
|
|
711
|
+
return {
|
|
712
|
+
frontmatter,
|
|
713
|
+
content: mainContent,
|
|
714
|
+
filename: "claude-main",
|
|
715
|
+
filepath
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
async function parseClaudeMemoryFiles(memoryDir) {
|
|
719
|
+
const rules = [];
|
|
720
|
+
try {
|
|
721
|
+
const { readdir: readdir2 } = await import("fs/promises");
|
|
722
|
+
const files = await readdir2(memoryDir);
|
|
723
|
+
for (const file of files) {
|
|
724
|
+
if (file.endsWith(".md")) {
|
|
725
|
+
const filePath = (0, import_node_path10.join)(memoryDir, file);
|
|
726
|
+
const content = await readFileContent(filePath);
|
|
727
|
+
if (content.trim()) {
|
|
728
|
+
const filename = (0, import_node_path10.basename)(file, ".md");
|
|
729
|
+
const frontmatter = {
|
|
730
|
+
root: false,
|
|
731
|
+
targets: ["claude"],
|
|
732
|
+
description: `Memory file: ${filename}`,
|
|
733
|
+
globs: ["**/*"]
|
|
734
|
+
};
|
|
735
|
+
rules.push({
|
|
736
|
+
frontmatter,
|
|
737
|
+
content: content.trim(),
|
|
738
|
+
filename: `claude-memory-${filename}`,
|
|
739
|
+
filepath: filePath
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
} catch (_error) {
|
|
745
|
+
}
|
|
746
|
+
return rules;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// src/parsers/cline.ts
|
|
750
|
+
var import_node_path11 = require("path");
|
|
751
|
+
async function parseClineConfiguration(baseDir = process.cwd()) {
|
|
752
|
+
const errors = [];
|
|
753
|
+
const rules = [];
|
|
754
|
+
const clineFilePath = (0, import_node_path11.join)(baseDir, ".cline", "instructions.md");
|
|
755
|
+
if (!await fileExists(clineFilePath)) {
|
|
756
|
+
errors.push(".cline/instructions.md file not found");
|
|
757
|
+
return { rules, errors };
|
|
758
|
+
}
|
|
759
|
+
try {
|
|
760
|
+
const content = await readFileContent(clineFilePath);
|
|
761
|
+
if (content.trim()) {
|
|
762
|
+
const frontmatter = {
|
|
763
|
+
root: false,
|
|
764
|
+
targets: ["cline"],
|
|
765
|
+
description: "Cline AI assistant instructions",
|
|
766
|
+
globs: ["**/*"]
|
|
767
|
+
};
|
|
768
|
+
rules.push({
|
|
769
|
+
frontmatter,
|
|
770
|
+
content: content.trim(),
|
|
771
|
+
filename: "cline-instructions",
|
|
772
|
+
filepath: clineFilePath
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
} catch (error) {
|
|
776
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
777
|
+
errors.push(`Failed to parse Cline configuration: ${errorMessage}`);
|
|
778
|
+
}
|
|
779
|
+
return { rules, errors };
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// src/parsers/copilot.ts
|
|
783
|
+
var import_node_path12 = require("path");
|
|
784
|
+
async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
785
|
+
const errors = [];
|
|
786
|
+
const rules = [];
|
|
787
|
+
const copilotFilePath = (0, import_node_path12.join)(baseDir, ".github", "copilot-instructions.md");
|
|
788
|
+
if (!await fileExists(copilotFilePath)) {
|
|
789
|
+
errors.push(".github/copilot-instructions.md file not found");
|
|
790
|
+
return { rules, errors };
|
|
791
|
+
}
|
|
792
|
+
try {
|
|
793
|
+
const content = await readFileContent(copilotFilePath);
|
|
794
|
+
if (content.trim()) {
|
|
795
|
+
const frontmatter = {
|
|
796
|
+
root: false,
|
|
797
|
+
targets: ["copilot"],
|
|
798
|
+
description: "GitHub Copilot instructions",
|
|
799
|
+
globs: ["**/*"]
|
|
800
|
+
};
|
|
801
|
+
rules.push({
|
|
802
|
+
frontmatter,
|
|
803
|
+
content: content.trim(),
|
|
804
|
+
filename: "copilot-instructions",
|
|
805
|
+
filepath: copilotFilePath
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
} catch (error) {
|
|
809
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
810
|
+
errors.push(`Failed to parse Copilot configuration: ${errorMessage}`);
|
|
811
|
+
}
|
|
812
|
+
return { rules, errors };
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// src/parsers/cursor.ts
|
|
816
|
+
var import_node_path13 = require("path");
|
|
817
|
+
async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
818
|
+
const errors = [];
|
|
819
|
+
const rules = [];
|
|
820
|
+
const cursorFilePath = (0, import_node_path13.join)(baseDir, ".cursorrules");
|
|
821
|
+
if (!await fileExists(cursorFilePath)) {
|
|
822
|
+
errors.push(".cursorrules file not found");
|
|
823
|
+
return { rules, errors };
|
|
824
|
+
}
|
|
825
|
+
try {
|
|
826
|
+
const content = await readFileContent(cursorFilePath);
|
|
827
|
+
if (content.trim()) {
|
|
828
|
+
const frontmatter = {
|
|
829
|
+
root: false,
|
|
830
|
+
targets: ["cursor"],
|
|
831
|
+
description: "Cursor IDE configuration rules",
|
|
832
|
+
globs: ["**/*"]
|
|
833
|
+
};
|
|
834
|
+
rules.push({
|
|
835
|
+
frontmatter,
|
|
836
|
+
content: content.trim(),
|
|
837
|
+
filename: "cursor-rules",
|
|
838
|
+
filepath: cursorFilePath
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
} catch (error) {
|
|
842
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
843
|
+
errors.push(`Failed to parse Cursor configuration: ${errorMessage}`);
|
|
844
|
+
}
|
|
845
|
+
return { rules, errors };
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// src/parsers/roo.ts
|
|
849
|
+
var import_node_path14 = require("path");
|
|
850
|
+
async function parseRooConfiguration(baseDir = process.cwd()) {
|
|
851
|
+
const errors = [];
|
|
852
|
+
const rules = [];
|
|
853
|
+
const rooFilePath = (0, import_node_path14.join)(baseDir, ".roo", "instructions.md");
|
|
854
|
+
if (!await fileExists(rooFilePath)) {
|
|
855
|
+
errors.push(".roo/instructions.md file not found");
|
|
856
|
+
return { rules, errors };
|
|
857
|
+
}
|
|
858
|
+
try {
|
|
859
|
+
const content = await readFileContent(rooFilePath);
|
|
860
|
+
if (content.trim()) {
|
|
861
|
+
const frontmatter = {
|
|
862
|
+
root: false,
|
|
863
|
+
targets: ["roo"],
|
|
864
|
+
description: "Roo Code AI assistant instructions",
|
|
865
|
+
globs: ["**/*"]
|
|
866
|
+
};
|
|
867
|
+
rules.push({
|
|
868
|
+
frontmatter,
|
|
869
|
+
content: content.trim(),
|
|
870
|
+
filename: "roo-instructions",
|
|
871
|
+
filepath: rooFilePath
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
} catch (error) {
|
|
875
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
876
|
+
errors.push(`Failed to parse Roo configuration: ${errorMessage}`);
|
|
877
|
+
}
|
|
878
|
+
return { rules, errors };
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// src/core/importer.ts
|
|
882
|
+
async function importConfiguration(options) {
|
|
883
|
+
const { tool, baseDir = process.cwd(), rulesDir = ".rulesync", verbose = false } = options;
|
|
884
|
+
const errors = [];
|
|
885
|
+
let rules = [];
|
|
886
|
+
if (verbose) {
|
|
887
|
+
console.log(`Importing ${tool} configuration from ${baseDir}...`);
|
|
888
|
+
}
|
|
889
|
+
try {
|
|
890
|
+
switch (tool) {
|
|
891
|
+
case "claude": {
|
|
892
|
+
const claudeResult = await parseClaudeConfiguration(baseDir);
|
|
893
|
+
rules = claudeResult.rules;
|
|
894
|
+
errors.push(...claudeResult.errors);
|
|
895
|
+
break;
|
|
896
|
+
}
|
|
897
|
+
case "cursor": {
|
|
898
|
+
const cursorResult = await parseCursorConfiguration(baseDir);
|
|
899
|
+
rules = cursorResult.rules;
|
|
900
|
+
errors.push(...cursorResult.errors);
|
|
901
|
+
break;
|
|
902
|
+
}
|
|
903
|
+
case "copilot": {
|
|
904
|
+
const copilotResult = await parseCopilotConfiguration(baseDir);
|
|
905
|
+
rules = copilotResult.rules;
|
|
906
|
+
errors.push(...copilotResult.errors);
|
|
907
|
+
break;
|
|
908
|
+
}
|
|
909
|
+
case "cline": {
|
|
910
|
+
const clineResult = await parseClineConfiguration(baseDir);
|
|
911
|
+
rules = clineResult.rules;
|
|
912
|
+
errors.push(...clineResult.errors);
|
|
913
|
+
break;
|
|
914
|
+
}
|
|
915
|
+
case "roo": {
|
|
916
|
+
const rooResult = await parseRooConfiguration(baseDir);
|
|
917
|
+
rules = rooResult.rules;
|
|
918
|
+
errors.push(...rooResult.errors);
|
|
919
|
+
break;
|
|
920
|
+
}
|
|
921
|
+
default:
|
|
922
|
+
errors.push(`Unsupported tool: ${tool}`);
|
|
923
|
+
return { success: false, rulesCreated: 0, errors };
|
|
924
|
+
}
|
|
925
|
+
} catch (error) {
|
|
926
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
927
|
+
errors.push(`Failed to parse ${tool} configuration: ${errorMessage}`);
|
|
928
|
+
return { success: false, rulesCreated: 0, errors };
|
|
929
|
+
}
|
|
930
|
+
if (rules.length === 0) {
|
|
931
|
+
return { success: false, rulesCreated: 0, errors };
|
|
932
|
+
}
|
|
933
|
+
const rulesDirPath = (0, import_node_path15.join)(baseDir, rulesDir);
|
|
934
|
+
try {
|
|
935
|
+
const { mkdir: mkdir3 } = await import("fs/promises");
|
|
936
|
+
await mkdir3(rulesDirPath, { recursive: true });
|
|
937
|
+
} catch (error) {
|
|
938
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
939
|
+
errors.push(`Failed to create rules directory: ${errorMessage}`);
|
|
940
|
+
return { success: false, rulesCreated: 0, errors };
|
|
941
|
+
}
|
|
942
|
+
let rulesCreated = 0;
|
|
943
|
+
for (const rule of rules) {
|
|
944
|
+
try {
|
|
945
|
+
const baseFilename = `${tool}__${rule.filename}`;
|
|
946
|
+
const filename = await generateUniqueFilename(rulesDirPath, baseFilename);
|
|
947
|
+
const filePath = (0, import_node_path15.join)(rulesDirPath, `${filename}.md`);
|
|
948
|
+
const content = generateRuleFileContent(rule);
|
|
949
|
+
await writeFileContent(filePath, content);
|
|
950
|
+
rulesCreated++;
|
|
951
|
+
if (verbose) {
|
|
952
|
+
console.log(`\u2705 Created rule file: ${filePath}`);
|
|
953
|
+
}
|
|
954
|
+
} catch (error) {
|
|
955
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
956
|
+
errors.push(`Failed to create rule file for ${rule.filename}: ${errorMessage}`);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
return {
|
|
960
|
+
success: rulesCreated > 0,
|
|
961
|
+
rulesCreated,
|
|
962
|
+
errors
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
function generateRuleFileContent(rule) {
|
|
966
|
+
const frontmatter = import_gray_matter2.default.stringify("", rule.frontmatter);
|
|
967
|
+
return frontmatter + rule.content;
|
|
968
|
+
}
|
|
969
|
+
async function generateUniqueFilename(rulesDir, baseFilename) {
|
|
970
|
+
let filename = baseFilename;
|
|
971
|
+
let counter = 1;
|
|
972
|
+
while (await fileExists((0, import_node_path15.join)(rulesDir, `${filename}.md`))) {
|
|
973
|
+
filename = `${baseFilename}-${counter}`;
|
|
974
|
+
counter++;
|
|
975
|
+
}
|
|
976
|
+
return filename;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// src/cli/commands/import.ts
|
|
980
|
+
async function importCommand(options = {}) {
|
|
981
|
+
const tools = [];
|
|
982
|
+
if (options.claude) tools.push("claude");
|
|
983
|
+
if (options.cursor) tools.push("cursor");
|
|
984
|
+
if (options.copilot) tools.push("copilot");
|
|
985
|
+
if (options.cline) tools.push("cline");
|
|
986
|
+
if (options.roo) tools.push("roo");
|
|
987
|
+
if (tools.length === 0) {
|
|
988
|
+
console.error(
|
|
989
|
+
"\u274C Please specify at least one tool to import from (--claude, --cursor, --copilot, --cline, --roo)"
|
|
990
|
+
);
|
|
991
|
+
process.exit(1);
|
|
992
|
+
}
|
|
993
|
+
console.log("Importing configuration files...");
|
|
994
|
+
let totalRulesCreated = 0;
|
|
995
|
+
const allErrors = [];
|
|
996
|
+
for (const tool of tools) {
|
|
997
|
+
if (options.verbose) {
|
|
998
|
+
console.log(`
|
|
999
|
+
Importing from ${tool}...`);
|
|
1000
|
+
}
|
|
1001
|
+
try {
|
|
1002
|
+
const result = await importConfiguration({
|
|
1003
|
+
tool,
|
|
1004
|
+
verbose: options.verbose ?? false
|
|
1005
|
+
});
|
|
1006
|
+
if (result.success) {
|
|
1007
|
+
console.log(`\u2705 Imported ${result.rulesCreated} rule(s) from ${tool}`);
|
|
1008
|
+
totalRulesCreated += result.rulesCreated;
|
|
1009
|
+
} else if (result.errors.length > 0) {
|
|
1010
|
+
console.warn(`\u26A0\uFE0F Failed to import from ${tool}: ${result.errors[0]}`);
|
|
1011
|
+
if (options.verbose) {
|
|
1012
|
+
allErrors.push(...result.errors);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
} catch (error) {
|
|
1016
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1017
|
+
console.error(`\u274C Error importing from ${tool}: ${errorMessage}`);
|
|
1018
|
+
allErrors.push(`${tool}: ${errorMessage}`);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
if (totalRulesCreated > 0) {
|
|
1022
|
+
console.log(`
|
|
1023
|
+
\u{1F389} Successfully imported ${totalRulesCreated} rule(s) total!`);
|
|
1024
|
+
console.log("You can now run 'rulesync generate' to create tool-specific configurations.");
|
|
1025
|
+
} else {
|
|
1026
|
+
console.warn(
|
|
1027
|
+
"\n\u26A0\uFE0F No rules were imported. Please check that configuration files exist for the selected tools."
|
|
1028
|
+
);
|
|
1029
|
+
}
|
|
1030
|
+
if (options.verbose && allErrors.length > 0) {
|
|
1031
|
+
console.log("\nDetailed errors:");
|
|
1032
|
+
for (const error of allErrors) {
|
|
1033
|
+
console.log(` - ${error}`);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// src/cli/commands/init.ts
|
|
1039
|
+
var import_node_path16 = require("path");
|
|
661
1040
|
async function initCommand() {
|
|
662
1041
|
const aiRulesDir = ".rulesync";
|
|
663
1042
|
console.log("Initializing rulesync...");
|
|
@@ -787,7 +1166,7 @@ globs: ["src/api/**/*.ts", "src/services/**/*.ts", "src/models/**/*.ts"]
|
|
|
787
1166
|
}
|
|
788
1167
|
];
|
|
789
1168
|
for (const file of sampleFiles) {
|
|
790
|
-
const filepath = (0,
|
|
1169
|
+
const filepath = (0, import_node_path16.join)(aiRulesDir, file.filename);
|
|
791
1170
|
if (!await fileExists(filepath)) {
|
|
792
1171
|
await writeFileContent(filepath, file.content);
|
|
793
1172
|
console.log(`Created ${filepath}`);
|
|
@@ -814,13 +1193,13 @@ async function statusCommand() {
|
|
|
814
1193
|
console.log(`
|
|
815
1194
|
\u{1F4CB} Rules: ${rules.length} total`);
|
|
816
1195
|
if (rules.length > 0) {
|
|
817
|
-
const
|
|
818
|
-
const
|
|
819
|
-
console.log(` -
|
|
820
|
-
console.log(` -
|
|
821
|
-
const targetCounts = { copilot: 0, cursor: 0, cline: 0 };
|
|
1196
|
+
const rootRules = rules.filter((r) => r.frontmatter.root).length;
|
|
1197
|
+
const nonRootRules = rules.length - rootRules;
|
|
1198
|
+
console.log(` - Root rules: ${rootRules}`);
|
|
1199
|
+
console.log(` - Non-root rules: ${nonRootRules}`);
|
|
1200
|
+
const targetCounts = { copilot: 0, cursor: 0, cline: 0, claude: 0, roo: 0 };
|
|
822
1201
|
for (const rule of rules) {
|
|
823
|
-
const targets = rule.frontmatter.targets
|
|
1202
|
+
const targets = rule.frontmatter.targets[0] === "*" ? config.defaultTargets : rule.frontmatter.targets;
|
|
824
1203
|
for (const target of targets) {
|
|
825
1204
|
if (target in targetCounts) {
|
|
826
1205
|
targetCounts[target]++;
|
|
@@ -831,6 +1210,8 @@ async function statusCommand() {
|
|
|
831
1210
|
console.log(` - Copilot: ${targetCounts.copilot} rules`);
|
|
832
1211
|
console.log(` - Cursor: ${targetCounts.cursor} rules`);
|
|
833
1212
|
console.log(` - Cline: ${targetCounts.cline} rules`);
|
|
1213
|
+
console.log(` - Claude: ${targetCounts.claude} rules`);
|
|
1214
|
+
console.log(` - Roo: ${targetCounts.roo} rules`);
|
|
834
1215
|
}
|
|
835
1216
|
console.log("\n\u{1F4E4} Generated files:");
|
|
836
1217
|
for (const [tool, outputPath] of Object.entries(config.outputPaths)) {
|
|
@@ -928,11 +1309,12 @@ async function watchCommand() {
|
|
|
928
1309
|
|
|
929
1310
|
// src/cli/index.ts
|
|
930
1311
|
var program = new import_commander.Command();
|
|
931
|
-
program.name("rulesync").description("Unified AI rules management CLI tool").version("0.
|
|
1312
|
+
program.name("rulesync").description("Unified AI rules management CLI tool").version("0.20.0");
|
|
932
1313
|
program.command("init").description("Initialize rulesync in current directory").action(initCommand);
|
|
933
1314
|
program.command("add <filename>").description("Add a new rule file").action(addCommand);
|
|
934
1315
|
program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
|
|
935
|
-
program.command("
|
|
1316
|
+
program.command("import").description("Import configurations from AI tools to rulesync format").option("--claude", "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("-v, --verbose", "Verbose output").action(importCommand);
|
|
1317
|
+
program.command("generate").description("Generate configuration files for AI tools").option("--copilot", "Generate only for GitHub Copilot").option("--cursor", "Generate only for Cursor").option("--cline", "Generate only for Cline").option("--claude", "Generate only for Claude Code").option("--roo", "Generate only for Roo Code").option("--delete", "Delete all existing files in output directories before generating").option(
|
|
936
1318
|
"-b, --base-dir <paths>",
|
|
937
1319
|
"Base directories to generate files (comma-separated for multiple paths)"
|
|
938
1320
|
).option("-v, --verbose", "Verbose output").action(async (options) => {
|
|
@@ -941,6 +1323,7 @@ program.command("generate").description("Generate configuration files for AI too
|
|
|
941
1323
|
if (options.cursor) tools.push("cursor");
|
|
942
1324
|
if (options.cline) tools.push("cline");
|
|
943
1325
|
if (options.claude) tools.push("claude");
|
|
1326
|
+
if (options.roo) tools.push("roo");
|
|
944
1327
|
const generateOptions = {
|
|
945
1328
|
verbose: options.verbose,
|
|
946
1329
|
delete: options.delete
|
package/dist/index.mjs
CHANGED
|
@@ -23,7 +23,7 @@ function getDefaultConfig() {
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
function resolveTargets(targets, config) {
|
|
26
|
-
if (targets
|
|
26
|
+
if (targets[0] === "*") {
|
|
27
27
|
return config.defaultTargets;
|
|
28
28
|
}
|
|
29
29
|
return targets;
|
|
@@ -93,32 +93,24 @@ function generateClaudeMarkdown(rootRules, detailRules) {
|
|
|
93
93
|
if (detailRules.length > 0) {
|
|
94
94
|
lines.push("Please also reference the following documents as needed:");
|
|
95
95
|
lines.push("");
|
|
96
|
+
lines.push("| Document | Description | File Patterns |");
|
|
97
|
+
lines.push("|----------|-------------|---------------|");
|
|
96
98
|
for (const rule of detailRules) {
|
|
97
|
-
|
|
99
|
+
const globsText = rule.frontmatter.globs.length > 0 ? rule.frontmatter.globs.join(", ") : "-";
|
|
100
|
+
lines.push(
|
|
101
|
+
`| @.claude/memories/${rule.filename}.md | ${rule.frontmatter.description} | ${globsText} |`
|
|
102
|
+
);
|
|
98
103
|
}
|
|
99
104
|
lines.push("");
|
|
100
105
|
}
|
|
101
|
-
lines.push("# Claude Code Memory - Project Instructions");
|
|
102
|
-
lines.push("");
|
|
103
|
-
lines.push(
|
|
104
|
-
"Generated from rulesync configuration. These instructions guide Claude Code's behavior for this project."
|
|
105
|
-
);
|
|
106
|
-
lines.push("");
|
|
107
106
|
if (rootRules.length > 0) {
|
|
108
107
|
for (const rule of rootRules) {
|
|
109
|
-
lines.push(
|
|
108
|
+
lines.push(rule.content);
|
|
109
|
+
lines.push("");
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
return lines.join("\n");
|
|
113
113
|
}
|
|
114
|
-
function formatRuleForClaude(rule) {
|
|
115
|
-
const lines = [];
|
|
116
|
-
lines.push(`### ${rule.frontmatter.description}`);
|
|
117
|
-
lines.push("");
|
|
118
|
-
lines.push(rule.content);
|
|
119
|
-
lines.push("");
|
|
120
|
-
return lines;
|
|
121
|
-
}
|
|
122
114
|
function generateMemoryFile(rule) {
|
|
123
115
|
return rule.content.trim();
|
|
124
116
|
}
|
|
@@ -302,6 +294,12 @@ async function removeClaudeGeneratedFiles() {
|
|
|
302
294
|
async function generateConfigurations(rules, config, targetTools, baseDir) {
|
|
303
295
|
const outputs = [];
|
|
304
296
|
const toolsToGenerate = targetTools || config.defaultTargets;
|
|
297
|
+
const rootFiles = rules.filter((rule) => rule.frontmatter.root === true);
|
|
298
|
+
if (rootFiles.length === 0) {
|
|
299
|
+
console.warn(
|
|
300
|
+
"\u26A0\uFE0F Warning: No files with 'root: true' found. This may result in incomplete configurations."
|
|
301
|
+
);
|
|
302
|
+
}
|
|
305
303
|
for (const tool of toolsToGenerate) {
|
|
306
304
|
const relevantRules = filterRulesForTool(rules, tool, config);
|
|
307
305
|
if (relevantRules.length === 0) {
|
|
@@ -362,7 +360,9 @@ ${errors.join("\n")}`);
|
|
|
362
360
|
const rootRules = rules.filter((rule) => rule.frontmatter.root);
|
|
363
361
|
if (rootRules.length > 1) {
|
|
364
362
|
const rootRuleFiles = rootRules.map((rule) => rule.filepath).join(", ");
|
|
365
|
-
throw new Error(
|
|
363
|
+
throw new Error(
|
|
364
|
+
`Multiple root rules found: ${rootRuleFiles}. Only one rule can have root: true.`
|
|
365
|
+
);
|
|
366
366
|
}
|
|
367
367
|
return rules;
|
|
368
368
|
}
|
|
@@ -633,8 +633,387 @@ ${linesToAdd.join("\n")}
|
|
|
633
633
|
}
|
|
634
634
|
};
|
|
635
635
|
|
|
636
|
+
// src/core/importer.ts
|
|
637
|
+
import { join as join13 } from "path";
|
|
638
|
+
import matter2 from "gray-matter";
|
|
639
|
+
|
|
640
|
+
// src/parsers/claude.ts
|
|
641
|
+
import { basename as basename2, join as join8 } from "path";
|
|
642
|
+
async function parseClaudeConfiguration(baseDir = process.cwd()) {
|
|
643
|
+
const errors = [];
|
|
644
|
+
const rules = [];
|
|
645
|
+
const claudeFilePath = join8(baseDir, "CLAUDE.md");
|
|
646
|
+
if (!await fileExists(claudeFilePath)) {
|
|
647
|
+
errors.push("CLAUDE.md file not found");
|
|
648
|
+
return { rules, errors };
|
|
649
|
+
}
|
|
650
|
+
try {
|
|
651
|
+
const claudeContent = await readFileContent(claudeFilePath);
|
|
652
|
+
const mainRule = parseClaudeMainFile(claudeContent, claudeFilePath);
|
|
653
|
+
if (mainRule) {
|
|
654
|
+
rules.push(mainRule);
|
|
655
|
+
}
|
|
656
|
+
const memoryDir = join8(baseDir, ".claude", "memories");
|
|
657
|
+
if (await fileExists(memoryDir)) {
|
|
658
|
+
const memoryRules = await parseClaudeMemoryFiles(memoryDir);
|
|
659
|
+
rules.push(...memoryRules);
|
|
660
|
+
}
|
|
661
|
+
} catch (error) {
|
|
662
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
663
|
+
errors.push(`Failed to parse Claude configuration: ${errorMessage}`);
|
|
664
|
+
}
|
|
665
|
+
return { rules, errors };
|
|
666
|
+
}
|
|
667
|
+
function parseClaudeMainFile(content, filepath) {
|
|
668
|
+
const lines = content.split("\n");
|
|
669
|
+
let contentStartIndex = 0;
|
|
670
|
+
if (lines.some((line) => line.includes("| Document | Description | File Patterns |"))) {
|
|
671
|
+
const tableEndIndex = lines.findIndex(
|
|
672
|
+
(line, index) => index > 0 && line.trim() === "" && lines[index - 1]?.includes("|") && !lines[index + 1]?.includes("|")
|
|
673
|
+
);
|
|
674
|
+
if (tableEndIndex !== -1) {
|
|
675
|
+
contentStartIndex = tableEndIndex + 1;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
const mainContent = lines.slice(contentStartIndex).join("\n").trim();
|
|
679
|
+
if (!mainContent) {
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
const frontmatter = {
|
|
683
|
+
root: false,
|
|
684
|
+
targets: ["claude"],
|
|
685
|
+
description: "Main Claude Code configuration",
|
|
686
|
+
globs: ["**/*"]
|
|
687
|
+
};
|
|
688
|
+
return {
|
|
689
|
+
frontmatter,
|
|
690
|
+
content: mainContent,
|
|
691
|
+
filename: "claude-main",
|
|
692
|
+
filepath
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
async function parseClaudeMemoryFiles(memoryDir) {
|
|
696
|
+
const rules = [];
|
|
697
|
+
try {
|
|
698
|
+
const { readdir: readdir2 } = await import("fs/promises");
|
|
699
|
+
const files = await readdir2(memoryDir);
|
|
700
|
+
for (const file of files) {
|
|
701
|
+
if (file.endsWith(".md")) {
|
|
702
|
+
const filePath = join8(memoryDir, file);
|
|
703
|
+
const content = await readFileContent(filePath);
|
|
704
|
+
if (content.trim()) {
|
|
705
|
+
const filename = basename2(file, ".md");
|
|
706
|
+
const frontmatter = {
|
|
707
|
+
root: false,
|
|
708
|
+
targets: ["claude"],
|
|
709
|
+
description: `Memory file: ${filename}`,
|
|
710
|
+
globs: ["**/*"]
|
|
711
|
+
};
|
|
712
|
+
rules.push({
|
|
713
|
+
frontmatter,
|
|
714
|
+
content: content.trim(),
|
|
715
|
+
filename: `claude-memory-${filename}`,
|
|
716
|
+
filepath: filePath
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
} catch (_error) {
|
|
722
|
+
}
|
|
723
|
+
return rules;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// src/parsers/cline.ts
|
|
727
|
+
import { join as join9 } from "path";
|
|
728
|
+
async function parseClineConfiguration(baseDir = process.cwd()) {
|
|
729
|
+
const errors = [];
|
|
730
|
+
const rules = [];
|
|
731
|
+
const clineFilePath = join9(baseDir, ".cline", "instructions.md");
|
|
732
|
+
if (!await fileExists(clineFilePath)) {
|
|
733
|
+
errors.push(".cline/instructions.md file not found");
|
|
734
|
+
return { rules, errors };
|
|
735
|
+
}
|
|
736
|
+
try {
|
|
737
|
+
const content = await readFileContent(clineFilePath);
|
|
738
|
+
if (content.trim()) {
|
|
739
|
+
const frontmatter = {
|
|
740
|
+
root: false,
|
|
741
|
+
targets: ["cline"],
|
|
742
|
+
description: "Cline AI assistant instructions",
|
|
743
|
+
globs: ["**/*"]
|
|
744
|
+
};
|
|
745
|
+
rules.push({
|
|
746
|
+
frontmatter,
|
|
747
|
+
content: content.trim(),
|
|
748
|
+
filename: "cline-instructions",
|
|
749
|
+
filepath: clineFilePath
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
} catch (error) {
|
|
753
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
754
|
+
errors.push(`Failed to parse Cline configuration: ${errorMessage}`);
|
|
755
|
+
}
|
|
756
|
+
return { rules, errors };
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// src/parsers/copilot.ts
|
|
760
|
+
import { join as join10 } from "path";
|
|
761
|
+
async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
762
|
+
const errors = [];
|
|
763
|
+
const rules = [];
|
|
764
|
+
const copilotFilePath = join10(baseDir, ".github", "copilot-instructions.md");
|
|
765
|
+
if (!await fileExists(copilotFilePath)) {
|
|
766
|
+
errors.push(".github/copilot-instructions.md file not found");
|
|
767
|
+
return { rules, errors };
|
|
768
|
+
}
|
|
769
|
+
try {
|
|
770
|
+
const content = await readFileContent(copilotFilePath);
|
|
771
|
+
if (content.trim()) {
|
|
772
|
+
const frontmatter = {
|
|
773
|
+
root: false,
|
|
774
|
+
targets: ["copilot"],
|
|
775
|
+
description: "GitHub Copilot instructions",
|
|
776
|
+
globs: ["**/*"]
|
|
777
|
+
};
|
|
778
|
+
rules.push({
|
|
779
|
+
frontmatter,
|
|
780
|
+
content: content.trim(),
|
|
781
|
+
filename: "copilot-instructions",
|
|
782
|
+
filepath: copilotFilePath
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
} catch (error) {
|
|
786
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
787
|
+
errors.push(`Failed to parse Copilot configuration: ${errorMessage}`);
|
|
788
|
+
}
|
|
789
|
+
return { rules, errors };
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// src/parsers/cursor.ts
|
|
793
|
+
import { join as join11 } from "path";
|
|
794
|
+
async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
795
|
+
const errors = [];
|
|
796
|
+
const rules = [];
|
|
797
|
+
const cursorFilePath = join11(baseDir, ".cursorrules");
|
|
798
|
+
if (!await fileExists(cursorFilePath)) {
|
|
799
|
+
errors.push(".cursorrules file not found");
|
|
800
|
+
return { rules, errors };
|
|
801
|
+
}
|
|
802
|
+
try {
|
|
803
|
+
const content = await readFileContent(cursorFilePath);
|
|
804
|
+
if (content.trim()) {
|
|
805
|
+
const frontmatter = {
|
|
806
|
+
root: false,
|
|
807
|
+
targets: ["cursor"],
|
|
808
|
+
description: "Cursor IDE configuration rules",
|
|
809
|
+
globs: ["**/*"]
|
|
810
|
+
};
|
|
811
|
+
rules.push({
|
|
812
|
+
frontmatter,
|
|
813
|
+
content: content.trim(),
|
|
814
|
+
filename: "cursor-rules",
|
|
815
|
+
filepath: cursorFilePath
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
} catch (error) {
|
|
819
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
820
|
+
errors.push(`Failed to parse Cursor configuration: ${errorMessage}`);
|
|
821
|
+
}
|
|
822
|
+
return { rules, errors };
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// src/parsers/roo.ts
|
|
826
|
+
import { join as join12 } from "path";
|
|
827
|
+
async function parseRooConfiguration(baseDir = process.cwd()) {
|
|
828
|
+
const errors = [];
|
|
829
|
+
const rules = [];
|
|
830
|
+
const rooFilePath = join12(baseDir, ".roo", "instructions.md");
|
|
831
|
+
if (!await fileExists(rooFilePath)) {
|
|
832
|
+
errors.push(".roo/instructions.md file not found");
|
|
833
|
+
return { rules, errors };
|
|
834
|
+
}
|
|
835
|
+
try {
|
|
836
|
+
const content = await readFileContent(rooFilePath);
|
|
837
|
+
if (content.trim()) {
|
|
838
|
+
const frontmatter = {
|
|
839
|
+
root: false,
|
|
840
|
+
targets: ["roo"],
|
|
841
|
+
description: "Roo Code AI assistant instructions",
|
|
842
|
+
globs: ["**/*"]
|
|
843
|
+
};
|
|
844
|
+
rules.push({
|
|
845
|
+
frontmatter,
|
|
846
|
+
content: content.trim(),
|
|
847
|
+
filename: "roo-instructions",
|
|
848
|
+
filepath: rooFilePath
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
} catch (error) {
|
|
852
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
853
|
+
errors.push(`Failed to parse Roo configuration: ${errorMessage}`);
|
|
854
|
+
}
|
|
855
|
+
return { rules, errors };
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// src/core/importer.ts
|
|
859
|
+
async function importConfiguration(options) {
|
|
860
|
+
const { tool, baseDir = process.cwd(), rulesDir = ".rulesync", verbose = false } = options;
|
|
861
|
+
const errors = [];
|
|
862
|
+
let rules = [];
|
|
863
|
+
if (verbose) {
|
|
864
|
+
console.log(`Importing ${tool} configuration from ${baseDir}...`);
|
|
865
|
+
}
|
|
866
|
+
try {
|
|
867
|
+
switch (tool) {
|
|
868
|
+
case "claude": {
|
|
869
|
+
const claudeResult = await parseClaudeConfiguration(baseDir);
|
|
870
|
+
rules = claudeResult.rules;
|
|
871
|
+
errors.push(...claudeResult.errors);
|
|
872
|
+
break;
|
|
873
|
+
}
|
|
874
|
+
case "cursor": {
|
|
875
|
+
const cursorResult = await parseCursorConfiguration(baseDir);
|
|
876
|
+
rules = cursorResult.rules;
|
|
877
|
+
errors.push(...cursorResult.errors);
|
|
878
|
+
break;
|
|
879
|
+
}
|
|
880
|
+
case "copilot": {
|
|
881
|
+
const copilotResult = await parseCopilotConfiguration(baseDir);
|
|
882
|
+
rules = copilotResult.rules;
|
|
883
|
+
errors.push(...copilotResult.errors);
|
|
884
|
+
break;
|
|
885
|
+
}
|
|
886
|
+
case "cline": {
|
|
887
|
+
const clineResult = await parseClineConfiguration(baseDir);
|
|
888
|
+
rules = clineResult.rules;
|
|
889
|
+
errors.push(...clineResult.errors);
|
|
890
|
+
break;
|
|
891
|
+
}
|
|
892
|
+
case "roo": {
|
|
893
|
+
const rooResult = await parseRooConfiguration(baseDir);
|
|
894
|
+
rules = rooResult.rules;
|
|
895
|
+
errors.push(...rooResult.errors);
|
|
896
|
+
break;
|
|
897
|
+
}
|
|
898
|
+
default:
|
|
899
|
+
errors.push(`Unsupported tool: ${tool}`);
|
|
900
|
+
return { success: false, rulesCreated: 0, errors };
|
|
901
|
+
}
|
|
902
|
+
} catch (error) {
|
|
903
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
904
|
+
errors.push(`Failed to parse ${tool} configuration: ${errorMessage}`);
|
|
905
|
+
return { success: false, rulesCreated: 0, errors };
|
|
906
|
+
}
|
|
907
|
+
if (rules.length === 0) {
|
|
908
|
+
return { success: false, rulesCreated: 0, errors };
|
|
909
|
+
}
|
|
910
|
+
const rulesDirPath = join13(baseDir, rulesDir);
|
|
911
|
+
try {
|
|
912
|
+
const { mkdir: mkdir3 } = await import("fs/promises");
|
|
913
|
+
await mkdir3(rulesDirPath, { recursive: true });
|
|
914
|
+
} catch (error) {
|
|
915
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
916
|
+
errors.push(`Failed to create rules directory: ${errorMessage}`);
|
|
917
|
+
return { success: false, rulesCreated: 0, errors };
|
|
918
|
+
}
|
|
919
|
+
let rulesCreated = 0;
|
|
920
|
+
for (const rule of rules) {
|
|
921
|
+
try {
|
|
922
|
+
const baseFilename = `${tool}__${rule.filename}`;
|
|
923
|
+
const filename = await generateUniqueFilename(rulesDirPath, baseFilename);
|
|
924
|
+
const filePath = join13(rulesDirPath, `${filename}.md`);
|
|
925
|
+
const content = generateRuleFileContent(rule);
|
|
926
|
+
await writeFileContent(filePath, content);
|
|
927
|
+
rulesCreated++;
|
|
928
|
+
if (verbose) {
|
|
929
|
+
console.log(`\u2705 Created rule file: ${filePath}`);
|
|
930
|
+
}
|
|
931
|
+
} catch (error) {
|
|
932
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
933
|
+
errors.push(`Failed to create rule file for ${rule.filename}: ${errorMessage}`);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
return {
|
|
937
|
+
success: rulesCreated > 0,
|
|
938
|
+
rulesCreated,
|
|
939
|
+
errors
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
function generateRuleFileContent(rule) {
|
|
943
|
+
const frontmatter = matter2.stringify("", rule.frontmatter);
|
|
944
|
+
return frontmatter + rule.content;
|
|
945
|
+
}
|
|
946
|
+
async function generateUniqueFilename(rulesDir, baseFilename) {
|
|
947
|
+
let filename = baseFilename;
|
|
948
|
+
let counter = 1;
|
|
949
|
+
while (await fileExists(join13(rulesDir, `${filename}.md`))) {
|
|
950
|
+
filename = `${baseFilename}-${counter}`;
|
|
951
|
+
counter++;
|
|
952
|
+
}
|
|
953
|
+
return filename;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// src/cli/commands/import.ts
|
|
957
|
+
async function importCommand(options = {}) {
|
|
958
|
+
const tools = [];
|
|
959
|
+
if (options.claude) tools.push("claude");
|
|
960
|
+
if (options.cursor) tools.push("cursor");
|
|
961
|
+
if (options.copilot) tools.push("copilot");
|
|
962
|
+
if (options.cline) tools.push("cline");
|
|
963
|
+
if (options.roo) tools.push("roo");
|
|
964
|
+
if (tools.length === 0) {
|
|
965
|
+
console.error(
|
|
966
|
+
"\u274C Please specify at least one tool to import from (--claude, --cursor, --copilot, --cline, --roo)"
|
|
967
|
+
);
|
|
968
|
+
process.exit(1);
|
|
969
|
+
}
|
|
970
|
+
console.log("Importing configuration files...");
|
|
971
|
+
let totalRulesCreated = 0;
|
|
972
|
+
const allErrors = [];
|
|
973
|
+
for (const tool of tools) {
|
|
974
|
+
if (options.verbose) {
|
|
975
|
+
console.log(`
|
|
976
|
+
Importing from ${tool}...`);
|
|
977
|
+
}
|
|
978
|
+
try {
|
|
979
|
+
const result = await importConfiguration({
|
|
980
|
+
tool,
|
|
981
|
+
verbose: options.verbose ?? false
|
|
982
|
+
});
|
|
983
|
+
if (result.success) {
|
|
984
|
+
console.log(`\u2705 Imported ${result.rulesCreated} rule(s) from ${tool}`);
|
|
985
|
+
totalRulesCreated += result.rulesCreated;
|
|
986
|
+
} else if (result.errors.length > 0) {
|
|
987
|
+
console.warn(`\u26A0\uFE0F Failed to import from ${tool}: ${result.errors[0]}`);
|
|
988
|
+
if (options.verbose) {
|
|
989
|
+
allErrors.push(...result.errors);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
} catch (error) {
|
|
993
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
994
|
+
console.error(`\u274C Error importing from ${tool}: ${errorMessage}`);
|
|
995
|
+
allErrors.push(`${tool}: ${errorMessage}`);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
if (totalRulesCreated > 0) {
|
|
999
|
+
console.log(`
|
|
1000
|
+
\u{1F389} Successfully imported ${totalRulesCreated} rule(s) total!`);
|
|
1001
|
+
console.log("You can now run 'rulesync generate' to create tool-specific configurations.");
|
|
1002
|
+
} else {
|
|
1003
|
+
console.warn(
|
|
1004
|
+
"\n\u26A0\uFE0F No rules were imported. Please check that configuration files exist for the selected tools."
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
1007
|
+
if (options.verbose && allErrors.length > 0) {
|
|
1008
|
+
console.log("\nDetailed errors:");
|
|
1009
|
+
for (const error of allErrors) {
|
|
1010
|
+
console.log(` - ${error}`);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
636
1015
|
// src/cli/commands/init.ts
|
|
637
|
-
import { join as
|
|
1016
|
+
import { join as join14 } from "path";
|
|
638
1017
|
async function initCommand() {
|
|
639
1018
|
const aiRulesDir = ".rulesync";
|
|
640
1019
|
console.log("Initializing rulesync...");
|
|
@@ -764,7 +1143,7 @@ globs: ["src/api/**/*.ts", "src/services/**/*.ts", "src/models/**/*.ts"]
|
|
|
764
1143
|
}
|
|
765
1144
|
];
|
|
766
1145
|
for (const file of sampleFiles) {
|
|
767
|
-
const filepath =
|
|
1146
|
+
const filepath = join14(aiRulesDir, file.filename);
|
|
768
1147
|
if (!await fileExists(filepath)) {
|
|
769
1148
|
await writeFileContent(filepath, file.content);
|
|
770
1149
|
console.log(`Created ${filepath}`);
|
|
@@ -791,13 +1170,13 @@ async function statusCommand() {
|
|
|
791
1170
|
console.log(`
|
|
792
1171
|
\u{1F4CB} Rules: ${rules.length} total`);
|
|
793
1172
|
if (rules.length > 0) {
|
|
794
|
-
const
|
|
795
|
-
const
|
|
796
|
-
console.log(` -
|
|
797
|
-
console.log(` -
|
|
798
|
-
const targetCounts = { copilot: 0, cursor: 0, cline: 0 };
|
|
1173
|
+
const rootRules = rules.filter((r) => r.frontmatter.root).length;
|
|
1174
|
+
const nonRootRules = rules.length - rootRules;
|
|
1175
|
+
console.log(` - Root rules: ${rootRules}`);
|
|
1176
|
+
console.log(` - Non-root rules: ${nonRootRules}`);
|
|
1177
|
+
const targetCounts = { copilot: 0, cursor: 0, cline: 0, claude: 0, roo: 0 };
|
|
799
1178
|
for (const rule of rules) {
|
|
800
|
-
const targets = rule.frontmatter.targets
|
|
1179
|
+
const targets = rule.frontmatter.targets[0] === "*" ? config.defaultTargets : rule.frontmatter.targets;
|
|
801
1180
|
for (const target of targets) {
|
|
802
1181
|
if (target in targetCounts) {
|
|
803
1182
|
targetCounts[target]++;
|
|
@@ -808,6 +1187,8 @@ async function statusCommand() {
|
|
|
808
1187
|
console.log(` - Copilot: ${targetCounts.copilot} rules`);
|
|
809
1188
|
console.log(` - Cursor: ${targetCounts.cursor} rules`);
|
|
810
1189
|
console.log(` - Cline: ${targetCounts.cline} rules`);
|
|
1190
|
+
console.log(` - Claude: ${targetCounts.claude} rules`);
|
|
1191
|
+
console.log(` - Roo: ${targetCounts.roo} rules`);
|
|
811
1192
|
}
|
|
812
1193
|
console.log("\n\u{1F4E4} Generated files:");
|
|
813
1194
|
for (const [tool, outputPath] of Object.entries(config.outputPaths)) {
|
|
@@ -905,11 +1286,12 @@ async function watchCommand() {
|
|
|
905
1286
|
|
|
906
1287
|
// src/cli/index.ts
|
|
907
1288
|
var program = new Command();
|
|
908
|
-
program.name("rulesync").description("Unified AI rules management CLI tool").version("0.
|
|
1289
|
+
program.name("rulesync").description("Unified AI rules management CLI tool").version("0.20.0");
|
|
909
1290
|
program.command("init").description("Initialize rulesync in current directory").action(initCommand);
|
|
910
1291
|
program.command("add <filename>").description("Add a new rule file").action(addCommand);
|
|
911
1292
|
program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
|
|
912
|
-
program.command("
|
|
1293
|
+
program.command("import").description("Import configurations from AI tools to rulesync format").option("--claude", "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("-v, --verbose", "Verbose output").action(importCommand);
|
|
1294
|
+
program.command("generate").description("Generate configuration files for AI tools").option("--copilot", "Generate only for GitHub Copilot").option("--cursor", "Generate only for Cursor").option("--cline", "Generate only for Cline").option("--claude", "Generate only for Claude Code").option("--roo", "Generate only for Roo Code").option("--delete", "Delete all existing files in output directories before generating").option(
|
|
913
1295
|
"-b, --base-dir <paths>",
|
|
914
1296
|
"Base directories to generate files (comma-separated for multiple paths)"
|
|
915
1297
|
).option("-v, --verbose", "Verbose output").action(async (options) => {
|
|
@@ -918,6 +1300,7 @@ program.command("generate").description("Generate configuration files for AI too
|
|
|
918
1300
|
if (options.cursor) tools.push("cursor");
|
|
919
1301
|
if (options.cline) tools.push("cline");
|
|
920
1302
|
if (options.claude) tools.push("claude");
|
|
1303
|
+
if (options.roo) tools.push("roo");
|
|
921
1304
|
const generateOptions = {
|
|
922
1305
|
verbose: options.verbose,
|
|
923
1306
|
delete: options.delete
|
package/package.json
CHANGED
|
@@ -1,19 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rulesync",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Unified AI rules management CLI tool that generates configuration files for various AI development tools",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"module": "dist/index.mjs",
|
|
7
|
-
"types": "dist/index.d.ts",
|
|
8
|
-
"bin": {
|
|
9
|
-
"rulesync": "dist/index.js"
|
|
10
|
-
},
|
|
11
|
-
"files": [
|
|
12
|
-
"dist"
|
|
13
|
-
],
|
|
14
|
-
"engines": {
|
|
15
|
-
"node": ">=20.0.0"
|
|
16
|
-
},
|
|
17
5
|
"keywords": [
|
|
18
6
|
"ai",
|
|
19
7
|
"rules",
|
|
@@ -24,20 +12,31 @@
|
|
|
24
12
|
"configuration",
|
|
25
13
|
"development"
|
|
26
14
|
],
|
|
27
|
-
"
|
|
28
|
-
"
|
|
15
|
+
"homepage": "https://github.com/dyoshikawa/rulesync#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/dyoshikawa/rulesync/issues"
|
|
18
|
+
},
|
|
29
19
|
"repository": {
|
|
30
20
|
"type": "git",
|
|
31
21
|
"url": "https://github.com/dyoshikawa/rulesync.git"
|
|
32
22
|
},
|
|
33
|
-
"
|
|
34
|
-
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": "dyoshikawa",
|
|
25
|
+
"main": "dist/index.js",
|
|
26
|
+
"module": "dist/index.mjs",
|
|
27
|
+
"types": "dist/index.d.ts",
|
|
28
|
+
"bin": {
|
|
29
|
+
"rulesync": "dist/index.js"
|
|
35
30
|
},
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
"files": [
|
|
32
|
+
"dist"
|
|
33
|
+
],
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"chokidar": "4.0.3",
|
|
36
|
+
"commander": "14.0.0",
|
|
37
|
+
"gray-matter": "4.0.3",
|
|
38
|
+
"marked": "15.0.12"
|
|
39
39
|
},
|
|
40
|
-
"packageManager": "pnpm@7.33.7",
|
|
41
40
|
"devDependencies": {
|
|
42
41
|
"@biomejs/biome": "2.0.0",
|
|
43
42
|
"@secretlint/secretlint-rule-preset-recommend": "10.1.0",
|
|
@@ -45,29 +44,36 @@
|
|
|
45
44
|
"@types/node": "24.0.3",
|
|
46
45
|
"@vitest/coverage-v8": "3.2.4",
|
|
47
46
|
"secretlint": "10.1.0",
|
|
47
|
+
"sort-package-json": "3.2.1",
|
|
48
48
|
"tsup": "8.5.0",
|
|
49
49
|
"tsx": "4.20.3",
|
|
50
50
|
"typescript": "5.8.3",
|
|
51
51
|
"vitest": "3.2.4"
|
|
52
52
|
},
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
"packageManager": "pnpm@7.33.7",
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=20.0.0"
|
|
56
|
+
},
|
|
57
|
+
"publishConfig": {
|
|
58
|
+
"access": "public"
|
|
58
59
|
},
|
|
59
60
|
"scripts": {
|
|
60
|
-
"
|
|
61
|
+
"bcheck": "biome check src/",
|
|
62
|
+
"bcheck:fix": "biome check --write src/",
|
|
61
63
|
"build": "tsup src/cli/index.ts --format cjs,esm --dts --clean",
|
|
64
|
+
"check": "pnpm run lint && pnpm run format && pnpm run bcheck && pnpm run typecheck",
|
|
65
|
+
"dev": "tsx src/cli/index.ts",
|
|
66
|
+
"fix": "pnpm run lint:fix && pnpm run format:fix && pnpm run bcheck:fix",
|
|
67
|
+
"format": "biome format src/",
|
|
68
|
+
"format:fix": "biome format --write src/",
|
|
69
|
+
"postinstall": "pnpm sort && pnpm secretlint && pnpm check && pnpm test",
|
|
62
70
|
"lint": "biome lint src/",
|
|
63
71
|
"lint:fix": "biome lint --write src/",
|
|
64
|
-
"format": "biome format --write src/",
|
|
65
|
-
"format:check": "biome format src/",
|
|
66
|
-
"check": "biome check src/",
|
|
67
|
-
"check:fix": "biome check --write src/",
|
|
68
72
|
"secretlint": "secretlint \"**/*\"",
|
|
73
|
+
"sort": "sort-package-json",
|
|
69
74
|
"test": "vitest",
|
|
75
|
+
"test:coverage": "vitest --coverage",
|
|
70
76
|
"test:watch": "vitest --watch",
|
|
71
|
-
"
|
|
77
|
+
"typecheck": "tsc --noEmit"
|
|
72
78
|
}
|
|
73
79
|
}
|