rulesync 0.45.0 → 0.48.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 +377 -172
- package/dist/index.js +297 -142
- 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;
|
|
@@ -318,13 +427,13 @@ async function updateClaudeSettings(settingsPath, ignorePatterns) {
|
|
|
318
427
|
}
|
|
319
428
|
|
|
320
429
|
// src/generators/rules/cline.ts
|
|
321
|
-
import { join as
|
|
430
|
+
import { join as join6 } from "path";
|
|
322
431
|
async function generateClineConfig(rules, config, baseDir) {
|
|
323
432
|
const outputs = [];
|
|
324
433
|
for (const rule of rules) {
|
|
325
434
|
const content = generateClineMarkdown(rule);
|
|
326
|
-
const outputDir = baseDir ?
|
|
327
|
-
const filepath =
|
|
435
|
+
const outputDir = baseDir ? join6(baseDir, config.outputPaths.cline) : config.outputPaths.cline;
|
|
436
|
+
const filepath = join6(outputDir, `${rule.filename}.md`);
|
|
328
437
|
outputs.push({
|
|
329
438
|
tool: "cline",
|
|
330
439
|
filepath,
|
|
@@ -333,7 +442,7 @@ async function generateClineConfig(rules, config, baseDir) {
|
|
|
333
442
|
}
|
|
334
443
|
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
335
444
|
if (ignorePatterns.patterns.length > 0) {
|
|
336
|
-
const clineIgnorePath = baseDir ?
|
|
445
|
+
const clineIgnorePath = baseDir ? join6(baseDir, ".clineignore") : ".clineignore";
|
|
337
446
|
const clineIgnoreContent = generateClineIgnore(ignorePatterns.patterns);
|
|
338
447
|
outputs.push({
|
|
339
448
|
tool: "cline",
|
|
@@ -357,14 +466,14 @@ function generateClineIgnore(patterns) {
|
|
|
357
466
|
}
|
|
358
467
|
|
|
359
468
|
// src/generators/rules/copilot.ts
|
|
360
|
-
import { join as
|
|
469
|
+
import { join as join7 } from "path";
|
|
361
470
|
async function generateCopilotConfig(rules, config, baseDir) {
|
|
362
471
|
const outputs = [];
|
|
363
472
|
for (const rule of rules) {
|
|
364
473
|
const content = generateCopilotMarkdown(rule);
|
|
365
474
|
const baseFilename = rule.filename.replace(/\.md$/, "");
|
|
366
|
-
const outputDir = baseDir ?
|
|
367
|
-
const filepath =
|
|
475
|
+
const outputDir = baseDir ? join7(baseDir, config.outputPaths.copilot) : config.outputPaths.copilot;
|
|
476
|
+
const filepath = join7(outputDir, `${baseFilename}.instructions.md`);
|
|
368
477
|
outputs.push({
|
|
369
478
|
tool: "copilot",
|
|
370
479
|
filepath,
|
|
@@ -373,7 +482,7 @@ async function generateCopilotConfig(rules, config, baseDir) {
|
|
|
373
482
|
}
|
|
374
483
|
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
375
484
|
if (ignorePatterns.patterns.length > 0) {
|
|
376
|
-
const copilotIgnorePath = baseDir ?
|
|
485
|
+
const copilotIgnorePath = baseDir ? join7(baseDir, ".copilotignore") : ".copilotignore";
|
|
377
486
|
const copilotIgnoreContent = generateCopilotIgnore(ignorePatterns.patterns);
|
|
378
487
|
outputs.push({
|
|
379
488
|
tool: "copilot",
|
|
@@ -409,13 +518,13 @@ function generateCopilotIgnore(patterns) {
|
|
|
409
518
|
}
|
|
410
519
|
|
|
411
520
|
// src/generators/rules/cursor.ts
|
|
412
|
-
import { join as
|
|
521
|
+
import { join as join8 } from "path";
|
|
413
522
|
async function generateCursorConfig(rules, config, baseDir) {
|
|
414
523
|
const outputs = [];
|
|
415
524
|
for (const rule of rules) {
|
|
416
525
|
const content = generateCursorMarkdown(rule);
|
|
417
|
-
const outputDir = baseDir ?
|
|
418
|
-
const filepath =
|
|
526
|
+
const outputDir = baseDir ? join8(baseDir, config.outputPaths.cursor) : config.outputPaths.cursor;
|
|
527
|
+
const filepath = join8(outputDir, `${rule.filename}.mdc`);
|
|
419
528
|
outputs.push({
|
|
420
529
|
tool: "cursor",
|
|
421
530
|
filepath,
|
|
@@ -424,7 +533,7 @@ async function generateCursorConfig(rules, config, baseDir) {
|
|
|
424
533
|
}
|
|
425
534
|
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
426
535
|
if (ignorePatterns.patterns.length > 0) {
|
|
427
|
-
const cursorIgnorePath = baseDir ?
|
|
536
|
+
const cursorIgnorePath = baseDir ? join8(baseDir, ".cursorignore") : ".cursorignore";
|
|
428
537
|
const cursorIgnoreContent = generateCursorIgnore(ignorePatterns.patterns);
|
|
429
538
|
outputs.push({
|
|
430
539
|
tool: "cursor",
|
|
@@ -497,15 +606,15 @@ function generateCursorIgnore(patterns) {
|
|
|
497
606
|
}
|
|
498
607
|
|
|
499
608
|
// src/generators/rules/geminicli.ts
|
|
500
|
-
import { join as
|
|
609
|
+
import { join as join9 } from "path";
|
|
501
610
|
async function generateGeminiConfig(rules, config, baseDir) {
|
|
502
611
|
const outputs = [];
|
|
503
612
|
const rootRule = rules.find((rule) => rule.frontmatter.root === true);
|
|
504
613
|
const memoryRules = rules.filter((rule) => rule.frontmatter.root === false);
|
|
505
614
|
for (const rule of memoryRules) {
|
|
506
615
|
const content = generateGeminiMemoryMarkdown(rule);
|
|
507
|
-
const outputDir = baseDir ?
|
|
508
|
-
const filepath =
|
|
616
|
+
const outputDir = baseDir ? join9(baseDir, config.outputPaths.geminicli) : config.outputPaths.geminicli;
|
|
617
|
+
const filepath = join9(outputDir, `${rule.filename}.md`);
|
|
509
618
|
outputs.push({
|
|
510
619
|
tool: "geminicli",
|
|
511
620
|
filepath,
|
|
@@ -513,7 +622,7 @@ async function generateGeminiConfig(rules, config, baseDir) {
|
|
|
513
622
|
});
|
|
514
623
|
}
|
|
515
624
|
const rootContent = generateGeminiRootMarkdown(rootRule, memoryRules, baseDir);
|
|
516
|
-
const rootFilepath = baseDir ?
|
|
625
|
+
const rootFilepath = baseDir ? join9(baseDir, "GEMINI.md") : "GEMINI.md";
|
|
517
626
|
outputs.push({
|
|
518
627
|
tool: "geminicli",
|
|
519
628
|
filepath: rootFilepath,
|
|
@@ -521,7 +630,7 @@ async function generateGeminiConfig(rules, config, baseDir) {
|
|
|
521
630
|
});
|
|
522
631
|
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
523
632
|
if (ignorePatterns.patterns.length > 0) {
|
|
524
|
-
const aiexcludePath = baseDir ?
|
|
633
|
+
const aiexcludePath = baseDir ? join9(baseDir, ".aiexclude") : ".aiexclude";
|
|
525
634
|
const aiexcludeContent = generateAiexclude(ignorePatterns.patterns);
|
|
526
635
|
outputs.push({
|
|
527
636
|
tool: "geminicli",
|
|
@@ -568,14 +677,34 @@ function generateAiexclude(patterns) {
|
|
|
568
677
|
return lines.join("\n");
|
|
569
678
|
}
|
|
570
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
|
+
|
|
571
700
|
// src/generators/rules/roo.ts
|
|
572
|
-
import { join as
|
|
701
|
+
import { join as join11 } from "path";
|
|
573
702
|
async function generateRooConfig(rules, config, baseDir) {
|
|
574
703
|
const outputs = [];
|
|
575
704
|
for (const rule of rules) {
|
|
576
705
|
const content = generateRooMarkdown(rule);
|
|
577
|
-
const outputDir = baseDir ?
|
|
578
|
-
const filepath =
|
|
706
|
+
const outputDir = baseDir ? join11(baseDir, config.outputPaths.roo) : config.outputPaths.roo;
|
|
707
|
+
const filepath = join11(outputDir, `${rule.filename}.md`);
|
|
579
708
|
outputs.push({
|
|
580
709
|
tool: "roo",
|
|
581
710
|
filepath,
|
|
@@ -584,7 +713,7 @@ async function generateRooConfig(rules, config, baseDir) {
|
|
|
584
713
|
}
|
|
585
714
|
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
586
715
|
if (ignorePatterns.patterns.length > 0) {
|
|
587
|
-
const rooIgnorePath = baseDir ?
|
|
716
|
+
const rooIgnorePath = baseDir ? join11(baseDir, ".rooignore") : ".rooignore";
|
|
588
717
|
const rooIgnoreContent = generateRooIgnore(ignorePatterns.patterns);
|
|
589
718
|
outputs.push({
|
|
590
719
|
tool: "roo",
|
|
@@ -650,6 +779,11 @@ async function generateForTool(tool, rules, config, baseDir) {
|
|
|
650
779
|
return generateRooConfig(rules, config, baseDir);
|
|
651
780
|
case "geminicli":
|
|
652
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
|
+
}
|
|
653
787
|
default:
|
|
654
788
|
console.warn(`Unknown tool: ${tool}`);
|
|
655
789
|
return null;
|
|
@@ -661,7 +795,7 @@ import { basename } from "path";
|
|
|
661
795
|
import matter from "gray-matter";
|
|
662
796
|
|
|
663
797
|
// src/types/config.ts
|
|
664
|
-
import { z as z2 } from "zod/
|
|
798
|
+
import { z as z2 } from "zod/mini";
|
|
665
799
|
var ConfigSchema = z2.object({
|
|
666
800
|
aiRulesDir: z2.string(),
|
|
667
801
|
outputPaths: z2.record(ToolTargetSchema, z2.string()),
|
|
@@ -670,7 +804,7 @@ var ConfigSchema = z2.object({
|
|
|
670
804
|
});
|
|
671
805
|
|
|
672
806
|
// src/types/mcp.ts
|
|
673
|
-
import { z as z3 } from "zod/
|
|
807
|
+
import { z as z3 } from "zod/mini";
|
|
674
808
|
var McpTransportTypeSchema = z3.enum(["stdio", "sse", "http"]);
|
|
675
809
|
var McpServerBaseSchema = z3.object({
|
|
676
810
|
command: z3.optional(z3.string()),
|
|
@@ -686,7 +820,9 @@ var McpServerBaseSchema = z3.object({
|
|
|
686
820
|
transport: z3.optional(McpTransportTypeSchema),
|
|
687
821
|
type: z3.optional(z3.enum(["sse", "streamable-http"])),
|
|
688
822
|
alwaysAllow: z3.optional(z3.array(z3.string())),
|
|
689
|
-
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()))
|
|
690
826
|
});
|
|
691
827
|
var RulesyncMcpServerSchema = z3.extend(McpServerBaseSchema, {
|
|
692
828
|
targets: z3.optional(RulesyncTargetsSchema)
|
|
@@ -699,7 +835,7 @@ var RulesyncMcpConfigSchema = z3.object({
|
|
|
699
835
|
});
|
|
700
836
|
|
|
701
837
|
// src/types/rules.ts
|
|
702
|
-
import { z as z4 } from "zod/
|
|
838
|
+
import { z as z4 } from "zod/mini";
|
|
703
839
|
var RuleFrontmatterSchema = z4.object({
|
|
704
840
|
root: z4.boolean(),
|
|
705
841
|
targets: RulesyncTargetsSchema,
|
|
@@ -727,7 +863,8 @@ var GenerateOptionsSchema = z4.object({
|
|
|
727
863
|
// src/core/parser.ts
|
|
728
864
|
async function parseRulesFromDirectory(aiRulesDir) {
|
|
729
865
|
const ignorePatterns = await loadIgnorePatterns();
|
|
730
|
-
const
|
|
866
|
+
const allRuleFiles = await findFiles(aiRulesDir, ".md");
|
|
867
|
+
const ruleFiles = filterIgnoredFiles(allRuleFiles, ignorePatterns.patterns);
|
|
731
868
|
const rules = [];
|
|
732
869
|
const errors = [];
|
|
733
870
|
if (ignorePatterns.patterns.length > 0) {
|
|
@@ -888,6 +1025,11 @@ async function generateMcpConfigs(projectRoot, baseDir) {
|
|
|
888
1025
|
path: path3.join(targetRoot, ".gemini", "settings.json"),
|
|
889
1026
|
generate: () => generateGeminiCliMcp(config)
|
|
890
1027
|
},
|
|
1028
|
+
{
|
|
1029
|
+
tool: "kiro-project",
|
|
1030
|
+
path: path3.join(targetRoot, ".kiro", "mcp.json"),
|
|
1031
|
+
generate: () => generateKiroMcp(config)
|
|
1032
|
+
},
|
|
891
1033
|
{
|
|
892
1034
|
tool: "roo-project",
|
|
893
1035
|
path: path3.join(targetRoot, ".roo", "mcp.json"),
|
|
@@ -898,7 +1040,7 @@ async function generateMcpConfigs(projectRoot, baseDir) {
|
|
|
898
1040
|
try {
|
|
899
1041
|
const content = generator.generate();
|
|
900
1042
|
const parsed = JSON.parse(content);
|
|
901
|
-
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")) {
|
|
902
1044
|
if (!parsed.mcpServers || Object.keys(parsed.mcpServers).length === 0) {
|
|
903
1045
|
results.push({
|
|
904
1046
|
tool: generator.tool,
|
|
@@ -984,6 +1126,9 @@ async function generateCommand(options = {}) {
|
|
|
984
1126
|
case "geminicli":
|
|
985
1127
|
deleteTasks.push(removeDirectory(config.outputPaths.geminicli));
|
|
986
1128
|
break;
|
|
1129
|
+
case "kiro":
|
|
1130
|
+
deleteTasks.push(removeDirectory(config.outputPaths.kiro));
|
|
1131
|
+
break;
|
|
987
1132
|
}
|
|
988
1133
|
}
|
|
989
1134
|
await Promise.all(deleteTasks);
|
|
@@ -1014,11 +1159,10 @@ Generating configurations for base directory: ${baseDir}`);
|
|
|
1014
1159
|
console.warn("\u26A0\uFE0F No configurations generated");
|
|
1015
1160
|
return;
|
|
1016
1161
|
}
|
|
1017
|
-
console.log(`
|
|
1018
|
-
\u{1F389} Successfully generated ${totalOutputs} configuration file(s)!`);
|
|
1019
1162
|
if (options.verbose) {
|
|
1020
1163
|
console.log("\nGenerating MCP configurations...");
|
|
1021
1164
|
}
|
|
1165
|
+
let totalMcpOutputs = 0;
|
|
1022
1166
|
for (const baseDir of baseDirs) {
|
|
1023
1167
|
const mcpResults = await generateMcpConfigs(
|
|
1024
1168
|
process.cwd(),
|
|
@@ -1033,6 +1177,7 @@ Generating configurations for base directory: ${baseDir}`);
|
|
|
1033
1177
|
for (const result of mcpResults) {
|
|
1034
1178
|
if (result.status === "success") {
|
|
1035
1179
|
console.log(`\u2705 Generated ${result.tool} MCP configuration: ${result.path}`);
|
|
1180
|
+
totalMcpOutputs++;
|
|
1036
1181
|
} else if (result.status === "error") {
|
|
1037
1182
|
console.error(`\u274C Failed to generate ${result.tool} MCP configuration: ${result.error}`);
|
|
1038
1183
|
} else if (options.verbose && result.status === "skipped") {
|
|
@@ -1040,6 +1185,13 @@ Generating configurations for base directory: ${baseDir}`);
|
|
|
1040
1185
|
}
|
|
1041
1186
|
}
|
|
1042
1187
|
}
|
|
1188
|
+
const totalGenerated = totalOutputs + totalMcpOutputs;
|
|
1189
|
+
if (totalGenerated > 0) {
|
|
1190
|
+
console.log(
|
|
1191
|
+
`
|
|
1192
|
+
\u{1F389} All done! Generated ${totalGenerated} file(s) total (${totalOutputs} configurations + ${totalMcpOutputs} MCP configurations)`
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1043
1195
|
} catch (error) {
|
|
1044
1196
|
console.error("\u274C Failed to generate configurations:", error);
|
|
1045
1197
|
process.exit(1);
|
|
@@ -1048,9 +1200,9 @@ Generating configurations for base directory: ${baseDir}`);
|
|
|
1048
1200
|
|
|
1049
1201
|
// src/cli/commands/gitignore.ts
|
|
1050
1202
|
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
1051
|
-
import { join as
|
|
1203
|
+
import { join as join14 } from "path";
|
|
1052
1204
|
var gitignoreCommand = async () => {
|
|
1053
|
-
const gitignorePath =
|
|
1205
|
+
const gitignorePath = join14(process.cwd(), ".gitignore");
|
|
1054
1206
|
const rulesFilesToIgnore = [
|
|
1055
1207
|
"# Generated by rulesync - AI tool configuration files",
|
|
1056
1208
|
"**/.github/copilot-instructions.md",
|
|
@@ -1067,6 +1219,8 @@ var gitignoreCommand = async () => {
|
|
|
1067
1219
|
"**/GEMINI.md",
|
|
1068
1220
|
"**/.gemini/memories/",
|
|
1069
1221
|
"**/.aiexclude",
|
|
1222
|
+
"**/.aiignore",
|
|
1223
|
+
"**/.kiro/steering/",
|
|
1070
1224
|
"**/.mcp.json",
|
|
1071
1225
|
"!.rulesync/.mcp.json",
|
|
1072
1226
|
"**/.cursor/mcp.json",
|
|
@@ -1104,17 +1258,17 @@ ${linesToAdd.join("\n")}
|
|
|
1104
1258
|
};
|
|
1105
1259
|
|
|
1106
1260
|
// src/core/importer.ts
|
|
1107
|
-
import { join as
|
|
1261
|
+
import { join as join21 } from "path";
|
|
1108
1262
|
import matter4 from "gray-matter";
|
|
1109
1263
|
|
|
1110
1264
|
// src/parsers/claudecode.ts
|
|
1111
|
-
import { basename as basename2, join as
|
|
1265
|
+
import { basename as basename2, join as join15 } from "path";
|
|
1112
1266
|
async function parseClaudeConfiguration(baseDir = process.cwd()) {
|
|
1113
1267
|
const errors = [];
|
|
1114
1268
|
const rules = [];
|
|
1115
1269
|
let ignorePatterns;
|
|
1116
1270
|
let mcpServers;
|
|
1117
|
-
const claudeFilePath =
|
|
1271
|
+
const claudeFilePath = join15(baseDir, "CLAUDE.md");
|
|
1118
1272
|
if (!await fileExists(claudeFilePath)) {
|
|
1119
1273
|
errors.push("CLAUDE.md file not found");
|
|
1120
1274
|
return { rules, errors };
|
|
@@ -1125,12 +1279,12 @@ async function parseClaudeConfiguration(baseDir = process.cwd()) {
|
|
|
1125
1279
|
if (mainRule) {
|
|
1126
1280
|
rules.push(mainRule);
|
|
1127
1281
|
}
|
|
1128
|
-
const memoryDir =
|
|
1282
|
+
const memoryDir = join15(baseDir, ".claude", "memories");
|
|
1129
1283
|
if (await fileExists(memoryDir)) {
|
|
1130
1284
|
const memoryRules = await parseClaudeMemoryFiles(memoryDir);
|
|
1131
1285
|
rules.push(...memoryRules);
|
|
1132
1286
|
}
|
|
1133
|
-
const settingsPath =
|
|
1287
|
+
const settingsPath = join15(baseDir, ".claude", "settings.json");
|
|
1134
1288
|
if (await fileExists(settingsPath)) {
|
|
1135
1289
|
const settingsResult = await parseClaudeSettings(settingsPath);
|
|
1136
1290
|
if (settingsResult.ignorePatterns) {
|
|
@@ -1187,7 +1341,7 @@ async function parseClaudeMemoryFiles(memoryDir) {
|
|
|
1187
1341
|
const files = await readdir2(memoryDir);
|
|
1188
1342
|
for (const file of files) {
|
|
1189
1343
|
if (file.endsWith(".md")) {
|
|
1190
|
-
const filePath =
|
|
1344
|
+
const filePath = join15(memoryDir, file);
|
|
1191
1345
|
const content = await readFileContent(filePath);
|
|
1192
1346
|
if (content.trim()) {
|
|
1193
1347
|
const filename = basename2(file, ".md");
|
|
@@ -1250,11 +1404,11 @@ async function parseClaudeSettings(settingsPath) {
|
|
|
1250
1404
|
}
|
|
1251
1405
|
|
|
1252
1406
|
// src/parsers/cline.ts
|
|
1253
|
-
import { join as
|
|
1407
|
+
import { join as join16 } from "path";
|
|
1254
1408
|
async function parseClineConfiguration(baseDir = process.cwd()) {
|
|
1255
1409
|
const errors = [];
|
|
1256
1410
|
const rules = [];
|
|
1257
|
-
const clineFilePath =
|
|
1411
|
+
const clineFilePath = join16(baseDir, ".cline", "instructions.md");
|
|
1258
1412
|
if (await fileExists(clineFilePath)) {
|
|
1259
1413
|
try {
|
|
1260
1414
|
const content = await readFileContent(clineFilePath);
|
|
@@ -1277,14 +1431,14 @@ async function parseClineConfiguration(baseDir = process.cwd()) {
|
|
|
1277
1431
|
errors.push(`Failed to parse .cline/instructions.md: ${errorMessage}`);
|
|
1278
1432
|
}
|
|
1279
1433
|
}
|
|
1280
|
-
const clinerulesDirPath =
|
|
1434
|
+
const clinerulesDirPath = join16(baseDir, ".clinerules");
|
|
1281
1435
|
if (await fileExists(clinerulesDirPath)) {
|
|
1282
1436
|
try {
|
|
1283
1437
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
1284
1438
|
const files = await readdir2(clinerulesDirPath);
|
|
1285
1439
|
for (const file of files) {
|
|
1286
1440
|
if (file.endsWith(".md")) {
|
|
1287
|
-
const filePath =
|
|
1441
|
+
const filePath = join16(clinerulesDirPath, file);
|
|
1288
1442
|
try {
|
|
1289
1443
|
const content = await readFileContent(filePath);
|
|
1290
1444
|
if (content.trim()) {
|
|
@@ -1320,12 +1474,12 @@ async function parseClineConfiguration(baseDir = process.cwd()) {
|
|
|
1320
1474
|
}
|
|
1321
1475
|
|
|
1322
1476
|
// src/parsers/copilot.ts
|
|
1323
|
-
import { basename as basename3, join as
|
|
1477
|
+
import { basename as basename3, join as join17 } from "path";
|
|
1324
1478
|
import matter2 from "gray-matter";
|
|
1325
1479
|
async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
1326
1480
|
const errors = [];
|
|
1327
1481
|
const rules = [];
|
|
1328
|
-
const copilotFilePath =
|
|
1482
|
+
const copilotFilePath = join17(baseDir, ".github", "copilot-instructions.md");
|
|
1329
1483
|
if (await fileExists(copilotFilePath)) {
|
|
1330
1484
|
try {
|
|
1331
1485
|
const rawContent = await readFileContent(copilotFilePath);
|
|
@@ -1350,14 +1504,14 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
|
1350
1504
|
errors.push(`Failed to parse copilot-instructions.md: ${errorMessage}`);
|
|
1351
1505
|
}
|
|
1352
1506
|
}
|
|
1353
|
-
const instructionsDir =
|
|
1507
|
+
const instructionsDir = join17(baseDir, ".github", "instructions");
|
|
1354
1508
|
if (await fileExists(instructionsDir)) {
|
|
1355
1509
|
try {
|
|
1356
1510
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
1357
1511
|
const files = await readdir2(instructionsDir);
|
|
1358
1512
|
for (const file of files) {
|
|
1359
1513
|
if (file.endsWith(".instructions.md")) {
|
|
1360
|
-
const filePath =
|
|
1514
|
+
const filePath = join17(instructionsDir, file);
|
|
1361
1515
|
const rawContent = await readFileContent(filePath);
|
|
1362
1516
|
const parsed = matter2(rawContent);
|
|
1363
1517
|
const content = parsed.content.trim();
|
|
@@ -1392,10 +1546,10 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
|
1392
1546
|
}
|
|
1393
1547
|
|
|
1394
1548
|
// src/parsers/cursor.ts
|
|
1395
|
-
import { basename as basename4, join as
|
|
1549
|
+
import { basename as basename4, join as join18 } from "path";
|
|
1396
1550
|
import matter3 from "gray-matter";
|
|
1397
1551
|
import { DEFAULT_SCHEMA, FAILSAFE_SCHEMA, load } from "js-yaml";
|
|
1398
|
-
import { z as z5 } from "zod/
|
|
1552
|
+
import { z as z5 } from "zod/mini";
|
|
1399
1553
|
var customMatterOptions = {
|
|
1400
1554
|
engines: {
|
|
1401
1555
|
yaml: {
|
|
@@ -1517,7 +1671,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
1517
1671
|
const rules = [];
|
|
1518
1672
|
let ignorePatterns;
|
|
1519
1673
|
let mcpServers;
|
|
1520
|
-
const cursorFilePath =
|
|
1674
|
+
const cursorFilePath = join18(baseDir, ".cursorrules");
|
|
1521
1675
|
if (await fileExists(cursorFilePath)) {
|
|
1522
1676
|
try {
|
|
1523
1677
|
const rawContent = await readFileContent(cursorFilePath);
|
|
@@ -1538,14 +1692,14 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
1538
1692
|
errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
|
|
1539
1693
|
}
|
|
1540
1694
|
}
|
|
1541
|
-
const cursorRulesDir =
|
|
1695
|
+
const cursorRulesDir = join18(baseDir, ".cursor", "rules");
|
|
1542
1696
|
if (await fileExists(cursorRulesDir)) {
|
|
1543
1697
|
try {
|
|
1544
1698
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
1545
1699
|
const files = await readdir2(cursorRulesDir);
|
|
1546
1700
|
for (const file of files) {
|
|
1547
1701
|
if (file.endsWith(".mdc")) {
|
|
1548
|
-
const filePath =
|
|
1702
|
+
const filePath = join18(cursorRulesDir, file);
|
|
1549
1703
|
try {
|
|
1550
1704
|
const rawContent = await readFileContent(filePath);
|
|
1551
1705
|
const parsed = matter3(rawContent, customMatterOptions);
|
|
@@ -1574,7 +1728,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
1574
1728
|
if (rules.length === 0) {
|
|
1575
1729
|
errors.push("No Cursor configuration files found (.cursorrules or .cursor/rules/*.mdc)");
|
|
1576
1730
|
}
|
|
1577
|
-
const cursorIgnorePath =
|
|
1731
|
+
const cursorIgnorePath = join18(baseDir, ".cursorignore");
|
|
1578
1732
|
if (await fileExists(cursorIgnorePath)) {
|
|
1579
1733
|
try {
|
|
1580
1734
|
const content = await readFileContent(cursorIgnorePath);
|
|
@@ -1587,7 +1741,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
1587
1741
|
errors.push(`Failed to parse .cursorignore: ${errorMessage}`);
|
|
1588
1742
|
}
|
|
1589
1743
|
}
|
|
1590
|
-
const cursorMcpPath =
|
|
1744
|
+
const cursorMcpPath = join18(baseDir, ".cursor", "mcp.json");
|
|
1591
1745
|
if (await fileExists(cursorMcpPath)) {
|
|
1592
1746
|
try {
|
|
1593
1747
|
const content = await readFileContent(cursorMcpPath);
|
|
@@ -1610,13 +1764,13 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
1610
1764
|
}
|
|
1611
1765
|
|
|
1612
1766
|
// src/parsers/geminicli.ts
|
|
1613
|
-
import { basename as basename5, join as
|
|
1767
|
+
import { basename as basename5, join as join19 } from "path";
|
|
1614
1768
|
async function parseGeminiConfiguration(baseDir = process.cwd()) {
|
|
1615
1769
|
const errors = [];
|
|
1616
1770
|
const rules = [];
|
|
1617
1771
|
let ignorePatterns;
|
|
1618
1772
|
let mcpServers;
|
|
1619
|
-
const geminiFilePath =
|
|
1773
|
+
const geminiFilePath = join19(baseDir, "GEMINI.md");
|
|
1620
1774
|
if (!await fileExists(geminiFilePath)) {
|
|
1621
1775
|
errors.push("GEMINI.md file not found");
|
|
1622
1776
|
return { rules, errors };
|
|
@@ -1627,12 +1781,12 @@ async function parseGeminiConfiguration(baseDir = process.cwd()) {
|
|
|
1627
1781
|
if (mainRule) {
|
|
1628
1782
|
rules.push(mainRule);
|
|
1629
1783
|
}
|
|
1630
|
-
const memoryDir =
|
|
1784
|
+
const memoryDir = join19(baseDir, ".gemini", "memories");
|
|
1631
1785
|
if (await fileExists(memoryDir)) {
|
|
1632
1786
|
const memoryRules = await parseGeminiMemoryFiles(memoryDir);
|
|
1633
1787
|
rules.push(...memoryRules);
|
|
1634
1788
|
}
|
|
1635
|
-
const settingsPath =
|
|
1789
|
+
const settingsPath = join19(baseDir, ".gemini", "settings.json");
|
|
1636
1790
|
if (await fileExists(settingsPath)) {
|
|
1637
1791
|
const settingsResult = await parseGeminiSettings(settingsPath);
|
|
1638
1792
|
if (settingsResult.ignorePatterns) {
|
|
@@ -1643,7 +1797,7 @@ async function parseGeminiConfiguration(baseDir = process.cwd()) {
|
|
|
1643
1797
|
}
|
|
1644
1798
|
errors.push(...settingsResult.errors);
|
|
1645
1799
|
}
|
|
1646
|
-
const aiexcludePath =
|
|
1800
|
+
const aiexcludePath = join19(baseDir, ".aiexclude");
|
|
1647
1801
|
if (await fileExists(aiexcludePath)) {
|
|
1648
1802
|
const aiexcludePatterns = await parseAiexclude(aiexcludePath);
|
|
1649
1803
|
if (aiexcludePatterns.length > 0) {
|
|
@@ -1696,7 +1850,7 @@ async function parseGeminiMemoryFiles(memoryDir) {
|
|
|
1696
1850
|
const files = await readdir2(memoryDir);
|
|
1697
1851
|
for (const file of files) {
|
|
1698
1852
|
if (file.endsWith(".md")) {
|
|
1699
|
-
const filePath =
|
|
1853
|
+
const filePath = join19(memoryDir, file);
|
|
1700
1854
|
const content = await readFileContent(filePath);
|
|
1701
1855
|
if (content.trim()) {
|
|
1702
1856
|
const filename = basename5(file, ".md");
|
|
@@ -1749,11 +1903,11 @@ async function parseAiexclude(aiexcludePath) {
|
|
|
1749
1903
|
}
|
|
1750
1904
|
|
|
1751
1905
|
// src/parsers/roo.ts
|
|
1752
|
-
import { join as
|
|
1906
|
+
import { join as join20 } from "path";
|
|
1753
1907
|
async function parseRooConfiguration(baseDir = process.cwd()) {
|
|
1754
1908
|
const errors = [];
|
|
1755
1909
|
const rules = [];
|
|
1756
|
-
const rooFilePath =
|
|
1910
|
+
const rooFilePath = join20(baseDir, ".roo", "instructions.md");
|
|
1757
1911
|
if (await fileExists(rooFilePath)) {
|
|
1758
1912
|
try {
|
|
1759
1913
|
const content = await readFileContent(rooFilePath);
|
|
@@ -1776,14 +1930,14 @@ async function parseRooConfiguration(baseDir = process.cwd()) {
|
|
|
1776
1930
|
errors.push(`Failed to parse .roo/instructions.md: ${errorMessage}`);
|
|
1777
1931
|
}
|
|
1778
1932
|
}
|
|
1779
|
-
const rooRulesDir =
|
|
1933
|
+
const rooRulesDir = join20(baseDir, ".roo", "rules");
|
|
1780
1934
|
if (await fileExists(rooRulesDir)) {
|
|
1781
1935
|
try {
|
|
1782
1936
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
1783
1937
|
const files = await readdir2(rooRulesDir);
|
|
1784
1938
|
for (const file of files) {
|
|
1785
1939
|
if (file.endsWith(".md")) {
|
|
1786
|
-
const filePath =
|
|
1940
|
+
const filePath = join20(rooRulesDir, file);
|
|
1787
1941
|
try {
|
|
1788
1942
|
const content = await readFileContent(filePath);
|
|
1789
1943
|
if (content.trim()) {
|
|
@@ -1884,7 +2038,7 @@ async function importConfiguration(options) {
|
|
|
1884
2038
|
if (rules.length === 0 && !ignorePatterns && !mcpServers) {
|
|
1885
2039
|
return { success: false, rulesCreated: 0, errors };
|
|
1886
2040
|
}
|
|
1887
|
-
const rulesDirPath =
|
|
2041
|
+
const rulesDirPath = join21(baseDir, rulesDir);
|
|
1888
2042
|
try {
|
|
1889
2043
|
const { mkdir: mkdir3 } = await import("fs/promises");
|
|
1890
2044
|
await mkdir3(rulesDirPath, { recursive: true });
|
|
@@ -1898,7 +2052,7 @@ async function importConfiguration(options) {
|
|
|
1898
2052
|
try {
|
|
1899
2053
|
const baseFilename = `${tool}__${rule.filename}`;
|
|
1900
2054
|
const filename = await generateUniqueFilename(rulesDirPath, baseFilename);
|
|
1901
|
-
const filePath =
|
|
2055
|
+
const filePath = join21(rulesDirPath, `${filename}.md`);
|
|
1902
2056
|
const content = generateRuleFileContent(rule);
|
|
1903
2057
|
await writeFileContent(filePath, content);
|
|
1904
2058
|
rulesCreated++;
|
|
@@ -1913,7 +2067,7 @@ async function importConfiguration(options) {
|
|
|
1913
2067
|
let ignoreFileCreated = false;
|
|
1914
2068
|
if (ignorePatterns && ignorePatterns.length > 0) {
|
|
1915
2069
|
try {
|
|
1916
|
-
const rulesyncignorePath =
|
|
2070
|
+
const rulesyncignorePath = join21(baseDir, ".rulesyncignore");
|
|
1917
2071
|
const ignoreContent = `${ignorePatterns.join("\n")}
|
|
1918
2072
|
`;
|
|
1919
2073
|
await writeFileContent(rulesyncignorePath, ignoreContent);
|
|
@@ -1929,7 +2083,7 @@ async function importConfiguration(options) {
|
|
|
1929
2083
|
let mcpFileCreated = false;
|
|
1930
2084
|
if (mcpServers && Object.keys(mcpServers).length > 0) {
|
|
1931
2085
|
try {
|
|
1932
|
-
const mcpPath =
|
|
2086
|
+
const mcpPath = join21(baseDir, rulesDir, ".mcp.json");
|
|
1933
2087
|
const mcpContent = `${JSON.stringify({ mcpServers }, null, 2)}
|
|
1934
2088
|
`;
|
|
1935
2089
|
await writeFileContent(mcpPath, mcpContent);
|
|
@@ -1943,7 +2097,7 @@ async function importConfiguration(options) {
|
|
|
1943
2097
|
}
|
|
1944
2098
|
}
|
|
1945
2099
|
return {
|
|
1946
|
-
success: rulesCreated > 0 || ignoreFileCreated || mcpFileCreated,
|
|
2100
|
+
success: errors.length === 0 && (rulesCreated > 0 || ignoreFileCreated || mcpFileCreated),
|
|
1947
2101
|
rulesCreated,
|
|
1948
2102
|
errors,
|
|
1949
2103
|
ignoreFileCreated,
|
|
@@ -1957,7 +2111,7 @@ function generateRuleFileContent(rule) {
|
|
|
1957
2111
|
async function generateUniqueFilename(rulesDir, baseFilename) {
|
|
1958
2112
|
let filename = baseFilename;
|
|
1959
2113
|
let counter = 1;
|
|
1960
|
-
while (await fileExists(
|
|
2114
|
+
while (await fileExists(join21(rulesDir, `${filename}.md`))) {
|
|
1961
2115
|
filename = `${baseFilename}-${counter}`;
|
|
1962
2116
|
counter++;
|
|
1963
2117
|
}
|
|
@@ -2022,7 +2176,7 @@ async function importCommand(options = {}) {
|
|
|
2022
2176
|
}
|
|
2023
2177
|
|
|
2024
2178
|
// src/cli/commands/init.ts
|
|
2025
|
-
import { join as
|
|
2179
|
+
import { join as join22 } from "path";
|
|
2026
2180
|
async function initCommand() {
|
|
2027
2181
|
const aiRulesDir = ".rulesync";
|
|
2028
2182
|
console.log("Initializing rulesync...");
|
|
@@ -2069,7 +2223,7 @@ globs: ["**/*"]
|
|
|
2069
2223
|
- Follow single responsibility principle
|
|
2070
2224
|
`
|
|
2071
2225
|
};
|
|
2072
|
-
const filepath =
|
|
2226
|
+
const filepath = join22(aiRulesDir, sampleFile.filename);
|
|
2073
2227
|
if (!await fileExists(filepath)) {
|
|
2074
2228
|
await writeFileContent(filepath, sampleFile.content);
|
|
2075
2229
|
console.log(`Created ${filepath}`);
|
|
@@ -2213,12 +2367,12 @@ async function watchCommand() {
|
|
|
2213
2367
|
|
|
2214
2368
|
// src/cli/index.ts
|
|
2215
2369
|
var program = new Command();
|
|
2216
|
-
program.name("rulesync").description("Unified AI rules management CLI tool").version("0.
|
|
2370
|
+
program.name("rulesync").description("Unified AI rules management CLI tool").version("0.48.0");
|
|
2217
2371
|
program.command("init").description("Initialize rulesync in current directory").action(initCommand);
|
|
2218
2372
|
program.command("add <filename>").description("Add a new rule file").action(addCommand);
|
|
2219
2373
|
program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
|
|
2220
2374
|
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);
|
|
2221
|
-
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(
|
|
2375
|
+
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(
|
|
2222
2376
|
"-b, --base-dir <paths>",
|
|
2223
2377
|
"Base directories to generate files (comma-separated for multiple paths)"
|
|
2224
2378
|
).option("-v, --verbose", "Verbose output").action(async (options) => {
|
|
@@ -2229,6 +2383,7 @@ program.command("generate").description("Generate configuration files for AI too
|
|
|
2229
2383
|
if (options.claudecode) tools.push("claudecode");
|
|
2230
2384
|
if (options.roo) tools.push("roo");
|
|
2231
2385
|
if (options.geminicli) tools.push("geminicli");
|
|
2386
|
+
if (options.kiro) tools.push("kiro");
|
|
2232
2387
|
const generateOptions = {
|
|
2233
2388
|
verbose: options.verbose,
|
|
2234
2389
|
delete: options.delete
|