rulesync 0.44.0 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +62 -2
- package/README.md +62 -2
- package/dist/{chunk-FAZT3ILF.js → chunk-7UVBAWYG.js} +1 -1
- package/dist/{chunk-I5XVU7C6.js → chunk-7ZIUEZZQ.js} +3 -2
- package/dist/{chunk-BD37M3ZH.js → chunk-BY6RI77W.js} +1 -1
- package/dist/chunk-D365OP7N.js +86 -0
- package/dist/{chunk-DCSO5MY7.js → chunk-JWN6GRG6.js} +1 -1
- package/dist/{chunk-PJUNIIF4.js → chunk-L2JTXZZB.js} +1 -1
- package/dist/{chunk-22GWBUIP.js → chunk-OTCCHS7Q.js} +1 -1
- package/dist/{chunk-ZORSPGDD.js → chunk-P6KQZULZ.js} +1 -1
- package/dist/{claudecode-KSK2BEI7.js → claudecode-Y3GIXDUN.js} +2 -2
- package/dist/{cline-T5YVGYBF.js → cline-NS3OPXM2.js} +2 -2
- package/dist/{copilot-UDCWNUAH.js → copilot-QN2SC7Y2.js} +2 -2
- package/dist/{cursor-KPV6OVST.js → cursor-DV2IS7JF.js} +2 -2
- package/dist/{geminicli-2DC5F34J.js → geminicli-MRYTLT2T.js} +2 -2
- package/dist/index.cjs +381 -266
- package/dist/index.js +301 -236
- package/dist/kiro-S5TSM7VW.js +9 -0
- package/dist/{roo-DRA2SU4L.js → roo-NWLD3YYN.js} +2 -2
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -1,27 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
generateClaudeMcp
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-OTCCHS7Q.js";
|
|
5
5
|
import {
|
|
6
6
|
generateClineMcp
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-BY6RI77W.js";
|
|
8
8
|
import {
|
|
9
9
|
generateCopilotMcp
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-P6KQZULZ.js";
|
|
11
11
|
import {
|
|
12
12
|
generateCursorMcp
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-7UVBAWYG.js";
|
|
14
14
|
import {
|
|
15
15
|
generateGeminiCliMcp
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-JWN6GRG6.js";
|
|
17
|
+
import {
|
|
18
|
+
generateKiroMcp
|
|
19
|
+
} from "./chunk-D365OP7N.js";
|
|
17
20
|
import {
|
|
18
21
|
generateRooMcp
|
|
19
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-L2JTXZZB.js";
|
|
20
23
|
import {
|
|
21
24
|
RulesyncTargetsSchema,
|
|
22
25
|
ToolTargetSchema,
|
|
23
26
|
ToolTargetsSchema
|
|
24
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-7ZIUEZZQ.js";
|
|
25
28
|
|
|
26
29
|
// src/cli/index.ts
|
|
27
30
|
import { Command } from "commander";
|
|
@@ -40,10 +43,11 @@ function getDefaultConfig() {
|
|
|
40
43
|
cline: ".clinerules",
|
|
41
44
|
claudecode: ".",
|
|
42
45
|
roo: ".roo/rules",
|
|
43
|
-
geminicli: ".gemini/memories"
|
|
46
|
+
geminicli: ".gemini/memories",
|
|
47
|
+
kiro: ".kiro/steering"
|
|
44
48
|
},
|
|
45
49
|
watchEnabled: false,
|
|
46
|
-
defaultTargets: ["copilot", "cursor", "cline", "claudecode", "roo", "geminicli"]
|
|
50
|
+
defaultTargets: ["copilot", "cursor", "cline", "claudecode", "roo", "geminicli", "kiro"]
|
|
47
51
|
};
|
|
48
52
|
}
|
|
49
53
|
function resolveTargets(targets, config) {
|
|
@@ -90,11 +94,126 @@ async function addCommand(filename) {
|
|
|
90
94
|
}
|
|
91
95
|
}
|
|
92
96
|
|
|
97
|
+
// src/generators/ignore/kiro.ts
|
|
98
|
+
import { join as join2 } from "path";
|
|
99
|
+
async function generateKiroIgnoreFiles(rules, config, baseDir) {
|
|
100
|
+
const outputs = [];
|
|
101
|
+
const aiignoreContent = generateAiignoreContent(rules);
|
|
102
|
+
const outputPath = baseDir || process.cwd();
|
|
103
|
+
const filepath = join2(outputPath, ".aiignore");
|
|
104
|
+
outputs.push({
|
|
105
|
+
tool: "kiro",
|
|
106
|
+
filepath,
|
|
107
|
+
content: aiignoreContent
|
|
108
|
+
});
|
|
109
|
+
return outputs;
|
|
110
|
+
}
|
|
111
|
+
function generateAiignoreContent(rules) {
|
|
112
|
+
const lines = [
|
|
113
|
+
"# Generated by rulesync - Kiro AI-specific exclusions",
|
|
114
|
+
"# This file excludes files that can be in Git but shouldn't be read by the AI",
|
|
115
|
+
""
|
|
116
|
+
];
|
|
117
|
+
lines.push(
|
|
118
|
+
"# Data files AI shouldn't process",
|
|
119
|
+
"*.csv",
|
|
120
|
+
"*.tsv",
|
|
121
|
+
"*.sqlite",
|
|
122
|
+
"*.db",
|
|
123
|
+
"",
|
|
124
|
+
"# Large binary files",
|
|
125
|
+
"*.zip",
|
|
126
|
+
"*.tar.gz",
|
|
127
|
+
"*.rar",
|
|
128
|
+
"",
|
|
129
|
+
"# Sensitive documentation",
|
|
130
|
+
"internal-docs/",
|
|
131
|
+
"confidential/",
|
|
132
|
+
"",
|
|
133
|
+
"# Test data that might confuse AI",
|
|
134
|
+
"test/fixtures/large-*.json",
|
|
135
|
+
"benchmark-results/",
|
|
136
|
+
"",
|
|
137
|
+
"# Reinforce critical exclusions from .gitignore",
|
|
138
|
+
"*.pem",
|
|
139
|
+
"*.key",
|
|
140
|
+
".env*",
|
|
141
|
+
""
|
|
142
|
+
);
|
|
143
|
+
const rulePatterns = extractIgnorePatternsFromRules(rules);
|
|
144
|
+
if (rulePatterns.length > 0) {
|
|
145
|
+
lines.push("# Project-specific exclusions from rulesync rules");
|
|
146
|
+
lines.push(...rulePatterns);
|
|
147
|
+
lines.push("");
|
|
148
|
+
}
|
|
149
|
+
return lines.join("\n");
|
|
150
|
+
}
|
|
151
|
+
function extractIgnorePatternsFromRules(rules) {
|
|
152
|
+
const patterns = [];
|
|
153
|
+
for (const rule of rules) {
|
|
154
|
+
if (rule.frontmatter.globs && rule.frontmatter.globs.length > 0) {
|
|
155
|
+
for (const glob of rule.frontmatter.globs) {
|
|
156
|
+
if (shouldExcludeFromAI(glob)) {
|
|
157
|
+
patterns.push(`# Exclude: ${rule.frontmatter.description}`);
|
|
158
|
+
patterns.push(glob);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const contentPatterns = extractIgnorePatternsFromContent(rule.content);
|
|
163
|
+
patterns.push(...contentPatterns);
|
|
164
|
+
}
|
|
165
|
+
return patterns;
|
|
166
|
+
}
|
|
167
|
+
function shouldExcludeFromAI(glob) {
|
|
168
|
+
const excludePatterns = [
|
|
169
|
+
// Test and fixture files that might be large or confusing
|
|
170
|
+
"**/test/fixtures/**",
|
|
171
|
+
"**/tests/fixtures/**",
|
|
172
|
+
"**/*.fixture.*",
|
|
173
|
+
// Build and generated files
|
|
174
|
+
"**/dist/**",
|
|
175
|
+
"**/build/**",
|
|
176
|
+
"**/coverage/**",
|
|
177
|
+
// Configuration that might contain sensitive data
|
|
178
|
+
"**/config/production/**",
|
|
179
|
+
"**/config/prod/**",
|
|
180
|
+
"**/*.prod.*",
|
|
181
|
+
// Documentation that might be sensitive
|
|
182
|
+
"**/internal/**",
|
|
183
|
+
"**/private/**",
|
|
184
|
+
"**/confidential/**"
|
|
185
|
+
];
|
|
186
|
+
return excludePatterns.some((pattern) => {
|
|
187
|
+
const regex = new RegExp(pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*"));
|
|
188
|
+
return regex.test(glob);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
function extractIgnorePatternsFromContent(content) {
|
|
192
|
+
const patterns = [];
|
|
193
|
+
const lines = content.split("\n");
|
|
194
|
+
for (const line of lines) {
|
|
195
|
+
const trimmed = line.trim();
|
|
196
|
+
if (trimmed.startsWith("# IGNORE:") || trimmed.startsWith("# aiignore:")) {
|
|
197
|
+
const pattern = trimmed.replace(/^# (IGNORE|aiignore):\s*/, "").trim();
|
|
198
|
+
if (pattern) {
|
|
199
|
+
patterns.push(pattern);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (trimmed.includes("exclude") || trimmed.includes("ignore")) {
|
|
203
|
+
const matches = trimmed.match(/['"`]([^'"`]+\.(log|tmp|cache|temp))['"`]/g);
|
|
204
|
+
if (matches) {
|
|
205
|
+
patterns.push(...matches.map((m) => m.replace(/['"`]/g, "")));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return patterns;
|
|
210
|
+
}
|
|
211
|
+
|
|
93
212
|
// src/generators/rules/claudecode.ts
|
|
94
|
-
import { join as
|
|
213
|
+
import { join as join5 } from "path";
|
|
95
214
|
|
|
96
215
|
// src/types/claudecode.ts
|
|
97
|
-
import { z } from "zod/
|
|
216
|
+
import { z } from "zod/mini";
|
|
98
217
|
var ClaudeSettingsSchema = z.looseObject({
|
|
99
218
|
permissions: z._default(
|
|
100
219
|
z.looseObject({
|
|
@@ -105,12 +224,8 @@ var ClaudeSettingsSchema = z.looseObject({
|
|
|
105
224
|
});
|
|
106
225
|
|
|
107
226
|
// src/utils/file.ts
|
|
108
|
-
import { readdir, rm } from "fs/promises";
|
|
109
|
-
import { join as join3 } from "path";
|
|
110
|
-
|
|
111
|
-
// src/utils/file-ops.ts
|
|
112
|
-
import { mkdir as mkdir2, readFile, stat, writeFile as writeFile2 } from "fs/promises";
|
|
113
|
-
import { dirname } from "path";
|
|
227
|
+
import { mkdir as mkdir2, readdir, readFile, rm, stat, writeFile as writeFile2 } from "fs/promises";
|
|
228
|
+
import { dirname, join as join3 } from "path";
|
|
114
229
|
async function ensureDir(dirPath) {
|
|
115
230
|
try {
|
|
116
231
|
await stat(dirPath);
|
|
@@ -133,16 +248,57 @@ async function fileExists(filepath) {
|
|
|
133
248
|
return false;
|
|
134
249
|
}
|
|
135
250
|
}
|
|
251
|
+
async function findFiles(dir, extension = ".md") {
|
|
252
|
+
try {
|
|
253
|
+
const files = await readdir(dir);
|
|
254
|
+
return files.filter((file) => file.endsWith(extension)).map((file) => join3(dir, file));
|
|
255
|
+
} catch {
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async function removeDirectory(dirPath) {
|
|
260
|
+
const dangerousPaths = [".", "/", "~", "src", "node_modules"];
|
|
261
|
+
if (dangerousPaths.includes(dirPath) || dirPath === "") {
|
|
262
|
+
console.warn(`Skipping deletion of dangerous path: ${dirPath}`);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
try {
|
|
266
|
+
if (await fileExists(dirPath)) {
|
|
267
|
+
await rm(dirPath, { recursive: true, force: true });
|
|
268
|
+
}
|
|
269
|
+
} catch (error) {
|
|
270
|
+
console.warn(`Failed to remove directory ${dirPath}:`, error);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
async function removeFile(filepath) {
|
|
274
|
+
try {
|
|
275
|
+
if (await fileExists(filepath)) {
|
|
276
|
+
await rm(filepath);
|
|
277
|
+
}
|
|
278
|
+
} catch (error) {
|
|
279
|
+
console.warn(`Failed to remove file ${filepath}:`, error);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async function removeClaudeGeneratedFiles() {
|
|
283
|
+
const filesToRemove = ["CLAUDE.md", ".claude/memories"];
|
|
284
|
+
for (const fileOrDir of filesToRemove) {
|
|
285
|
+
if (fileOrDir.endsWith("/memories")) {
|
|
286
|
+
await removeDirectory(fileOrDir);
|
|
287
|
+
} else {
|
|
288
|
+
await removeFile(fileOrDir);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
136
292
|
|
|
137
293
|
// src/utils/ignore.ts
|
|
138
|
-
import { join as
|
|
294
|
+
import { join as join4 } from "path";
|
|
139
295
|
import micromatch from "micromatch";
|
|
140
296
|
var cachedIgnorePatterns = null;
|
|
141
297
|
async function loadIgnorePatterns(baseDir = process.cwd()) {
|
|
142
298
|
if (cachedIgnorePatterns) {
|
|
143
299
|
return cachedIgnorePatterns;
|
|
144
300
|
}
|
|
145
|
-
const ignorePath =
|
|
301
|
+
const ignorePath = join4(baseDir, ".rulesyncignore");
|
|
146
302
|
if (!await fileExists(ignorePath)) {
|
|
147
303
|
cachedIgnorePatterns = { patterns: [] };
|
|
148
304
|
return cachedIgnorePatterns;
|
|
@@ -185,76 +341,29 @@ function filterIgnoredFiles(files, ignorePatterns) {
|
|
|
185
341
|
return files.filter((file) => !isFileIgnored(file, ignorePatterns));
|
|
186
342
|
}
|
|
187
343
|
|
|
188
|
-
// src/utils/file.ts
|
|
189
|
-
async function findFiles(dir, extension = ".md", ignorePatterns) {
|
|
190
|
-
try {
|
|
191
|
-
const files = await readdir(dir);
|
|
192
|
-
const filtered = files.filter((file) => file.endsWith(extension)).map((file) => join3(dir, file));
|
|
193
|
-
if (ignorePatterns && ignorePatterns.length > 0) {
|
|
194
|
-
return filterIgnoredFiles(filtered, ignorePatterns);
|
|
195
|
-
}
|
|
196
|
-
return filtered;
|
|
197
|
-
} catch {
|
|
198
|
-
return [];
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
async function removeDirectory(dirPath) {
|
|
202
|
-
const dangerousPaths = [".", "/", "~", "src", "node_modules"];
|
|
203
|
-
if (dangerousPaths.includes(dirPath) || dirPath === "") {
|
|
204
|
-
console.warn(`Skipping deletion of dangerous path: ${dirPath}`);
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
try {
|
|
208
|
-
if (await fileExists(dirPath)) {
|
|
209
|
-
await rm(dirPath, { recursive: true, force: true });
|
|
210
|
-
}
|
|
211
|
-
} catch (error) {
|
|
212
|
-
console.warn(`Failed to remove directory ${dirPath}:`, error);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
async function removeFile(filepath) {
|
|
216
|
-
try {
|
|
217
|
-
if (await fileExists(filepath)) {
|
|
218
|
-
await rm(filepath);
|
|
219
|
-
}
|
|
220
|
-
} catch (error) {
|
|
221
|
-
console.warn(`Failed to remove file ${filepath}:`, error);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
async function removeClaudeGeneratedFiles() {
|
|
225
|
-
const filesToRemove = ["CLAUDE.md", ".claude/memories"];
|
|
226
|
-
for (const fileOrDir of filesToRemove) {
|
|
227
|
-
if (fileOrDir.endsWith("/memories")) {
|
|
228
|
-
await removeDirectory(fileOrDir);
|
|
229
|
-
} else {
|
|
230
|
-
await removeFile(fileOrDir);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
344
|
// src/generators/rules/claudecode.ts
|
|
236
345
|
async function generateClaudecodeConfig(rules, config, baseDir) {
|
|
237
346
|
const outputs = [];
|
|
238
347
|
const rootRules = rules.filter((r) => r.frontmatter.root === true);
|
|
239
348
|
const detailRules = rules.filter((r) => r.frontmatter.root === false);
|
|
240
349
|
const claudeMdContent = generateClaudeMarkdown(rootRules, detailRules);
|
|
241
|
-
const claudeOutputDir = baseDir ?
|
|
350
|
+
const claudeOutputDir = baseDir ? join5(baseDir, config.outputPaths.claudecode) : config.outputPaths.claudecode;
|
|
242
351
|
outputs.push({
|
|
243
352
|
tool: "claudecode",
|
|
244
|
-
filepath:
|
|
353
|
+
filepath: join5(claudeOutputDir, "CLAUDE.md"),
|
|
245
354
|
content: claudeMdContent
|
|
246
355
|
});
|
|
247
356
|
for (const rule of detailRules) {
|
|
248
357
|
const memoryContent = generateMemoryFile(rule);
|
|
249
358
|
outputs.push({
|
|
250
359
|
tool: "claudecode",
|
|
251
|
-
filepath:
|
|
360
|
+
filepath: join5(claudeOutputDir, ".claude", "memories", `${rule.filename}.md`),
|
|
252
361
|
content: memoryContent
|
|
253
362
|
});
|
|
254
363
|
}
|
|
255
364
|
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
256
365
|
if (ignorePatterns.patterns.length > 0) {
|
|
257
|
-
const settingsPath = baseDir ?
|
|
366
|
+
const settingsPath = baseDir ? join5(baseDir, ".claude", "settings.json") : join5(".claude", "settings.json");
|
|
258
367
|
await updateClaudeSettings(settingsPath, ignorePatterns.patterns);
|
|
259
368
|
}
|
|
260
369
|
return outputs;
|
|
@@ -265,9 +374,10 @@ function generateClaudeMarkdown(rootRules, detailRules) {
|
|
|
265
374
|
lines.push("Please also reference the following documents as needed:");
|
|
266
375
|
lines.push("");
|
|
267
376
|
for (const rule of detailRules) {
|
|
268
|
-
const
|
|
377
|
+
const escapedDescription = rule.frontmatter.description.replace(/"/g, '\\"');
|
|
378
|
+
const globsText = rule.frontmatter.globs.join(",");
|
|
269
379
|
lines.push(
|
|
270
|
-
`@.claude/memories/${rule.filename}.md ${
|
|
380
|
+
`@.claude/memories/${rule.filename}.md description: "${escapedDescription}" globs: "${globsText}"`
|
|
271
381
|
);
|
|
272
382
|
}
|
|
273
383
|
lines.push("");
|
|
@@ -317,13 +427,13 @@ async function updateClaudeSettings(settingsPath, ignorePatterns) {
|
|
|
317
427
|
}
|
|
318
428
|
|
|
319
429
|
// src/generators/rules/cline.ts
|
|
320
|
-
import { join as
|
|
430
|
+
import { join as join6 } from "path";
|
|
321
431
|
async function generateClineConfig(rules, config, baseDir) {
|
|
322
432
|
const outputs = [];
|
|
323
433
|
for (const rule of rules) {
|
|
324
434
|
const content = generateClineMarkdown(rule);
|
|
325
|
-
const outputDir = baseDir ?
|
|
326
|
-
const filepath =
|
|
435
|
+
const outputDir = baseDir ? join6(baseDir, config.outputPaths.cline) : config.outputPaths.cline;
|
|
436
|
+
const filepath = join6(outputDir, `${rule.filename}.md`);
|
|
327
437
|
outputs.push({
|
|
328
438
|
tool: "cline",
|
|
329
439
|
filepath,
|
|
@@ -332,7 +442,7 @@ async function generateClineConfig(rules, config, baseDir) {
|
|
|
332
442
|
}
|
|
333
443
|
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
334
444
|
if (ignorePatterns.patterns.length > 0) {
|
|
335
|
-
const clineIgnorePath = baseDir ?
|
|
445
|
+
const clineIgnorePath = baseDir ? join6(baseDir, ".clineignore") : ".clineignore";
|
|
336
446
|
const clineIgnoreContent = generateClineIgnore(ignorePatterns.patterns);
|
|
337
447
|
outputs.push({
|
|
338
448
|
tool: "cline",
|
|
@@ -356,14 +466,14 @@ function generateClineIgnore(patterns) {
|
|
|
356
466
|
}
|
|
357
467
|
|
|
358
468
|
// src/generators/rules/copilot.ts
|
|
359
|
-
import { join as
|
|
469
|
+
import { join as join7 } from "path";
|
|
360
470
|
async function generateCopilotConfig(rules, config, baseDir) {
|
|
361
471
|
const outputs = [];
|
|
362
472
|
for (const rule of rules) {
|
|
363
473
|
const content = generateCopilotMarkdown(rule);
|
|
364
474
|
const baseFilename = rule.filename.replace(/\.md$/, "");
|
|
365
|
-
const outputDir = baseDir ?
|
|
366
|
-
const filepath =
|
|
475
|
+
const outputDir = baseDir ? join7(baseDir, config.outputPaths.copilot) : config.outputPaths.copilot;
|
|
476
|
+
const filepath = join7(outputDir, `${baseFilename}.instructions.md`);
|
|
367
477
|
outputs.push({
|
|
368
478
|
tool: "copilot",
|
|
369
479
|
filepath,
|
|
@@ -372,7 +482,7 @@ async function generateCopilotConfig(rules, config, baseDir) {
|
|
|
372
482
|
}
|
|
373
483
|
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
374
484
|
if (ignorePatterns.patterns.length > 0) {
|
|
375
|
-
const copilotIgnorePath = baseDir ?
|
|
485
|
+
const copilotIgnorePath = baseDir ? join7(baseDir, ".copilotignore") : ".copilotignore";
|
|
376
486
|
const copilotIgnoreContent = generateCopilotIgnore(ignorePatterns.patterns);
|
|
377
487
|
outputs.push({
|
|
378
488
|
tool: "copilot",
|
|
@@ -408,13 +518,13 @@ function generateCopilotIgnore(patterns) {
|
|
|
408
518
|
}
|
|
409
519
|
|
|
410
520
|
// src/generators/rules/cursor.ts
|
|
411
|
-
import { join as
|
|
521
|
+
import { join as join8 } from "path";
|
|
412
522
|
async function generateCursorConfig(rules, config, baseDir) {
|
|
413
523
|
const outputs = [];
|
|
414
524
|
for (const rule of rules) {
|
|
415
525
|
const content = generateCursorMarkdown(rule);
|
|
416
|
-
const outputDir = baseDir ?
|
|
417
|
-
const filepath =
|
|
526
|
+
const outputDir = baseDir ? join8(baseDir, config.outputPaths.cursor) : config.outputPaths.cursor;
|
|
527
|
+
const filepath = join8(outputDir, `${rule.filename}.mdc`);
|
|
418
528
|
outputs.push({
|
|
419
529
|
tool: "cursor",
|
|
420
530
|
filepath,
|
|
@@ -423,7 +533,7 @@ async function generateCursorConfig(rules, config, baseDir) {
|
|
|
423
533
|
}
|
|
424
534
|
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
425
535
|
if (ignorePatterns.patterns.length > 0) {
|
|
426
|
-
const cursorIgnorePath = baseDir ?
|
|
536
|
+
const cursorIgnorePath = baseDir ? join8(baseDir, ".cursorignore") : ".cursorignore";
|
|
427
537
|
const cursorIgnoreContent = generateCursorIgnore(ignorePatterns.patterns);
|
|
428
538
|
outputs.push({
|
|
429
539
|
tool: "cursor",
|
|
@@ -496,15 +606,15 @@ function generateCursorIgnore(patterns) {
|
|
|
496
606
|
}
|
|
497
607
|
|
|
498
608
|
// src/generators/rules/geminicli.ts
|
|
499
|
-
import { join as
|
|
609
|
+
import { join as join9 } from "path";
|
|
500
610
|
async function generateGeminiConfig(rules, config, baseDir) {
|
|
501
611
|
const outputs = [];
|
|
502
612
|
const rootRule = rules.find((rule) => rule.frontmatter.root === true);
|
|
503
613
|
const memoryRules = rules.filter((rule) => rule.frontmatter.root === false);
|
|
504
614
|
for (const rule of memoryRules) {
|
|
505
615
|
const content = generateGeminiMemoryMarkdown(rule);
|
|
506
|
-
const outputDir = baseDir ?
|
|
507
|
-
const filepath =
|
|
616
|
+
const outputDir = baseDir ? join9(baseDir, config.outputPaths.geminicli) : config.outputPaths.geminicli;
|
|
617
|
+
const filepath = join9(outputDir, `${rule.filename}.md`);
|
|
508
618
|
outputs.push({
|
|
509
619
|
tool: "geminicli",
|
|
510
620
|
filepath,
|
|
@@ -512,7 +622,7 @@ async function generateGeminiConfig(rules, config, baseDir) {
|
|
|
512
622
|
});
|
|
513
623
|
}
|
|
514
624
|
const rootContent = generateGeminiRootMarkdown(rootRule, memoryRules, baseDir);
|
|
515
|
-
const rootFilepath = baseDir ?
|
|
625
|
+
const rootFilepath = baseDir ? join9(baseDir, "GEMINI.md") : "GEMINI.md";
|
|
516
626
|
outputs.push({
|
|
517
627
|
tool: "geminicli",
|
|
518
628
|
filepath: rootFilepath,
|
|
@@ -520,7 +630,7 @@ async function generateGeminiConfig(rules, config, baseDir) {
|
|
|
520
630
|
});
|
|
521
631
|
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
522
632
|
if (ignorePatterns.patterns.length > 0) {
|
|
523
|
-
const aiexcludePath = baseDir ?
|
|
633
|
+
const aiexcludePath = baseDir ? join9(baseDir, ".aiexclude") : ".aiexclude";
|
|
524
634
|
const aiexcludeContent = generateAiexclude(ignorePatterns.patterns);
|
|
525
635
|
outputs.push({
|
|
526
636
|
tool: "geminicli",
|
|
@@ -567,14 +677,34 @@ function generateAiexclude(patterns) {
|
|
|
567
677
|
return lines.join("\n");
|
|
568
678
|
}
|
|
569
679
|
|
|
680
|
+
// src/generators/rules/kiro.ts
|
|
681
|
+
import { join as join10 } from "path";
|
|
682
|
+
async function generateKiroConfig(rules, config, baseDir) {
|
|
683
|
+
const outputs = [];
|
|
684
|
+
for (const rule of rules) {
|
|
685
|
+
const content = generateKiroMarkdown(rule);
|
|
686
|
+
const outputDir = baseDir ? join10(baseDir, config.outputPaths.kiro) : config.outputPaths.kiro;
|
|
687
|
+
const filepath = join10(outputDir, `${rule.filename}.md`);
|
|
688
|
+
outputs.push({
|
|
689
|
+
tool: "kiro",
|
|
690
|
+
filepath,
|
|
691
|
+
content
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
return outputs;
|
|
695
|
+
}
|
|
696
|
+
function generateKiroMarkdown(rule) {
|
|
697
|
+
return rule.content.trim();
|
|
698
|
+
}
|
|
699
|
+
|
|
570
700
|
// src/generators/rules/roo.ts
|
|
571
|
-
import { join as
|
|
701
|
+
import { join as join11 } from "path";
|
|
572
702
|
async function generateRooConfig(rules, config, baseDir) {
|
|
573
703
|
const outputs = [];
|
|
574
704
|
for (const rule of rules) {
|
|
575
705
|
const content = generateRooMarkdown(rule);
|
|
576
|
-
const outputDir = baseDir ?
|
|
577
|
-
const filepath =
|
|
706
|
+
const outputDir = baseDir ? join11(baseDir, config.outputPaths.roo) : config.outputPaths.roo;
|
|
707
|
+
const filepath = join11(outputDir, `${rule.filename}.md`);
|
|
578
708
|
outputs.push({
|
|
579
709
|
tool: "roo",
|
|
580
710
|
filepath,
|
|
@@ -583,7 +713,7 @@ async function generateRooConfig(rules, config, baseDir) {
|
|
|
583
713
|
}
|
|
584
714
|
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
585
715
|
if (ignorePatterns.patterns.length > 0) {
|
|
586
|
-
const rooIgnorePath = baseDir ?
|
|
716
|
+
const rooIgnorePath = baseDir ? join11(baseDir, ".rooignore") : ".rooignore";
|
|
587
717
|
const rooIgnoreContent = generateRooIgnore(ignorePatterns.patterns);
|
|
588
718
|
outputs.push({
|
|
589
719
|
tool: "roo",
|
|
@@ -649,6 +779,11 @@ async function generateForTool(tool, rules, config, baseDir) {
|
|
|
649
779
|
return generateRooConfig(rules, config, baseDir);
|
|
650
780
|
case "geminicli":
|
|
651
781
|
return generateGeminiConfig(rules, config, baseDir);
|
|
782
|
+
case "kiro": {
|
|
783
|
+
const kiroRulesOutputs = await generateKiroConfig(rules, config, baseDir);
|
|
784
|
+
const kiroIgnoreOutputs = await generateKiroIgnoreFiles(rules, config, baseDir);
|
|
785
|
+
return [...kiroRulesOutputs, ...kiroIgnoreOutputs];
|
|
786
|
+
}
|
|
652
787
|
default:
|
|
653
788
|
console.warn(`Unknown tool: ${tool}`);
|
|
654
789
|
return null;
|
|
@@ -660,7 +795,7 @@ import { basename } from "path";
|
|
|
660
795
|
import matter from "gray-matter";
|
|
661
796
|
|
|
662
797
|
// src/types/config.ts
|
|
663
|
-
import { z as z2 } from "zod/
|
|
798
|
+
import { z as z2 } from "zod/mini";
|
|
664
799
|
var ConfigSchema = z2.object({
|
|
665
800
|
aiRulesDir: z2.string(),
|
|
666
801
|
outputPaths: z2.record(ToolTargetSchema, z2.string()),
|
|
@@ -669,7 +804,7 @@ var ConfigSchema = z2.object({
|
|
|
669
804
|
});
|
|
670
805
|
|
|
671
806
|
// src/types/mcp.ts
|
|
672
|
-
import { z as z3 } from "zod/
|
|
807
|
+
import { z as z3 } from "zod/mini";
|
|
673
808
|
var McpTransportTypeSchema = z3.enum(["stdio", "sse", "http"]);
|
|
674
809
|
var McpServerBaseSchema = z3.object({
|
|
675
810
|
command: z3.optional(z3.string()),
|
|
@@ -685,7 +820,9 @@ var McpServerBaseSchema = z3.object({
|
|
|
685
820
|
transport: z3.optional(McpTransportTypeSchema),
|
|
686
821
|
type: z3.optional(z3.enum(["sse", "streamable-http"])),
|
|
687
822
|
alwaysAllow: z3.optional(z3.array(z3.string())),
|
|
688
|
-
tools: z3.optional(z3.array(z3.string()))
|
|
823
|
+
tools: z3.optional(z3.array(z3.string())),
|
|
824
|
+
kiroAutoApprove: z3.optional(z3.array(z3.string())),
|
|
825
|
+
kiroAutoBlock: z3.optional(z3.array(z3.string()))
|
|
689
826
|
});
|
|
690
827
|
var RulesyncMcpServerSchema = z3.extend(McpServerBaseSchema, {
|
|
691
828
|
targets: z3.optional(RulesyncTargetsSchema)
|
|
@@ -698,7 +835,7 @@ var RulesyncMcpConfigSchema = z3.object({
|
|
|
698
835
|
});
|
|
699
836
|
|
|
700
837
|
// src/types/rules.ts
|
|
701
|
-
import { z as z4 } from "zod/
|
|
838
|
+
import { z as z4 } from "zod/mini";
|
|
702
839
|
var RuleFrontmatterSchema = z4.object({
|
|
703
840
|
root: z4.boolean(),
|
|
704
841
|
targets: RulesyncTargetsSchema,
|
|
@@ -726,7 +863,8 @@ var GenerateOptionsSchema = z4.object({
|
|
|
726
863
|
// src/core/parser.ts
|
|
727
864
|
async function parseRulesFromDirectory(aiRulesDir) {
|
|
728
865
|
const ignorePatterns = await loadIgnorePatterns();
|
|
729
|
-
const
|
|
866
|
+
const allRuleFiles = await findFiles(aiRulesDir, ".md");
|
|
867
|
+
const ruleFiles = filterIgnoredFiles(allRuleFiles, ignorePatterns.patterns);
|
|
730
868
|
const rules = [];
|
|
731
869
|
const errors = [];
|
|
732
870
|
if (ignorePatterns.patterns.length > 0) {
|
|
@@ -887,6 +1025,11 @@ async function generateMcpConfigs(projectRoot, baseDir) {
|
|
|
887
1025
|
path: path3.join(targetRoot, ".gemini", "settings.json"),
|
|
888
1026
|
generate: () => generateGeminiCliMcp(config)
|
|
889
1027
|
},
|
|
1028
|
+
{
|
|
1029
|
+
tool: "kiro-project",
|
|
1030
|
+
path: path3.join(targetRoot, ".kiro", "mcp.json"),
|
|
1031
|
+
generate: () => generateKiroMcp(config)
|
|
1032
|
+
},
|
|
890
1033
|
{
|
|
891
1034
|
tool: "roo-project",
|
|
892
1035
|
path: path3.join(targetRoot, ".roo", "mcp.json"),
|
|
@@ -897,7 +1040,7 @@ async function generateMcpConfigs(projectRoot, baseDir) {
|
|
|
897
1040
|
try {
|
|
898
1041
|
const content = generator.generate();
|
|
899
1042
|
const parsed = JSON.parse(content);
|
|
900
|
-
if (generator.tool.includes("claude") || generator.tool.includes("cline") || generator.tool.includes("cursor") || generator.tool.includes("gemini") || generator.tool.includes("roo")) {
|
|
1043
|
+
if (generator.tool.includes("claude") || generator.tool.includes("cline") || generator.tool.includes("cursor") || generator.tool.includes("gemini") || generator.tool.includes("kiro") || generator.tool.includes("roo")) {
|
|
901
1044
|
if (!parsed.mcpServers || Object.keys(parsed.mcpServers).length === 0) {
|
|
902
1045
|
results.push({
|
|
903
1046
|
tool: generator.tool,
|
|
@@ -983,6 +1126,9 @@ async function generateCommand(options = {}) {
|
|
|
983
1126
|
case "geminicli":
|
|
984
1127
|
deleteTasks.push(removeDirectory(config.outputPaths.geminicli));
|
|
985
1128
|
break;
|
|
1129
|
+
case "kiro":
|
|
1130
|
+
deleteTasks.push(removeDirectory(config.outputPaths.kiro));
|
|
1131
|
+
break;
|
|
986
1132
|
}
|
|
987
1133
|
}
|
|
988
1134
|
await Promise.all(deleteTasks);
|
|
@@ -1047,9 +1193,9 @@ Generating configurations for base directory: ${baseDir}`);
|
|
|
1047
1193
|
|
|
1048
1194
|
// src/cli/commands/gitignore.ts
|
|
1049
1195
|
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
1050
|
-
import { join as
|
|
1196
|
+
import { join as join14 } from "path";
|
|
1051
1197
|
var gitignoreCommand = async () => {
|
|
1052
|
-
const gitignorePath =
|
|
1198
|
+
const gitignorePath = join14(process.cwd(), ".gitignore");
|
|
1053
1199
|
const rulesFilesToIgnore = [
|
|
1054
1200
|
"# Generated by rulesync - AI tool configuration files",
|
|
1055
1201
|
"**/.github/copilot-instructions.md",
|
|
@@ -1066,6 +1212,8 @@ var gitignoreCommand = async () => {
|
|
|
1066
1212
|
"**/GEMINI.md",
|
|
1067
1213
|
"**/.gemini/memories/",
|
|
1068
1214
|
"**/.aiexclude",
|
|
1215
|
+
"**/.aiignore",
|
|
1216
|
+
"**/.kiro/steering/",
|
|
1069
1217
|
"**/.mcp.json",
|
|
1070
1218
|
"!.rulesync/.mcp.json",
|
|
1071
1219
|
"**/.cursor/mcp.json",
|
|
@@ -1103,17 +1251,17 @@ ${linesToAdd.join("\n")}
|
|
|
1103
1251
|
};
|
|
1104
1252
|
|
|
1105
1253
|
// src/core/importer.ts
|
|
1106
|
-
import { join as
|
|
1254
|
+
import { join as join21 } from "path";
|
|
1107
1255
|
import matter4 from "gray-matter";
|
|
1108
1256
|
|
|
1109
1257
|
// src/parsers/claudecode.ts
|
|
1110
|
-
import { basename as basename2, join as
|
|
1258
|
+
import { basename as basename2, join as join15 } from "path";
|
|
1111
1259
|
async function parseClaudeConfiguration(baseDir = process.cwd()) {
|
|
1112
1260
|
const errors = [];
|
|
1113
1261
|
const rules = [];
|
|
1114
1262
|
let ignorePatterns;
|
|
1115
1263
|
let mcpServers;
|
|
1116
|
-
const claudeFilePath =
|
|
1264
|
+
const claudeFilePath = join15(baseDir, "CLAUDE.md");
|
|
1117
1265
|
if (!await fileExists(claudeFilePath)) {
|
|
1118
1266
|
errors.push("CLAUDE.md file not found");
|
|
1119
1267
|
return { rules, errors };
|
|
@@ -1124,12 +1272,12 @@ async function parseClaudeConfiguration(baseDir = process.cwd()) {
|
|
|
1124
1272
|
if (mainRule) {
|
|
1125
1273
|
rules.push(mainRule);
|
|
1126
1274
|
}
|
|
1127
|
-
const memoryDir =
|
|
1275
|
+
const memoryDir = join15(baseDir, ".claude", "memories");
|
|
1128
1276
|
if (await fileExists(memoryDir)) {
|
|
1129
1277
|
const memoryRules = await parseClaudeMemoryFiles(memoryDir);
|
|
1130
1278
|
rules.push(...memoryRules);
|
|
1131
1279
|
}
|
|
1132
|
-
const settingsPath =
|
|
1280
|
+
const settingsPath = join15(baseDir, ".claude", "settings.json");
|
|
1133
1281
|
if (await fileExists(settingsPath)) {
|
|
1134
1282
|
const settingsResult = await parseClaudeSettings(settingsPath);
|
|
1135
1283
|
if (settingsResult.ignorePatterns) {
|
|
@@ -1186,7 +1334,7 @@ async function parseClaudeMemoryFiles(memoryDir) {
|
|
|
1186
1334
|
const files = await readdir2(memoryDir);
|
|
1187
1335
|
for (const file of files) {
|
|
1188
1336
|
if (file.endsWith(".md")) {
|
|
1189
|
-
const filePath =
|
|
1337
|
+
const filePath = join15(memoryDir, file);
|
|
1190
1338
|
const content = await readFileContent(filePath);
|
|
1191
1339
|
if (content.trim()) {
|
|
1192
1340
|
const filename = basename2(file, ".md");
|
|
@@ -1249,11 +1397,11 @@ async function parseClaudeSettings(settingsPath) {
|
|
|
1249
1397
|
}
|
|
1250
1398
|
|
|
1251
1399
|
// src/parsers/cline.ts
|
|
1252
|
-
import { join as
|
|
1400
|
+
import { join as join16 } from "path";
|
|
1253
1401
|
async function parseClineConfiguration(baseDir = process.cwd()) {
|
|
1254
1402
|
const errors = [];
|
|
1255
1403
|
const rules = [];
|
|
1256
|
-
const clineFilePath =
|
|
1404
|
+
const clineFilePath = join16(baseDir, ".cline", "instructions.md");
|
|
1257
1405
|
if (await fileExists(clineFilePath)) {
|
|
1258
1406
|
try {
|
|
1259
1407
|
const content = await readFileContent(clineFilePath);
|
|
@@ -1276,14 +1424,14 @@ async function parseClineConfiguration(baseDir = process.cwd()) {
|
|
|
1276
1424
|
errors.push(`Failed to parse .cline/instructions.md: ${errorMessage}`);
|
|
1277
1425
|
}
|
|
1278
1426
|
}
|
|
1279
|
-
const clinerulesDirPath =
|
|
1427
|
+
const clinerulesDirPath = join16(baseDir, ".clinerules");
|
|
1280
1428
|
if (await fileExists(clinerulesDirPath)) {
|
|
1281
1429
|
try {
|
|
1282
1430
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
1283
1431
|
const files = await readdir2(clinerulesDirPath);
|
|
1284
1432
|
for (const file of files) {
|
|
1285
1433
|
if (file.endsWith(".md")) {
|
|
1286
|
-
const filePath =
|
|
1434
|
+
const filePath = join16(clinerulesDirPath, file);
|
|
1287
1435
|
try {
|
|
1288
1436
|
const content = await readFileContent(filePath);
|
|
1289
1437
|
if (content.trim()) {
|
|
@@ -1319,12 +1467,12 @@ async function parseClineConfiguration(baseDir = process.cwd()) {
|
|
|
1319
1467
|
}
|
|
1320
1468
|
|
|
1321
1469
|
// src/parsers/copilot.ts
|
|
1322
|
-
import { basename as basename3, join as
|
|
1470
|
+
import { basename as basename3, join as join17 } from "path";
|
|
1323
1471
|
import matter2 from "gray-matter";
|
|
1324
1472
|
async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
1325
1473
|
const errors = [];
|
|
1326
1474
|
const rules = [];
|
|
1327
|
-
const copilotFilePath =
|
|
1475
|
+
const copilotFilePath = join17(baseDir, ".github", "copilot-instructions.md");
|
|
1328
1476
|
if (await fileExists(copilotFilePath)) {
|
|
1329
1477
|
try {
|
|
1330
1478
|
const rawContent = await readFileContent(copilotFilePath);
|
|
@@ -1349,14 +1497,14 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
|
1349
1497
|
errors.push(`Failed to parse copilot-instructions.md: ${errorMessage}`);
|
|
1350
1498
|
}
|
|
1351
1499
|
}
|
|
1352
|
-
const instructionsDir =
|
|
1500
|
+
const instructionsDir = join17(baseDir, ".github", "instructions");
|
|
1353
1501
|
if (await fileExists(instructionsDir)) {
|
|
1354
1502
|
try {
|
|
1355
1503
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
1356
1504
|
const files = await readdir2(instructionsDir);
|
|
1357
1505
|
for (const file of files) {
|
|
1358
1506
|
if (file.endsWith(".instructions.md")) {
|
|
1359
|
-
const filePath =
|
|
1507
|
+
const filePath = join17(instructionsDir, file);
|
|
1360
1508
|
const rawContent = await readFileContent(filePath);
|
|
1361
1509
|
const parsed = matter2(rawContent);
|
|
1362
1510
|
const content = parsed.content.trim();
|
|
@@ -1391,10 +1539,10 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
|
1391
1539
|
}
|
|
1392
1540
|
|
|
1393
1541
|
// src/parsers/cursor.ts
|
|
1394
|
-
import { basename as basename4, join as
|
|
1542
|
+
import { basename as basename4, join as join18 } from "path";
|
|
1395
1543
|
import matter3 from "gray-matter";
|
|
1396
1544
|
import { DEFAULT_SCHEMA, FAILSAFE_SCHEMA, load } from "js-yaml";
|
|
1397
|
-
import { z as z5 } from "zod/
|
|
1545
|
+
import { z as z5 } from "zod/mini";
|
|
1398
1546
|
var customMatterOptions = {
|
|
1399
1547
|
engines: {
|
|
1400
1548
|
yaml: {
|
|
@@ -1516,7 +1664,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
1516
1664
|
const rules = [];
|
|
1517
1665
|
let ignorePatterns;
|
|
1518
1666
|
let mcpServers;
|
|
1519
|
-
const cursorFilePath =
|
|
1667
|
+
const cursorFilePath = join18(baseDir, ".cursorrules");
|
|
1520
1668
|
if (await fileExists(cursorFilePath)) {
|
|
1521
1669
|
try {
|
|
1522
1670
|
const rawContent = await readFileContent(cursorFilePath);
|
|
@@ -1537,14 +1685,14 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
1537
1685
|
errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
|
|
1538
1686
|
}
|
|
1539
1687
|
}
|
|
1540
|
-
const cursorRulesDir =
|
|
1688
|
+
const cursorRulesDir = join18(baseDir, ".cursor", "rules");
|
|
1541
1689
|
if (await fileExists(cursorRulesDir)) {
|
|
1542
1690
|
try {
|
|
1543
1691
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
1544
1692
|
const files = await readdir2(cursorRulesDir);
|
|
1545
1693
|
for (const file of files) {
|
|
1546
1694
|
if (file.endsWith(".mdc")) {
|
|
1547
|
-
const filePath =
|
|
1695
|
+
const filePath = join18(cursorRulesDir, file);
|
|
1548
1696
|
try {
|
|
1549
1697
|
const rawContent = await readFileContent(filePath);
|
|
1550
1698
|
const parsed = matter3(rawContent, customMatterOptions);
|
|
@@ -1573,7 +1721,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
1573
1721
|
if (rules.length === 0) {
|
|
1574
1722
|
errors.push("No Cursor configuration files found (.cursorrules or .cursor/rules/*.mdc)");
|
|
1575
1723
|
}
|
|
1576
|
-
const cursorIgnorePath =
|
|
1724
|
+
const cursorIgnorePath = join18(baseDir, ".cursorignore");
|
|
1577
1725
|
if (await fileExists(cursorIgnorePath)) {
|
|
1578
1726
|
try {
|
|
1579
1727
|
const content = await readFileContent(cursorIgnorePath);
|
|
@@ -1586,7 +1734,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
1586
1734
|
errors.push(`Failed to parse .cursorignore: ${errorMessage}`);
|
|
1587
1735
|
}
|
|
1588
1736
|
}
|
|
1589
|
-
const cursorMcpPath =
|
|
1737
|
+
const cursorMcpPath = join18(baseDir, ".cursor", "mcp.json");
|
|
1590
1738
|
if (await fileExists(cursorMcpPath)) {
|
|
1591
1739
|
try {
|
|
1592
1740
|
const content = await readFileContent(cursorMcpPath);
|
|
@@ -1609,13 +1757,13 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
1609
1757
|
}
|
|
1610
1758
|
|
|
1611
1759
|
// src/parsers/geminicli.ts
|
|
1612
|
-
import { basename as basename5, join as
|
|
1760
|
+
import { basename as basename5, join as join19 } from "path";
|
|
1613
1761
|
async function parseGeminiConfiguration(baseDir = process.cwd()) {
|
|
1614
1762
|
const errors = [];
|
|
1615
1763
|
const rules = [];
|
|
1616
1764
|
let ignorePatterns;
|
|
1617
1765
|
let mcpServers;
|
|
1618
|
-
const geminiFilePath =
|
|
1766
|
+
const geminiFilePath = join19(baseDir, "GEMINI.md");
|
|
1619
1767
|
if (!await fileExists(geminiFilePath)) {
|
|
1620
1768
|
errors.push("GEMINI.md file not found");
|
|
1621
1769
|
return { rules, errors };
|
|
@@ -1626,12 +1774,12 @@ async function parseGeminiConfiguration(baseDir = process.cwd()) {
|
|
|
1626
1774
|
if (mainRule) {
|
|
1627
1775
|
rules.push(mainRule);
|
|
1628
1776
|
}
|
|
1629
|
-
const memoryDir =
|
|
1777
|
+
const memoryDir = join19(baseDir, ".gemini", "memories");
|
|
1630
1778
|
if (await fileExists(memoryDir)) {
|
|
1631
1779
|
const memoryRules = await parseGeminiMemoryFiles(memoryDir);
|
|
1632
1780
|
rules.push(...memoryRules);
|
|
1633
1781
|
}
|
|
1634
|
-
const settingsPath =
|
|
1782
|
+
const settingsPath = join19(baseDir, ".gemini", "settings.json");
|
|
1635
1783
|
if (await fileExists(settingsPath)) {
|
|
1636
1784
|
const settingsResult = await parseGeminiSettings(settingsPath);
|
|
1637
1785
|
if (settingsResult.ignorePatterns) {
|
|
@@ -1642,7 +1790,7 @@ async function parseGeminiConfiguration(baseDir = process.cwd()) {
|
|
|
1642
1790
|
}
|
|
1643
1791
|
errors.push(...settingsResult.errors);
|
|
1644
1792
|
}
|
|
1645
|
-
const aiexcludePath =
|
|
1793
|
+
const aiexcludePath = join19(baseDir, ".aiexclude");
|
|
1646
1794
|
if (await fileExists(aiexcludePath)) {
|
|
1647
1795
|
const aiexcludePatterns = await parseAiexclude(aiexcludePath);
|
|
1648
1796
|
if (aiexcludePatterns.length > 0) {
|
|
@@ -1695,7 +1843,7 @@ async function parseGeminiMemoryFiles(memoryDir) {
|
|
|
1695
1843
|
const files = await readdir2(memoryDir);
|
|
1696
1844
|
for (const file of files) {
|
|
1697
1845
|
if (file.endsWith(".md")) {
|
|
1698
|
-
const filePath =
|
|
1846
|
+
const filePath = join19(memoryDir, file);
|
|
1699
1847
|
const content = await readFileContent(filePath);
|
|
1700
1848
|
if (content.trim()) {
|
|
1701
1849
|
const filename = basename5(file, ".md");
|
|
@@ -1748,11 +1896,11 @@ async function parseAiexclude(aiexcludePath) {
|
|
|
1748
1896
|
}
|
|
1749
1897
|
|
|
1750
1898
|
// src/parsers/roo.ts
|
|
1751
|
-
import { join as
|
|
1899
|
+
import { join as join20 } from "path";
|
|
1752
1900
|
async function parseRooConfiguration(baseDir = process.cwd()) {
|
|
1753
1901
|
const errors = [];
|
|
1754
1902
|
const rules = [];
|
|
1755
|
-
const rooFilePath =
|
|
1903
|
+
const rooFilePath = join20(baseDir, ".roo", "instructions.md");
|
|
1756
1904
|
if (await fileExists(rooFilePath)) {
|
|
1757
1905
|
try {
|
|
1758
1906
|
const content = await readFileContent(rooFilePath);
|
|
@@ -1775,14 +1923,14 @@ async function parseRooConfiguration(baseDir = process.cwd()) {
|
|
|
1775
1923
|
errors.push(`Failed to parse .roo/instructions.md: ${errorMessage}`);
|
|
1776
1924
|
}
|
|
1777
1925
|
}
|
|
1778
|
-
const rooRulesDir =
|
|
1926
|
+
const rooRulesDir = join20(baseDir, ".roo", "rules");
|
|
1779
1927
|
if (await fileExists(rooRulesDir)) {
|
|
1780
1928
|
try {
|
|
1781
1929
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
1782
1930
|
const files = await readdir2(rooRulesDir);
|
|
1783
1931
|
for (const file of files) {
|
|
1784
1932
|
if (file.endsWith(".md")) {
|
|
1785
|
-
const filePath =
|
|
1933
|
+
const filePath = join20(rooRulesDir, file);
|
|
1786
1934
|
try {
|
|
1787
1935
|
const content = await readFileContent(filePath);
|
|
1788
1936
|
if (content.trim()) {
|
|
@@ -1883,7 +2031,7 @@ async function importConfiguration(options) {
|
|
|
1883
2031
|
if (rules.length === 0 && !ignorePatterns && !mcpServers) {
|
|
1884
2032
|
return { success: false, rulesCreated: 0, errors };
|
|
1885
2033
|
}
|
|
1886
|
-
const rulesDirPath =
|
|
2034
|
+
const rulesDirPath = join21(baseDir, rulesDir);
|
|
1887
2035
|
try {
|
|
1888
2036
|
const { mkdir: mkdir3 } = await import("fs/promises");
|
|
1889
2037
|
await mkdir3(rulesDirPath, { recursive: true });
|
|
@@ -1897,7 +2045,7 @@ async function importConfiguration(options) {
|
|
|
1897
2045
|
try {
|
|
1898
2046
|
const baseFilename = `${tool}__${rule.filename}`;
|
|
1899
2047
|
const filename = await generateUniqueFilename(rulesDirPath, baseFilename);
|
|
1900
|
-
const filePath =
|
|
2048
|
+
const filePath = join21(rulesDirPath, `${filename}.md`);
|
|
1901
2049
|
const content = generateRuleFileContent(rule);
|
|
1902
2050
|
await writeFileContent(filePath, content);
|
|
1903
2051
|
rulesCreated++;
|
|
@@ -1912,7 +2060,7 @@ async function importConfiguration(options) {
|
|
|
1912
2060
|
let ignoreFileCreated = false;
|
|
1913
2061
|
if (ignorePatterns && ignorePatterns.length > 0) {
|
|
1914
2062
|
try {
|
|
1915
|
-
const rulesyncignorePath =
|
|
2063
|
+
const rulesyncignorePath = join21(baseDir, ".rulesyncignore");
|
|
1916
2064
|
const ignoreContent = `${ignorePatterns.join("\n")}
|
|
1917
2065
|
`;
|
|
1918
2066
|
await writeFileContent(rulesyncignorePath, ignoreContent);
|
|
@@ -1928,7 +2076,7 @@ async function importConfiguration(options) {
|
|
|
1928
2076
|
let mcpFileCreated = false;
|
|
1929
2077
|
if (mcpServers && Object.keys(mcpServers).length > 0) {
|
|
1930
2078
|
try {
|
|
1931
|
-
const mcpPath =
|
|
2079
|
+
const mcpPath = join21(baseDir, rulesDir, ".mcp.json");
|
|
1932
2080
|
const mcpContent = `${JSON.stringify({ mcpServers }, null, 2)}
|
|
1933
2081
|
`;
|
|
1934
2082
|
await writeFileContent(mcpPath, mcpContent);
|
|
@@ -1942,7 +2090,7 @@ async function importConfiguration(options) {
|
|
|
1942
2090
|
}
|
|
1943
2091
|
}
|
|
1944
2092
|
return {
|
|
1945
|
-
success: rulesCreated > 0 || ignoreFileCreated || mcpFileCreated,
|
|
2093
|
+
success: errors.length === 0 && (rulesCreated > 0 || ignoreFileCreated || mcpFileCreated),
|
|
1946
2094
|
rulesCreated,
|
|
1947
2095
|
errors,
|
|
1948
2096
|
ignoreFileCreated,
|
|
@@ -1956,7 +2104,7 @@ function generateRuleFileContent(rule) {
|
|
|
1956
2104
|
async function generateUniqueFilename(rulesDir, baseFilename) {
|
|
1957
2105
|
let filename = baseFilename;
|
|
1958
2106
|
let counter = 1;
|
|
1959
|
-
while (await fileExists(
|
|
2107
|
+
while (await fileExists(join21(rulesDir, `${filename}.md`))) {
|
|
1960
2108
|
filename = `${baseFilename}-${counter}`;
|
|
1961
2109
|
counter++;
|
|
1962
2110
|
}
|
|
@@ -2021,7 +2169,7 @@ async function importCommand(options = {}) {
|
|
|
2021
2169
|
}
|
|
2022
2170
|
|
|
2023
2171
|
// src/cli/commands/init.ts
|
|
2024
|
-
import { join as
|
|
2172
|
+
import { join as join22 } from "path";
|
|
2025
2173
|
async function initCommand() {
|
|
2026
2174
|
const aiRulesDir = ".rulesync";
|
|
2027
2175
|
console.log("Initializing rulesync...");
|
|
@@ -2033,14 +2181,13 @@ async function initCommand() {
|
|
|
2033
2181
|
console.log("2. Run 'rulesync generate' to create configuration files");
|
|
2034
2182
|
}
|
|
2035
2183
|
async function createSampleFiles(aiRulesDir) {
|
|
2036
|
-
const
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
content: `---
|
|
2184
|
+
const sampleFile = {
|
|
2185
|
+
filename: "overview.md",
|
|
2186
|
+
content: `---
|
|
2040
2187
|
root: true
|
|
2041
2188
|
targets: ["*"]
|
|
2042
2189
|
description: "Project overview and general development guidelines"
|
|
2043
|
-
globs: ["
|
|
2190
|
+
globs: ["**/*"]
|
|
2044
2191
|
---
|
|
2045
2192
|
|
|
2046
2193
|
# Project Overview
|
|
@@ -2068,96 +2215,13 @@ globs: ["**/*.ts", "**/*.js", "**/*.tsx", "**/*.jsx"]
|
|
|
2068
2215
|
- Implement proper error handling
|
|
2069
2216
|
- Follow single responsibility principle
|
|
2070
2217
|
`
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
globs: ["src/components/**/*.tsx", "src/pages/**/*.tsx", "**/*.css", "**/*.scss"]
|
|
2079
|
-
---
|
|
2080
|
-
|
|
2081
|
-
# Frontend Development Rules
|
|
2082
|
-
|
|
2083
|
-
## React Components
|
|
2084
|
-
|
|
2085
|
-
- Use functional components with hooks
|
|
2086
|
-
- Follow PascalCase naming for components
|
|
2087
|
-
- Use TypeScript interfaces for props
|
|
2088
|
-
- Implement proper error boundaries
|
|
2089
|
-
|
|
2090
|
-
## Styling
|
|
2091
|
-
|
|
2092
|
-
- Use CSS modules or styled-components
|
|
2093
|
-
- Follow BEM methodology for CSS classes
|
|
2094
|
-
- Prefer flexbox and grid for layouts
|
|
2095
|
-
- Use semantic HTML elements
|
|
2096
|
-
|
|
2097
|
-
## State Management
|
|
2098
|
-
|
|
2099
|
-
- Use React hooks for local state
|
|
2100
|
-
- Consider Redux or Zustand for global state
|
|
2101
|
-
- Avoid prop drilling with context API
|
|
2102
|
-
- Keep state as close to where it's used as possible
|
|
2103
|
-
|
|
2104
|
-
## Performance
|
|
2105
|
-
|
|
2106
|
-
- Use React.memo for expensive components
|
|
2107
|
-
- Implement lazy loading for routes
|
|
2108
|
-
- Optimize images and assets
|
|
2109
|
-
- Use proper key props in lists
|
|
2110
|
-
`
|
|
2111
|
-
},
|
|
2112
|
-
{
|
|
2113
|
-
filename: "backend.md",
|
|
2114
|
-
content: `---
|
|
2115
|
-
root: false
|
|
2116
|
-
targets: ["*"]
|
|
2117
|
-
description: "Backend development rules and API guidelines"
|
|
2118
|
-
globs: ["src/api/**/*.ts", "src/services/**/*.ts", "src/models/**/*.ts"]
|
|
2119
|
-
---
|
|
2120
|
-
|
|
2121
|
-
# Backend Development Rules
|
|
2122
|
-
|
|
2123
|
-
## API Design
|
|
2124
|
-
|
|
2125
|
-
- Follow RESTful conventions
|
|
2126
|
-
- Use consistent HTTP status codes
|
|
2127
|
-
- Implement proper error handling with meaningful messages
|
|
2128
|
-
- Use API versioning when necessary
|
|
2129
|
-
|
|
2130
|
-
## Database
|
|
2131
|
-
|
|
2132
|
-
- Use proper indexing for performance
|
|
2133
|
-
- Implement database migrations
|
|
2134
|
-
- Follow naming conventions for tables and columns
|
|
2135
|
-
- Use transactions for data consistency
|
|
2136
|
-
|
|
2137
|
-
## Security
|
|
2138
|
-
|
|
2139
|
-
- Validate all input data
|
|
2140
|
-
- Use proper authentication and authorization
|
|
2141
|
-
- Implement rate limiting
|
|
2142
|
-
- Sanitize database queries to prevent SQL injection
|
|
2143
|
-
|
|
2144
|
-
## Code Organization
|
|
2145
|
-
|
|
2146
|
-
- Use service layer pattern
|
|
2147
|
-
- Implement proper logging
|
|
2148
|
-
- Use environment variables for configuration
|
|
2149
|
-
- Write comprehensive tests for business logic
|
|
2150
|
-
`
|
|
2151
|
-
}
|
|
2152
|
-
];
|
|
2153
|
-
for (const file of sampleFiles) {
|
|
2154
|
-
const filepath = join20(aiRulesDir, file.filename);
|
|
2155
|
-
if (!await fileExists(filepath)) {
|
|
2156
|
-
await writeFileContent(filepath, file.content);
|
|
2157
|
-
console.log(`Created ${filepath}`);
|
|
2158
|
-
} else {
|
|
2159
|
-
console.log(`Skipped ${filepath} (already exists)`);
|
|
2160
|
-
}
|
|
2218
|
+
};
|
|
2219
|
+
const filepath = join22(aiRulesDir, sampleFile.filename);
|
|
2220
|
+
if (!await fileExists(filepath)) {
|
|
2221
|
+
await writeFileContent(filepath, sampleFile.content);
|
|
2222
|
+
console.log(`Created ${filepath}`);
|
|
2223
|
+
} else {
|
|
2224
|
+
console.log(`Skipped ${filepath} (already exists)`);
|
|
2161
2225
|
}
|
|
2162
2226
|
}
|
|
2163
2227
|
|
|
@@ -2296,12 +2360,12 @@ async function watchCommand() {
|
|
|
2296
2360
|
|
|
2297
2361
|
// src/cli/index.ts
|
|
2298
2362
|
var program = new Command();
|
|
2299
|
-
program.name("rulesync").description("Unified AI rules management CLI tool").version("0.
|
|
2363
|
+
program.name("rulesync").description("Unified AI rules management CLI tool").version("0.47.0");
|
|
2300
2364
|
program.command("init").description("Initialize rulesync in current directory").action(initCommand);
|
|
2301
2365
|
program.command("add <filename>").description("Add a new rule file").action(addCommand);
|
|
2302
2366
|
program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
|
|
2303
2367
|
program.command("import").description("Import configurations from AI tools to rulesync format").option("--claudecode", "Import from Claude Code (CLAUDE.md)").option("--cursor", "Import from Cursor (.cursorrules)").option("--copilot", "Import from GitHub Copilot (.github/copilot-instructions.md)").option("--cline", "Import from Cline (.cline/instructions.md)").option("--roo", "Import from Roo Code (.roo/instructions.md)").option("--geminicli", "Import from Gemini CLI (GEMINI.md)").option("-v, --verbose", "Verbose output").action(importCommand);
|
|
2304
|
-
program.command("generate").description("Generate configuration files for AI tools").option("--copilot", "Generate only for GitHub Copilot").option("--cursor", "Generate only for Cursor").option("--cline", "Generate only for Cline").option("--claudecode", "Generate only for Claude Code").option("--roo", "Generate only for Roo Code").option("--geminicli", "Generate only for Gemini CLI").option("--delete", "Delete all existing files in output directories before generating").option(
|
|
2368
|
+
program.command("generate").description("Generate configuration files for AI tools").option("--copilot", "Generate only for GitHub Copilot").option("--cursor", "Generate only for Cursor").option("--cline", "Generate only for Cline").option("--claudecode", "Generate only for Claude Code").option("--roo", "Generate only for Roo Code").option("--geminicli", "Generate only for Gemini CLI").option("--kiro", "Generate only for Kiro IDE").option("--delete", "Delete all existing files in output directories before generating").option(
|
|
2305
2369
|
"-b, --base-dir <paths>",
|
|
2306
2370
|
"Base directories to generate files (comma-separated for multiple paths)"
|
|
2307
2371
|
).option("-v, --verbose", "Verbose output").action(async (options) => {
|
|
@@ -2312,6 +2376,7 @@ program.command("generate").description("Generate configuration files for AI too
|
|
|
2312
2376
|
if (options.claudecode) tools.push("claudecode");
|
|
2313
2377
|
if (options.roo) tools.push("roo");
|
|
2314
2378
|
if (options.geminicli) tools.push("geminicli");
|
|
2379
|
+
if (options.kiro) tools.push("kiro");
|
|
2315
2380
|
const generateOptions = {
|
|
2316
2381
|
verbose: options.verbose,
|
|
2317
2382
|
delete: options.delete
|