rulesync 0.20.0 → 0.22.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 +400 -11
- package/dist/index.mjs +400 -11
- 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;
|
|
@@ -317,6 +317,12 @@ async function removeClaudeGeneratedFiles() {
|
|
|
317
317
|
async function generateConfigurations(rules, config, targetTools, baseDir) {
|
|
318
318
|
const outputs = [];
|
|
319
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
|
+
}
|
|
320
326
|
for (const tool of toolsToGenerate) {
|
|
321
327
|
const relevantRules = filterRulesForTool(rules, tool, config);
|
|
322
328
|
if (relevantRules.length === 0) {
|
|
@@ -650,8 +656,387 @@ ${linesToAdd.join("\n")}
|
|
|
650
656
|
}
|
|
651
657
|
};
|
|
652
658
|
|
|
653
|
-
// 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
|
|
654
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");
|
|
655
1040
|
async function initCommand() {
|
|
656
1041
|
const aiRulesDir = ".rulesync";
|
|
657
1042
|
console.log("Initializing rulesync...");
|
|
@@ -781,7 +1166,7 @@ globs: ["src/api/**/*.ts", "src/services/**/*.ts", "src/models/**/*.ts"]
|
|
|
781
1166
|
}
|
|
782
1167
|
];
|
|
783
1168
|
for (const file of sampleFiles) {
|
|
784
|
-
const filepath = (0,
|
|
1169
|
+
const filepath = (0, import_node_path16.join)(aiRulesDir, file.filename);
|
|
785
1170
|
if (!await fileExists(filepath)) {
|
|
786
1171
|
await writeFileContent(filepath, file.content);
|
|
787
1172
|
console.log(`Created ${filepath}`);
|
|
@@ -808,13 +1193,13 @@ async function statusCommand() {
|
|
|
808
1193
|
console.log(`
|
|
809
1194
|
\u{1F4CB} Rules: ${rules.length} total`);
|
|
810
1195
|
if (rules.length > 0) {
|
|
811
|
-
const
|
|
812
|
-
const
|
|
813
|
-
console.log(` -
|
|
814
|
-
console.log(` -
|
|
815
|
-
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 };
|
|
816
1201
|
for (const rule of rules) {
|
|
817
|
-
const targets = rule.frontmatter.targets
|
|
1202
|
+
const targets = rule.frontmatter.targets[0] === "*" ? config.defaultTargets : rule.frontmatter.targets;
|
|
818
1203
|
for (const target of targets) {
|
|
819
1204
|
if (target in targetCounts) {
|
|
820
1205
|
targetCounts[target]++;
|
|
@@ -825,6 +1210,8 @@ async function statusCommand() {
|
|
|
825
1210
|
console.log(` - Copilot: ${targetCounts.copilot} rules`);
|
|
826
1211
|
console.log(` - Cursor: ${targetCounts.cursor} rules`);
|
|
827
1212
|
console.log(` - Cline: ${targetCounts.cline} rules`);
|
|
1213
|
+
console.log(` - Claude: ${targetCounts.claude} rules`);
|
|
1214
|
+
console.log(` - Roo: ${targetCounts.roo} rules`);
|
|
828
1215
|
}
|
|
829
1216
|
console.log("\n\u{1F4E4} Generated files:");
|
|
830
1217
|
for (const [tool, outputPath] of Object.entries(config.outputPaths)) {
|
|
@@ -922,11 +1309,12 @@ async function watchCommand() {
|
|
|
922
1309
|
|
|
923
1310
|
// src/cli/index.ts
|
|
924
1311
|
var program = new import_commander.Command();
|
|
925
|
-
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");
|
|
926
1313
|
program.command("init").description("Initialize rulesync in current directory").action(initCommand);
|
|
927
1314
|
program.command("add <filename>").description("Add a new rule file").action(addCommand);
|
|
928
1315
|
program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
|
|
929
|
-
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(
|
|
930
1318
|
"-b, --base-dir <paths>",
|
|
931
1319
|
"Base directories to generate files (comma-separated for multiple paths)"
|
|
932
1320
|
).option("-v, --verbose", "Verbose output").action(async (options) => {
|
|
@@ -935,6 +1323,7 @@ program.command("generate").description("Generate configuration files for AI too
|
|
|
935
1323
|
if (options.cursor) tools.push("cursor");
|
|
936
1324
|
if (options.cline) tools.push("cline");
|
|
937
1325
|
if (options.claude) tools.push("claude");
|
|
1326
|
+
if (options.roo) tools.push("roo");
|
|
938
1327
|
const generateOptions = {
|
|
939
1328
|
verbose: options.verbose,
|
|
940
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;
|
|
@@ -294,6 +294,12 @@ async function removeClaudeGeneratedFiles() {
|
|
|
294
294
|
async function generateConfigurations(rules, config, targetTools, baseDir) {
|
|
295
295
|
const outputs = [];
|
|
296
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
|
+
}
|
|
297
303
|
for (const tool of toolsToGenerate) {
|
|
298
304
|
const relevantRules = filterRulesForTool(rules, tool, config);
|
|
299
305
|
if (relevantRules.length === 0) {
|
|
@@ -627,8 +633,387 @@ ${linesToAdd.join("\n")}
|
|
|
627
633
|
}
|
|
628
634
|
};
|
|
629
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
|
+
|
|
630
1015
|
// src/cli/commands/init.ts
|
|
631
|
-
import { join as
|
|
1016
|
+
import { join as join14 } from "path";
|
|
632
1017
|
async function initCommand() {
|
|
633
1018
|
const aiRulesDir = ".rulesync";
|
|
634
1019
|
console.log("Initializing rulesync...");
|
|
@@ -758,7 +1143,7 @@ globs: ["src/api/**/*.ts", "src/services/**/*.ts", "src/models/**/*.ts"]
|
|
|
758
1143
|
}
|
|
759
1144
|
];
|
|
760
1145
|
for (const file of sampleFiles) {
|
|
761
|
-
const filepath =
|
|
1146
|
+
const filepath = join14(aiRulesDir, file.filename);
|
|
762
1147
|
if (!await fileExists(filepath)) {
|
|
763
1148
|
await writeFileContent(filepath, file.content);
|
|
764
1149
|
console.log(`Created ${filepath}`);
|
|
@@ -785,13 +1170,13 @@ async function statusCommand() {
|
|
|
785
1170
|
console.log(`
|
|
786
1171
|
\u{1F4CB} Rules: ${rules.length} total`);
|
|
787
1172
|
if (rules.length > 0) {
|
|
788
|
-
const
|
|
789
|
-
const
|
|
790
|
-
console.log(` -
|
|
791
|
-
console.log(` -
|
|
792
|
-
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 };
|
|
793
1178
|
for (const rule of rules) {
|
|
794
|
-
const targets = rule.frontmatter.targets
|
|
1179
|
+
const targets = rule.frontmatter.targets[0] === "*" ? config.defaultTargets : rule.frontmatter.targets;
|
|
795
1180
|
for (const target of targets) {
|
|
796
1181
|
if (target in targetCounts) {
|
|
797
1182
|
targetCounts[target]++;
|
|
@@ -802,6 +1187,8 @@ async function statusCommand() {
|
|
|
802
1187
|
console.log(` - Copilot: ${targetCounts.copilot} rules`);
|
|
803
1188
|
console.log(` - Cursor: ${targetCounts.cursor} rules`);
|
|
804
1189
|
console.log(` - Cline: ${targetCounts.cline} rules`);
|
|
1190
|
+
console.log(` - Claude: ${targetCounts.claude} rules`);
|
|
1191
|
+
console.log(` - Roo: ${targetCounts.roo} rules`);
|
|
805
1192
|
}
|
|
806
1193
|
console.log("\n\u{1F4E4} Generated files:");
|
|
807
1194
|
for (const [tool, outputPath] of Object.entries(config.outputPaths)) {
|
|
@@ -899,11 +1286,12 @@ async function watchCommand() {
|
|
|
899
1286
|
|
|
900
1287
|
// src/cli/index.ts
|
|
901
1288
|
var program = new Command();
|
|
902
|
-
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");
|
|
903
1290
|
program.command("init").description("Initialize rulesync in current directory").action(initCommand);
|
|
904
1291
|
program.command("add <filename>").description("Add a new rule file").action(addCommand);
|
|
905
1292
|
program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
|
|
906
|
-
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(
|
|
907
1295
|
"-b, --base-dir <paths>",
|
|
908
1296
|
"Base directories to generate files (comma-separated for multiple paths)"
|
|
909
1297
|
).option("-v, --verbose", "Verbose output").action(async (options) => {
|
|
@@ -912,6 +1300,7 @@ program.command("generate").description("Generate configuration files for AI too
|
|
|
912
1300
|
if (options.cursor) tools.push("cursor");
|
|
913
1301
|
if (options.cline) tools.push("cline");
|
|
914
1302
|
if (options.claude) tools.push("claude");
|
|
1303
|
+
if (options.roo) tools.push("roo");
|
|
915
1304
|
const generateOptions = {
|
|
916
1305
|
verbose: options.verbose,
|
|
917
1306
|
delete: options.delete
|
package/package.json
CHANGED
|
@@ -1,19 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rulesync",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.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
|
}
|