rulesync 0.32.0 → 0.34.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 +9 -2
- package/README.md +9 -2
- package/dist/index.js +106 -43
- package/dist/index.mjs +100 -37
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -16,6 +16,7 @@ rulesyncは以下のAI開発ツールの**生成**と**インポート**の両
|
|
|
16
16
|
- **Cline Rules** (`.clinerules/*.md` + `.cline/instructions.md`)
|
|
17
17
|
- **Claude Code Memory** (`./CLAUDE.md` + `.claude/memories/*.md`)
|
|
18
18
|
- **Roo Code Rules** (`.roo/rules/*.md` + `.roo/instructions.md`)
|
|
19
|
+
- **Gemini CLI** (`GEMINI.md` + `.gemini/memories/*.md`)
|
|
19
20
|
|
|
20
21
|
## インストール
|
|
21
22
|
|
|
@@ -68,6 +69,7 @@ yarn global add rulesync
|
|
|
68
69
|
npx rulesync import --copilot # .github/copilot-instructions.mdから
|
|
69
70
|
npx rulesync import --cline # .cline/instructions.mdから
|
|
70
71
|
npx rulesync import --roo # .roo/instructions.mdから
|
|
72
|
+
npx rulesync import --geminicli # GEMINI.mdと.gemini/memories/*.mdから
|
|
71
73
|
```
|
|
72
74
|
|
|
73
75
|
2. **`.rulesync/`ディレクトリのインポートされたルールを確認・編集**
|
|
@@ -93,9 +95,10 @@ AI開発ツールは新しいツールが頻繁に登場し、急速に進化し
|
|
|
93
95
|
- Cursor:リファクタリング
|
|
94
96
|
- Claude Code:アーキテクチャ設計
|
|
95
97
|
- Cline:デバッグ支援
|
|
98
|
+
- Gemini CLI:知的コード解析
|
|
96
99
|
|
|
97
100
|
### 🔓 **ベンダーロックインなし**
|
|
98
|
-
ベンダーロックインを完全に回避できます。rulesyncの使用を停止することを決定した場合でも、生成されたルールファイル(`.github/instructions/`、`.cursor/rules/`、`.clinerules/`、`CLAUDE.md`など)をそのまま使い続けることができます。
|
|
101
|
+
ベンダーロックインを完全に回避できます。rulesyncの使用を停止することを決定した場合でも、生成されたルールファイル(`.github/instructions/`、`.cursor/rules/`、`.clinerules/`、`CLAUDE.md`、`GEMINI.md`など)をそのまま使い続けることができます。
|
|
99
102
|
|
|
100
103
|
### 🎯 **ツール間の一貫性**
|
|
101
104
|
すべてのAIツールに一貫したルールを適用し、チーム全体のコード品質と開発体験を向上させます。
|
|
@@ -171,6 +174,7 @@ rulesyncは2レベルのルールシステムを使用します:
|
|
|
171
174
|
| **GitHub Copilot** | 標準フォーマット | 標準フォーマット | すべてのルールがフロントマター付きの同じフォーマットを使用 |
|
|
172
175
|
| **Cline** | 標準フォーマット | 標準フォーマット | すべてのルールがプレーンMarkdownフォーマットを使用 |
|
|
173
176
|
| **Roo Code** | 標準フォーマット | 標準フォーマット | すべてのルールが説明ヘッダー付きのプレーンMarkdownフォーマットを使用 |
|
|
177
|
+
| **Gemini CLI** | `GEMINI.md` | `.gemini/memories/*.md` | GEMINI.mdがメモリファイルへの`@filename`参照を含む |
|
|
174
178
|
|
|
175
179
|
### 3. 設定ファイルの生成
|
|
176
180
|
|
|
@@ -184,6 +188,7 @@ npx rulesync generate --cursor
|
|
|
184
188
|
npx rulesync generate --cline
|
|
185
189
|
npx rulesync generate --claudecode
|
|
186
190
|
npx rulesync generate --roo
|
|
191
|
+
npx rulesync generate --geminicli
|
|
187
192
|
|
|
188
193
|
# クリーンビルド(既存ファイルを最初に削除)
|
|
189
194
|
npx rulesync generate --delete
|
|
@@ -205,7 +210,7 @@ npx rulesync generate --base-dir ./apps/web,./apps/api,./packages/shared
|
|
|
205
210
|
|
|
206
211
|
- `--delete`: 新しいファイルを作成する前に既存の生成済みファイルをすべて削除
|
|
207
212
|
- `--verbose`: 生成プロセス中に詳細出力を表示
|
|
208
|
-
- `--copilot`, `--cursor`, `--cline`, `--claudecode`, `--roo`: 指定されたツールのみ生成
|
|
213
|
+
- `--copilot`, `--cursor`, `--cline`, `--claudecode`, `--roo`, `--geminicli`: 指定されたツールのみ生成
|
|
209
214
|
- `--base-dir <paths>`: 指定されたベースディレクトリに設定ファイルを生成(複数パスの場合はカンマ区切り)。異なるプロジェクトディレクトリにツール固有の設定を生成したいmonorepoセットアップに便利。
|
|
210
215
|
|
|
211
216
|
### 4. 既存設定のインポート
|
|
@@ -219,6 +224,7 @@ npx rulesync import --cursor # .cursorrulesと.cursor/rules/*.mdからイン
|
|
|
219
224
|
npx rulesync import --copilot # .github/copilot-instructions.mdと.github/instructions/*.instructions.mdからインポート
|
|
220
225
|
npx rulesync import --cline # .cline/instructions.mdからインポート
|
|
221
226
|
npx rulesync import --roo # .roo/instructions.mdからインポート
|
|
227
|
+
npx rulesync import --geminicli # GEMINI.mdと.gemini/memories/*.mdからインポート
|
|
222
228
|
|
|
223
229
|
# 複数のツールからインポート
|
|
224
230
|
npx rulesync import --claudecode --cursor --copilot
|
|
@@ -326,6 +332,7 @@ globs: "**/*.ts,**/*.tsx"
|
|
|
326
332
|
| **Cline** | `.clinerules/*.md` | プレーンMarkdown | 両レベルとも同じフォーマットを使用 |
|
|
327
333
|
| **Claude Code** | `./CLAUDE.md` (ルート)<br>`.claude/memories/*.md` (非ルート) | プレーンMarkdown | ルートはCLAUDE.mdに移動<br>非ルートは別メモリファイルに移動<br>CLAUDE.mdは`@filename`参照を含む |
|
|
328
334
|
| **Roo Code** | `.roo/rules/*.md` | プレーンMarkdown | 両レベルとも説明ヘッダー付きの同じフォーマットを使用 |
|
|
335
|
+
| **Gemini CLI** | `GEMINI.md` (ルート)<br>`.gemini/memories/*.md` (非ルート) | プレーンMarkdown | ルートはGEMINI.mdに移動<br>非ルートは別メモリファイルに移動<br>GEMINI.mdは`@filename`参照を含む |
|
|
329
336
|
|
|
330
337
|
## バリデーション
|
|
331
338
|
|
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ rulesync supports both **generation** and **import** for the following AI develo
|
|
|
16
16
|
- **Cline Rules** (`.clinerules/*.md` + `.cline/instructions.md`)
|
|
17
17
|
- **Claude Code Memory** (`./CLAUDE.md` + `.claude/memories/*.md`)
|
|
18
18
|
- **Roo Code Rules** (`.roo/rules/*.md` + `.roo/instructions.md`)
|
|
19
|
+
- **Gemini CLI** (`GEMINI.md` + `.gemini/memories/*.md`)
|
|
19
20
|
|
|
20
21
|
## Installation
|
|
21
22
|
|
|
@@ -68,6 +69,7 @@ If you already have AI tool configurations, you can import them into rulesync fo
|
|
|
68
69
|
npx rulesync import --copilot # From .github/copilot-instructions.md
|
|
69
70
|
npx rulesync import --cline # From .cline/instructions.md
|
|
70
71
|
npx rulesync import --roo # From .roo/instructions.md
|
|
72
|
+
npx rulesync import --geminicli # From GEMINI.md and .gemini/memories/*.md
|
|
71
73
|
```
|
|
72
74
|
|
|
73
75
|
2. **Review and edit** the imported rules in `.rulesync/` directory
|
|
@@ -93,9 +95,10 @@ Enable hybrid development workflows combining multiple AI tools:
|
|
|
93
95
|
- Cursor for refactoring
|
|
94
96
|
- Claude Code for architecture design
|
|
95
97
|
- Cline for debugging assistance
|
|
98
|
+
- Gemini CLI for intelligent code analysis
|
|
96
99
|
|
|
97
100
|
### 🔓 **No Vendor Lock-in**
|
|
98
|
-
Avoid vendor lock-in completely. If you decide to stop using rulesync, you can continue using the generated rule files (`.github/instructions/`, `.cursor/rules/`, `.clinerules/`, `CLAUDE.md`, etc.) as-is.
|
|
101
|
+
Avoid vendor lock-in completely. If you decide to stop using rulesync, you can continue using the generated rule files (`.github/instructions/`, `.cursor/rules/`, `.clinerules/`, `CLAUDE.md`, `GEMINI.md`, etc.) as-is.
|
|
99
102
|
|
|
100
103
|
### 🎯 **Consistency Across Tools**
|
|
101
104
|
Apply consistent rules across all AI tools, improving code quality and development experience for the entire team.
|
|
@@ -171,6 +174,7 @@ Each AI tool handles rule levels differently:
|
|
|
171
174
|
| **GitHub Copilot** | Standard format | Standard format | All rules use same format with frontmatter |
|
|
172
175
|
| **Cline** | Standard format | Standard format | All rules use plain Markdown format |
|
|
173
176
|
| **Roo Code** | Standard format | Standard format | All rules use plain Markdown format with description header |
|
|
177
|
+
| **Gemini CLI** | `GEMINI.md` | `.gemini/memories/*.md` | GEMINI.md includes `@filename` references to memory files |
|
|
174
178
|
|
|
175
179
|
### 3. Generate Configuration Files
|
|
176
180
|
|
|
@@ -184,6 +188,7 @@ npx rulesync generate --cursor
|
|
|
184
188
|
npx rulesync generate --cline
|
|
185
189
|
npx rulesync generate --claudecode
|
|
186
190
|
npx rulesync generate --roo
|
|
191
|
+
npx rulesync generate --geminicli
|
|
187
192
|
|
|
188
193
|
# Clean build (delete existing files first)
|
|
189
194
|
npx rulesync generate --delete
|
|
@@ -205,7 +210,7 @@ npx rulesync generate --base-dir ./apps/web,./apps/api,./packages/shared
|
|
|
205
210
|
|
|
206
211
|
- `--delete`: Remove all existing generated files before creating new ones
|
|
207
212
|
- `--verbose`: Show detailed output during generation process
|
|
208
|
-
- `--copilot`, `--cursor`, `--cline`, `--claudecode`, `--roo`: Generate only for specified tools
|
|
213
|
+
- `--copilot`, `--cursor`, `--cline`, `--claudecode`, `--roo`, `--geminicli`: Generate only for specified tools
|
|
209
214
|
- `--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.
|
|
210
215
|
|
|
211
216
|
### 4. Import Existing Configurations
|
|
@@ -219,6 +224,7 @@ npx rulesync import --cursor # Import from .cursorrules and .cursor/rules/*.
|
|
|
219
224
|
npx rulesync import --copilot # Import from .github/copilot-instructions.md and .github/instructions/*.instructions.md
|
|
220
225
|
npx rulesync import --cline # Import from .cline/instructions.md
|
|
221
226
|
npx rulesync import --roo # Import from .roo/instructions.md
|
|
227
|
+
npx rulesync import --geminicli # Import from GEMINI.md and .gemini/memories/*.md
|
|
222
228
|
|
|
223
229
|
# Import from multiple tools
|
|
224
230
|
npx rulesync import --claudecode --cursor --copilot
|
|
@@ -326,6 +332,7 @@ globs: "**/*.ts,**/*.tsx"
|
|
|
326
332
|
| **Cline** | `.clinerules/*.md` | Plain Markdown | Both levels use same format |
|
|
327
333
|
| **Claude Code** | `./CLAUDE.md` (root)<br>`.claude/memories/*.md` (non-root) | Plain Markdown | Root goes to CLAUDE.md<br>Non-root go to separate memory files<br>CLAUDE.md includes `@filename` references |
|
|
328
334
|
| **Roo Code** | `.roo/rules/*.md` | Plain Markdown | Both levels use same format with description header |
|
|
335
|
+
| **Gemini CLI** | `GEMINI.md` (root)<br>`.gemini/memories/*.md` (non-root) | Plain Markdown | Root goes to GEMINI.md<br>Non-root go to separate memory files<br>GEMINI.md includes `@filename` references |
|
|
329
336
|
|
|
330
337
|
## Validation
|
|
331
338
|
|
package/dist/index.js
CHANGED
|
@@ -39,10 +39,11 @@ function getDefaultConfig() {
|
|
|
39
39
|
cursor: ".cursor/rules",
|
|
40
40
|
cline: ".clinerules",
|
|
41
41
|
claudecode: ".",
|
|
42
|
-
roo: ".roo/rules"
|
|
42
|
+
roo: ".roo/rules",
|
|
43
|
+
geminicli: ".gemini/memories"
|
|
43
44
|
},
|
|
44
45
|
watchEnabled: false,
|
|
45
|
-
defaultTargets: ["copilot", "cursor", "cline", "claudecode", "roo"]
|
|
46
|
+
defaultTargets: ["copilot", "cursor", "cline", "claudecode", "roo", "geminicli"]
|
|
46
47
|
};
|
|
47
48
|
}
|
|
48
49
|
function resolveTargets(targets, config) {
|
|
@@ -226,14 +227,67 @@ function generateCursorMarkdown(rule) {
|
|
|
226
227
|
return lines.join("\n");
|
|
227
228
|
}
|
|
228
229
|
|
|
229
|
-
// src/generators/
|
|
230
|
+
// src/generators/geminicli.ts
|
|
230
231
|
var import_node_path6 = require("path");
|
|
232
|
+
async function generateGeminiConfig(rules, config, baseDir) {
|
|
233
|
+
const outputs = [];
|
|
234
|
+
const rootRule = rules.find((rule) => rule.frontmatter.root === true);
|
|
235
|
+
const memoryRules = rules.filter((rule) => rule.frontmatter.root === false);
|
|
236
|
+
for (const rule of memoryRules) {
|
|
237
|
+
const content = generateGeminiMemoryMarkdown(rule);
|
|
238
|
+
const outputDir = baseDir ? (0, import_node_path6.join)(baseDir, config.outputPaths.geminicli) : config.outputPaths.geminicli;
|
|
239
|
+
const filepath = (0, import_node_path6.join)(outputDir, `${rule.filename}.md`);
|
|
240
|
+
outputs.push({
|
|
241
|
+
tool: "geminicli",
|
|
242
|
+
filepath,
|
|
243
|
+
content
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
const rootContent = generateGeminiRootMarkdown(rootRule, memoryRules, baseDir);
|
|
247
|
+
const rootFilepath = baseDir ? (0, import_node_path6.join)(baseDir, "GEMINI.md") : "GEMINI.md";
|
|
248
|
+
outputs.push({
|
|
249
|
+
tool: "geminicli",
|
|
250
|
+
filepath: rootFilepath,
|
|
251
|
+
content: rootContent
|
|
252
|
+
});
|
|
253
|
+
return outputs;
|
|
254
|
+
}
|
|
255
|
+
function generateGeminiMemoryMarkdown(rule) {
|
|
256
|
+
return rule.content.trim();
|
|
257
|
+
}
|
|
258
|
+
function generateGeminiRootMarkdown(rootRule, memoryRules, _baseDir) {
|
|
259
|
+
const lines = [];
|
|
260
|
+
if (memoryRules.length > 0) {
|
|
261
|
+
lines.push("Please also reference the following documents as needed:");
|
|
262
|
+
lines.push("");
|
|
263
|
+
lines.push("| Document | Description | File Patterns |");
|
|
264
|
+
lines.push("|----------|-------------|---------------|");
|
|
265
|
+
for (const rule of memoryRules) {
|
|
266
|
+
const relativePath = `@.gemini/memories/${rule.filename}.md`;
|
|
267
|
+
const filePatterns = rule.frontmatter.globs.length > 0 ? rule.frontmatter.globs.join(", ") : "-";
|
|
268
|
+
lines.push(`| ${relativePath} | ${rule.frontmatter.description} | ${filePatterns} |`);
|
|
269
|
+
}
|
|
270
|
+
lines.push("");
|
|
271
|
+
lines.push("");
|
|
272
|
+
}
|
|
273
|
+
if (rootRule) {
|
|
274
|
+
lines.push(rootRule.content.trim());
|
|
275
|
+
} else if (memoryRules.length === 0) {
|
|
276
|
+
lines.push("# Gemini CLI Configuration");
|
|
277
|
+
lines.push("");
|
|
278
|
+
lines.push("No configuration rules have been defined yet.");
|
|
279
|
+
}
|
|
280
|
+
return lines.join("\n");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// src/generators/roo.ts
|
|
284
|
+
var import_node_path7 = require("path");
|
|
231
285
|
async function generateRooConfig(rules, config, baseDir) {
|
|
232
286
|
const outputs = [];
|
|
233
287
|
for (const rule of rules) {
|
|
234
288
|
const content = generateRooMarkdown(rule);
|
|
235
|
-
const outputDir = baseDir ? (0,
|
|
236
|
-
const filepath = (0,
|
|
289
|
+
const outputDir = baseDir ? (0, import_node_path7.join)(baseDir, config.outputPaths.roo) : config.outputPaths.roo;
|
|
290
|
+
const filepath = (0, import_node_path7.join)(outputDir, `${rule.filename}.md`);
|
|
237
291
|
outputs.push({
|
|
238
292
|
tool: "roo",
|
|
239
293
|
filepath,
|
|
@@ -248,7 +302,7 @@ function generateRooMarkdown(rule) {
|
|
|
248
302
|
|
|
249
303
|
// src/utils/file.ts
|
|
250
304
|
var import_promises2 = require("fs/promises");
|
|
251
|
-
var
|
|
305
|
+
var import_node_path8 = require("path");
|
|
252
306
|
async function ensureDir(dirPath) {
|
|
253
307
|
try {
|
|
254
308
|
await (0, import_promises2.stat)(dirPath);
|
|
@@ -260,13 +314,13 @@ async function readFileContent(filepath) {
|
|
|
260
314
|
return (0, import_promises2.readFile)(filepath, "utf-8");
|
|
261
315
|
}
|
|
262
316
|
async function writeFileContent(filepath, content) {
|
|
263
|
-
await ensureDir((0,
|
|
317
|
+
await ensureDir((0, import_node_path8.dirname)(filepath));
|
|
264
318
|
await (0, import_promises2.writeFile)(filepath, content, "utf-8");
|
|
265
319
|
}
|
|
266
320
|
async function findFiles(dir, extension = ".md") {
|
|
267
321
|
try {
|
|
268
322
|
const files = await (0, import_promises2.readdir)(dir);
|
|
269
|
-
return files.filter((file) => file.endsWith(extension)).map((file) => (0,
|
|
323
|
+
return files.filter((file) => file.endsWith(extension)).map((file) => (0, import_node_path8.join)(dir, file));
|
|
270
324
|
} catch {
|
|
271
325
|
return [];
|
|
272
326
|
}
|
|
@@ -354,6 +408,8 @@ async function generateForTool(tool, rules, config, baseDir) {
|
|
|
354
408
|
return await generateClaudecodeConfig(rules, config, baseDir);
|
|
355
409
|
case "roo":
|
|
356
410
|
return generateRooConfig(rules, config, baseDir);
|
|
411
|
+
case "geminicli":
|
|
412
|
+
return generateGeminiConfig(rules, config, baseDir);
|
|
357
413
|
default:
|
|
358
414
|
console.warn(`Unknown tool: ${tool}`);
|
|
359
415
|
return null;
|
|
@@ -361,7 +417,7 @@ async function generateForTool(tool, rules, config, baseDir) {
|
|
|
361
417
|
}
|
|
362
418
|
|
|
363
419
|
// src/core/parser.ts
|
|
364
|
-
var
|
|
420
|
+
var import_node_path9 = require("path");
|
|
365
421
|
var import_gray_matter = __toESM(require("gray-matter"));
|
|
366
422
|
async function parseRulesFromDirectory(aiRulesDir) {
|
|
367
423
|
const ruleFiles = await findFiles(aiRulesDir);
|
|
@@ -394,7 +450,7 @@ async function parseRuleFile(filepath) {
|
|
|
394
450
|
const parsed = (0, import_gray_matter.default)(content);
|
|
395
451
|
validateFrontmatter(parsed.data, filepath);
|
|
396
452
|
const frontmatter = parsed.data;
|
|
397
|
-
const filename = (0,
|
|
453
|
+
const filename = (0, import_node_path9.basename)(filepath, ".md");
|
|
398
454
|
return {
|
|
399
455
|
frontmatter,
|
|
400
456
|
content: parsed.content,
|
|
@@ -435,7 +491,7 @@ function validateFrontmatter(data, filepath) {
|
|
|
435
491
|
`Invalid "targets" field in ${filepath}: must be an array, got ${typeof obj.targets}`
|
|
436
492
|
);
|
|
437
493
|
}
|
|
438
|
-
const validTargets = ["copilot", "cursor", "cline", "
|
|
494
|
+
const validTargets = ["copilot", "cursor", "cline", "claudecode", "roo", "geminicli", "*"];
|
|
439
495
|
for (const target of obj.targets) {
|
|
440
496
|
if (typeof target !== "string" || !validTargets.includes(target)) {
|
|
441
497
|
throw new Error(
|
|
@@ -567,6 +623,9 @@ async function generateCommand(options = {}) {
|
|
|
567
623
|
case "roo":
|
|
568
624
|
deleteTasks.push(removeDirectory(config.outputPaths.roo));
|
|
569
625
|
break;
|
|
626
|
+
case "geminicli":
|
|
627
|
+
deleteTasks.push(removeDirectory(config.outputPaths.geminicli));
|
|
628
|
+
break;
|
|
570
629
|
}
|
|
571
630
|
}
|
|
572
631
|
await Promise.all(deleteTasks);
|
|
@@ -607,9 +666,9 @@ Generating configurations for base directory: ${baseDir}`);
|
|
|
607
666
|
|
|
608
667
|
// src/cli/commands/gitignore.ts
|
|
609
668
|
var import_node_fs = require("fs");
|
|
610
|
-
var
|
|
669
|
+
var import_node_path10 = require("path");
|
|
611
670
|
var gitignoreCommand = async () => {
|
|
612
|
-
const gitignorePath = (0,
|
|
671
|
+
const gitignorePath = (0, import_node_path10.join)(process.cwd(), ".gitignore");
|
|
613
672
|
const rulesFilesToIgnore = [
|
|
614
673
|
"# Generated by rulesync - AI tool configuration files",
|
|
615
674
|
"**/.github/copilot-instructions.md",
|
|
@@ -618,7 +677,9 @@ var gitignoreCommand = async () => {
|
|
|
618
677
|
"**/.clinerules/",
|
|
619
678
|
"**/CLAUDE.md",
|
|
620
679
|
"**/.claude/memories/",
|
|
621
|
-
"**/.roo/rules/"
|
|
680
|
+
"**/.roo/rules/",
|
|
681
|
+
"**/GEMINI.md",
|
|
682
|
+
"**/.gemini/memories/"
|
|
622
683
|
];
|
|
623
684
|
let gitignoreContent = "";
|
|
624
685
|
if ((0, import_node_fs.existsSync)(gitignorePath)) {
|
|
@@ -649,15 +710,15 @@ ${linesToAdd.join("\n")}
|
|
|
649
710
|
};
|
|
650
711
|
|
|
651
712
|
// src/core/importer.ts
|
|
652
|
-
var
|
|
713
|
+
var import_node_path16 = require("path");
|
|
653
714
|
var import_gray_matter4 = __toESM(require("gray-matter"));
|
|
654
715
|
|
|
655
716
|
// src/parsers/claudecode.ts
|
|
656
|
-
var
|
|
717
|
+
var import_node_path11 = require("path");
|
|
657
718
|
async function parseClaudeConfiguration(baseDir = process.cwd()) {
|
|
658
719
|
const errors = [];
|
|
659
720
|
const rules = [];
|
|
660
|
-
const claudeFilePath = (0,
|
|
721
|
+
const claudeFilePath = (0, import_node_path11.join)(baseDir, "CLAUDE.md");
|
|
661
722
|
if (!await fileExists(claudeFilePath)) {
|
|
662
723
|
errors.push("CLAUDE.md file not found");
|
|
663
724
|
return { rules, errors };
|
|
@@ -668,7 +729,7 @@ async function parseClaudeConfiguration(baseDir = process.cwd()) {
|
|
|
668
729
|
if (mainRule) {
|
|
669
730
|
rules.push(mainRule);
|
|
670
731
|
}
|
|
671
|
-
const memoryDir = (0,
|
|
732
|
+
const memoryDir = (0, import_node_path11.join)(baseDir, ".claude", "memories");
|
|
672
733
|
if (await fileExists(memoryDir)) {
|
|
673
734
|
const memoryRules = await parseClaudeMemoryFiles(memoryDir);
|
|
674
735
|
rules.push(...memoryRules);
|
|
@@ -714,10 +775,10 @@ async function parseClaudeMemoryFiles(memoryDir) {
|
|
|
714
775
|
const files = await readdir2(memoryDir);
|
|
715
776
|
for (const file of files) {
|
|
716
777
|
if (file.endsWith(".md")) {
|
|
717
|
-
const filePath = (0,
|
|
778
|
+
const filePath = (0, import_node_path11.join)(memoryDir, file);
|
|
718
779
|
const content = await readFileContent(filePath);
|
|
719
780
|
if (content.trim()) {
|
|
720
|
-
const filename = (0,
|
|
781
|
+
const filename = (0, import_node_path11.basename)(file, ".md");
|
|
721
782
|
const frontmatter = {
|
|
722
783
|
root: false,
|
|
723
784
|
targets: ["claudecode"],
|
|
@@ -739,11 +800,11 @@ async function parseClaudeMemoryFiles(memoryDir) {
|
|
|
739
800
|
}
|
|
740
801
|
|
|
741
802
|
// src/parsers/cline.ts
|
|
742
|
-
var
|
|
803
|
+
var import_node_path12 = require("path");
|
|
743
804
|
async function parseClineConfiguration(baseDir = process.cwd()) {
|
|
744
805
|
const errors = [];
|
|
745
806
|
const rules = [];
|
|
746
|
-
const clineFilePath = (0,
|
|
807
|
+
const clineFilePath = (0, import_node_path12.join)(baseDir, ".cline", "instructions.md");
|
|
747
808
|
if (!await fileExists(clineFilePath)) {
|
|
748
809
|
errors.push(".cline/instructions.md file not found");
|
|
749
810
|
return { rules, errors };
|
|
@@ -772,12 +833,12 @@ async function parseClineConfiguration(baseDir = process.cwd()) {
|
|
|
772
833
|
}
|
|
773
834
|
|
|
774
835
|
// src/parsers/copilot.ts
|
|
775
|
-
var
|
|
836
|
+
var import_node_path13 = require("path");
|
|
776
837
|
var import_gray_matter2 = __toESM(require("gray-matter"));
|
|
777
838
|
async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
778
839
|
const errors = [];
|
|
779
840
|
const rules = [];
|
|
780
|
-
const copilotFilePath = (0,
|
|
841
|
+
const copilotFilePath = (0, import_node_path13.join)(baseDir, ".github", "copilot-instructions.md");
|
|
781
842
|
if (await fileExists(copilotFilePath)) {
|
|
782
843
|
try {
|
|
783
844
|
const rawContent = await readFileContent(copilotFilePath);
|
|
@@ -802,19 +863,19 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
|
802
863
|
errors.push(`Failed to parse copilot-instructions.md: ${errorMessage}`);
|
|
803
864
|
}
|
|
804
865
|
}
|
|
805
|
-
const instructionsDir = (0,
|
|
866
|
+
const instructionsDir = (0, import_node_path13.join)(baseDir, ".github", "instructions");
|
|
806
867
|
if (await fileExists(instructionsDir)) {
|
|
807
868
|
try {
|
|
808
869
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
809
870
|
const files = await readdir2(instructionsDir);
|
|
810
871
|
for (const file of files) {
|
|
811
872
|
if (file.endsWith(".instructions.md")) {
|
|
812
|
-
const filePath = (0,
|
|
873
|
+
const filePath = (0, import_node_path13.join)(instructionsDir, file);
|
|
813
874
|
const rawContent = await readFileContent(filePath);
|
|
814
875
|
const parsed = (0, import_gray_matter2.default)(rawContent);
|
|
815
876
|
const content = parsed.content.trim();
|
|
816
877
|
if (content) {
|
|
817
|
-
const filename = (0,
|
|
878
|
+
const filename = (0, import_node_path13.basename)(file, ".instructions.md");
|
|
818
879
|
const frontmatter = {
|
|
819
880
|
root: false,
|
|
820
881
|
targets: ["copilot"],
|
|
@@ -844,7 +905,7 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
|
844
905
|
}
|
|
845
906
|
|
|
846
907
|
// src/parsers/cursor.ts
|
|
847
|
-
var
|
|
908
|
+
var import_node_path14 = require("path");
|
|
848
909
|
var import_gray_matter3 = __toESM(require("gray-matter"));
|
|
849
910
|
var import_js_yaml = __toESM(require("js-yaml"));
|
|
850
911
|
var customMatterOptions = {
|
|
@@ -868,7 +929,7 @@ var customMatterOptions = {
|
|
|
868
929
|
async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
869
930
|
const errors = [];
|
|
870
931
|
const rules = [];
|
|
871
|
-
const cursorFilePath = (0,
|
|
932
|
+
const cursorFilePath = (0, import_node_path14.join)(baseDir, ".cursorrules");
|
|
872
933
|
if (await fileExists(cursorFilePath)) {
|
|
873
934
|
try {
|
|
874
935
|
const rawContent = await readFileContent(cursorFilePath);
|
|
@@ -893,20 +954,20 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
893
954
|
errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
|
|
894
955
|
}
|
|
895
956
|
}
|
|
896
|
-
const cursorRulesDir = (0,
|
|
957
|
+
const cursorRulesDir = (0, import_node_path14.join)(baseDir, ".cursor", "rules");
|
|
897
958
|
if (await fileExists(cursorRulesDir)) {
|
|
898
959
|
try {
|
|
899
960
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
900
961
|
const files = await readdir2(cursorRulesDir);
|
|
901
962
|
for (const file of files) {
|
|
902
963
|
if (file.endsWith(".mdc")) {
|
|
903
|
-
const filePath = (0,
|
|
964
|
+
const filePath = (0, import_node_path14.join)(cursorRulesDir, file);
|
|
904
965
|
try {
|
|
905
966
|
const rawContent = await readFileContent(filePath);
|
|
906
967
|
const parsed = (0, import_gray_matter3.default)(rawContent, customMatterOptions);
|
|
907
968
|
const content = parsed.content.trim();
|
|
908
969
|
if (content) {
|
|
909
|
-
const filename = (0,
|
|
970
|
+
const filename = (0, import_node_path14.basename)(file, ".mdc");
|
|
910
971
|
const frontmatter = {
|
|
911
972
|
root: false,
|
|
912
973
|
targets: ["cursor"],
|
|
@@ -938,11 +999,11 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
938
999
|
}
|
|
939
1000
|
|
|
940
1001
|
// src/parsers/roo.ts
|
|
941
|
-
var
|
|
1002
|
+
var import_node_path15 = require("path");
|
|
942
1003
|
async function parseRooConfiguration(baseDir = process.cwd()) {
|
|
943
1004
|
const errors = [];
|
|
944
1005
|
const rules = [];
|
|
945
|
-
const rooFilePath = (0,
|
|
1006
|
+
const rooFilePath = (0, import_node_path15.join)(baseDir, ".roo", "instructions.md");
|
|
946
1007
|
if (!await fileExists(rooFilePath)) {
|
|
947
1008
|
errors.push(".roo/instructions.md file not found");
|
|
948
1009
|
return { rules, errors };
|
|
@@ -1022,7 +1083,7 @@ async function importConfiguration(options) {
|
|
|
1022
1083
|
if (rules.length === 0) {
|
|
1023
1084
|
return { success: false, rulesCreated: 0, errors };
|
|
1024
1085
|
}
|
|
1025
|
-
const rulesDirPath = (0,
|
|
1086
|
+
const rulesDirPath = (0, import_node_path16.join)(baseDir, rulesDir);
|
|
1026
1087
|
try {
|
|
1027
1088
|
const { mkdir: mkdir3 } = await import("fs/promises");
|
|
1028
1089
|
await mkdir3(rulesDirPath, { recursive: true });
|
|
@@ -1036,7 +1097,7 @@ async function importConfiguration(options) {
|
|
|
1036
1097
|
try {
|
|
1037
1098
|
const baseFilename = `${tool}__${rule.filename}`;
|
|
1038
1099
|
const filename = await generateUniqueFilename(rulesDirPath, baseFilename);
|
|
1039
|
-
const filePath = (0,
|
|
1100
|
+
const filePath = (0, import_node_path16.join)(rulesDirPath, `${filename}.md`);
|
|
1040
1101
|
const content = generateRuleFileContent(rule);
|
|
1041
1102
|
await writeFileContent(filePath, content);
|
|
1042
1103
|
rulesCreated++;
|
|
@@ -1061,7 +1122,7 @@ function generateRuleFileContent(rule) {
|
|
|
1061
1122
|
async function generateUniqueFilename(rulesDir, baseFilename) {
|
|
1062
1123
|
let filename = baseFilename;
|
|
1063
1124
|
let counter = 1;
|
|
1064
|
-
while (await fileExists((0,
|
|
1125
|
+
while (await fileExists((0, import_node_path16.join)(rulesDir, `${filename}.md`))) {
|
|
1065
1126
|
filename = `${baseFilename}-${counter}`;
|
|
1066
1127
|
counter++;
|
|
1067
1128
|
}
|
|
@@ -1076,9 +1137,10 @@ async function importCommand(options = {}) {
|
|
|
1076
1137
|
if (options.copilot) tools.push("copilot");
|
|
1077
1138
|
if (options.cline) tools.push("cline");
|
|
1078
1139
|
if (options.roo) tools.push("roo");
|
|
1140
|
+
if (options.geminicli) tools.push("geminicli");
|
|
1079
1141
|
if (tools.length === 0) {
|
|
1080
1142
|
console.error(
|
|
1081
|
-
"\u274C Please specify at least one tool to import from (--claudecode, --cursor, --copilot, --cline, --roo)"
|
|
1143
|
+
"\u274C Please specify at least one tool to import from (--claudecode, --cursor, --copilot, --cline, --roo, --geminicli)"
|
|
1082
1144
|
);
|
|
1083
1145
|
process.exit(1);
|
|
1084
1146
|
}
|
|
@@ -1128,7 +1190,7 @@ Importing from ${tool}...`);
|
|
|
1128
1190
|
}
|
|
1129
1191
|
|
|
1130
1192
|
// src/cli/commands/init.ts
|
|
1131
|
-
var
|
|
1193
|
+
var import_node_path17 = require("path");
|
|
1132
1194
|
async function initCommand() {
|
|
1133
1195
|
const aiRulesDir = ".rulesync";
|
|
1134
1196
|
console.log("Initializing rulesync...");
|
|
@@ -1258,7 +1320,7 @@ globs: ["src/api/**/*.ts", "src/services/**/*.ts", "src/models/**/*.ts"]
|
|
|
1258
1320
|
}
|
|
1259
1321
|
];
|
|
1260
1322
|
for (const file of sampleFiles) {
|
|
1261
|
-
const filepath = (0,
|
|
1323
|
+
const filepath = (0, import_node_path17.join)(aiRulesDir, file.filename);
|
|
1262
1324
|
if (!await fileExists(filepath)) {
|
|
1263
1325
|
await writeFileContent(filepath, file.content);
|
|
1264
1326
|
console.log(`Created ${filepath}`);
|
|
@@ -1401,12 +1463,12 @@ async function watchCommand() {
|
|
|
1401
1463
|
|
|
1402
1464
|
// src/cli/index.ts
|
|
1403
1465
|
var program = new import_commander.Command();
|
|
1404
|
-
program.name("rulesync").description("Unified AI rules management CLI tool").version("0.
|
|
1466
|
+
program.name("rulesync").description("Unified AI rules management CLI tool").version("0.34.0");
|
|
1405
1467
|
program.command("init").description("Initialize rulesync in current directory").action(initCommand);
|
|
1406
1468
|
program.command("add <filename>").description("Add a new rule file").action(addCommand);
|
|
1407
1469
|
program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
|
|
1408
|
-
program.command("import").description("Import configurations from AI tools to rulesync format").option("--claudecode", "Import from Claude Code (CLAUDE.md)").option("--cursor", "Import from Cursor (.cursorrules)").option("--copilot", "Import from GitHub Copilot (.github/copilot-instructions.md)").option("--cline", "Import from Cline (.cline/instructions.md)").option("--roo", "Import from Roo Code (.roo/instructions.md)").option("-v, --verbose", "Verbose output").action(importCommand);
|
|
1409
|
-
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("--claudecode", "Generate only for Claude Code").option("--roo", "Generate only for Roo Code").option("--delete", "Delete all existing files in output directories before generating").option(
|
|
1470
|
+
program.command("import").description("Import configurations from AI tools to rulesync format").option("--claudecode", "Import from Claude Code (CLAUDE.md)").option("--cursor", "Import from Cursor (.cursorrules)").option("--copilot", "Import from GitHub Copilot (.github/copilot-instructions.md)").option("--cline", "Import from Cline (.cline/instructions.md)").option("--roo", "Import from Roo Code (.roo/instructions.md)").option("--geminicli", "Import from Gemini CLI (GEMINI.md)").option("-v, --verbose", "Verbose output").action(importCommand);
|
|
1471
|
+
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("--claudecode", "Generate only for Claude Code").option("--roo", "Generate only for Roo Code").option("--geminicli", "Generate only for Gemini CLI").option("--delete", "Delete all existing files in output directories before generating").option(
|
|
1410
1472
|
"-b, --base-dir <paths>",
|
|
1411
1473
|
"Base directories to generate files (comma-separated for multiple paths)"
|
|
1412
1474
|
).option("-v, --verbose", "Verbose output").action(async (options) => {
|
|
@@ -1416,6 +1478,7 @@ program.command("generate").description("Generate configuration files for AI too
|
|
|
1416
1478
|
if (options.cline) tools.push("cline");
|
|
1417
1479
|
if (options.claudecode) tools.push("claudecode");
|
|
1418
1480
|
if (options.roo) tools.push("roo");
|
|
1481
|
+
if (options.geminicli) tools.push("geminicli");
|
|
1419
1482
|
const generateOptions = {
|
|
1420
1483
|
verbose: options.verbose,
|
|
1421
1484
|
delete: options.delete
|
package/dist/index.mjs
CHANGED
|
@@ -16,10 +16,11 @@ function getDefaultConfig() {
|
|
|
16
16
|
cursor: ".cursor/rules",
|
|
17
17
|
cline: ".clinerules",
|
|
18
18
|
claudecode: ".",
|
|
19
|
-
roo: ".roo/rules"
|
|
19
|
+
roo: ".roo/rules",
|
|
20
|
+
geminicli: ".gemini/memories"
|
|
20
21
|
},
|
|
21
22
|
watchEnabled: false,
|
|
22
|
-
defaultTargets: ["copilot", "cursor", "cline", "claudecode", "roo"]
|
|
23
|
+
defaultTargets: ["copilot", "cursor", "cline", "claudecode", "roo", "geminicli"]
|
|
23
24
|
};
|
|
24
25
|
}
|
|
25
26
|
function resolveTargets(targets, config) {
|
|
@@ -203,14 +204,67 @@ function generateCursorMarkdown(rule) {
|
|
|
203
204
|
return lines.join("\n");
|
|
204
205
|
}
|
|
205
206
|
|
|
206
|
-
// src/generators/
|
|
207
|
+
// src/generators/geminicli.ts
|
|
207
208
|
import { join as join5 } from "path";
|
|
209
|
+
async function generateGeminiConfig(rules, config, baseDir) {
|
|
210
|
+
const outputs = [];
|
|
211
|
+
const rootRule = rules.find((rule) => rule.frontmatter.root === true);
|
|
212
|
+
const memoryRules = rules.filter((rule) => rule.frontmatter.root === false);
|
|
213
|
+
for (const rule of memoryRules) {
|
|
214
|
+
const content = generateGeminiMemoryMarkdown(rule);
|
|
215
|
+
const outputDir = baseDir ? join5(baseDir, config.outputPaths.geminicli) : config.outputPaths.geminicli;
|
|
216
|
+
const filepath = join5(outputDir, `${rule.filename}.md`);
|
|
217
|
+
outputs.push({
|
|
218
|
+
tool: "geminicli",
|
|
219
|
+
filepath,
|
|
220
|
+
content
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
const rootContent = generateGeminiRootMarkdown(rootRule, memoryRules, baseDir);
|
|
224
|
+
const rootFilepath = baseDir ? join5(baseDir, "GEMINI.md") : "GEMINI.md";
|
|
225
|
+
outputs.push({
|
|
226
|
+
tool: "geminicli",
|
|
227
|
+
filepath: rootFilepath,
|
|
228
|
+
content: rootContent
|
|
229
|
+
});
|
|
230
|
+
return outputs;
|
|
231
|
+
}
|
|
232
|
+
function generateGeminiMemoryMarkdown(rule) {
|
|
233
|
+
return rule.content.trim();
|
|
234
|
+
}
|
|
235
|
+
function generateGeminiRootMarkdown(rootRule, memoryRules, _baseDir) {
|
|
236
|
+
const lines = [];
|
|
237
|
+
if (memoryRules.length > 0) {
|
|
238
|
+
lines.push("Please also reference the following documents as needed:");
|
|
239
|
+
lines.push("");
|
|
240
|
+
lines.push("| Document | Description | File Patterns |");
|
|
241
|
+
lines.push("|----------|-------------|---------------|");
|
|
242
|
+
for (const rule of memoryRules) {
|
|
243
|
+
const relativePath = `@.gemini/memories/${rule.filename}.md`;
|
|
244
|
+
const filePatterns = rule.frontmatter.globs.length > 0 ? rule.frontmatter.globs.join(", ") : "-";
|
|
245
|
+
lines.push(`| ${relativePath} | ${rule.frontmatter.description} | ${filePatterns} |`);
|
|
246
|
+
}
|
|
247
|
+
lines.push("");
|
|
248
|
+
lines.push("");
|
|
249
|
+
}
|
|
250
|
+
if (rootRule) {
|
|
251
|
+
lines.push(rootRule.content.trim());
|
|
252
|
+
} else if (memoryRules.length === 0) {
|
|
253
|
+
lines.push("# Gemini CLI Configuration");
|
|
254
|
+
lines.push("");
|
|
255
|
+
lines.push("No configuration rules have been defined yet.");
|
|
256
|
+
}
|
|
257
|
+
return lines.join("\n");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// src/generators/roo.ts
|
|
261
|
+
import { join as join6 } from "path";
|
|
208
262
|
async function generateRooConfig(rules, config, baseDir) {
|
|
209
263
|
const outputs = [];
|
|
210
264
|
for (const rule of rules) {
|
|
211
265
|
const content = generateRooMarkdown(rule);
|
|
212
|
-
const outputDir = baseDir ?
|
|
213
|
-
const filepath =
|
|
266
|
+
const outputDir = baseDir ? join6(baseDir, config.outputPaths.roo) : config.outputPaths.roo;
|
|
267
|
+
const filepath = join6(outputDir, `${rule.filename}.md`);
|
|
214
268
|
outputs.push({
|
|
215
269
|
tool: "roo",
|
|
216
270
|
filepath,
|
|
@@ -225,7 +279,7 @@ function generateRooMarkdown(rule) {
|
|
|
225
279
|
|
|
226
280
|
// src/utils/file.ts
|
|
227
281
|
import { mkdir as mkdir2, readdir, readFile, rm, stat, writeFile as writeFile2 } from "fs/promises";
|
|
228
|
-
import { dirname, join as
|
|
282
|
+
import { dirname, join as join7 } from "path";
|
|
229
283
|
async function ensureDir(dirPath) {
|
|
230
284
|
try {
|
|
231
285
|
await stat(dirPath);
|
|
@@ -243,7 +297,7 @@ async function writeFileContent(filepath, content) {
|
|
|
243
297
|
async function findFiles(dir, extension = ".md") {
|
|
244
298
|
try {
|
|
245
299
|
const files = await readdir(dir);
|
|
246
|
-
return files.filter((file) => file.endsWith(extension)).map((file) =>
|
|
300
|
+
return files.filter((file) => file.endsWith(extension)).map((file) => join7(dir, file));
|
|
247
301
|
} catch {
|
|
248
302
|
return [];
|
|
249
303
|
}
|
|
@@ -331,6 +385,8 @@ async function generateForTool(tool, rules, config, baseDir) {
|
|
|
331
385
|
return await generateClaudecodeConfig(rules, config, baseDir);
|
|
332
386
|
case "roo":
|
|
333
387
|
return generateRooConfig(rules, config, baseDir);
|
|
388
|
+
case "geminicli":
|
|
389
|
+
return generateGeminiConfig(rules, config, baseDir);
|
|
334
390
|
default:
|
|
335
391
|
console.warn(`Unknown tool: ${tool}`);
|
|
336
392
|
return null;
|
|
@@ -412,7 +468,7 @@ function validateFrontmatter(data, filepath) {
|
|
|
412
468
|
`Invalid "targets" field in ${filepath}: must be an array, got ${typeof obj.targets}`
|
|
413
469
|
);
|
|
414
470
|
}
|
|
415
|
-
const validTargets = ["copilot", "cursor", "cline", "
|
|
471
|
+
const validTargets = ["copilot", "cursor", "cline", "claudecode", "roo", "geminicli", "*"];
|
|
416
472
|
for (const target of obj.targets) {
|
|
417
473
|
if (typeof target !== "string" || !validTargets.includes(target)) {
|
|
418
474
|
throw new Error(
|
|
@@ -544,6 +600,9 @@ async function generateCommand(options = {}) {
|
|
|
544
600
|
case "roo":
|
|
545
601
|
deleteTasks.push(removeDirectory(config.outputPaths.roo));
|
|
546
602
|
break;
|
|
603
|
+
case "geminicli":
|
|
604
|
+
deleteTasks.push(removeDirectory(config.outputPaths.geminicli));
|
|
605
|
+
break;
|
|
547
606
|
}
|
|
548
607
|
}
|
|
549
608
|
await Promise.all(deleteTasks);
|
|
@@ -584,9 +643,9 @@ Generating configurations for base directory: ${baseDir}`);
|
|
|
584
643
|
|
|
585
644
|
// src/cli/commands/gitignore.ts
|
|
586
645
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
587
|
-
import { join as
|
|
646
|
+
import { join as join8 } from "path";
|
|
588
647
|
var gitignoreCommand = async () => {
|
|
589
|
-
const gitignorePath =
|
|
648
|
+
const gitignorePath = join8(process.cwd(), ".gitignore");
|
|
590
649
|
const rulesFilesToIgnore = [
|
|
591
650
|
"# Generated by rulesync - AI tool configuration files",
|
|
592
651
|
"**/.github/copilot-instructions.md",
|
|
@@ -595,7 +654,9 @@ var gitignoreCommand = async () => {
|
|
|
595
654
|
"**/.clinerules/",
|
|
596
655
|
"**/CLAUDE.md",
|
|
597
656
|
"**/.claude/memories/",
|
|
598
|
-
"**/.roo/rules/"
|
|
657
|
+
"**/.roo/rules/",
|
|
658
|
+
"**/GEMINI.md",
|
|
659
|
+
"**/.gemini/memories/"
|
|
599
660
|
];
|
|
600
661
|
let gitignoreContent = "";
|
|
601
662
|
if (existsSync(gitignorePath)) {
|
|
@@ -626,15 +687,15 @@ ${linesToAdd.join("\n")}
|
|
|
626
687
|
};
|
|
627
688
|
|
|
628
689
|
// src/core/importer.ts
|
|
629
|
-
import { join as
|
|
690
|
+
import { join as join14 } from "path";
|
|
630
691
|
import matter4 from "gray-matter";
|
|
631
692
|
|
|
632
693
|
// src/parsers/claudecode.ts
|
|
633
|
-
import { basename as basename2, join as
|
|
694
|
+
import { basename as basename2, join as join9 } from "path";
|
|
634
695
|
async function parseClaudeConfiguration(baseDir = process.cwd()) {
|
|
635
696
|
const errors = [];
|
|
636
697
|
const rules = [];
|
|
637
|
-
const claudeFilePath =
|
|
698
|
+
const claudeFilePath = join9(baseDir, "CLAUDE.md");
|
|
638
699
|
if (!await fileExists(claudeFilePath)) {
|
|
639
700
|
errors.push("CLAUDE.md file not found");
|
|
640
701
|
return { rules, errors };
|
|
@@ -645,7 +706,7 @@ async function parseClaudeConfiguration(baseDir = process.cwd()) {
|
|
|
645
706
|
if (mainRule) {
|
|
646
707
|
rules.push(mainRule);
|
|
647
708
|
}
|
|
648
|
-
const memoryDir =
|
|
709
|
+
const memoryDir = join9(baseDir, ".claude", "memories");
|
|
649
710
|
if (await fileExists(memoryDir)) {
|
|
650
711
|
const memoryRules = await parseClaudeMemoryFiles(memoryDir);
|
|
651
712
|
rules.push(...memoryRules);
|
|
@@ -691,7 +752,7 @@ async function parseClaudeMemoryFiles(memoryDir) {
|
|
|
691
752
|
const files = await readdir2(memoryDir);
|
|
692
753
|
for (const file of files) {
|
|
693
754
|
if (file.endsWith(".md")) {
|
|
694
|
-
const filePath =
|
|
755
|
+
const filePath = join9(memoryDir, file);
|
|
695
756
|
const content = await readFileContent(filePath);
|
|
696
757
|
if (content.trim()) {
|
|
697
758
|
const filename = basename2(file, ".md");
|
|
@@ -716,11 +777,11 @@ async function parseClaudeMemoryFiles(memoryDir) {
|
|
|
716
777
|
}
|
|
717
778
|
|
|
718
779
|
// src/parsers/cline.ts
|
|
719
|
-
import { join as
|
|
780
|
+
import { join as join10 } from "path";
|
|
720
781
|
async function parseClineConfiguration(baseDir = process.cwd()) {
|
|
721
782
|
const errors = [];
|
|
722
783
|
const rules = [];
|
|
723
|
-
const clineFilePath =
|
|
784
|
+
const clineFilePath = join10(baseDir, ".cline", "instructions.md");
|
|
724
785
|
if (!await fileExists(clineFilePath)) {
|
|
725
786
|
errors.push(".cline/instructions.md file not found");
|
|
726
787
|
return { rules, errors };
|
|
@@ -749,12 +810,12 @@ async function parseClineConfiguration(baseDir = process.cwd()) {
|
|
|
749
810
|
}
|
|
750
811
|
|
|
751
812
|
// src/parsers/copilot.ts
|
|
752
|
-
import { basename as basename3, join as
|
|
813
|
+
import { basename as basename3, join as join11 } from "path";
|
|
753
814
|
import matter2 from "gray-matter";
|
|
754
815
|
async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
755
816
|
const errors = [];
|
|
756
817
|
const rules = [];
|
|
757
|
-
const copilotFilePath =
|
|
818
|
+
const copilotFilePath = join11(baseDir, ".github", "copilot-instructions.md");
|
|
758
819
|
if (await fileExists(copilotFilePath)) {
|
|
759
820
|
try {
|
|
760
821
|
const rawContent = await readFileContent(copilotFilePath);
|
|
@@ -779,14 +840,14 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
|
779
840
|
errors.push(`Failed to parse copilot-instructions.md: ${errorMessage}`);
|
|
780
841
|
}
|
|
781
842
|
}
|
|
782
|
-
const instructionsDir =
|
|
843
|
+
const instructionsDir = join11(baseDir, ".github", "instructions");
|
|
783
844
|
if (await fileExists(instructionsDir)) {
|
|
784
845
|
try {
|
|
785
846
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
786
847
|
const files = await readdir2(instructionsDir);
|
|
787
848
|
for (const file of files) {
|
|
788
849
|
if (file.endsWith(".instructions.md")) {
|
|
789
|
-
const filePath =
|
|
850
|
+
const filePath = join11(instructionsDir, file);
|
|
790
851
|
const rawContent = await readFileContent(filePath);
|
|
791
852
|
const parsed = matter2(rawContent);
|
|
792
853
|
const content = parsed.content.trim();
|
|
@@ -821,7 +882,7 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
|
821
882
|
}
|
|
822
883
|
|
|
823
884
|
// src/parsers/cursor.ts
|
|
824
|
-
import { basename as basename4, join as
|
|
885
|
+
import { basename as basename4, join as join12 } from "path";
|
|
825
886
|
import matter3 from "gray-matter";
|
|
826
887
|
import yaml from "js-yaml";
|
|
827
888
|
var customMatterOptions = {
|
|
@@ -845,7 +906,7 @@ var customMatterOptions = {
|
|
|
845
906
|
async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
846
907
|
const errors = [];
|
|
847
908
|
const rules = [];
|
|
848
|
-
const cursorFilePath =
|
|
909
|
+
const cursorFilePath = join12(baseDir, ".cursorrules");
|
|
849
910
|
if (await fileExists(cursorFilePath)) {
|
|
850
911
|
try {
|
|
851
912
|
const rawContent = await readFileContent(cursorFilePath);
|
|
@@ -870,14 +931,14 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
870
931
|
errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
|
|
871
932
|
}
|
|
872
933
|
}
|
|
873
|
-
const cursorRulesDir =
|
|
934
|
+
const cursorRulesDir = join12(baseDir, ".cursor", "rules");
|
|
874
935
|
if (await fileExists(cursorRulesDir)) {
|
|
875
936
|
try {
|
|
876
937
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
877
938
|
const files = await readdir2(cursorRulesDir);
|
|
878
939
|
for (const file of files) {
|
|
879
940
|
if (file.endsWith(".mdc")) {
|
|
880
|
-
const filePath =
|
|
941
|
+
const filePath = join12(cursorRulesDir, file);
|
|
881
942
|
try {
|
|
882
943
|
const rawContent = await readFileContent(filePath);
|
|
883
944
|
const parsed = matter3(rawContent, customMatterOptions);
|
|
@@ -915,11 +976,11 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
915
976
|
}
|
|
916
977
|
|
|
917
978
|
// src/parsers/roo.ts
|
|
918
|
-
import { join as
|
|
979
|
+
import { join as join13 } from "path";
|
|
919
980
|
async function parseRooConfiguration(baseDir = process.cwd()) {
|
|
920
981
|
const errors = [];
|
|
921
982
|
const rules = [];
|
|
922
|
-
const rooFilePath =
|
|
983
|
+
const rooFilePath = join13(baseDir, ".roo", "instructions.md");
|
|
923
984
|
if (!await fileExists(rooFilePath)) {
|
|
924
985
|
errors.push(".roo/instructions.md file not found");
|
|
925
986
|
return { rules, errors };
|
|
@@ -999,7 +1060,7 @@ async function importConfiguration(options) {
|
|
|
999
1060
|
if (rules.length === 0) {
|
|
1000
1061
|
return { success: false, rulesCreated: 0, errors };
|
|
1001
1062
|
}
|
|
1002
|
-
const rulesDirPath =
|
|
1063
|
+
const rulesDirPath = join14(baseDir, rulesDir);
|
|
1003
1064
|
try {
|
|
1004
1065
|
const { mkdir: mkdir3 } = await import("fs/promises");
|
|
1005
1066
|
await mkdir3(rulesDirPath, { recursive: true });
|
|
@@ -1013,7 +1074,7 @@ async function importConfiguration(options) {
|
|
|
1013
1074
|
try {
|
|
1014
1075
|
const baseFilename = `${tool}__${rule.filename}`;
|
|
1015
1076
|
const filename = await generateUniqueFilename(rulesDirPath, baseFilename);
|
|
1016
|
-
const filePath =
|
|
1077
|
+
const filePath = join14(rulesDirPath, `${filename}.md`);
|
|
1017
1078
|
const content = generateRuleFileContent(rule);
|
|
1018
1079
|
await writeFileContent(filePath, content);
|
|
1019
1080
|
rulesCreated++;
|
|
@@ -1038,7 +1099,7 @@ function generateRuleFileContent(rule) {
|
|
|
1038
1099
|
async function generateUniqueFilename(rulesDir, baseFilename) {
|
|
1039
1100
|
let filename = baseFilename;
|
|
1040
1101
|
let counter = 1;
|
|
1041
|
-
while (await fileExists(
|
|
1102
|
+
while (await fileExists(join14(rulesDir, `${filename}.md`))) {
|
|
1042
1103
|
filename = `${baseFilename}-${counter}`;
|
|
1043
1104
|
counter++;
|
|
1044
1105
|
}
|
|
@@ -1053,9 +1114,10 @@ async function importCommand(options = {}) {
|
|
|
1053
1114
|
if (options.copilot) tools.push("copilot");
|
|
1054
1115
|
if (options.cline) tools.push("cline");
|
|
1055
1116
|
if (options.roo) tools.push("roo");
|
|
1117
|
+
if (options.geminicli) tools.push("geminicli");
|
|
1056
1118
|
if (tools.length === 0) {
|
|
1057
1119
|
console.error(
|
|
1058
|
-
"\u274C Please specify at least one tool to import from (--claudecode, --cursor, --copilot, --cline, --roo)"
|
|
1120
|
+
"\u274C Please specify at least one tool to import from (--claudecode, --cursor, --copilot, --cline, --roo, --geminicli)"
|
|
1059
1121
|
);
|
|
1060
1122
|
process.exit(1);
|
|
1061
1123
|
}
|
|
@@ -1105,7 +1167,7 @@ Importing from ${tool}...`);
|
|
|
1105
1167
|
}
|
|
1106
1168
|
|
|
1107
1169
|
// src/cli/commands/init.ts
|
|
1108
|
-
import { join as
|
|
1170
|
+
import { join as join15 } from "path";
|
|
1109
1171
|
async function initCommand() {
|
|
1110
1172
|
const aiRulesDir = ".rulesync";
|
|
1111
1173
|
console.log("Initializing rulesync...");
|
|
@@ -1235,7 +1297,7 @@ globs: ["src/api/**/*.ts", "src/services/**/*.ts", "src/models/**/*.ts"]
|
|
|
1235
1297
|
}
|
|
1236
1298
|
];
|
|
1237
1299
|
for (const file of sampleFiles) {
|
|
1238
|
-
const filepath =
|
|
1300
|
+
const filepath = join15(aiRulesDir, file.filename);
|
|
1239
1301
|
if (!await fileExists(filepath)) {
|
|
1240
1302
|
await writeFileContent(filepath, file.content);
|
|
1241
1303
|
console.log(`Created ${filepath}`);
|
|
@@ -1378,12 +1440,12 @@ async function watchCommand() {
|
|
|
1378
1440
|
|
|
1379
1441
|
// src/cli/index.ts
|
|
1380
1442
|
var program = new Command();
|
|
1381
|
-
program.name("rulesync").description("Unified AI rules management CLI tool").version("0.
|
|
1443
|
+
program.name("rulesync").description("Unified AI rules management CLI tool").version("0.34.0");
|
|
1382
1444
|
program.command("init").description("Initialize rulesync in current directory").action(initCommand);
|
|
1383
1445
|
program.command("add <filename>").description("Add a new rule file").action(addCommand);
|
|
1384
1446
|
program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
|
|
1385
|
-
program.command("import").description("Import configurations from AI tools to rulesync format").option("--claudecode", "Import from Claude Code (CLAUDE.md)").option("--cursor", "Import from Cursor (.cursorrules)").option("--copilot", "Import from GitHub Copilot (.github/copilot-instructions.md)").option("--cline", "Import from Cline (.cline/instructions.md)").option("--roo", "Import from Roo Code (.roo/instructions.md)").option("-v, --verbose", "Verbose output").action(importCommand);
|
|
1386
|
-
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("--claudecode", "Generate only for Claude Code").option("--roo", "Generate only for Roo Code").option("--delete", "Delete all existing files in output directories before generating").option(
|
|
1447
|
+
program.command("import").description("Import configurations from AI tools to rulesync format").option("--claudecode", "Import from Claude Code (CLAUDE.md)").option("--cursor", "Import from Cursor (.cursorrules)").option("--copilot", "Import from GitHub Copilot (.github/copilot-instructions.md)").option("--cline", "Import from Cline (.cline/instructions.md)").option("--roo", "Import from Roo Code (.roo/instructions.md)").option("--geminicli", "Import from Gemini CLI (GEMINI.md)").option("-v, --verbose", "Verbose output").action(importCommand);
|
|
1448
|
+
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("--claudecode", "Generate only for Claude Code").option("--roo", "Generate only for Roo Code").option("--geminicli", "Generate only for Gemini CLI").option("--delete", "Delete all existing files in output directories before generating").option(
|
|
1387
1449
|
"-b, --base-dir <paths>",
|
|
1388
1450
|
"Base directories to generate files (comma-separated for multiple paths)"
|
|
1389
1451
|
).option("-v, --verbose", "Verbose output").action(async (options) => {
|
|
@@ -1393,6 +1455,7 @@ program.command("generate").description("Generate configuration files for AI too
|
|
|
1393
1455
|
if (options.cline) tools.push("cline");
|
|
1394
1456
|
if (options.claudecode) tools.push("claudecode");
|
|
1395
1457
|
if (options.roo) tools.push("roo");
|
|
1458
|
+
if (options.geminicli) tools.push("geminicli");
|
|
1396
1459
|
const generateOptions = {
|
|
1397
1460
|
verbose: options.verbose,
|
|
1398
1461
|
delete: options.delete
|