rulesync 0.3.0 → 0.5.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.md +237 -37
- package/dist/index.js +55 -16
- package/dist/index.mjs +56 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,9 +8,9 @@ A Node.js CLI tool that automatically generates configuration files for various
|
|
|
8
8
|
## Supported Tools
|
|
9
9
|
|
|
10
10
|
- **GitHub Copilot Custom Instructions** (`.github/instructions/*.instructions.md`)
|
|
11
|
-
- **Cursor Project Rules** (`.cursor/rules/*.
|
|
11
|
+
- **Cursor Project Rules** (`.cursor/rules/*.mdc`)
|
|
12
12
|
- **Cline Rules** (`.clinerules/*.md`)
|
|
13
|
-
- **Claude Code Memory** (`./CLAUDE.md`)
|
|
13
|
+
- **Claude Code Memory** (`./CLAUDE.md` + `.claude/memories/*.md`)
|
|
14
14
|
|
|
15
15
|
## Installation
|
|
16
16
|
|
|
@@ -22,6 +22,132 @@ pnpm add -g rulesync
|
|
|
22
22
|
yarn global add rulesync
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
## Getting Started
|
|
26
|
+
|
|
27
|
+
### Quick Start Example
|
|
28
|
+
|
|
29
|
+
1. **Initialize your project:**
|
|
30
|
+
```bash
|
|
31
|
+
rulesync init
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
2. **Create an overview file** (`.rulesync/overview.md`):
|
|
35
|
+
```markdown
|
|
36
|
+
---
|
|
37
|
+
root: true
|
|
38
|
+
targets: ["*"]
|
|
39
|
+
description: "Project overview and development philosophy"
|
|
40
|
+
globs: ["src/**/*.ts", "src/**/*.js"]
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
# Project Development Guidelines
|
|
44
|
+
|
|
45
|
+
This is a TypeScript/JavaScript project following clean architecture principles.
|
|
46
|
+
We prioritize code readability, maintainability, and type safety.
|
|
47
|
+
|
|
48
|
+
## Tech Stack
|
|
49
|
+
- TypeScript for type safety
|
|
50
|
+
- Node.js runtime
|
|
51
|
+
- Modern ES6+ features
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
3. **Create detail rules** (`.rulesync/coding-rules.md`):
|
|
55
|
+
```markdown
|
|
56
|
+
---
|
|
57
|
+
root: false
|
|
58
|
+
targets: ["copilot", "cursor", "cline"]
|
|
59
|
+
description: "TypeScript coding standards and best practices"
|
|
60
|
+
globs: ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"]
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
# TypeScript Coding Rules
|
|
64
|
+
|
|
65
|
+
## Code Style
|
|
66
|
+
- Use strict TypeScript configuration
|
|
67
|
+
- Prefer `const` over `let` when possible
|
|
68
|
+
- Use meaningful, descriptive variable names
|
|
69
|
+
- Write JSDoc comments for public APIs
|
|
70
|
+
|
|
71
|
+
## Type Definitions
|
|
72
|
+
- Prefer interfaces over types for object shapes
|
|
73
|
+
- Use union types for controlled values
|
|
74
|
+
- Avoid `any` type - use `unknown` instead
|
|
75
|
+
- Define return types for functions explicitly
|
|
76
|
+
|
|
77
|
+
## Error Handling
|
|
78
|
+
- Use Result pattern for error handling
|
|
79
|
+
- Throw errors only for unexpected conditions
|
|
80
|
+
- Validate input parameters at function boundaries
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
4. **Generate configuration files:**
|
|
84
|
+
```bash
|
|
85
|
+
rulesync generate
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
5. **Optional: Add generated files to .gitignore:**
|
|
89
|
+
```bash
|
|
90
|
+
rulesync gitignore
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
This will create tool-specific configuration files that your AI coding assistants can use automatically.
|
|
94
|
+
|
|
95
|
+
## Why rulesync?
|
|
96
|
+
|
|
97
|
+
### 🔧 **Tool Flexibility**
|
|
98
|
+
Team members can freely choose their preferred AI coding tools. Whether it's GitHub Copilot, Cursor, Cline, or Claude Code, each developer can use the tool that maximizes their productivity.
|
|
99
|
+
|
|
100
|
+
### 📈 **Future-Proof Development**
|
|
101
|
+
AI development tools evolve rapidly with new tools emerging frequently. With rulesync, switching between tools doesn't require redefining your rules from scratch.
|
|
102
|
+
|
|
103
|
+
### 🎯 **Multi-Tool Workflow**
|
|
104
|
+
Enable hybrid development workflows combining multiple AI tools:
|
|
105
|
+
- GitHub Copilot for code completion
|
|
106
|
+
- Cursor for refactoring
|
|
107
|
+
- Claude Code for architecture design
|
|
108
|
+
- Cline for debugging assistance
|
|
109
|
+
|
|
110
|
+
### 🔓 **No Vendor Lock-in**
|
|
111
|
+
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.
|
|
112
|
+
|
|
113
|
+
### 🎯 **Consistency Across Tools**
|
|
114
|
+
Apply consistent rules across all AI tools, improving code quality and development experience for the entire team.
|
|
115
|
+
|
|
116
|
+
## Claude Code Integration
|
|
117
|
+
|
|
118
|
+
### Creating Custom Slash Commands
|
|
119
|
+
|
|
120
|
+
Instead of using Claude Code's built-in `/init` command, we recommend creating a custom slash command specifically for rulesync.
|
|
121
|
+
|
|
122
|
+
Refer to the [Claude Code slash commands documentation](https://docs.anthropic.com/en/docs/claude-code/slash-commands) and add the following custom command:
|
|
123
|
+
|
|
124
|
+
**`.claude/commands/init-rulesync.md`**
|
|
125
|
+
|
|
126
|
+
```markdown
|
|
127
|
+
Review this project's content and update .rulesync/*.md files as needed.
|
|
128
|
+
|
|
129
|
+
Steps:
|
|
130
|
+
1. Analyze project structure and codebase
|
|
131
|
+
2. Review existing .rulesync/ files
|
|
132
|
+
3. Consider project's tech stack, architecture, and coding conventions
|
|
133
|
+
4. Update .rulesync/*.md files if missing elements or improvements are found
|
|
134
|
+
5. Run rulesync generate if necessary
|
|
135
|
+
|
|
136
|
+
Project characteristics to consider:
|
|
137
|
+
- Technology stack
|
|
138
|
+
- Architecture patterns
|
|
139
|
+
- Coding conventions
|
|
140
|
+
- Security requirements
|
|
141
|
+
- Performance considerations
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Integration Benefits
|
|
145
|
+
|
|
146
|
+
- **Project-Specific Initialization**: Optimized rule configuration for each project
|
|
147
|
+
- **Automatic Rule Updates**: Rules adapt to project changes automatically
|
|
148
|
+
- **Team Standardization**: All members use the same rule set
|
|
149
|
+
- **Continuous Improvement**: Rules evolve with project growth
|
|
150
|
+
|
|
25
151
|
## Usage
|
|
26
152
|
|
|
27
153
|
### 1. Initialize
|
|
@@ -38,7 +164,7 @@ Define metadata in front matter for each Markdown file:
|
|
|
38
164
|
|
|
39
165
|
```markdown
|
|
40
166
|
---
|
|
41
|
-
|
|
167
|
+
root: true # or false
|
|
42
168
|
targets: ["*"] # or [copilot, cursor, cline, claudecode]
|
|
43
169
|
description: "TypeScript coding rules"
|
|
44
170
|
globs: ["**/*.ts", "**/*.tsx"]
|
|
@@ -52,12 +178,25 @@ globs: ["**/*.ts", "**/*.tsx"]
|
|
|
52
178
|
|
|
53
179
|
### Rule Levels
|
|
54
180
|
|
|
55
|
-
|
|
56
|
-
|
|
181
|
+
rulesync uses a two-level rule system:
|
|
182
|
+
|
|
183
|
+
- **root: true**: Project-wide overview and policies
|
|
184
|
+
- Only **one** root file is allowed per project
|
|
185
|
+
- Contains high-level guidelines and project context
|
|
186
|
+
- **root: false**: Specific implementation rules and detailed guidelines
|
|
187
|
+
- Multiple non-root files are allowed
|
|
188
|
+
- Contains specific coding rules, naming conventions, etc.
|
|
189
|
+
|
|
190
|
+
#### Tool-Specific Behavior
|
|
191
|
+
|
|
192
|
+
Each AI tool handles rule levels differently:
|
|
57
193
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
194
|
+
| Tool | Root Rules | Non-Root Rules | Special Behavior |
|
|
195
|
+
|------|------------|----------------|------------------|
|
|
196
|
+
| **Claude Code** | `./CLAUDE.md` | `.claude/memories/*.md` | CLAUDE.md includes `@filename` references to detail files |
|
|
197
|
+
| **Cursor** | `ruletype: always` | `ruletype: autoattached` | Detail rules without globs use `ruletype: agentrequested` |
|
|
198
|
+
| **GitHub Copilot** | Standard format | Standard format | All rules use same format with frontmatter |
|
|
199
|
+
| **Cline** | Standard format | Standard format | All rules use plain Markdown format |
|
|
61
200
|
|
|
62
201
|
### 3. Generate Configuration Files
|
|
63
202
|
|
|
@@ -70,62 +209,123 @@ rulesync generate --copilot
|
|
|
70
209
|
rulesync generate --cursor
|
|
71
210
|
rulesync generate --cline
|
|
72
211
|
rulesync generate --claude
|
|
212
|
+
|
|
213
|
+
# Clean build (delete existing files first)
|
|
214
|
+
rulesync generate --delete
|
|
215
|
+
|
|
216
|
+
# Clean build for specific tools
|
|
217
|
+
rulesync generate --copilot --cursor --delete
|
|
218
|
+
|
|
219
|
+
# Verbose output
|
|
220
|
+
rulesync generate --verbose
|
|
221
|
+
rulesync generate --delete --verbose
|
|
73
222
|
```
|
|
74
223
|
|
|
224
|
+
#### Generate Options
|
|
225
|
+
|
|
226
|
+
- `--delete`: Remove all existing generated files before creating new ones
|
|
227
|
+
- `--verbose`: Show detailed output during generation process
|
|
228
|
+
- `--copilot`, `--cursor`, `--cline`, `--claude`: Generate only for specified tools
|
|
229
|
+
|
|
75
230
|
### 4. Other Commands
|
|
76
231
|
|
|
77
232
|
```bash
|
|
78
|
-
#
|
|
233
|
+
# Initialize project with sample files
|
|
234
|
+
rulesync init
|
|
235
|
+
|
|
236
|
+
# Validate rule files
|
|
79
237
|
rulesync validate
|
|
80
238
|
|
|
81
|
-
# Check current status
|
|
239
|
+
# Check current status
|
|
82
240
|
rulesync status
|
|
83
241
|
|
|
84
242
|
# Watch files and auto-generate
|
|
85
243
|
rulesync watch
|
|
244
|
+
|
|
245
|
+
# Add generated files to .gitignore
|
|
246
|
+
rulesync gitignore
|
|
86
247
|
```
|
|
87
248
|
|
|
88
249
|
## Configuration File Structure
|
|
89
250
|
|
|
90
251
|
```
|
|
91
252
|
.rulesync/
|
|
92
|
-
├──
|
|
93
|
-
├──
|
|
94
|
-
├──
|
|
95
|
-
├──
|
|
96
|
-
|
|
253
|
+
├── overview.md # Project overview (root: true, only one)
|
|
254
|
+
├── coding-rules.md # Coding rules (root: false)
|
|
255
|
+
├── naming-conventions.md # Naming conventions (root: false)
|
|
256
|
+
├── architecture.md # Architecture guidelines (root: false)
|
|
257
|
+
├── security.md # Security rules (root: false)
|
|
258
|
+
└── custom.md # Project-specific rules (root: false)
|
|
97
259
|
```
|
|
98
260
|
|
|
99
|
-
|
|
261
|
+
### Frontmatter Schema
|
|
100
262
|
|
|
101
|
-
|
|
102
|
-
|------|------------|--------|
|
|
103
|
-
| GitHub Copilot | `.github/instructions/*.instructions.md` | Front Matter + Markdown |
|
|
104
|
-
| Cursor | `.cursor/rules/*.md` | MDC (YAML header + Markdown) |
|
|
105
|
-
| Cline | `.clinerules/*.md` | Plain Markdown |
|
|
106
|
-
| Claude Code | `./CLAUDE.md` (overview), `.claude/memories/*.md` (detail) | Plain Markdown |
|
|
263
|
+
Each rule file must include frontmatter with the following fields:
|
|
107
264
|
|
|
108
|
-
|
|
265
|
+
```yaml
|
|
266
|
+
---
|
|
267
|
+
root: true | false # Required: Rule level (true for overview, false for details)
|
|
268
|
+
targets: ["*"] # Required: Target tools (* = all, or specific tools)
|
|
269
|
+
description: "Brief description" # Required: Rule description
|
|
270
|
+
globs: ["**/*.ts", "**/*.js"] # Required: File patterns (can be empty array)
|
|
271
|
+
---
|
|
272
|
+
```
|
|
109
273
|
|
|
110
|
-
|
|
111
|
-
# Install dependencies
|
|
112
|
-
pnpm install
|
|
274
|
+
### Example Files
|
|
113
275
|
|
|
114
|
-
|
|
115
|
-
|
|
276
|
+
**Root file** (`.rulesync/overview.md`):
|
|
277
|
+
```markdown
|
|
278
|
+
---
|
|
279
|
+
root: true
|
|
280
|
+
targets: ["*"]
|
|
281
|
+
description: "Project overview and development philosophy"
|
|
282
|
+
globs: ["src/**/*.ts"]
|
|
283
|
+
---
|
|
116
284
|
|
|
117
|
-
#
|
|
118
|
-
pnpm build
|
|
285
|
+
# Project Development Guidelines
|
|
119
286
|
|
|
120
|
-
|
|
121
|
-
|
|
287
|
+
This project follows TypeScript-first development with clean architecture principles.
|
|
288
|
+
```
|
|
122
289
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
290
|
+
**Non-root file** (`.rulesync/coding-rules.md`):
|
|
291
|
+
```markdown
|
|
292
|
+
---
|
|
293
|
+
root: false
|
|
294
|
+
targets: ["copilot", "cursor"]
|
|
295
|
+
description: "TypeScript coding standards"
|
|
296
|
+
globs: ["**/*.ts", "**/*.tsx"]
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
# TypeScript Coding Rules
|
|
300
|
+
|
|
301
|
+
- Use strict TypeScript configuration
|
|
302
|
+
- Prefer interfaces over types for object shapes
|
|
303
|
+
- Use meaningful variable names
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Generated Configuration Files
|
|
307
|
+
|
|
308
|
+
| Tool | Output Path | Format | Rule Level Handling |
|
|
309
|
+
|------|------------|--------|-------------------|
|
|
310
|
+
| **GitHub Copilot** | `.github/instructions/*.instructions.md` | Front Matter + Markdown | Both levels use same format |
|
|
311
|
+
| **Cursor** | `.cursor/rules/*.mdc` | MDC (YAML header + Markdown) | Root: `ruletype: always`<br>Non-root: `ruletype: autoattached`<br>Non-root without globs: `ruletype: agentrequested` |
|
|
312
|
+
| **Cline** | `.clinerules/*.md` | Plain Markdown | Both levels use same format |
|
|
313
|
+
| **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 |
|
|
314
|
+
|
|
315
|
+
## Validation
|
|
316
|
+
|
|
317
|
+
rulesync validates your rule files and provides helpful error messages:
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
rulesync validate
|
|
127
321
|
```
|
|
128
322
|
|
|
323
|
+
Common validation rules:
|
|
324
|
+
- Only one root file (root: true) is allowed per project
|
|
325
|
+
- All frontmatter fields are required and properly formatted
|
|
326
|
+
- File patterns (globs) use valid syntax
|
|
327
|
+
- Target tools are recognized values
|
|
328
|
+
|
|
129
329
|
## License
|
|
130
330
|
|
|
131
331
|
MIT License
|
|
@@ -134,4 +334,4 @@ MIT License
|
|
|
134
334
|
|
|
135
335
|
Issues and Pull Requests are welcome!
|
|
136
336
|
|
|
137
|
-
For
|
|
337
|
+
For development setup and contribution guidelines, see [CONTRIBUTING.md](./CONTRIBUTING.md).
|
package/dist/index.js
CHANGED
|
@@ -30,9 +30,9 @@ var import_commander = require("commander");
|
|
|
30
30
|
var import_node_path = require("path");
|
|
31
31
|
async function generateClaudeConfig(rules, config) {
|
|
32
32
|
const outputs = [];
|
|
33
|
-
const
|
|
34
|
-
const detailRules = rules.filter((r) => r.frontmatter.
|
|
35
|
-
const claudeMdContent = generateClaudeMarkdown(
|
|
33
|
+
const rootRules = rules.filter((r) => r.frontmatter.root === true);
|
|
34
|
+
const detailRules = rules.filter((r) => r.frontmatter.root === false);
|
|
35
|
+
const claudeMdContent = generateClaudeMarkdown(rootRules, detailRules);
|
|
36
36
|
outputs.push({
|
|
37
37
|
tool: "claude",
|
|
38
38
|
filepath: (0, import_node_path.join)(config.outputPaths.claude, "CLAUDE.md"),
|
|
@@ -48,7 +48,7 @@ async function generateClaudeConfig(rules, config) {
|
|
|
48
48
|
}
|
|
49
49
|
return outputs;
|
|
50
50
|
}
|
|
51
|
-
function generateClaudeMarkdown(
|
|
51
|
+
function generateClaudeMarkdown(rootRules, detailRules) {
|
|
52
52
|
const lines = [];
|
|
53
53
|
if (detailRules.length > 0) {
|
|
54
54
|
for (const rule of detailRules) {
|
|
@@ -62,8 +62,8 @@ function generateClaudeMarkdown(overviewRules, detailRules) {
|
|
|
62
62
|
"Generated from rulesync configuration. These instructions guide Claude Code's behavior for this project."
|
|
63
63
|
);
|
|
64
64
|
lines.push("");
|
|
65
|
-
if (
|
|
66
|
-
for (const rule of
|
|
65
|
+
if (rootRules.length > 0) {
|
|
66
|
+
for (const rule of rootRules) {
|
|
67
67
|
lines.push(...formatRuleForClaude(rule));
|
|
68
68
|
}
|
|
69
69
|
}
|
|
@@ -169,7 +169,7 @@ async function generateCursorConfig(rules, config) {
|
|
|
169
169
|
const outputs = [];
|
|
170
170
|
for (const rule of rules) {
|
|
171
171
|
const content = generateCursorMarkdown(rule);
|
|
172
|
-
const filepath = (0, import_node_path4.join)(config.outputPaths.cursor, `${rule.filename}.
|
|
172
|
+
const filepath = (0, import_node_path4.join)(config.outputPaths.cursor, `${rule.filename}.mdc`);
|
|
173
173
|
outputs.push({
|
|
174
174
|
tool: "cursor",
|
|
175
175
|
filepath,
|
|
@@ -186,9 +186,9 @@ function generateCursorMarkdown(rule) {
|
|
|
186
186
|
lines.push(`globs: [${rule.frontmatter.globs.map((g) => `"${g}"`).join(", ")}]`);
|
|
187
187
|
}
|
|
188
188
|
let ruletype;
|
|
189
|
-
if (rule.frontmatter.
|
|
189
|
+
if (rule.frontmatter.root === true) {
|
|
190
190
|
ruletype = "always";
|
|
191
|
-
} else if (rule.frontmatter.
|
|
191
|
+
} else if (rule.frontmatter.root === false && rule.frontmatter.globs.length === 0) {
|
|
192
192
|
ruletype = "agentrequested";
|
|
193
193
|
} else {
|
|
194
194
|
ruletype = "autoattached";
|
|
@@ -254,6 +254,15 @@ async function fileExists(filepath) {
|
|
|
254
254
|
return false;
|
|
255
255
|
}
|
|
256
256
|
}
|
|
257
|
+
async function removeDirectory(dirPath) {
|
|
258
|
+
try {
|
|
259
|
+
if (await fileExists(dirPath)) {
|
|
260
|
+
await (0, import_promises.rm)(dirPath, { recursive: true, force: true });
|
|
261
|
+
}
|
|
262
|
+
} catch (error) {
|
|
263
|
+
console.warn(`Failed to remove directory ${dirPath}:`, error);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
257
266
|
|
|
258
267
|
// src/core/generator.ts
|
|
259
268
|
async function generateConfigurations(rules, config, targetTools) {
|
|
@@ -328,8 +337,8 @@ function validateFrontmatter(data, filepath) {
|
|
|
328
337
|
throw new Error(`Invalid frontmatter in ${filepath}: must be an object`);
|
|
329
338
|
}
|
|
330
339
|
const obj = data;
|
|
331
|
-
if (
|
|
332
|
-
throw new Error(`Invalid
|
|
340
|
+
if (typeof obj.root !== "boolean") {
|
|
341
|
+
throw new Error(`Invalid root in ${filepath}: must be a boolean`);
|
|
333
342
|
}
|
|
334
343
|
if (!Array.isArray(obj.targets)) {
|
|
335
344
|
throw new Error(`Invalid targets in ${filepath}: must be an array`);
|
|
@@ -366,9 +375,11 @@ async function validateRules(rules) {
|
|
|
366
375
|
}
|
|
367
376
|
filenames.add(rule.filename);
|
|
368
377
|
}
|
|
369
|
-
const
|
|
370
|
-
if (
|
|
371
|
-
errors.push(
|
|
378
|
+
const rootRules = rules.filter((rule) => rule.frontmatter.root === true);
|
|
379
|
+
if (rootRules.length > 1) {
|
|
380
|
+
errors.push(
|
|
381
|
+
`Multiple root rules found: ${rootRules.map((r) => r.filename).join(", ")}. Only one root rule is allowed.`
|
|
382
|
+
);
|
|
372
383
|
}
|
|
373
384
|
for (const rule of rules) {
|
|
374
385
|
const ruleValidation = await validateRule(rule);
|
|
@@ -423,6 +434,33 @@ async function generateCommand(options = {}) {
|
|
|
423
434
|
if (options.verbose) {
|
|
424
435
|
console.log(`Found ${rules.length} rule(s)`);
|
|
425
436
|
}
|
|
437
|
+
if (options.delete) {
|
|
438
|
+
if (options.verbose) {
|
|
439
|
+
console.log("Deleting existing output directories...");
|
|
440
|
+
}
|
|
441
|
+
const targetTools = options.tools || config.defaultTargets;
|
|
442
|
+
const deleteTasks = [];
|
|
443
|
+
for (const tool of targetTools) {
|
|
444
|
+
switch (tool) {
|
|
445
|
+
case "copilot":
|
|
446
|
+
deleteTasks.push(removeDirectory(config.outputPaths.copilot));
|
|
447
|
+
break;
|
|
448
|
+
case "cursor":
|
|
449
|
+
deleteTasks.push(removeDirectory(config.outputPaths.cursor));
|
|
450
|
+
break;
|
|
451
|
+
case "cline":
|
|
452
|
+
deleteTasks.push(removeDirectory(config.outputPaths.cline));
|
|
453
|
+
break;
|
|
454
|
+
case "claude":
|
|
455
|
+
deleteTasks.push(removeDirectory(config.outputPaths.claude));
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
await Promise.all(deleteTasks);
|
|
460
|
+
if (options.verbose) {
|
|
461
|
+
console.log("Deleted existing output directories");
|
|
462
|
+
}
|
|
463
|
+
}
|
|
426
464
|
const outputs = await generateConfigurations(rules, config, options.tools);
|
|
427
465
|
if (outputs.length === 0) {
|
|
428
466
|
console.warn("\u26A0\uFE0F No configurations generated");
|
|
@@ -715,14 +753,15 @@ var program = new import_commander.Command();
|
|
|
715
753
|
program.name("rulesync").description("Unified AI rules management CLI tool").version("0.1.0");
|
|
716
754
|
program.command("init").description("Initialize rulesync in current directory").action(initCommand);
|
|
717
755
|
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) => {
|
|
756
|
+
program.command("generate").description("Generate configuration files for AI tools").option("--copilot", "Generate only for GitHub Copilot").option("--cursor", "Generate only for Cursor").option("--cline", "Generate only for Cline").option("--claude", "Generate only for Claude Code").option("--delete", "Delete all existing files in output directories before generating").option("-v, --verbose", "Verbose output").action(async (options) => {
|
|
719
757
|
const tools = [];
|
|
720
758
|
if (options.copilot) tools.push("copilot");
|
|
721
759
|
if (options.cursor) tools.push("cursor");
|
|
722
760
|
if (options.cline) tools.push("cline");
|
|
723
761
|
if (options.claude) tools.push("claude");
|
|
724
762
|
const generateOptions = {
|
|
725
|
-
verbose: options.verbose
|
|
763
|
+
verbose: options.verbose,
|
|
764
|
+
delete: options.delete
|
|
726
765
|
};
|
|
727
766
|
if (tools.length > 0) {
|
|
728
767
|
generateOptions.tools = tools;
|
package/dist/index.mjs
CHANGED
|
@@ -7,9 +7,9 @@ import { Command } from "commander";
|
|
|
7
7
|
import { join } from "path";
|
|
8
8
|
async function generateClaudeConfig(rules, config) {
|
|
9
9
|
const outputs = [];
|
|
10
|
-
const
|
|
11
|
-
const detailRules = rules.filter((r) => r.frontmatter.
|
|
12
|
-
const claudeMdContent = generateClaudeMarkdown(
|
|
10
|
+
const rootRules = rules.filter((r) => r.frontmatter.root === true);
|
|
11
|
+
const detailRules = rules.filter((r) => r.frontmatter.root === false);
|
|
12
|
+
const claudeMdContent = generateClaudeMarkdown(rootRules, detailRules);
|
|
13
13
|
outputs.push({
|
|
14
14
|
tool: "claude",
|
|
15
15
|
filepath: join(config.outputPaths.claude, "CLAUDE.md"),
|
|
@@ -25,7 +25,7 @@ async function generateClaudeConfig(rules, config) {
|
|
|
25
25
|
}
|
|
26
26
|
return outputs;
|
|
27
27
|
}
|
|
28
|
-
function generateClaudeMarkdown(
|
|
28
|
+
function generateClaudeMarkdown(rootRules, detailRules) {
|
|
29
29
|
const lines = [];
|
|
30
30
|
if (detailRules.length > 0) {
|
|
31
31
|
for (const rule of detailRules) {
|
|
@@ -39,8 +39,8 @@ function generateClaudeMarkdown(overviewRules, detailRules) {
|
|
|
39
39
|
"Generated from rulesync configuration. These instructions guide Claude Code's behavior for this project."
|
|
40
40
|
);
|
|
41
41
|
lines.push("");
|
|
42
|
-
if (
|
|
43
|
-
for (const rule of
|
|
42
|
+
if (rootRules.length > 0) {
|
|
43
|
+
for (const rule of rootRules) {
|
|
44
44
|
lines.push(...formatRuleForClaude(rule));
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -146,7 +146,7 @@ async function generateCursorConfig(rules, config) {
|
|
|
146
146
|
const outputs = [];
|
|
147
147
|
for (const rule of rules) {
|
|
148
148
|
const content = generateCursorMarkdown(rule);
|
|
149
|
-
const filepath = join4(config.outputPaths.cursor, `${rule.filename}.
|
|
149
|
+
const filepath = join4(config.outputPaths.cursor, `${rule.filename}.mdc`);
|
|
150
150
|
outputs.push({
|
|
151
151
|
tool: "cursor",
|
|
152
152
|
filepath,
|
|
@@ -163,9 +163,9 @@ function generateCursorMarkdown(rule) {
|
|
|
163
163
|
lines.push(`globs: [${rule.frontmatter.globs.map((g) => `"${g}"`).join(", ")}]`);
|
|
164
164
|
}
|
|
165
165
|
let ruletype;
|
|
166
|
-
if (rule.frontmatter.
|
|
166
|
+
if (rule.frontmatter.root === true) {
|
|
167
167
|
ruletype = "always";
|
|
168
|
-
} else if (rule.frontmatter.
|
|
168
|
+
} else if (rule.frontmatter.root === false && rule.frontmatter.globs.length === 0) {
|
|
169
169
|
ruletype = "agentrequested";
|
|
170
170
|
} else {
|
|
171
171
|
ruletype = "autoattached";
|
|
@@ -199,7 +199,7 @@ function resolveTargets(targets, config) {
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
// src/utils/file.ts
|
|
202
|
-
import { mkdir, readdir, readFile, stat, writeFile } from "fs/promises";
|
|
202
|
+
import { mkdir, readdir, readFile, rm, stat, writeFile } from "fs/promises";
|
|
203
203
|
import { dirname, join as join5 } from "path";
|
|
204
204
|
async function ensureDir(dirPath) {
|
|
205
205
|
try {
|
|
@@ -231,6 +231,15 @@ async function fileExists(filepath) {
|
|
|
231
231
|
return false;
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
|
+
async function removeDirectory(dirPath) {
|
|
235
|
+
try {
|
|
236
|
+
if (await fileExists(dirPath)) {
|
|
237
|
+
await rm(dirPath, { recursive: true, force: true });
|
|
238
|
+
}
|
|
239
|
+
} catch (error) {
|
|
240
|
+
console.warn(`Failed to remove directory ${dirPath}:`, error);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
234
243
|
|
|
235
244
|
// src/core/generator.ts
|
|
236
245
|
async function generateConfigurations(rules, config, targetTools) {
|
|
@@ -305,8 +314,8 @@ function validateFrontmatter(data, filepath) {
|
|
|
305
314
|
throw new Error(`Invalid frontmatter in ${filepath}: must be an object`);
|
|
306
315
|
}
|
|
307
316
|
const obj = data;
|
|
308
|
-
if (
|
|
309
|
-
throw new Error(`Invalid
|
|
317
|
+
if (typeof obj.root !== "boolean") {
|
|
318
|
+
throw new Error(`Invalid root in ${filepath}: must be a boolean`);
|
|
310
319
|
}
|
|
311
320
|
if (!Array.isArray(obj.targets)) {
|
|
312
321
|
throw new Error(`Invalid targets in ${filepath}: must be an array`);
|
|
@@ -343,9 +352,11 @@ async function validateRules(rules) {
|
|
|
343
352
|
}
|
|
344
353
|
filenames.add(rule.filename);
|
|
345
354
|
}
|
|
346
|
-
const
|
|
347
|
-
if (
|
|
348
|
-
errors.push(
|
|
355
|
+
const rootRules = rules.filter((rule) => rule.frontmatter.root === true);
|
|
356
|
+
if (rootRules.length > 1) {
|
|
357
|
+
errors.push(
|
|
358
|
+
`Multiple root rules found: ${rootRules.map((r) => r.filename).join(", ")}. Only one root rule is allowed.`
|
|
359
|
+
);
|
|
349
360
|
}
|
|
350
361
|
for (const rule of rules) {
|
|
351
362
|
const ruleValidation = await validateRule(rule);
|
|
@@ -400,6 +411,33 @@ async function generateCommand(options = {}) {
|
|
|
400
411
|
if (options.verbose) {
|
|
401
412
|
console.log(`Found ${rules.length} rule(s)`);
|
|
402
413
|
}
|
|
414
|
+
if (options.delete) {
|
|
415
|
+
if (options.verbose) {
|
|
416
|
+
console.log("Deleting existing output directories...");
|
|
417
|
+
}
|
|
418
|
+
const targetTools = options.tools || config.defaultTargets;
|
|
419
|
+
const deleteTasks = [];
|
|
420
|
+
for (const tool of targetTools) {
|
|
421
|
+
switch (tool) {
|
|
422
|
+
case "copilot":
|
|
423
|
+
deleteTasks.push(removeDirectory(config.outputPaths.copilot));
|
|
424
|
+
break;
|
|
425
|
+
case "cursor":
|
|
426
|
+
deleteTasks.push(removeDirectory(config.outputPaths.cursor));
|
|
427
|
+
break;
|
|
428
|
+
case "cline":
|
|
429
|
+
deleteTasks.push(removeDirectory(config.outputPaths.cline));
|
|
430
|
+
break;
|
|
431
|
+
case "claude":
|
|
432
|
+
deleteTasks.push(removeDirectory(config.outputPaths.claude));
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
await Promise.all(deleteTasks);
|
|
437
|
+
if (options.verbose) {
|
|
438
|
+
console.log("Deleted existing output directories");
|
|
439
|
+
}
|
|
440
|
+
}
|
|
403
441
|
const outputs = await generateConfigurations(rules, config, options.tools);
|
|
404
442
|
if (outputs.length === 0) {
|
|
405
443
|
console.warn("\u26A0\uFE0F No configurations generated");
|
|
@@ -692,14 +730,15 @@ var program = new Command();
|
|
|
692
730
|
program.name("rulesync").description("Unified AI rules management CLI tool").version("0.1.0");
|
|
693
731
|
program.command("init").description("Initialize rulesync in current directory").action(initCommand);
|
|
694
732
|
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) => {
|
|
733
|
+
program.command("generate").description("Generate configuration files for AI tools").option("--copilot", "Generate only for GitHub Copilot").option("--cursor", "Generate only for Cursor").option("--cline", "Generate only for Cline").option("--claude", "Generate only for Claude Code").option("--delete", "Delete all existing files in output directories before generating").option("-v, --verbose", "Verbose output").action(async (options) => {
|
|
696
734
|
const tools = [];
|
|
697
735
|
if (options.copilot) tools.push("copilot");
|
|
698
736
|
if (options.cursor) tools.push("cursor");
|
|
699
737
|
if (options.cline) tools.push("cline");
|
|
700
738
|
if (options.claude) tools.push("claude");
|
|
701
739
|
const generateOptions = {
|
|
702
|
-
verbose: options.verbose
|
|
740
|
+
verbose: options.verbose,
|
|
741
|
+
delete: options.delete
|
|
703
742
|
};
|
|
704
743
|
if (tools.length > 0) {
|
|
705
744
|
generateOptions.tools = tools;
|
package/package.json
CHANGED