rulesync 0.2.0 → 0.3.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 dyoshikawa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -3,123 +3,135 @@
3
3
  [![CI](https://github.com/dyoshikawa/rulesync/actions/workflows/ci.yml/badge.svg)](https://github.com/dyoshikawa/rulesync/actions/workflows/ci.yml)
4
4
  [![npm version](https://badge.fury.io/js/rulesync.svg)](https://www.npmjs.com/package/rulesync)
5
5
 
6
- 統一されたAIルール設定ファイル(`.rulesync/*.md`)から、各種AI開発支援ツールの設定ファイルを自動生成するNode.js CLIツールです。
6
+ A Node.js CLI tool that automatically generates configuration files for various AI development tools from unified AI rule files (`.rulesync/*.md`).
7
7
 
8
- ## 対応ツール
8
+ ## Supported Tools
9
9
 
10
10
  - **GitHub Copilot Custom Instructions** (`.github/instructions/*.instructions.md`)
11
11
  - **Cursor Project Rules** (`.cursor/rules/*.md`)
12
12
  - **Cline Rules** (`.clinerules/*.md`)
13
+ - **Claude Code Memory** (`./CLAUDE.md`)
13
14
 
14
- ## インストール
15
+ ## Installation
15
16
 
16
17
  ```bash
17
18
  npm install -g rulesync
18
- # または
19
+ # or
19
20
  pnpm add -g rulesync
20
- # または
21
+ # or
21
22
  yarn global add rulesync
22
23
  ```
23
24
 
24
- ## 使用方法
25
+ ## Usage
25
26
 
26
- ### 1. 初期化
27
+ ### 1. Initialize
27
28
 
28
29
  ```bash
29
30
  rulesync init
30
31
  ```
31
32
 
32
- `.rulesync/` ディレクトリとサンプルルールファイルが作成されます。
33
+ This creates a `.rulesync/` directory with sample rule files.
33
34
 
34
- ### 2. ルールファイルの編集
35
+ ### 2. Edit Rule Files
35
36
 
36
- Markdownファイルにフロントマターでメタデータを記述します:
37
+ Define metadata in front matter for each Markdown file:
37
38
 
38
39
  ```markdown
39
40
  ---
40
- priority: high
41
- targets: ["*"] # または [copilot, cursor, cline]
42
- description: "TypeScriptコーディングルール"
41
+ ruleLevel: overview # or detail
42
+ targets: ["*"] # or [copilot, cursor, cline, claudecode]
43
+ description: "TypeScript coding rules"
43
44
  globs: ["**/*.ts", "**/*.tsx"]
44
45
  ---
45
46
 
46
47
  # TypeScript Rules
47
48
 
48
- - TypeScriptを使用する
49
- - 型注釈を明確に記述する
49
+ - Use TypeScript
50
+ - Write clear type annotations
50
51
  ```
51
52
 
52
- ### 3. 設定ファイル生成
53
+ ### Rule Levels
54
+
55
+ - **overview**: Project-wide overview and policies (only one file allowed)
56
+ - **detail**: Specific implementation rules and detailed guidelines (multiple files allowed)
57
+
58
+ Each tool handles rule levels differently:
59
+ - **Claude Code**: overview → `CLAUDE.md`, detail → `.claude/memories/*.md`
60
+ - **Cursor**: overview → `ruletype: always`, detail → `ruletype: autoattached`
61
+
62
+ ### 3. Generate Configuration Files
53
63
 
54
64
  ```bash
55
- # 全ツール用設定ファイル生成
65
+ # Generate for all tools
56
66
  rulesync generate
57
67
 
58
- # 特定ツールのみ
68
+ # Generate for specific tools
59
69
  rulesync generate --copilot
60
70
  rulesync generate --cursor
61
71
  rulesync generate --cline
72
+ rulesync generate --claude
62
73
  ```
63
74
 
64
- ### 4. その他のコマンド
75
+ ### 4. Other Commands
65
76
 
66
77
  ```bash
67
- # 設定の妥当性チェック
78
+ # Validate configuration
68
79
  rulesync validate
69
80
 
70
- # 現在の状況確認
81
+ # Check current status
71
82
  rulesync status
72
83
 
73
- # ファイル監視・自動生成
84
+ # Watch files and auto-generate
74
85
  rulesync watch
75
86
  ```
76
87
 
77
- ## 設定ファイル構造
88
+ ## Configuration File Structure
78
89
 
79
90
  ```
80
- .ai-rules/
81
- ├── coding-rules.md # コーディングルール
82
- ├── naming-conventions.md # 命名規則
83
- ├── architecture.md # アーキテクチャガイドライン
84
- ├── security.md # セキュリティルール
85
- └── custom.md # プロジェクト固有ルール
91
+ .rulesync/
92
+ ├── coding-rules.md # Coding rules
93
+ ├── naming-conventions.md # Naming conventions
94
+ ├── architecture.md # Architecture guidelines
95
+ ├── security.md # Security rules
96
+ └── custom.md # Project-specific rules
86
97
  ```
87
98
 
88
- ## 生成される設定ファイル
99
+ ## Generated Configuration Files
89
100
 
90
- | ツール | 出力先 | 形式 |
91
- |--------|--------|------|
101
+ | Tool | Output Path | Format |
102
+ |------|------------|--------|
92
103
  | GitHub Copilot | `.github/instructions/*.instructions.md` | Front Matter + Markdown |
93
104
  | Cursor | `.cursor/rules/*.md` | MDC (YAML header + Markdown) |
94
- | Cline | `.clinerules/*.md` | プレーンMarkdown |
105
+ | Cline | `.clinerules/*.md` | Plain Markdown |
106
+ | Claude Code | `./CLAUDE.md` (overview), `.claude/memories/*.md` (detail) | Plain Markdown |
95
107
 
96
- ## 開発
108
+ ## Development
97
109
 
98
110
  ```bash
99
- # 依存関係インストール
111
+ # Install dependencies
100
112
  pnpm install
101
113
 
102
- # 開発実行
114
+ # Development run
103
115
  pnpm dev
104
116
 
105
- # ビルド
117
+ # Build
106
118
  pnpm build
107
119
 
108
- # テスト
120
+ # Test
109
121
  pnpm test
110
122
 
111
- # コード品質チェック
123
+ # Code quality checks
112
124
  pnpm lint
113
125
  pnpm format
114
126
  pnpm secretlint
115
127
  ```
116
128
 
117
- ## ライセンス
129
+ ## License
118
130
 
119
131
  MIT License
120
132
 
121
- ## 貢献
133
+ ## Contributing
122
134
 
123
- Issue Pull Request をお待ちしています!
135
+ Issues and Pull Requests are welcome!
124
136
 
125
- 詳細な仕様については [SPECIFICATION.md](./SPECIFICATION.md) をご覧ください。
137
+ For detailed specifications, see [SPECIFICATION.md](./SPECIFICATION.md).
package/dist/index.js CHANGED
@@ -26,196 +26,178 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  // src/cli/index.ts
27
27
  var import_commander = require("commander");
28
28
 
29
- // src/generators/cline.ts
29
+ // src/generators/claude.ts
30
30
  var import_node_path = require("path");
31
- async function generateClineConfig(rules, config) {
32
- const sortedRules = rules.sort((a, b) => {
33
- if (a.frontmatter.priority !== b.frontmatter.priority) {
34
- return a.frontmatter.priority === "high" ? -1 : 1;
35
- }
36
- return a.filename.localeCompare(b.filename);
31
+ async function generateClaudeConfig(rules, config) {
32
+ const outputs = [];
33
+ const overviewRules = rules.filter((r) => r.frontmatter.ruleLevel === "overview");
34
+ const detailRules = rules.filter((r) => r.frontmatter.ruleLevel === "detail");
35
+ const claudeMdContent = generateClaudeMarkdown(overviewRules, detailRules);
36
+ outputs.push({
37
+ tool: "claude",
38
+ filepath: (0, import_node_path.join)(config.outputPaths.claude, "CLAUDE.md"),
39
+ content: claudeMdContent
37
40
  });
38
- const content = generateClineMarkdown(sortedRules);
39
- const filepath = (0, import_node_path.join)(config.outputPaths.cline, "01-ai-rules.md");
40
- return {
41
- tool: "cline",
42
- filepath,
43
- content
44
- };
41
+ for (const rule of detailRules) {
42
+ const memoryContent = generateMemoryFile(rule);
43
+ outputs.push({
44
+ tool: "claude",
45
+ filepath: (0, import_node_path.join)(config.outputPaths.claude, ".claude", "memories", `${rule.filename}.md`),
46
+ content: memoryContent
47
+ });
48
+ }
49
+ return outputs;
45
50
  }
46
- function generateClineMarkdown(rules) {
51
+ function generateClaudeMarkdown(overviewRules, detailRules) {
47
52
  const lines = [];
48
- lines.push("# Cline AI Assistant Rules");
49
- lines.push("");
50
- lines.push("Configuration rules for Cline AI Assistant. Generated from ai-rules configuration.");
51
- lines.push("");
52
- lines.push("These rules provide project-specific guidance for AI-assisted development.");
53
- lines.push("");
54
- const highPriorityRules = rules.filter((r) => r.frontmatter.priority === "high");
55
- const lowPriorityRules = rules.filter((r) => r.frontmatter.priority === "low");
56
- if (highPriorityRules.length > 0) {
57
- lines.push("## High Priority Guidelines");
58
- lines.push("");
59
- lines.push("These are critical rules that should always be followed:");
60
- lines.push("");
61
- for (const rule of highPriorityRules) {
62
- lines.push(...formatRuleForCline(rule));
53
+ if (detailRules.length > 0) {
54
+ for (const rule of detailRules) {
55
+ lines.push(`@${rule.filename}`);
63
56
  }
64
- }
65
- if (lowPriorityRules.length > 0) {
66
- lines.push("## Standard Guidelines");
67
- lines.push("");
68
- lines.push("These are recommended practices for this project:");
69
57
  lines.push("");
70
- for (const rule of lowPriorityRules) {
71
- lines.push(...formatRuleForCline(rule));
58
+ }
59
+ lines.push("# Claude Code Memory - Project Instructions");
60
+ lines.push("");
61
+ lines.push(
62
+ "Generated from rulesync configuration. These instructions guide Claude Code's behavior for this project."
63
+ );
64
+ lines.push("");
65
+ if (overviewRules.length > 0) {
66
+ for (const rule of overviewRules) {
67
+ lines.push(...formatRuleForClaude(rule));
72
68
  }
73
69
  }
74
70
  return lines.join("\n");
75
71
  }
76
- function formatRuleForCline(rule) {
72
+ function formatRuleForClaude(rule) {
77
73
  const lines = [];
78
74
  lines.push(`### ${rule.filename}`);
79
75
  lines.push("");
80
- lines.push(`**Description:** ${rule.frontmatter.description}`);
81
- lines.push("");
82
- if (rule.frontmatter.globs.length > 0) {
83
- lines.push(`**Applies to files:** ${rule.frontmatter.globs.join(", ")}`);
76
+ if (rule.frontmatter.description) {
77
+ lines.push(`**Description:** ${rule.frontmatter.description}`);
78
+ lines.push("");
79
+ }
80
+ if (rule.frontmatter.globs && rule.frontmatter.globs.length > 0) {
81
+ lines.push(`**File patterns:** ${rule.frontmatter.globs.join(", ")}`);
84
82
  lines.push("");
85
83
  }
86
- lines.push("**Guidelines:**");
87
- lines.push("");
88
84
  lines.push(rule.content);
89
85
  lines.push("");
90
- lines.push("---");
91
- lines.push("");
92
86
  return lines;
93
87
  }
94
-
95
- // src/generators/copilot.ts
96
- var import_node_path2 = require("path");
97
- async function generateCopilotConfig(rules, config) {
98
- const sortedRules = rules.sort((a, b) => {
99
- if (a.frontmatter.priority !== b.frontmatter.priority) {
100
- return a.frontmatter.priority === "high" ? -1 : 1;
101
- }
102
- return a.filename.localeCompare(b.filename);
103
- });
104
- const content = generateCopilotMarkdown(sortedRules);
105
- const filepath = (0, import_node_path2.join)(config.outputPaths.copilot, "ai-rules.instructions.md");
106
- return {
107
- tool: "copilot",
108
- filepath,
109
- content
110
- };
111
- }
112
- function generateCopilotMarkdown(rules) {
88
+ function generateMemoryFile(rule) {
113
89
  const lines = [];
114
- lines.push("---");
115
- lines.push('description: "AI rules configuration for GitHub Copilot"');
116
- lines.push('applyTo: "**"');
90
+ lines.push("Please also refer to the following files as needed:");
91
+ lines.push("");
117
92
  lines.push("---");
118
93
  lines.push("");
119
- lines.push("# GitHub Copilot Instructions");
94
+ lines.push(`# ${rule.filename}`);
120
95
  lines.push("");
121
- lines.push(
122
- "Generated from ai-rules configuration. These instructions guide GitHub Copilot's code suggestions."
123
- );
124
- lines.push("");
125
- const highPriorityRules = rules.filter((r) => r.frontmatter.priority === "high");
126
- const lowPriorityRules = rules.filter((r) => r.frontmatter.priority === "low");
127
- if (highPriorityRules.length > 0) {
128
- lines.push("## High Priority Rules");
96
+ if (rule.frontmatter.description) {
97
+ lines.push(`**Description:** ${rule.frontmatter.description}`);
129
98
  lines.push("");
130
- for (const rule of highPriorityRules) {
131
- lines.push(...formatRuleForCopilot(rule));
132
- }
133
99
  }
134
- if (lowPriorityRules.length > 0) {
135
- lines.push("## Standard Rules");
100
+ if (rule.frontmatter.globs && rule.frontmatter.globs.length > 0) {
101
+ lines.push(`**File patterns:** ${rule.frontmatter.globs.join(", ")}`);
136
102
  lines.push("");
137
- for (const rule of lowPriorityRules) {
138
- lines.push(...formatRuleForCopilot(rule));
139
- }
140
103
  }
104
+ lines.push(rule.content);
141
105
  return lines.join("\n");
142
106
  }
143
- function formatRuleForCopilot(rule) {
107
+
108
+ // src/generators/cline.ts
109
+ var import_node_path2 = require("path");
110
+ async function generateClineConfig(rules, config) {
111
+ const outputs = [];
112
+ for (const rule of rules) {
113
+ const content = generateClineMarkdown(rule);
114
+ const filepath = (0, import_node_path2.join)(config.outputPaths.cline, `${rule.filename}.md`);
115
+ outputs.push({
116
+ tool: "cline",
117
+ filepath,
118
+ content
119
+ });
120
+ }
121
+ return outputs;
122
+ }
123
+ function generateClineMarkdown(rule) {
144
124
  const lines = [];
145
- lines.push(`### ${rule.filename}`);
146
- lines.push("");
147
- lines.push(`**Description:** ${rule.frontmatter.description}`);
125
+ lines.push(`# ${rule.frontmatter.description}`);
148
126
  lines.push("");
149
127
  if (rule.frontmatter.globs.length > 0) {
150
- lines.push(`**Applies to:** ${rule.frontmatter.globs.join(", ")}`);
128
+ lines.push(`**Applies to files:** ${rule.frontmatter.globs.join(", ")}`);
151
129
  lines.push("");
152
130
  }
153
131
  lines.push(rule.content);
154
- lines.push("");
155
- return lines;
132
+ return lines.join("\n");
156
133
  }
157
134
 
158
- // src/generators/cursor.ts
135
+ // src/generators/copilot.ts
159
136
  var import_node_path3 = require("path");
160
- async function generateCursorConfig(rules, config) {
161
- const sortedRules = rules.sort((a, b) => {
162
- if (a.frontmatter.priority !== b.frontmatter.priority) {
163
- return a.frontmatter.priority === "high" ? -1 : 1;
164
- }
165
- return a.filename.localeCompare(b.filename);
166
- });
167
- const content = generateCursorMarkdown(sortedRules);
168
- const filepath = (0, import_node_path3.join)(config.outputPaths.cursor, "ai-rules.md");
169
- return {
170
- tool: "cursor",
171
- filepath,
172
- content
173
- };
137
+ async function generateCopilotConfig(rules, config) {
138
+ const outputs = [];
139
+ for (const rule of rules) {
140
+ const content = generateCopilotMarkdown(rule);
141
+ const baseFilename = rule.filename.replace(/\.md$/, "");
142
+ const filepath = (0, import_node_path3.join)(config.outputPaths.copilot, `${baseFilename}.instructions.md`);
143
+ outputs.push({
144
+ tool: "copilot",
145
+ filepath,
146
+ content
147
+ });
148
+ }
149
+ return outputs;
174
150
  }
175
- function generateCursorMarkdown(rules) {
151
+ function generateCopilotMarkdown(rule) {
176
152
  const lines = [];
177
153
  lines.push("---");
178
- lines.push("description: AI rules configuration for Cursor IDE");
179
- lines.push("alwaysApply: true");
154
+ lines.push(`description: "${rule.frontmatter.description}"`);
155
+ if (rule.frontmatter.globs.length > 0) {
156
+ lines.push(`applyTo: "${rule.frontmatter.globs.join(", ")}"`);
157
+ } else {
158
+ lines.push('applyTo: "**"');
159
+ }
180
160
  lines.push("---");
181
161
  lines.push("");
182
- lines.push("# Cursor IDE Rules");
183
- lines.push("");
184
- lines.push("These rules configure Cursor IDE's AI assistant behavior.");
185
- lines.push("");
162
+ lines.push(rule.content);
163
+ return lines.join("\n");
164
+ }
165
+
166
+ // src/generators/cursor.ts
167
+ var import_node_path4 = require("path");
168
+ async function generateCursorConfig(rules, config) {
169
+ const outputs = [];
186
170
  for (const rule of rules) {
187
- lines.push(...formatRuleForCursor(rule));
171
+ const content = generateCursorMarkdown(rule);
172
+ const filepath = (0, import_node_path4.join)(config.outputPaths.cursor, `${rule.filename}.md`);
173
+ outputs.push({
174
+ tool: "cursor",
175
+ filepath,
176
+ content
177
+ });
188
178
  }
189
- return lines.join("\n");
179
+ return outputs;
190
180
  }
191
- function formatRuleForCursor(rule) {
181
+ function generateCursorMarkdown(rule) {
192
182
  const lines = [];
193
- const priorityBadge = rule.frontmatter.priority === "high" ? "\u{1F534} HIGH" : "\u{1F7E1} STANDARD";
194
183
  lines.push("---");
195
184
  lines.push(`description: ${rule.frontmatter.description}`);
196
185
  if (rule.frontmatter.globs.length > 0) {
197
186
  lines.push(`globs: [${rule.frontmatter.globs.map((g) => `"${g}"`).join(", ")}]`);
198
187
  }
199
- lines.push(`alwaysApply: ${rule.frontmatter.priority === "high"}`);
200
- lines.push("---");
201
- lines.push("");
202
- lines.push(`## ${rule.filename} ${priorityBadge}`);
203
- lines.push("");
204
- lines.push(`**Priority:** ${rule.frontmatter.priority.toUpperCase()}`);
205
- lines.push("");
206
- if (rule.frontmatter.globs.length > 0) {
207
- lines.push("**File Patterns:**");
208
- for (const glob of rule.frontmatter.globs) {
209
- lines.push(`- \`${glob}\``);
210
- }
211
- lines.push("");
188
+ let ruletype;
189
+ if (rule.frontmatter.ruleLevel === "overview") {
190
+ ruletype = "always";
191
+ } else if (rule.frontmatter.ruleLevel === "detail" && rule.frontmatter.globs.length === 0) {
192
+ ruletype = "agentrequested";
193
+ } else {
194
+ ruletype = "autoattached";
212
195
  }
213
- lines.push("**Rule:**");
214
- lines.push(rule.content);
215
- lines.push("");
196
+ lines.push(`ruletype: ${ruletype}`);
216
197
  lines.push("---");
217
198
  lines.push("");
218
- return lines;
199
+ lines.push(rule.content);
200
+ return lines.join("\n");
219
201
  }
220
202
 
221
203
  // src/utils/config.ts
@@ -225,10 +207,11 @@ function getDefaultConfig() {
225
207
  outputPaths: {
226
208
  copilot: ".github/instructions",
227
209
  cursor: ".cursor/rules",
228
- cline: ".clinerules"
210
+ cline: ".clinerules",
211
+ claude: "."
229
212
  },
230
213
  watchEnabled: false,
231
- defaultTargets: ["copilot", "cursor", "cline"]
214
+ defaultTargets: ["copilot", "cursor", "cline", "claude"]
232
215
  };
233
216
  }
234
217
  function resolveTargets(targets, config) {
@@ -240,7 +223,7 @@ function resolveTargets(targets, config) {
240
223
 
241
224
  // src/utils/file.ts
242
225
  var import_promises = require("fs/promises");
243
- var import_node_path4 = require("path");
226
+ var import_node_path5 = require("path");
244
227
  async function ensureDir(dirPath) {
245
228
  try {
246
229
  await (0, import_promises.stat)(dirPath);
@@ -252,13 +235,13 @@ async function readFileContent(filepath) {
252
235
  return (0, import_promises.readFile)(filepath, "utf-8");
253
236
  }
254
237
  async function writeFileContent(filepath, content) {
255
- await ensureDir((0, import_node_path4.dirname)(filepath));
238
+ await ensureDir((0, import_node_path5.dirname)(filepath));
256
239
  await (0, import_promises.writeFile)(filepath, content, "utf-8");
257
240
  }
258
241
  async function findFiles(dir, extension = ".md") {
259
242
  try {
260
243
  const files = await (0, import_promises.readdir)(dir);
261
- return files.filter((file) => file.endsWith(extension)).map((file) => (0, import_node_path4.join)(dir, file));
244
+ return files.filter((file) => file.endsWith(extension)).map((file) => (0, import_node_path5.join)(dir, file));
262
245
  } catch {
263
246
  return [];
264
247
  }
@@ -282,9 +265,9 @@ async function generateConfigurations(rules, config, targetTools) {
282
265
  console.warn(`No rules found for tool: ${tool}`);
283
266
  continue;
284
267
  }
285
- const output = await generateForTool(tool, relevantRules, config);
286
- if (output) {
287
- outputs.push(output);
268
+ const toolOutputs = await generateForTool(tool, relevantRules, config);
269
+ if (toolOutputs) {
270
+ outputs.push(...toolOutputs);
288
271
  }
289
272
  }
290
273
  return outputs;
@@ -303,6 +286,8 @@ async function generateForTool(tool, rules, config) {
303
286
  return generateCursorConfig(rules, config);
304
287
  case "cline":
305
288
  return generateClineConfig(rules, config);
289
+ case "claude":
290
+ return await generateClaudeConfig(rules, config);
306
291
  default:
307
292
  console.warn(`Unknown tool: ${tool}`);
308
293
  return null;
@@ -310,7 +295,7 @@ async function generateForTool(tool, rules, config) {
310
295
  }
311
296
 
312
297
  // src/core/parser.ts
313
- var import_node_path5 = require("path");
298
+ var import_node_path6 = require("path");
314
299
  var import_gray_matter = __toESM(require("gray-matter"));
315
300
  async function parseRulesFromDirectory(aiRulesDir) {
316
301
  const ruleFiles = await findFiles(aiRulesDir);
@@ -330,7 +315,7 @@ async function parseRuleFile(filepath) {
330
315
  const parsed = (0, import_gray_matter.default)(content);
331
316
  validateFrontmatter(parsed.data, filepath);
332
317
  const frontmatter = parsed.data;
333
- const filename = (0, import_node_path5.basename)(filepath, ".md");
318
+ const filename = (0, import_node_path6.basename)(filepath, ".md");
334
319
  return {
335
320
  frontmatter,
336
321
  content: parsed.content,
@@ -343,8 +328,8 @@ function validateFrontmatter(data, filepath) {
343
328
  throw new Error(`Invalid frontmatter in ${filepath}: must be an object`);
344
329
  }
345
330
  const obj = data;
346
- if (!obj.priority || !["high", "low"].includes(obj.priority)) {
347
- throw new Error(`Invalid priority in ${filepath}: must be "high" or "low"`);
331
+ if (!obj.ruleLevel || !["overview", "detail"].includes(obj.ruleLevel)) {
332
+ throw new Error(`Invalid ruleLevel in ${filepath}: must be "overview" or "detail"`);
348
333
  }
349
334
  if (!Array.isArray(obj.targets)) {
350
335
  throw new Error(`Invalid targets in ${filepath}: must be an array`);
@@ -381,6 +366,10 @@ async function validateRules(rules) {
381
366
  }
382
367
  filenames.add(rule.filename);
383
368
  }
369
+ const overviewRules = rules.filter((rule) => rule.frontmatter.ruleLevel === "overview");
370
+ if (overviewRules.length > 1) {
371
+ errors.push(`Multiple overview rules found: ${overviewRules.map((r) => r.filename).join(", ")}. Only one overview rule is allowed.`);
372
+ }
384
373
  for (const rule of rules) {
385
374
  const ruleValidation = await validateRule(rule);
386
375
  errors.push(...ruleValidation.errors);
@@ -451,17 +440,57 @@ async function generateCommand(options = {}) {
451
440
  }
452
441
  }
453
442
 
443
+ // src/cli/commands/gitignore.ts
444
+ var import_node_fs = require("fs");
445
+ var import_node_path7 = require("path");
446
+ var gitignoreCommand = async () => {
447
+ const gitignorePath = (0, import_node_path7.join)(process.cwd(), ".gitignore");
448
+ const rulesFilesToIgnore = [
449
+ "# Generated by rulesync - AI tool configuration files",
450
+ ".github/instructions/",
451
+ ".cursor/rules/",
452
+ ".clinerules/",
453
+ "CLAUDE.md"
454
+ ];
455
+ let gitignoreContent = "";
456
+ if ((0, import_node_fs.existsSync)(gitignorePath)) {
457
+ gitignoreContent = (0, import_node_fs.readFileSync)(gitignorePath, "utf-8");
458
+ }
459
+ const linesToAdd = [];
460
+ for (const rule of rulesFilesToIgnore) {
461
+ if (!gitignoreContent.includes(rule)) {
462
+ linesToAdd.push(rule);
463
+ }
464
+ }
465
+ if (linesToAdd.length === 0) {
466
+ console.log("\u2705 .gitignore\u306F\u65E2\u306B\u6700\u65B0\u3067\u3059");
467
+ return;
468
+ }
469
+ const newContent = gitignoreContent ? `${gitignoreContent.trimEnd()}
470
+
471
+ ${linesToAdd.join("\n")}
472
+ ` : `${linesToAdd.join("\n")}
473
+ `;
474
+ (0, import_node_fs.writeFileSync)(gitignorePath, newContent);
475
+ console.log(`\u2705 .gitignore\u306B${linesToAdd.length}\u500B\u306E\u30EB\u30FC\u30EB\u3092\u8FFD\u52A0\u3057\u307E\u3057\u305F:`);
476
+ for (const line of linesToAdd) {
477
+ if (!line.startsWith("#")) {
478
+ console.log(` ${line}`);
479
+ }
480
+ }
481
+ };
482
+
454
483
  // src/cli/commands/init.ts
455
- var import_node_path6 = require("path");
484
+ var import_node_path8 = require("path");
456
485
  async function initCommand() {
457
486
  const aiRulesDir = ".rulesync";
458
- console.log("Initializing ai-rules...");
487
+ console.log("Initializing rulesync...");
459
488
  await ensureDir(aiRulesDir);
460
489
  await createSampleFiles(aiRulesDir);
461
- console.log("\u2705 ai-rules initialized successfully!");
490
+ console.log("\u2705 rulesync initialized successfully!");
462
491
  console.log("\nNext steps:");
463
492
  console.log("1. Edit rule files in .rulesync/");
464
- console.log("2. Run 'ai-rules generate' to create configuration files");
493
+ console.log("2. Run 'rulesync generate' to create configuration files");
465
494
  }
466
495
  async function createSampleFiles(aiRulesDir) {
467
496
  const sampleFiles = [
@@ -542,7 +571,7 @@ globs: ["src/**/*.ts"]
542
571
  }
543
572
  ];
544
573
  for (const file of sampleFiles) {
545
- const filepath = (0, import_node_path6.join)(aiRulesDir, file.filename);
574
+ const filepath = (0, import_node_path8.join)(aiRulesDir, file.filename);
546
575
  if (!await fileExists(filepath)) {
547
576
  await writeFileContent(filepath, file.content);
548
577
  console.log(`Created ${filepath}`);
@@ -557,10 +586,10 @@ async function statusCommand() {
557
586
  const config = getDefaultConfig();
558
587
  console.log("rulesync Status");
559
588
  console.log("===============");
560
- const aiRulesExists = await fileExists(config.aiRulesDir);
589
+ const rulesyncExists = await fileExists(config.aiRulesDir);
561
590
  console.log(`
562
- \u{1F4C1} .rulesync directory: ${aiRulesExists ? "\u2705 Found" : "\u274C Not found"}`);
563
- if (!aiRulesExists) {
591
+ \u{1F4C1} .rulesync directory: ${rulesyncExists ? "\u2705 Found" : "\u274C Not found"}`);
592
+ if (!rulesyncExists) {
564
593
  console.log("\n\u{1F4A1} Run 'rulesync init' to get started");
565
594
  return;
566
595
  }
@@ -603,7 +632,7 @@ async function statusCommand() {
603
632
  // src/cli/commands/validate.ts
604
633
  async function validateCommand() {
605
634
  const config = getDefaultConfig();
606
- console.log("Validating ai-rules configuration...");
635
+ console.log("Validating rulesync configuration...");
607
636
  if (!await fileExists(config.aiRulesDir)) {
608
637
  console.error("\u274C .rulesync directory not found. Run 'rulesync init' first.");
609
638
  process.exit(1);
@@ -611,7 +640,7 @@ async function validateCommand() {
611
640
  try {
612
641
  const rules = await parseRulesFromDirectory(config.aiRulesDir);
613
642
  if (rules.length === 0) {
614
- console.warn("\u26A0\uFE0F No rules found in .ai-rules directory");
643
+ console.warn("\u26A0\uFE0F No rules found in .rulesync directory");
615
644
  return;
616
645
  }
617
646
  console.log(`Found ${rules.length} rule(s), validating...`);
@@ -683,13 +712,15 @@ async function watchCommand() {
683
712
 
684
713
  // src/cli/index.ts
685
714
  var program = new import_commander.Command();
686
- program.name("ai-rules").description("Unified AI rules management CLI tool").version("0.1.0");
687
- program.command("init").description("Initialize ai-rules in current directory").action(initCommand);
688
- 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("-v, --verbose", "Verbose output").action(async (options) => {
715
+ program.name("rulesync").description("Unified AI rules management CLI tool").version("0.1.0");
716
+ program.command("init").description("Initialize rulesync in current directory").action(initCommand);
717
+ program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
718
+ 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("-v, --verbose", "Verbose output").action(async (options) => {
689
719
  const tools = [];
690
720
  if (options.copilot) tools.push("copilot");
691
721
  if (options.cursor) tools.push("cursor");
692
722
  if (options.cline) tools.push("cline");
723
+ if (options.claude) tools.push("claude");
693
724
  const generateOptions = {
694
725
  verbose: options.verbose
695
726
  };
@@ -698,7 +729,7 @@ program.command("generate").description("Generate configuration files for AI too
698
729
  }
699
730
  await generateCommand(generateOptions);
700
731
  });
701
- program.command("validate").description("Validate ai-rules configuration").action(validateCommand);
702
- program.command("status").description("Show current status of ai-rules").action(statusCommand);
732
+ program.command("validate").description("Validate rulesync configuration").action(validateCommand);
733
+ program.command("status").description("Show current status of rulesync").action(statusCommand);
703
734
  program.command("watch").description("Watch for changes and auto-generate configurations").action(watchCommand);
704
735
  program.parse();
package/dist/index.mjs CHANGED
@@ -3,196 +3,178 @@
3
3
  // src/cli/index.ts
4
4
  import { Command } from "commander";
5
5
 
6
- // src/generators/cline.ts
6
+ // src/generators/claude.ts
7
7
  import { join } from "path";
8
- async function generateClineConfig(rules, config) {
9
- const sortedRules = rules.sort((a, b) => {
10
- if (a.frontmatter.priority !== b.frontmatter.priority) {
11
- return a.frontmatter.priority === "high" ? -1 : 1;
12
- }
13
- return a.filename.localeCompare(b.filename);
8
+ async function generateClaudeConfig(rules, config) {
9
+ const outputs = [];
10
+ const overviewRules = rules.filter((r) => r.frontmatter.ruleLevel === "overview");
11
+ const detailRules = rules.filter((r) => r.frontmatter.ruleLevel === "detail");
12
+ const claudeMdContent = generateClaudeMarkdown(overviewRules, detailRules);
13
+ outputs.push({
14
+ tool: "claude",
15
+ filepath: join(config.outputPaths.claude, "CLAUDE.md"),
16
+ content: claudeMdContent
14
17
  });
15
- const content = generateClineMarkdown(sortedRules);
16
- const filepath = join(config.outputPaths.cline, "01-ai-rules.md");
17
- return {
18
- tool: "cline",
19
- filepath,
20
- content
21
- };
18
+ for (const rule of detailRules) {
19
+ const memoryContent = generateMemoryFile(rule);
20
+ outputs.push({
21
+ tool: "claude",
22
+ filepath: join(config.outputPaths.claude, ".claude", "memories", `${rule.filename}.md`),
23
+ content: memoryContent
24
+ });
25
+ }
26
+ return outputs;
22
27
  }
23
- function generateClineMarkdown(rules) {
28
+ function generateClaudeMarkdown(overviewRules, detailRules) {
24
29
  const lines = [];
25
- lines.push("# Cline AI Assistant Rules");
26
- lines.push("");
27
- lines.push("Configuration rules for Cline AI Assistant. Generated from ai-rules configuration.");
28
- lines.push("");
29
- lines.push("These rules provide project-specific guidance for AI-assisted development.");
30
- lines.push("");
31
- const highPriorityRules = rules.filter((r) => r.frontmatter.priority === "high");
32
- const lowPriorityRules = rules.filter((r) => r.frontmatter.priority === "low");
33
- if (highPriorityRules.length > 0) {
34
- lines.push("## High Priority Guidelines");
35
- lines.push("");
36
- lines.push("These are critical rules that should always be followed:");
37
- lines.push("");
38
- for (const rule of highPriorityRules) {
39
- lines.push(...formatRuleForCline(rule));
30
+ if (detailRules.length > 0) {
31
+ for (const rule of detailRules) {
32
+ lines.push(`@${rule.filename}`);
40
33
  }
41
- }
42
- if (lowPriorityRules.length > 0) {
43
- lines.push("## Standard Guidelines");
44
34
  lines.push("");
45
- lines.push("These are recommended practices for this project:");
46
- lines.push("");
47
- for (const rule of lowPriorityRules) {
48
- lines.push(...formatRuleForCline(rule));
35
+ }
36
+ lines.push("# Claude Code Memory - Project Instructions");
37
+ lines.push("");
38
+ lines.push(
39
+ "Generated from rulesync configuration. These instructions guide Claude Code's behavior for this project."
40
+ );
41
+ lines.push("");
42
+ if (overviewRules.length > 0) {
43
+ for (const rule of overviewRules) {
44
+ lines.push(...formatRuleForClaude(rule));
49
45
  }
50
46
  }
51
47
  return lines.join("\n");
52
48
  }
53
- function formatRuleForCline(rule) {
49
+ function formatRuleForClaude(rule) {
54
50
  const lines = [];
55
51
  lines.push(`### ${rule.filename}`);
56
52
  lines.push("");
57
- lines.push(`**Description:** ${rule.frontmatter.description}`);
58
- lines.push("");
59
- if (rule.frontmatter.globs.length > 0) {
60
- lines.push(`**Applies to files:** ${rule.frontmatter.globs.join(", ")}`);
53
+ if (rule.frontmatter.description) {
54
+ lines.push(`**Description:** ${rule.frontmatter.description}`);
55
+ lines.push("");
56
+ }
57
+ if (rule.frontmatter.globs && rule.frontmatter.globs.length > 0) {
58
+ lines.push(`**File patterns:** ${rule.frontmatter.globs.join(", ")}`);
61
59
  lines.push("");
62
60
  }
63
- lines.push("**Guidelines:**");
64
- lines.push("");
65
61
  lines.push(rule.content);
66
62
  lines.push("");
67
- lines.push("---");
68
- lines.push("");
69
63
  return lines;
70
64
  }
71
-
72
- // src/generators/copilot.ts
73
- import { join as join2 } from "path";
74
- async function generateCopilotConfig(rules, config) {
75
- const sortedRules = rules.sort((a, b) => {
76
- if (a.frontmatter.priority !== b.frontmatter.priority) {
77
- return a.frontmatter.priority === "high" ? -1 : 1;
78
- }
79
- return a.filename.localeCompare(b.filename);
80
- });
81
- const content = generateCopilotMarkdown(sortedRules);
82
- const filepath = join2(config.outputPaths.copilot, "ai-rules.instructions.md");
83
- return {
84
- tool: "copilot",
85
- filepath,
86
- content
87
- };
88
- }
89
- function generateCopilotMarkdown(rules) {
65
+ function generateMemoryFile(rule) {
90
66
  const lines = [];
91
- lines.push("---");
92
- lines.push('description: "AI rules configuration for GitHub Copilot"');
93
- lines.push('applyTo: "**"');
67
+ lines.push("Please also refer to the following files as needed:");
68
+ lines.push("");
94
69
  lines.push("---");
95
70
  lines.push("");
96
- lines.push("# GitHub Copilot Instructions");
71
+ lines.push(`# ${rule.filename}`);
97
72
  lines.push("");
98
- lines.push(
99
- "Generated from ai-rules configuration. These instructions guide GitHub Copilot's code suggestions."
100
- );
101
- lines.push("");
102
- const highPriorityRules = rules.filter((r) => r.frontmatter.priority === "high");
103
- const lowPriorityRules = rules.filter((r) => r.frontmatter.priority === "low");
104
- if (highPriorityRules.length > 0) {
105
- lines.push("## High Priority Rules");
73
+ if (rule.frontmatter.description) {
74
+ lines.push(`**Description:** ${rule.frontmatter.description}`);
106
75
  lines.push("");
107
- for (const rule of highPriorityRules) {
108
- lines.push(...formatRuleForCopilot(rule));
109
- }
110
76
  }
111
- if (lowPriorityRules.length > 0) {
112
- lines.push("## Standard Rules");
77
+ if (rule.frontmatter.globs && rule.frontmatter.globs.length > 0) {
78
+ lines.push(`**File patterns:** ${rule.frontmatter.globs.join(", ")}`);
113
79
  lines.push("");
114
- for (const rule of lowPriorityRules) {
115
- lines.push(...formatRuleForCopilot(rule));
116
- }
117
80
  }
81
+ lines.push(rule.content);
118
82
  return lines.join("\n");
119
83
  }
120
- function formatRuleForCopilot(rule) {
84
+
85
+ // src/generators/cline.ts
86
+ import { join as join2 } from "path";
87
+ async function generateClineConfig(rules, config) {
88
+ const outputs = [];
89
+ for (const rule of rules) {
90
+ const content = generateClineMarkdown(rule);
91
+ const filepath = join2(config.outputPaths.cline, `${rule.filename}.md`);
92
+ outputs.push({
93
+ tool: "cline",
94
+ filepath,
95
+ content
96
+ });
97
+ }
98
+ return outputs;
99
+ }
100
+ function generateClineMarkdown(rule) {
121
101
  const lines = [];
122
- lines.push(`### ${rule.filename}`);
123
- lines.push("");
124
- lines.push(`**Description:** ${rule.frontmatter.description}`);
102
+ lines.push(`# ${rule.frontmatter.description}`);
125
103
  lines.push("");
126
104
  if (rule.frontmatter.globs.length > 0) {
127
- lines.push(`**Applies to:** ${rule.frontmatter.globs.join(", ")}`);
105
+ lines.push(`**Applies to files:** ${rule.frontmatter.globs.join(", ")}`);
128
106
  lines.push("");
129
107
  }
130
108
  lines.push(rule.content);
131
- lines.push("");
132
- return lines;
109
+ return lines.join("\n");
133
110
  }
134
111
 
135
- // src/generators/cursor.ts
112
+ // src/generators/copilot.ts
136
113
  import { join as join3 } from "path";
137
- async function generateCursorConfig(rules, config) {
138
- const sortedRules = rules.sort((a, b) => {
139
- if (a.frontmatter.priority !== b.frontmatter.priority) {
140
- return a.frontmatter.priority === "high" ? -1 : 1;
141
- }
142
- return a.filename.localeCompare(b.filename);
143
- });
144
- const content = generateCursorMarkdown(sortedRules);
145
- const filepath = join3(config.outputPaths.cursor, "ai-rules.md");
146
- return {
147
- tool: "cursor",
148
- filepath,
149
- content
150
- };
114
+ async function generateCopilotConfig(rules, config) {
115
+ const outputs = [];
116
+ for (const rule of rules) {
117
+ const content = generateCopilotMarkdown(rule);
118
+ const baseFilename = rule.filename.replace(/\.md$/, "");
119
+ const filepath = join3(config.outputPaths.copilot, `${baseFilename}.instructions.md`);
120
+ outputs.push({
121
+ tool: "copilot",
122
+ filepath,
123
+ content
124
+ });
125
+ }
126
+ return outputs;
151
127
  }
152
- function generateCursorMarkdown(rules) {
128
+ function generateCopilotMarkdown(rule) {
153
129
  const lines = [];
154
130
  lines.push("---");
155
- lines.push("description: AI rules configuration for Cursor IDE");
156
- lines.push("alwaysApply: true");
131
+ lines.push(`description: "${rule.frontmatter.description}"`);
132
+ if (rule.frontmatter.globs.length > 0) {
133
+ lines.push(`applyTo: "${rule.frontmatter.globs.join(", ")}"`);
134
+ } else {
135
+ lines.push('applyTo: "**"');
136
+ }
157
137
  lines.push("---");
158
138
  lines.push("");
159
- lines.push("# Cursor IDE Rules");
160
- lines.push("");
161
- lines.push("These rules configure Cursor IDE's AI assistant behavior.");
162
- lines.push("");
139
+ lines.push(rule.content);
140
+ return lines.join("\n");
141
+ }
142
+
143
+ // src/generators/cursor.ts
144
+ import { join as join4 } from "path";
145
+ async function generateCursorConfig(rules, config) {
146
+ const outputs = [];
163
147
  for (const rule of rules) {
164
- lines.push(...formatRuleForCursor(rule));
148
+ const content = generateCursorMarkdown(rule);
149
+ const filepath = join4(config.outputPaths.cursor, `${rule.filename}.md`);
150
+ outputs.push({
151
+ tool: "cursor",
152
+ filepath,
153
+ content
154
+ });
165
155
  }
166
- return lines.join("\n");
156
+ return outputs;
167
157
  }
168
- function formatRuleForCursor(rule) {
158
+ function generateCursorMarkdown(rule) {
169
159
  const lines = [];
170
- const priorityBadge = rule.frontmatter.priority === "high" ? "\u{1F534} HIGH" : "\u{1F7E1} STANDARD";
171
160
  lines.push("---");
172
161
  lines.push(`description: ${rule.frontmatter.description}`);
173
162
  if (rule.frontmatter.globs.length > 0) {
174
163
  lines.push(`globs: [${rule.frontmatter.globs.map((g) => `"${g}"`).join(", ")}]`);
175
164
  }
176
- lines.push(`alwaysApply: ${rule.frontmatter.priority === "high"}`);
177
- lines.push("---");
178
- lines.push("");
179
- lines.push(`## ${rule.filename} ${priorityBadge}`);
180
- lines.push("");
181
- lines.push(`**Priority:** ${rule.frontmatter.priority.toUpperCase()}`);
182
- lines.push("");
183
- if (rule.frontmatter.globs.length > 0) {
184
- lines.push("**File Patterns:**");
185
- for (const glob of rule.frontmatter.globs) {
186
- lines.push(`- \`${glob}\``);
187
- }
188
- lines.push("");
165
+ let ruletype;
166
+ if (rule.frontmatter.ruleLevel === "overview") {
167
+ ruletype = "always";
168
+ } else if (rule.frontmatter.ruleLevel === "detail" && rule.frontmatter.globs.length === 0) {
169
+ ruletype = "agentrequested";
170
+ } else {
171
+ ruletype = "autoattached";
189
172
  }
190
- lines.push("**Rule:**");
191
- lines.push(rule.content);
192
- lines.push("");
173
+ lines.push(`ruletype: ${ruletype}`);
193
174
  lines.push("---");
194
175
  lines.push("");
195
- return lines;
176
+ lines.push(rule.content);
177
+ return lines.join("\n");
196
178
  }
197
179
 
198
180
  // src/utils/config.ts
@@ -202,10 +184,11 @@ function getDefaultConfig() {
202
184
  outputPaths: {
203
185
  copilot: ".github/instructions",
204
186
  cursor: ".cursor/rules",
205
- cline: ".clinerules"
187
+ cline: ".clinerules",
188
+ claude: "."
206
189
  },
207
190
  watchEnabled: false,
208
- defaultTargets: ["copilot", "cursor", "cline"]
191
+ defaultTargets: ["copilot", "cursor", "cline", "claude"]
209
192
  };
210
193
  }
211
194
  function resolveTargets(targets, config) {
@@ -217,7 +200,7 @@ function resolveTargets(targets, config) {
217
200
 
218
201
  // src/utils/file.ts
219
202
  import { mkdir, readdir, readFile, stat, writeFile } from "fs/promises";
220
- import { dirname, join as join4 } from "path";
203
+ import { dirname, join as join5 } from "path";
221
204
  async function ensureDir(dirPath) {
222
205
  try {
223
206
  await stat(dirPath);
@@ -235,7 +218,7 @@ async function writeFileContent(filepath, content) {
235
218
  async function findFiles(dir, extension = ".md") {
236
219
  try {
237
220
  const files = await readdir(dir);
238
- return files.filter((file) => file.endsWith(extension)).map((file) => join4(dir, file));
221
+ return files.filter((file) => file.endsWith(extension)).map((file) => join5(dir, file));
239
222
  } catch {
240
223
  return [];
241
224
  }
@@ -259,9 +242,9 @@ async function generateConfigurations(rules, config, targetTools) {
259
242
  console.warn(`No rules found for tool: ${tool}`);
260
243
  continue;
261
244
  }
262
- const output = await generateForTool(tool, relevantRules, config);
263
- if (output) {
264
- outputs.push(output);
245
+ const toolOutputs = await generateForTool(tool, relevantRules, config);
246
+ if (toolOutputs) {
247
+ outputs.push(...toolOutputs);
265
248
  }
266
249
  }
267
250
  return outputs;
@@ -280,6 +263,8 @@ async function generateForTool(tool, rules, config) {
280
263
  return generateCursorConfig(rules, config);
281
264
  case "cline":
282
265
  return generateClineConfig(rules, config);
266
+ case "claude":
267
+ return await generateClaudeConfig(rules, config);
283
268
  default:
284
269
  console.warn(`Unknown tool: ${tool}`);
285
270
  return null;
@@ -320,8 +305,8 @@ function validateFrontmatter(data, filepath) {
320
305
  throw new Error(`Invalid frontmatter in ${filepath}: must be an object`);
321
306
  }
322
307
  const obj = data;
323
- if (!obj.priority || !["high", "low"].includes(obj.priority)) {
324
- throw new Error(`Invalid priority in ${filepath}: must be "high" or "low"`);
308
+ if (!obj.ruleLevel || !["overview", "detail"].includes(obj.ruleLevel)) {
309
+ throw new Error(`Invalid ruleLevel in ${filepath}: must be "overview" or "detail"`);
325
310
  }
326
311
  if (!Array.isArray(obj.targets)) {
327
312
  throw new Error(`Invalid targets in ${filepath}: must be an array`);
@@ -358,6 +343,10 @@ async function validateRules(rules) {
358
343
  }
359
344
  filenames.add(rule.filename);
360
345
  }
346
+ const overviewRules = rules.filter((rule) => rule.frontmatter.ruleLevel === "overview");
347
+ if (overviewRules.length > 1) {
348
+ errors.push(`Multiple overview rules found: ${overviewRules.map((r) => r.filename).join(", ")}. Only one overview rule is allowed.`);
349
+ }
361
350
  for (const rule of rules) {
362
351
  const ruleValidation = await validateRule(rule);
363
352
  errors.push(...ruleValidation.errors);
@@ -428,17 +417,57 @@ async function generateCommand(options = {}) {
428
417
  }
429
418
  }
430
419
 
420
+ // src/cli/commands/gitignore.ts
421
+ import { existsSync, readFileSync, writeFileSync } from "fs";
422
+ import { join as join6 } from "path";
423
+ var gitignoreCommand = async () => {
424
+ const gitignorePath = join6(process.cwd(), ".gitignore");
425
+ const rulesFilesToIgnore = [
426
+ "# Generated by rulesync - AI tool configuration files",
427
+ ".github/instructions/",
428
+ ".cursor/rules/",
429
+ ".clinerules/",
430
+ "CLAUDE.md"
431
+ ];
432
+ let gitignoreContent = "";
433
+ if (existsSync(gitignorePath)) {
434
+ gitignoreContent = readFileSync(gitignorePath, "utf-8");
435
+ }
436
+ const linesToAdd = [];
437
+ for (const rule of rulesFilesToIgnore) {
438
+ if (!gitignoreContent.includes(rule)) {
439
+ linesToAdd.push(rule);
440
+ }
441
+ }
442
+ if (linesToAdd.length === 0) {
443
+ console.log("\u2705 .gitignore\u306F\u65E2\u306B\u6700\u65B0\u3067\u3059");
444
+ return;
445
+ }
446
+ const newContent = gitignoreContent ? `${gitignoreContent.trimEnd()}
447
+
448
+ ${linesToAdd.join("\n")}
449
+ ` : `${linesToAdd.join("\n")}
450
+ `;
451
+ writeFileSync(gitignorePath, newContent);
452
+ console.log(`\u2705 .gitignore\u306B${linesToAdd.length}\u500B\u306E\u30EB\u30FC\u30EB\u3092\u8FFD\u52A0\u3057\u307E\u3057\u305F:`);
453
+ for (const line of linesToAdd) {
454
+ if (!line.startsWith("#")) {
455
+ console.log(` ${line}`);
456
+ }
457
+ }
458
+ };
459
+
431
460
  // src/cli/commands/init.ts
432
- import { join as join5 } from "path";
461
+ import { join as join7 } from "path";
433
462
  async function initCommand() {
434
463
  const aiRulesDir = ".rulesync";
435
- console.log("Initializing ai-rules...");
464
+ console.log("Initializing rulesync...");
436
465
  await ensureDir(aiRulesDir);
437
466
  await createSampleFiles(aiRulesDir);
438
- console.log("\u2705 ai-rules initialized successfully!");
467
+ console.log("\u2705 rulesync initialized successfully!");
439
468
  console.log("\nNext steps:");
440
469
  console.log("1. Edit rule files in .rulesync/");
441
- console.log("2. Run 'ai-rules generate' to create configuration files");
470
+ console.log("2. Run 'rulesync generate' to create configuration files");
442
471
  }
443
472
  async function createSampleFiles(aiRulesDir) {
444
473
  const sampleFiles = [
@@ -519,7 +548,7 @@ globs: ["src/**/*.ts"]
519
548
  }
520
549
  ];
521
550
  for (const file of sampleFiles) {
522
- const filepath = join5(aiRulesDir, file.filename);
551
+ const filepath = join7(aiRulesDir, file.filename);
523
552
  if (!await fileExists(filepath)) {
524
553
  await writeFileContent(filepath, file.content);
525
554
  console.log(`Created ${filepath}`);
@@ -534,10 +563,10 @@ async function statusCommand() {
534
563
  const config = getDefaultConfig();
535
564
  console.log("rulesync Status");
536
565
  console.log("===============");
537
- const aiRulesExists = await fileExists(config.aiRulesDir);
566
+ const rulesyncExists = await fileExists(config.aiRulesDir);
538
567
  console.log(`
539
- \u{1F4C1} .rulesync directory: ${aiRulesExists ? "\u2705 Found" : "\u274C Not found"}`);
540
- if (!aiRulesExists) {
568
+ \u{1F4C1} .rulesync directory: ${rulesyncExists ? "\u2705 Found" : "\u274C Not found"}`);
569
+ if (!rulesyncExists) {
541
570
  console.log("\n\u{1F4A1} Run 'rulesync init' to get started");
542
571
  return;
543
572
  }
@@ -580,7 +609,7 @@ async function statusCommand() {
580
609
  // src/cli/commands/validate.ts
581
610
  async function validateCommand() {
582
611
  const config = getDefaultConfig();
583
- console.log("Validating ai-rules configuration...");
612
+ console.log("Validating rulesync configuration...");
584
613
  if (!await fileExists(config.aiRulesDir)) {
585
614
  console.error("\u274C .rulesync directory not found. Run 'rulesync init' first.");
586
615
  process.exit(1);
@@ -588,7 +617,7 @@ async function validateCommand() {
588
617
  try {
589
618
  const rules = await parseRulesFromDirectory(config.aiRulesDir);
590
619
  if (rules.length === 0) {
591
- console.warn("\u26A0\uFE0F No rules found in .ai-rules directory");
620
+ console.warn("\u26A0\uFE0F No rules found in .rulesync directory");
592
621
  return;
593
622
  }
594
623
  console.log(`Found ${rules.length} rule(s), validating...`);
@@ -660,13 +689,15 @@ async function watchCommand() {
660
689
 
661
690
  // src/cli/index.ts
662
691
  var program = new Command();
663
- program.name("ai-rules").description("Unified AI rules management CLI tool").version("0.1.0");
664
- program.command("init").description("Initialize ai-rules in current directory").action(initCommand);
665
- 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("-v, --verbose", "Verbose output").action(async (options) => {
692
+ program.name("rulesync").description("Unified AI rules management CLI tool").version("0.1.0");
693
+ program.command("init").description("Initialize rulesync in current directory").action(initCommand);
694
+ program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
695
+ 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("-v, --verbose", "Verbose output").action(async (options) => {
666
696
  const tools = [];
667
697
  if (options.copilot) tools.push("copilot");
668
698
  if (options.cursor) tools.push("cursor");
669
699
  if (options.cline) tools.push("cline");
700
+ if (options.claude) tools.push("claude");
670
701
  const generateOptions = {
671
702
  verbose: options.verbose
672
703
  };
@@ -675,7 +706,7 @@ program.command("generate").description("Generate configuration files for AI too
675
706
  }
676
707
  await generateCommand(generateOptions);
677
708
  });
678
- program.command("validate").description("Validate ai-rules configuration").action(validateCommand);
679
- program.command("status").description("Show current status of ai-rules").action(statusCommand);
709
+ program.command("validate").description("Validate rulesync configuration").action(validateCommand);
710
+ program.command("status").description("Show current status of rulesync").action(statusCommand);
680
711
  program.command("watch").description("Watch for changes and auto-generate configurations").action(watchCommand);
681
712
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rulesync",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Unified AI rules management CLI tool that generates configuration files for various AI development tools",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",