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 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. Other Commands
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.includes("*")) {
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
- lines.push(`@.claude/memories/${rule.filename}.md`);
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(...formatRuleForClaude(rule));
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(`Multiple root rules found: ${rootRuleFiles}. Only one rule can have root: true.`);
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/cli/commands/init.ts
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, import_node_path10.join)(aiRulesDir, file.filename);
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 highPriority = rules.filter((r) => r.frontmatter.priority === "high").length;
818
- const lowPriority = rules.filter((r) => r.frontmatter.priority === "low").length;
819
- console.log(` - High priority: ${highPriority}`);
820
- console.log(` - Low priority: ${lowPriority}`);
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.includes("*") ? config.defaultTargets : 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.1.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("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("--delete", "Delete all existing files in output directories before generating").option(
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.includes("*")) {
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
- lines.push(`@.claude/memories/${rule.filename}.md`);
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(...formatRuleForClaude(rule));
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(`Multiple root rules found: ${rootRuleFiles}. Only one rule can have root: true.`);
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 join8 } from "path";
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 = join8(aiRulesDir, file.filename);
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 highPriority = rules.filter((r) => r.frontmatter.priority === "high").length;
795
- const lowPriority = rules.filter((r) => r.frontmatter.priority === "low").length;
796
- console.log(` - High priority: ${highPriority}`);
797
- console.log(` - Low priority: ${lowPriority}`);
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.includes("*") ? config.defaultTargets : 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.1.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("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("--delete", "Delete all existing files in output directories before generating").option(
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.19.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
- "author": "dyoshikawa",
28
- "license": "MIT",
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
- "bugs": {
34
- "url": "https://github.com/dyoshikawa/rulesync/issues"
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
- "homepage": "https://github.com/dyoshikawa/rulesync#readme",
37
- "publishConfig": {
38
- "access": "public"
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
- "dependencies": {
54
- "chokidar": "4.0.3",
55
- "commander": "14.0.0",
56
- "gray-matter": "4.0.3",
57
- "marked": "15.0.12"
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
- "dev": "tsx src/cli/index.ts",
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
- "test:coverage": "vitest --coverage"
77
+ "typecheck": "tsc --noEmit"
72
78
  }
73
79
  }