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 +21 -0
- package/README.md +55 -43
- package/dist/index.js +193 -162
- package/dist/index.mjs +190 -159
- package/package.json +1 -1
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
|
[](https://github.com/dyoshikawa/rulesync/actions/workflows/ci.yml)
|
|
4
4
|
[](https://www.npmjs.com/package/rulesync)
|
|
5
5
|
|
|
6
|
-
|
|
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
|
-
|
|
37
|
+
Define metadata in front matter for each Markdown file:
|
|
37
38
|
|
|
38
39
|
```markdown
|
|
39
40
|
---
|
|
40
|
-
|
|
41
|
-
targets: ["*"] #
|
|
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
|
-
###
|
|
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
|
-
.
|
|
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` |
|
|
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
|
-
|
|
135
|
+
Issues and Pull Requests are welcome!
|
|
124
136
|
|
|
125
|
-
|
|
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/
|
|
29
|
+
// src/generators/claude.ts
|
|
30
30
|
var import_node_path = require("path");
|
|
31
|
-
async function
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
51
|
+
function generateClaudeMarkdown(overviewRules, detailRules) {
|
|
47
52
|
const lines = [];
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
71
|
-
|
|
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
|
|
72
|
+
function formatRuleForClaude(rule) {
|
|
77
73
|
const lines = [];
|
|
78
74
|
lines.push(`### ${rule.filename}`);
|
|
79
75
|
lines.push("");
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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(
|
|
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(
|
|
94
|
+
lines.push(`# ${rule.filename}`);
|
|
120
95
|
lines.push("");
|
|
121
|
-
|
|
122
|
-
|
|
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 (
|
|
135
|
-
lines.push(
|
|
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
|
-
|
|
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(
|
|
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.
|
|
155
|
-
return lines;
|
|
132
|
+
return lines.join("\n");
|
|
156
133
|
}
|
|
157
134
|
|
|
158
|
-
// src/generators/
|
|
135
|
+
// src/generators/copilot.ts
|
|
159
136
|
var import_node_path3 = require("path");
|
|
160
|
-
async function
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
|
151
|
+
function generateCopilotMarkdown(rule) {
|
|
176
152
|
const lines = [];
|
|
177
153
|
lines.push("---");
|
|
178
|
-
lines.push(
|
|
179
|
-
|
|
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(
|
|
183
|
-
lines.
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
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
|
|
179
|
+
return outputs;
|
|
190
180
|
}
|
|
191
|
-
function
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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(
|
|
214
|
-
lines.push(rule.content);
|
|
215
|
-
lines.push("");
|
|
196
|
+
lines.push(`ruletype: ${ruletype}`);
|
|
216
197
|
lines.push("---");
|
|
217
198
|
lines.push("");
|
|
218
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
286
|
-
if (
|
|
287
|
-
outputs.push(
|
|
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
|
|
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,
|
|
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.
|
|
347
|
-
throw new Error(`Invalid
|
|
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
|
|
484
|
+
var import_node_path8 = require("path");
|
|
456
485
|
async function initCommand() {
|
|
457
486
|
const aiRulesDir = ".rulesync";
|
|
458
|
-
console.log("Initializing
|
|
487
|
+
console.log("Initializing rulesync...");
|
|
459
488
|
await ensureDir(aiRulesDir);
|
|
460
489
|
await createSampleFiles(aiRulesDir);
|
|
461
|
-
console.log("\u2705
|
|
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 '
|
|
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,
|
|
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
|
|
589
|
+
const rulesyncExists = await fileExists(config.aiRulesDir);
|
|
561
590
|
console.log(`
|
|
562
|
-
\u{1F4C1} .rulesync directory: ${
|
|
563
|
-
if (!
|
|
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
|
|
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 .
|
|
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("
|
|
687
|
-
program.command("init").description("Initialize
|
|
688
|
-
program.command("
|
|
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
|
|
702
|
-
program.command("status").description("Show current status of
|
|
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/
|
|
6
|
+
// src/generators/claude.ts
|
|
7
7
|
import { join } from "path";
|
|
8
|
-
async function
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
28
|
+
function generateClaudeMarkdown(overviewRules, detailRules) {
|
|
24
29
|
const lines = [];
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
49
|
+
function formatRuleForClaude(rule) {
|
|
54
50
|
const lines = [];
|
|
55
51
|
lines.push(`### ${rule.filename}`);
|
|
56
52
|
lines.push("");
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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(
|
|
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(
|
|
71
|
+
lines.push(`# ${rule.filename}`);
|
|
97
72
|
lines.push("");
|
|
98
|
-
|
|
99
|
-
|
|
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 (
|
|
112
|
-
lines.push(
|
|
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
|
-
|
|
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(
|
|
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.
|
|
132
|
-
return lines;
|
|
109
|
+
return lines.join("\n");
|
|
133
110
|
}
|
|
134
111
|
|
|
135
|
-
// src/generators/
|
|
112
|
+
// src/generators/copilot.ts
|
|
136
113
|
import { join as join3 } from "path";
|
|
137
|
-
async function
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
|
128
|
+
function generateCopilotMarkdown(rule) {
|
|
153
129
|
const lines = [];
|
|
154
130
|
lines.push("---");
|
|
155
|
-
lines.push(
|
|
156
|
-
|
|
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(
|
|
160
|
-
lines.
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
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
|
|
156
|
+
return outputs;
|
|
167
157
|
}
|
|
168
|
-
function
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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(
|
|
191
|
-
lines.push(rule.content);
|
|
192
|
-
lines.push("");
|
|
173
|
+
lines.push(`ruletype: ${ruletype}`);
|
|
193
174
|
lines.push("---");
|
|
194
175
|
lines.push("");
|
|
195
|
-
|
|
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
|
|
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) =>
|
|
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
|
|
263
|
-
if (
|
|
264
|
-
outputs.push(
|
|
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.
|
|
324
|
-
throw new Error(`Invalid
|
|
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
|
|
461
|
+
import { join as join7 } from "path";
|
|
433
462
|
async function initCommand() {
|
|
434
463
|
const aiRulesDir = ".rulesync";
|
|
435
|
-
console.log("Initializing
|
|
464
|
+
console.log("Initializing rulesync...");
|
|
436
465
|
await ensureDir(aiRulesDir);
|
|
437
466
|
await createSampleFiles(aiRulesDir);
|
|
438
|
-
console.log("\u2705
|
|
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 '
|
|
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 =
|
|
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
|
|
566
|
+
const rulesyncExists = await fileExists(config.aiRulesDir);
|
|
538
567
|
console.log(`
|
|
539
|
-
\u{1F4C1} .rulesync directory: ${
|
|
540
|
-
if (!
|
|
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
|
|
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 .
|
|
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("
|
|
664
|
-
program.command("init").description("Initialize
|
|
665
|
-
program.command("
|
|
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
|
|
679
|
-
program.command("status").description("Show current status of
|
|
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