rulesync 0.33.0 → 0.36.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 +104 -8
- package/README.md +104 -8
- package/dist/chunk-6PQ4APY4.mjs +70 -0
- package/dist/chunk-QHXMJZTJ.mjs +62 -0
- package/dist/chunk-QVPQ2X4L.mjs +77 -0
- package/dist/chunk-SBYRCTWS.mjs +64 -0
- package/dist/chunk-UNTCJDMQ.mjs +73 -0
- package/dist/chunk-YGXGGUBG.mjs +80 -0
- package/dist/claude-O4SRX6VC.mjs +8 -0
- package/dist/cline-H5JF2OPT.mjs +8 -0
- package/dist/copilot-GCIYHK4Q.mjs +8 -0
- package/dist/cursor-N75OH6WS.mjs +8 -0
- package/dist/geminicli-AGOQ32ZE.mjs +8 -0
- package/dist/index.js +1291 -214
- package/dist/index.mjs +990 -207
- package/dist/roo-V5YVC222.mjs +8 -0
- package/package.json +14 -6
package/dist/index.js
CHANGED
|
@@ -6,6 +6,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
9
12
|
var __copyProps = (to, from, except, desc) => {
|
|
10
13
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
14
|
for (let key of __getOwnPropNames(from))
|
|
@@ -23,6 +26,307 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
26
|
mod
|
|
24
27
|
));
|
|
25
28
|
|
|
29
|
+
// src/generators/mcp/claude.ts
|
|
30
|
+
function generateClaudeMcp(config, _target) {
|
|
31
|
+
const claudeSettings = {
|
|
32
|
+
mcpServers: {}
|
|
33
|
+
};
|
|
34
|
+
const shouldInclude = (server) => {
|
|
35
|
+
if (!server.rulesyncTargets || server.rulesyncTargets.length === 0) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
if (server.rulesyncTargets.length === 1 && server.rulesyncTargets[0] === "*") {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
return server.rulesyncTargets.includes("claude");
|
|
42
|
+
};
|
|
43
|
+
for (const [serverName, server] of Object.entries(config.mcpServers)) {
|
|
44
|
+
if (!shouldInclude(server)) continue;
|
|
45
|
+
const claudeServer = {};
|
|
46
|
+
if (server.command) {
|
|
47
|
+
claudeServer.command = server.command;
|
|
48
|
+
if (server.args) claudeServer.args = server.args;
|
|
49
|
+
} else if (server.url || server.httpUrl) {
|
|
50
|
+
const url = server.httpUrl || server.url;
|
|
51
|
+
if (url) {
|
|
52
|
+
claudeServer.url = url;
|
|
53
|
+
}
|
|
54
|
+
if (server.httpUrl) {
|
|
55
|
+
claudeServer.transport = "http";
|
|
56
|
+
} else if (server.transport === "sse") {
|
|
57
|
+
claudeServer.transport = "sse";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (server.env) {
|
|
61
|
+
claudeServer.env = server.env;
|
|
62
|
+
}
|
|
63
|
+
claudeSettings.mcpServers[serverName] = claudeServer;
|
|
64
|
+
}
|
|
65
|
+
return JSON.stringify(claudeSettings, null, 2);
|
|
66
|
+
}
|
|
67
|
+
var init_claude = __esm({
|
|
68
|
+
"src/generators/mcp/claude.ts"() {
|
|
69
|
+
"use strict";
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// src/generators/mcp/cline.ts
|
|
74
|
+
function generateClineMcp(config, _target) {
|
|
75
|
+
const clineConfig = {
|
|
76
|
+
mcpServers: {}
|
|
77
|
+
};
|
|
78
|
+
const shouldInclude = (server) => {
|
|
79
|
+
const targets = server.rulesyncTargets;
|
|
80
|
+
if (!targets || targets.length === 0) return true;
|
|
81
|
+
if (targets.length === 1 && targets[0] === "*") return true;
|
|
82
|
+
return targets.includes("cline");
|
|
83
|
+
};
|
|
84
|
+
for (const [serverName, server] of Object.entries(config.mcpServers)) {
|
|
85
|
+
if (!shouldInclude(server)) continue;
|
|
86
|
+
const clineServer = {};
|
|
87
|
+
if (server.command) {
|
|
88
|
+
clineServer.command = server.command;
|
|
89
|
+
if (server.args) clineServer.args = server.args;
|
|
90
|
+
} else if (server.url) {
|
|
91
|
+
clineServer.url = server.url;
|
|
92
|
+
}
|
|
93
|
+
if (server.env) {
|
|
94
|
+
clineServer.env = server.env;
|
|
95
|
+
}
|
|
96
|
+
if (server.disabled !== void 0) {
|
|
97
|
+
clineServer.disabled = server.disabled;
|
|
98
|
+
}
|
|
99
|
+
if (server.alwaysAllow) {
|
|
100
|
+
clineServer.alwaysAllow = server.alwaysAllow;
|
|
101
|
+
}
|
|
102
|
+
if (server.networkTimeout !== void 0) {
|
|
103
|
+
clineServer.networkTimeout = server.networkTimeout;
|
|
104
|
+
}
|
|
105
|
+
clineConfig.mcpServers[serverName] = clineServer;
|
|
106
|
+
}
|
|
107
|
+
return JSON.stringify(clineConfig, null, 2);
|
|
108
|
+
}
|
|
109
|
+
var init_cline = __esm({
|
|
110
|
+
"src/generators/mcp/cline.ts"() {
|
|
111
|
+
"use strict";
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// src/generators/mcp/copilot.ts
|
|
116
|
+
function generateCopilotMcp(config, target) {
|
|
117
|
+
const shouldInclude = (server) => {
|
|
118
|
+
const targets = server.rulesyncTargets;
|
|
119
|
+
if (!targets || targets.length === 0) return true;
|
|
120
|
+
if (targets.length === 1 && targets[0] === "*") return true;
|
|
121
|
+
return targets.includes("copilot");
|
|
122
|
+
};
|
|
123
|
+
const servers = {};
|
|
124
|
+
const inputs = [];
|
|
125
|
+
const inputMap = /* @__PURE__ */ new Map();
|
|
126
|
+
for (const [serverName, server] of Object.entries(config.mcpServers)) {
|
|
127
|
+
if (!shouldInclude(server)) continue;
|
|
128
|
+
const copilotServer = {};
|
|
129
|
+
if (server.command) {
|
|
130
|
+
copilotServer.command = server.command;
|
|
131
|
+
if (server.args) copilotServer.args = server.args;
|
|
132
|
+
} else if (server.url || server.httpUrl) {
|
|
133
|
+
const url = server.httpUrl || server.url;
|
|
134
|
+
if (url) {
|
|
135
|
+
copilotServer.url = url;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (server.env) {
|
|
139
|
+
copilotServer.env = {};
|
|
140
|
+
for (const [key, value] of Object.entries(server.env)) {
|
|
141
|
+
if (target === "editor" && value.includes("SECRET")) {
|
|
142
|
+
const inputId = `${serverName}_${key}`;
|
|
143
|
+
inputMap.set(inputId, value);
|
|
144
|
+
copilotServer.env[key] = `\${input:${inputId}}`;
|
|
145
|
+
inputs.push({
|
|
146
|
+
id: inputId,
|
|
147
|
+
type: "password",
|
|
148
|
+
description: `${key} for ${serverName}`
|
|
149
|
+
});
|
|
150
|
+
} else {
|
|
151
|
+
copilotServer.env[key] = value;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (server.tools) {
|
|
156
|
+
copilotServer.tools = server.tools;
|
|
157
|
+
} else if (server.alwaysAllow) {
|
|
158
|
+
copilotServer.tools = server.alwaysAllow;
|
|
159
|
+
}
|
|
160
|
+
servers[serverName] = copilotServer;
|
|
161
|
+
}
|
|
162
|
+
if (target === "codingAgent") {
|
|
163
|
+
const config2 = { mcpServers: servers };
|
|
164
|
+
return JSON.stringify(config2, null, 2);
|
|
165
|
+
} else {
|
|
166
|
+
const config2 = { servers };
|
|
167
|
+
if (inputs.length > 0) {
|
|
168
|
+
config2.inputs = inputs;
|
|
169
|
+
}
|
|
170
|
+
return JSON.stringify(config2, null, 2);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
var init_copilot = __esm({
|
|
174
|
+
"src/generators/mcp/copilot.ts"() {
|
|
175
|
+
"use strict";
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// src/generators/mcp/cursor.ts
|
|
180
|
+
function generateCursorMcp(config, _target) {
|
|
181
|
+
const cursorConfig = {
|
|
182
|
+
mcpServers: {}
|
|
183
|
+
};
|
|
184
|
+
const shouldInclude = (server) => {
|
|
185
|
+
const targets = server.rulesyncTargets;
|
|
186
|
+
if (!targets || targets.length === 0) return true;
|
|
187
|
+
if (targets.length === 1 && targets[0] === "*") return true;
|
|
188
|
+
return targets.includes("cursor");
|
|
189
|
+
};
|
|
190
|
+
for (const [serverName, server] of Object.entries(config.mcpServers)) {
|
|
191
|
+
if (!shouldInclude(server)) continue;
|
|
192
|
+
const cursorServer = {};
|
|
193
|
+
if (server.command) {
|
|
194
|
+
cursorServer.command = server.command;
|
|
195
|
+
if (server.args) cursorServer.args = server.args;
|
|
196
|
+
} else if (server.url || server.httpUrl) {
|
|
197
|
+
const url = server.httpUrl || server.url;
|
|
198
|
+
if (url) {
|
|
199
|
+
cursorServer.url = url;
|
|
200
|
+
}
|
|
201
|
+
if (server.httpUrl || server.transport === "http") {
|
|
202
|
+
cursorServer.type = "streamable-http";
|
|
203
|
+
} else if (server.transport === "sse" || server.type === "sse") {
|
|
204
|
+
cursorServer.type = "sse";
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (server.env) {
|
|
208
|
+
cursorServer.env = server.env;
|
|
209
|
+
}
|
|
210
|
+
if (server.cwd) {
|
|
211
|
+
cursorServer.cwd = server.cwd;
|
|
212
|
+
}
|
|
213
|
+
cursorConfig.mcpServers[serverName] = cursorServer;
|
|
214
|
+
}
|
|
215
|
+
return JSON.stringify(cursorConfig, null, 2);
|
|
216
|
+
}
|
|
217
|
+
var init_cursor = __esm({
|
|
218
|
+
"src/generators/mcp/cursor.ts"() {
|
|
219
|
+
"use strict";
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// src/generators/mcp/geminicli.ts
|
|
224
|
+
function generateGeminiCliMcp(config, _target) {
|
|
225
|
+
const geminiSettings = {
|
|
226
|
+
mcpServers: {}
|
|
227
|
+
};
|
|
228
|
+
const shouldInclude = (server) => {
|
|
229
|
+
const targets = server.rulesyncTargets;
|
|
230
|
+
if (!targets || targets.length === 0) return true;
|
|
231
|
+
if (targets.length === 1 && targets[0] === "*") return true;
|
|
232
|
+
return targets.includes("geminicli");
|
|
233
|
+
};
|
|
234
|
+
for (const [serverName, server] of Object.entries(config.mcpServers)) {
|
|
235
|
+
if (!shouldInclude(server)) continue;
|
|
236
|
+
const geminiServer = {};
|
|
237
|
+
if (server.command) {
|
|
238
|
+
geminiServer.command = server.command;
|
|
239
|
+
if (server.args) geminiServer.args = server.args;
|
|
240
|
+
} else if (server.url || server.httpUrl) {
|
|
241
|
+
if (server.httpUrl) {
|
|
242
|
+
geminiServer.httpUrl = server.httpUrl;
|
|
243
|
+
} else if (server.url) {
|
|
244
|
+
geminiServer.url = server.url;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (server.env) {
|
|
248
|
+
geminiServer.env = {};
|
|
249
|
+
for (const [key, value] of Object.entries(server.env)) {
|
|
250
|
+
if (value.startsWith("${") && value.endsWith("}")) {
|
|
251
|
+
geminiServer.env[key] = value;
|
|
252
|
+
} else {
|
|
253
|
+
geminiServer.env[key] = `\${${value}}`;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (server.timeout !== void 0) {
|
|
258
|
+
geminiServer.timeout = server.timeout;
|
|
259
|
+
}
|
|
260
|
+
if (server.trust !== void 0) {
|
|
261
|
+
geminiServer.trust = server.trust;
|
|
262
|
+
}
|
|
263
|
+
geminiSettings.mcpServers[serverName] = geminiServer;
|
|
264
|
+
}
|
|
265
|
+
return JSON.stringify(geminiSettings, null, 2);
|
|
266
|
+
}
|
|
267
|
+
var init_geminicli = __esm({
|
|
268
|
+
"src/generators/mcp/geminicli.ts"() {
|
|
269
|
+
"use strict";
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// src/generators/mcp/roo.ts
|
|
274
|
+
function generateRooMcp(config, _target) {
|
|
275
|
+
const rooConfig = {
|
|
276
|
+
mcpServers: {}
|
|
277
|
+
};
|
|
278
|
+
const shouldInclude = (server) => {
|
|
279
|
+
const targets = server.rulesyncTargets;
|
|
280
|
+
if (!targets || targets.length === 0) return true;
|
|
281
|
+
if (targets.length === 1 && targets[0] === "*") return true;
|
|
282
|
+
return targets.includes("roo");
|
|
283
|
+
};
|
|
284
|
+
for (const [serverName, server] of Object.entries(config.mcpServers)) {
|
|
285
|
+
if (!shouldInclude(server)) continue;
|
|
286
|
+
const rooServer = {};
|
|
287
|
+
if (server.command) {
|
|
288
|
+
rooServer.command = server.command;
|
|
289
|
+
if (server.args) rooServer.args = server.args;
|
|
290
|
+
} else if (server.url || server.httpUrl) {
|
|
291
|
+
const url = server.httpUrl || server.url;
|
|
292
|
+
if (url) {
|
|
293
|
+
rooServer.url = url;
|
|
294
|
+
}
|
|
295
|
+
if (server.httpUrl || server.transport === "http") {
|
|
296
|
+
rooServer.type = "streamable-http";
|
|
297
|
+
} else if (server.transport === "sse" || server.type === "sse") {
|
|
298
|
+
rooServer.type = "sse";
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (server.env) {
|
|
302
|
+
rooServer.env = {};
|
|
303
|
+
for (const [key, value] of Object.entries(server.env)) {
|
|
304
|
+
if (value.startsWith("${env:") && value.endsWith("}")) {
|
|
305
|
+
rooServer.env[key] = value;
|
|
306
|
+
} else {
|
|
307
|
+
rooServer.env[key] = `\${env:${value}}`;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (server.disabled !== void 0) {
|
|
312
|
+
rooServer.disabled = server.disabled;
|
|
313
|
+
}
|
|
314
|
+
if (server.alwaysAllow) {
|
|
315
|
+
rooServer.alwaysAllow = server.alwaysAllow;
|
|
316
|
+
}
|
|
317
|
+
if (server.networkTimeout !== void 0) {
|
|
318
|
+
rooServer.networkTimeout = Math.max(3e4, Math.min(3e5, server.networkTimeout));
|
|
319
|
+
}
|
|
320
|
+
rooConfig.mcpServers[serverName] = rooServer;
|
|
321
|
+
}
|
|
322
|
+
return JSON.stringify(rooConfig, null, 2);
|
|
323
|
+
}
|
|
324
|
+
var init_roo = __esm({
|
|
325
|
+
"src/generators/mcp/roo.ts"() {
|
|
326
|
+
"use strict";
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
26
330
|
// src/cli/index.ts
|
|
27
331
|
var import_commander = require("commander");
|
|
28
332
|
|
|
@@ -39,10 +343,12 @@ function getDefaultConfig() {
|
|
|
39
343
|
cursor: ".cursor/rules",
|
|
40
344
|
cline: ".clinerules",
|
|
41
345
|
claudecode: ".",
|
|
42
|
-
|
|
346
|
+
claude: ".",
|
|
347
|
+
roo: ".roo/rules",
|
|
348
|
+
geminicli: ".gemini/memories"
|
|
43
349
|
},
|
|
44
350
|
watchEnabled: false,
|
|
45
|
-
defaultTargets: ["copilot", "cursor", "cline", "claudecode", "roo"]
|
|
351
|
+
defaultTargets: ["copilot", "cursor", "cline", "claudecode", "claude", "roo", "geminicli"]
|
|
46
352
|
};
|
|
47
353
|
}
|
|
48
354
|
function resolveTargets(targets, config) {
|
|
@@ -88,27 +394,158 @@ async function addCommand(filename) {
|
|
|
88
394
|
}
|
|
89
395
|
}
|
|
90
396
|
|
|
91
|
-
// src/generators/claudecode.ts
|
|
397
|
+
// src/generators/rules/claudecode.ts
|
|
398
|
+
var import_node_path4 = require("path");
|
|
399
|
+
|
|
400
|
+
// src/utils/file.ts
|
|
401
|
+
var import_promises2 = require("fs/promises");
|
|
402
|
+
var import_node_path3 = require("path");
|
|
403
|
+
|
|
404
|
+
// src/utils/ignore.ts
|
|
92
405
|
var import_node_path2 = require("path");
|
|
406
|
+
var import_micromatch = __toESM(require("micromatch"));
|
|
407
|
+
var cachedIgnorePatterns = null;
|
|
408
|
+
async function loadIgnorePatterns(baseDir = process.cwd()) {
|
|
409
|
+
if (cachedIgnorePatterns) {
|
|
410
|
+
return cachedIgnorePatterns;
|
|
411
|
+
}
|
|
412
|
+
const ignorePath = (0, import_node_path2.join)(baseDir, ".rulesyncignore");
|
|
413
|
+
if (!await fileExists(ignorePath)) {
|
|
414
|
+
cachedIgnorePatterns = { patterns: [] };
|
|
415
|
+
return cachedIgnorePatterns;
|
|
416
|
+
}
|
|
417
|
+
try {
|
|
418
|
+
const content = await readFileContent(ignorePath);
|
|
419
|
+
const patterns = parseIgnoreFile(content);
|
|
420
|
+
cachedIgnorePatterns = { patterns };
|
|
421
|
+
return cachedIgnorePatterns;
|
|
422
|
+
} catch (error) {
|
|
423
|
+
console.warn(`Failed to read .rulesyncignore: ${error}`);
|
|
424
|
+
cachedIgnorePatterns = { patterns: [] };
|
|
425
|
+
return cachedIgnorePatterns;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function parseIgnoreFile(content) {
|
|
429
|
+
return content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
430
|
+
}
|
|
431
|
+
function isFileIgnored(filepath, ignorePatterns) {
|
|
432
|
+
if (ignorePatterns.length === 0) {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
const negationPatterns = ignorePatterns.filter((p) => p.startsWith("!"));
|
|
436
|
+
const positivePatterns = ignorePatterns.filter((p) => !p.startsWith("!"));
|
|
437
|
+
const isIgnored = positivePatterns.length > 0 && import_micromatch.default.isMatch(filepath, positivePatterns, {
|
|
438
|
+
dot: true
|
|
439
|
+
});
|
|
440
|
+
if (isIgnored && negationPatterns.length > 0) {
|
|
441
|
+
const negationPatternsWithoutPrefix = negationPatterns.map((p) => p.substring(1));
|
|
442
|
+
return !import_micromatch.default.isMatch(filepath, negationPatternsWithoutPrefix, {
|
|
443
|
+
dot: true
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
return isIgnored;
|
|
447
|
+
}
|
|
448
|
+
function filterIgnoredFiles(files, ignorePatterns) {
|
|
449
|
+
if (ignorePatterns.length === 0) {
|
|
450
|
+
return files;
|
|
451
|
+
}
|
|
452
|
+
return files.filter((file) => !isFileIgnored(file, ignorePatterns));
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// src/utils/file.ts
|
|
456
|
+
async function ensureDir(dirPath) {
|
|
457
|
+
try {
|
|
458
|
+
await (0, import_promises2.stat)(dirPath);
|
|
459
|
+
} catch {
|
|
460
|
+
await (0, import_promises2.mkdir)(dirPath, { recursive: true });
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
async function readFileContent(filepath) {
|
|
464
|
+
return (0, import_promises2.readFile)(filepath, "utf-8");
|
|
465
|
+
}
|
|
466
|
+
async function writeFileContent(filepath, content) {
|
|
467
|
+
await ensureDir((0, import_node_path3.dirname)(filepath));
|
|
468
|
+
await (0, import_promises2.writeFile)(filepath, content, "utf-8");
|
|
469
|
+
}
|
|
470
|
+
async function findFiles(dir, extension = ".md", ignorePatterns) {
|
|
471
|
+
try {
|
|
472
|
+
const files = await (0, import_promises2.readdir)(dir);
|
|
473
|
+
const filtered = files.filter((file) => file.endsWith(extension)).map((file) => (0, import_node_path3.join)(dir, file));
|
|
474
|
+
if (ignorePatterns && ignorePatterns.length > 0) {
|
|
475
|
+
return filterIgnoredFiles(filtered, ignorePatterns);
|
|
476
|
+
}
|
|
477
|
+
return filtered;
|
|
478
|
+
} catch {
|
|
479
|
+
return [];
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
async function fileExists(filepath) {
|
|
483
|
+
try {
|
|
484
|
+
await (0, import_promises2.stat)(filepath);
|
|
485
|
+
return true;
|
|
486
|
+
} catch {
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
async function removeDirectory(dirPath) {
|
|
491
|
+
const dangerousPaths = [".", "/", "~", "src", "node_modules"];
|
|
492
|
+
if (dangerousPaths.includes(dirPath) || dirPath === "") {
|
|
493
|
+
console.warn(`Skipping deletion of dangerous path: ${dirPath}`);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
try {
|
|
497
|
+
if (await fileExists(dirPath)) {
|
|
498
|
+
await (0, import_promises2.rm)(dirPath, { recursive: true, force: true });
|
|
499
|
+
}
|
|
500
|
+
} catch (error) {
|
|
501
|
+
console.warn(`Failed to remove directory ${dirPath}:`, error);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
async function removeFile(filepath) {
|
|
505
|
+
try {
|
|
506
|
+
if (await fileExists(filepath)) {
|
|
507
|
+
await (0, import_promises2.rm)(filepath);
|
|
508
|
+
}
|
|
509
|
+
} catch (error) {
|
|
510
|
+
console.warn(`Failed to remove file ${filepath}:`, error);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
async function removeClaudeGeneratedFiles() {
|
|
514
|
+
const filesToRemove = ["CLAUDE.md", ".claude/memories"];
|
|
515
|
+
for (const fileOrDir of filesToRemove) {
|
|
516
|
+
if (fileOrDir.endsWith("/memories")) {
|
|
517
|
+
await removeDirectory(fileOrDir);
|
|
518
|
+
} else {
|
|
519
|
+
await removeFile(fileOrDir);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// src/generators/rules/claudecode.ts
|
|
93
525
|
async function generateClaudecodeConfig(rules, config, baseDir) {
|
|
94
526
|
const outputs = [];
|
|
95
527
|
const rootRules = rules.filter((r) => r.frontmatter.root === true);
|
|
96
528
|
const detailRules = rules.filter((r) => r.frontmatter.root === false);
|
|
97
529
|
const claudeMdContent = generateClaudeMarkdown(rootRules, detailRules);
|
|
98
|
-
const claudeOutputDir = baseDir ? (0,
|
|
530
|
+
const claudeOutputDir = baseDir ? (0, import_node_path4.join)(baseDir, config.outputPaths.claudecode) : config.outputPaths.claudecode;
|
|
99
531
|
outputs.push({
|
|
100
532
|
tool: "claudecode",
|
|
101
|
-
filepath: (0,
|
|
533
|
+
filepath: (0, import_node_path4.join)(claudeOutputDir, "CLAUDE.md"),
|
|
102
534
|
content: claudeMdContent
|
|
103
535
|
});
|
|
104
536
|
for (const rule of detailRules) {
|
|
105
537
|
const memoryContent = generateMemoryFile(rule);
|
|
106
538
|
outputs.push({
|
|
107
539
|
tool: "claudecode",
|
|
108
|
-
filepath: (0,
|
|
540
|
+
filepath: (0, import_node_path4.join)(claudeOutputDir, ".claude", "memories", `${rule.filename}.md`),
|
|
109
541
|
content: memoryContent
|
|
110
542
|
});
|
|
111
543
|
}
|
|
544
|
+
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
545
|
+
if (ignorePatterns.patterns.length > 0) {
|
|
546
|
+
const settingsPath = baseDir ? (0, import_node_path4.join)(baseDir, ".claude", "settings.json") : (0, import_node_path4.join)(".claude", "settings.json");
|
|
547
|
+
await updateClaudeSettings(settingsPath, ignorePatterns.patterns);
|
|
548
|
+
}
|
|
112
549
|
return outputs;
|
|
113
550
|
}
|
|
114
551
|
function generateClaudeMarkdown(rootRules, detailRules) {
|
|
@@ -137,42 +574,101 @@ function generateClaudeMarkdown(rootRules, detailRules) {
|
|
|
137
574
|
function generateMemoryFile(rule) {
|
|
138
575
|
return rule.content.trim();
|
|
139
576
|
}
|
|
577
|
+
async function updateClaudeSettings(settingsPath, ignorePatterns) {
|
|
578
|
+
let settings = {};
|
|
579
|
+
if (await fileExists(settingsPath)) {
|
|
580
|
+
try {
|
|
581
|
+
const content = await readFileContent(settingsPath);
|
|
582
|
+
settings = JSON.parse(content);
|
|
583
|
+
} catch (_error) {
|
|
584
|
+
console.warn(`Failed to parse existing ${settingsPath}, creating new settings`);
|
|
585
|
+
settings = {};
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
if (!settings.permissions) {
|
|
589
|
+
settings.permissions = {};
|
|
590
|
+
}
|
|
591
|
+
if (!Array.isArray(settings.permissions.deny)) {
|
|
592
|
+
settings.permissions.deny = [];
|
|
593
|
+
}
|
|
594
|
+
const readDenyRules = ignorePatterns.map((pattern) => `Read(${pattern})`);
|
|
595
|
+
settings.permissions.deny = settings.permissions.deny.filter((rule) => {
|
|
596
|
+
if (!rule.startsWith("Read(")) return true;
|
|
597
|
+
const match = rule.match(/^Read\((.*)\)$/);
|
|
598
|
+
if (!match) return true;
|
|
599
|
+
return !ignorePatterns.includes(match[1] ?? "");
|
|
600
|
+
});
|
|
601
|
+
settings.permissions.deny.push(...readDenyRules);
|
|
602
|
+
settings.permissions.deny = [...new Set(settings.permissions.deny)];
|
|
603
|
+
const jsonContent = JSON.stringify(settings, null, 2);
|
|
604
|
+
await writeFileContent(settingsPath, jsonContent);
|
|
605
|
+
console.log(`\u2705 Updated Claude Code settings: ${settingsPath}`);
|
|
606
|
+
}
|
|
140
607
|
|
|
141
|
-
// src/generators/cline.ts
|
|
142
|
-
var
|
|
608
|
+
// src/generators/rules/cline.ts
|
|
609
|
+
var import_node_path5 = require("path");
|
|
143
610
|
async function generateClineConfig(rules, config, baseDir) {
|
|
144
611
|
const outputs = [];
|
|
145
612
|
for (const rule of rules) {
|
|
146
613
|
const content = generateClineMarkdown(rule);
|
|
147
|
-
const outputDir = baseDir ? (0,
|
|
148
|
-
const filepath = (0,
|
|
614
|
+
const outputDir = baseDir ? (0, import_node_path5.join)(baseDir, config.outputPaths.cline) : config.outputPaths.cline;
|
|
615
|
+
const filepath = (0, import_node_path5.join)(outputDir, `${rule.filename}.md`);
|
|
149
616
|
outputs.push({
|
|
150
617
|
tool: "cline",
|
|
151
618
|
filepath,
|
|
152
619
|
content
|
|
153
620
|
});
|
|
154
621
|
}
|
|
622
|
+
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
623
|
+
if (ignorePatterns.patterns.length > 0) {
|
|
624
|
+
const clineIgnorePath = baseDir ? (0, import_node_path5.join)(baseDir, ".clineignore") : ".clineignore";
|
|
625
|
+
const clineIgnoreContent = generateClineIgnore(ignorePatterns.patterns);
|
|
626
|
+
outputs.push({
|
|
627
|
+
tool: "cline",
|
|
628
|
+
filepath: clineIgnorePath,
|
|
629
|
+
content: clineIgnoreContent
|
|
630
|
+
});
|
|
631
|
+
}
|
|
155
632
|
return outputs;
|
|
156
633
|
}
|
|
157
634
|
function generateClineMarkdown(rule) {
|
|
158
635
|
return rule.content.trim();
|
|
159
636
|
}
|
|
637
|
+
function generateClineIgnore(patterns) {
|
|
638
|
+
const lines = [
|
|
639
|
+
"# Generated by rulesync from .rulesyncignore",
|
|
640
|
+
"# This file is automatically generated. Do not edit manually.",
|
|
641
|
+
"",
|
|
642
|
+
...patterns
|
|
643
|
+
];
|
|
644
|
+
return lines.join("\n");
|
|
645
|
+
}
|
|
160
646
|
|
|
161
|
-
// src/generators/copilot.ts
|
|
162
|
-
var
|
|
647
|
+
// src/generators/rules/copilot.ts
|
|
648
|
+
var import_node_path6 = require("path");
|
|
163
649
|
async function generateCopilotConfig(rules, config, baseDir) {
|
|
164
650
|
const outputs = [];
|
|
165
651
|
for (const rule of rules) {
|
|
166
652
|
const content = generateCopilotMarkdown(rule);
|
|
167
653
|
const baseFilename = rule.filename.replace(/\.md$/, "");
|
|
168
|
-
const outputDir = baseDir ? (0,
|
|
169
|
-
const filepath = (0,
|
|
654
|
+
const outputDir = baseDir ? (0, import_node_path6.join)(baseDir, config.outputPaths.copilot) : config.outputPaths.copilot;
|
|
655
|
+
const filepath = (0, import_node_path6.join)(outputDir, `${baseFilename}.instructions.md`);
|
|
170
656
|
outputs.push({
|
|
171
657
|
tool: "copilot",
|
|
172
658
|
filepath,
|
|
173
659
|
content
|
|
174
660
|
});
|
|
175
661
|
}
|
|
662
|
+
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
663
|
+
if (ignorePatterns.patterns.length > 0) {
|
|
664
|
+
const copilotIgnorePath = baseDir ? (0, import_node_path6.join)(baseDir, ".copilotignore") : ".copilotignore";
|
|
665
|
+
const copilotIgnoreContent = generateCopilotIgnore(ignorePatterns.patterns);
|
|
666
|
+
outputs.push({
|
|
667
|
+
tool: "copilot",
|
|
668
|
+
filepath: copilotIgnorePath,
|
|
669
|
+
content: copilotIgnoreContent
|
|
670
|
+
});
|
|
671
|
+
}
|
|
176
672
|
return outputs;
|
|
177
673
|
}
|
|
178
674
|
function generateCopilotMarkdown(rule) {
|
|
@@ -188,21 +684,42 @@ function generateCopilotMarkdown(rule) {
|
|
|
188
684
|
lines.push(rule.content);
|
|
189
685
|
return lines.join("\n");
|
|
190
686
|
}
|
|
687
|
+
function generateCopilotIgnore(patterns) {
|
|
688
|
+
const lines = [
|
|
689
|
+
"# Generated by rulesync from .rulesyncignore",
|
|
690
|
+
"# This file is automatically generated. Do not edit manually.",
|
|
691
|
+
"# Note: .copilotignore is not officially supported by GitHub Copilot.",
|
|
692
|
+
"# This file is for use with community tools like copilotignore-vscode extension.",
|
|
693
|
+
"",
|
|
694
|
+
...patterns
|
|
695
|
+
];
|
|
696
|
+
return lines.join("\n");
|
|
697
|
+
}
|
|
191
698
|
|
|
192
|
-
// src/generators/cursor.ts
|
|
193
|
-
var
|
|
699
|
+
// src/generators/rules/cursor.ts
|
|
700
|
+
var import_node_path7 = require("path");
|
|
194
701
|
async function generateCursorConfig(rules, config, baseDir) {
|
|
195
702
|
const outputs = [];
|
|
196
703
|
for (const rule of rules) {
|
|
197
704
|
const content = generateCursorMarkdown(rule);
|
|
198
|
-
const outputDir = baseDir ? (0,
|
|
199
|
-
const filepath = (0,
|
|
705
|
+
const outputDir = baseDir ? (0, import_node_path7.join)(baseDir, config.outputPaths.cursor) : config.outputPaths.cursor;
|
|
706
|
+
const filepath = (0, import_node_path7.join)(outputDir, `${rule.filename}.mdc`);
|
|
200
707
|
outputs.push({
|
|
201
708
|
tool: "cursor",
|
|
202
709
|
filepath,
|
|
203
710
|
content
|
|
204
711
|
});
|
|
205
712
|
}
|
|
713
|
+
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
714
|
+
if (ignorePatterns.patterns.length > 0) {
|
|
715
|
+
const cursorIgnorePath = baseDir ? (0, import_node_path7.join)(baseDir, ".cursorignore") : ".cursorignore";
|
|
716
|
+
const cursorIgnoreContent = generateCursorIgnore(ignorePatterns.patterns);
|
|
717
|
+
outputs.push({
|
|
718
|
+
tool: "cursor",
|
|
719
|
+
filepath: cursorIgnorePath,
|
|
720
|
+
content: cursorIgnoreContent
|
|
721
|
+
});
|
|
722
|
+
}
|
|
206
723
|
return outputs;
|
|
207
724
|
}
|
|
208
725
|
function generateCursorMarkdown(rule) {
|
|
@@ -225,92 +742,125 @@ function generateCursorMarkdown(rule) {
|
|
|
225
742
|
lines.push(rule.content);
|
|
226
743
|
return lines.join("\n");
|
|
227
744
|
}
|
|
745
|
+
function generateCursorIgnore(patterns) {
|
|
746
|
+
const lines = [
|
|
747
|
+
"# Generated by rulesync from .rulesyncignore",
|
|
748
|
+
"# This file is automatically generated. Do not edit manually.",
|
|
749
|
+
"",
|
|
750
|
+
...patterns
|
|
751
|
+
];
|
|
752
|
+
return lines.join("\n");
|
|
753
|
+
}
|
|
228
754
|
|
|
229
|
-
// src/generators/
|
|
230
|
-
var
|
|
231
|
-
async function
|
|
755
|
+
// src/generators/rules/geminicli.ts
|
|
756
|
+
var import_node_path8 = require("path");
|
|
757
|
+
async function generateGeminiConfig(rules, config, baseDir) {
|
|
232
758
|
const outputs = [];
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
const
|
|
759
|
+
const rootRule = rules.find((rule) => rule.frontmatter.root === true);
|
|
760
|
+
const memoryRules = rules.filter((rule) => rule.frontmatter.root === false);
|
|
761
|
+
for (const rule of memoryRules) {
|
|
762
|
+
const content = generateGeminiMemoryMarkdown(rule);
|
|
763
|
+
const outputDir = baseDir ? (0, import_node_path8.join)(baseDir, config.outputPaths.geminicli) : config.outputPaths.geminicli;
|
|
764
|
+
const filepath = (0, import_node_path8.join)(outputDir, `${rule.filename}.md`);
|
|
237
765
|
outputs.push({
|
|
238
|
-
tool: "
|
|
766
|
+
tool: "geminicli",
|
|
239
767
|
filepath,
|
|
240
768
|
content
|
|
241
769
|
});
|
|
242
770
|
}
|
|
771
|
+
const rootContent = generateGeminiRootMarkdown(rootRule, memoryRules, baseDir);
|
|
772
|
+
const rootFilepath = baseDir ? (0, import_node_path8.join)(baseDir, "GEMINI.md") : "GEMINI.md";
|
|
773
|
+
outputs.push({
|
|
774
|
+
tool: "geminicli",
|
|
775
|
+
filepath: rootFilepath,
|
|
776
|
+
content: rootContent
|
|
777
|
+
});
|
|
778
|
+
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
779
|
+
if (ignorePatterns.patterns.length > 0) {
|
|
780
|
+
const aiexcludePath = baseDir ? (0, import_node_path8.join)(baseDir, ".aiexclude") : ".aiexclude";
|
|
781
|
+
const aiexcludeContent = generateAiexclude(ignorePatterns.patterns);
|
|
782
|
+
outputs.push({
|
|
783
|
+
tool: "geminicli",
|
|
784
|
+
filepath: aiexcludePath,
|
|
785
|
+
content: aiexcludeContent
|
|
786
|
+
});
|
|
787
|
+
}
|
|
243
788
|
return outputs;
|
|
244
789
|
}
|
|
245
|
-
function
|
|
790
|
+
function generateGeminiMemoryMarkdown(rule) {
|
|
246
791
|
return rule.content.trim();
|
|
247
792
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
793
|
+
function generateGeminiRootMarkdown(rootRule, memoryRules, _baseDir) {
|
|
794
|
+
const lines = [];
|
|
795
|
+
if (memoryRules.length > 0) {
|
|
796
|
+
lines.push("Please also reference the following documents as needed:");
|
|
797
|
+
lines.push("");
|
|
798
|
+
lines.push("| Document | Description | File Patterns |");
|
|
799
|
+
lines.push("|----------|-------------|---------------|");
|
|
800
|
+
for (const rule of memoryRules) {
|
|
801
|
+
const relativePath = `@.gemini/memories/${rule.filename}.md`;
|
|
802
|
+
const filePatterns = rule.frontmatter.globs.length > 0 ? rule.frontmatter.globs.join(", ") : "-";
|
|
803
|
+
lines.push(`| ${relativePath} | ${rule.frontmatter.description} | ${filePatterns} |`);
|
|
804
|
+
}
|
|
805
|
+
lines.push("");
|
|
806
|
+
lines.push("");
|
|
257
807
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
await (0, import_promises2.writeFile)(filepath, content, "utf-8");
|
|
265
|
-
}
|
|
266
|
-
async function findFiles(dir, extension = ".md") {
|
|
267
|
-
try {
|
|
268
|
-
const files = await (0, import_promises2.readdir)(dir);
|
|
269
|
-
return files.filter((file) => file.endsWith(extension)).map((file) => (0, import_node_path7.join)(dir, file));
|
|
270
|
-
} catch {
|
|
271
|
-
return [];
|
|
808
|
+
if (rootRule) {
|
|
809
|
+
lines.push(rootRule.content.trim());
|
|
810
|
+
} else if (memoryRules.length === 0) {
|
|
811
|
+
lines.push("# Gemini CLI Configuration");
|
|
812
|
+
lines.push("");
|
|
813
|
+
lines.push("No configuration rules have been defined yet.");
|
|
272
814
|
}
|
|
815
|
+
return lines.join("\n");
|
|
273
816
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
817
|
+
function generateAiexclude(patterns) {
|
|
818
|
+
const lines = [
|
|
819
|
+
"# Generated by rulesync from .rulesyncignore",
|
|
820
|
+
"# This file is automatically generated. Do not edit manually.",
|
|
821
|
+
"",
|
|
822
|
+
...patterns
|
|
823
|
+
];
|
|
824
|
+
return lines.join("\n");
|
|
281
825
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
826
|
+
|
|
827
|
+
// src/generators/rules/roo.ts
|
|
828
|
+
var import_node_path9 = require("path");
|
|
829
|
+
async function generateRooConfig(rules, config, baseDir) {
|
|
830
|
+
const outputs = [];
|
|
831
|
+
for (const rule of rules) {
|
|
832
|
+
const content = generateRooMarkdown(rule);
|
|
833
|
+
const outputDir = baseDir ? (0, import_node_path9.join)(baseDir, config.outputPaths.roo) : config.outputPaths.roo;
|
|
834
|
+
const filepath = (0, import_node_path9.join)(outputDir, `${rule.filename}.md`);
|
|
835
|
+
outputs.push({
|
|
836
|
+
tool: "roo",
|
|
837
|
+
filepath,
|
|
838
|
+
content
|
|
839
|
+
});
|
|
287
840
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
841
|
+
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
842
|
+
if (ignorePatterns.patterns.length > 0) {
|
|
843
|
+
const rooIgnorePath = baseDir ? (0, import_node_path9.join)(baseDir, ".rooignore") : ".rooignore";
|
|
844
|
+
const rooIgnoreContent = generateRooIgnore(ignorePatterns.patterns);
|
|
845
|
+
outputs.push({
|
|
846
|
+
tool: "roo",
|
|
847
|
+
filepath: rooIgnorePath,
|
|
848
|
+
content: rooIgnoreContent
|
|
849
|
+
});
|
|
294
850
|
}
|
|
851
|
+
return outputs;
|
|
295
852
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if (await fileExists(filepath)) {
|
|
299
|
-
await (0, import_promises2.rm)(filepath);
|
|
300
|
-
}
|
|
301
|
-
} catch (error) {
|
|
302
|
-
console.warn(`Failed to remove file ${filepath}:`, error);
|
|
303
|
-
}
|
|
853
|
+
function generateRooMarkdown(rule) {
|
|
854
|
+
return rule.content.trim();
|
|
304
855
|
}
|
|
305
|
-
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
856
|
+
function generateRooIgnore(patterns) {
|
|
857
|
+
const lines = [
|
|
858
|
+
"# Generated by rulesync from .rulesyncignore",
|
|
859
|
+
"# This file is automatically generated. Do not edit manually.",
|
|
860
|
+
"",
|
|
861
|
+
...patterns
|
|
862
|
+
];
|
|
863
|
+
return lines.join("\n");
|
|
314
864
|
}
|
|
315
865
|
|
|
316
866
|
// src/core/generator.ts
|
|
@@ -354,6 +904,8 @@ async function generateForTool(tool, rules, config, baseDir) {
|
|
|
354
904
|
return await generateClaudecodeConfig(rules, config, baseDir);
|
|
355
905
|
case "roo":
|
|
356
906
|
return generateRooConfig(rules, config, baseDir);
|
|
907
|
+
case "geminicli":
|
|
908
|
+
return generateGeminiConfig(rules, config, baseDir);
|
|
357
909
|
default:
|
|
358
910
|
console.warn(`Unknown tool: ${tool}`);
|
|
359
911
|
return null;
|
|
@@ -361,12 +913,16 @@ async function generateForTool(tool, rules, config, baseDir) {
|
|
|
361
913
|
}
|
|
362
914
|
|
|
363
915
|
// src/core/parser.ts
|
|
364
|
-
var
|
|
916
|
+
var import_node_path10 = require("path");
|
|
365
917
|
var import_gray_matter = __toESM(require("gray-matter"));
|
|
366
918
|
async function parseRulesFromDirectory(aiRulesDir) {
|
|
367
|
-
const
|
|
919
|
+
const ignorePatterns = await loadIgnorePatterns();
|
|
920
|
+
const ruleFiles = await findFiles(aiRulesDir, ".md", ignorePatterns.patterns);
|
|
368
921
|
const rules = [];
|
|
369
922
|
const errors = [];
|
|
923
|
+
if (ignorePatterns.patterns.length > 0) {
|
|
924
|
+
console.log(`Loaded ${ignorePatterns.patterns.length} ignore patterns from .rulesyncignore`);
|
|
925
|
+
}
|
|
370
926
|
for (const filepath of ruleFiles) {
|
|
371
927
|
try {
|
|
372
928
|
const rule = await parseRuleFile(filepath);
|
|
@@ -394,7 +950,7 @@ async function parseRuleFile(filepath) {
|
|
|
394
950
|
const parsed = (0, import_gray_matter.default)(content);
|
|
395
951
|
validateFrontmatter(parsed.data, filepath);
|
|
396
952
|
const frontmatter = parsed.data;
|
|
397
|
-
const filename = (0,
|
|
953
|
+
const filename = (0, import_node_path10.basename)(filepath, ".md");
|
|
398
954
|
return {
|
|
399
955
|
frontmatter,
|
|
400
956
|
content: parsed.content,
|
|
@@ -435,7 +991,7 @@ function validateFrontmatter(data, filepath) {
|
|
|
435
991
|
`Invalid "targets" field in ${filepath}: must be an array, got ${typeof obj.targets}`
|
|
436
992
|
);
|
|
437
993
|
}
|
|
438
|
-
const validTargets = ["copilot", "cursor", "cline", "claudecode", "roo", "*"];
|
|
994
|
+
const validTargets = ["copilot", "cursor", "cline", "claudecode", "roo", "geminicli", "*"];
|
|
439
995
|
for (const target of obj.targets) {
|
|
440
996
|
if (typeof target !== "string" || !validTargets.includes(target)) {
|
|
441
997
|
throw new Error(
|
|
@@ -522,6 +1078,148 @@ async function validateRule(rule) {
|
|
|
522
1078
|
};
|
|
523
1079
|
}
|
|
524
1080
|
|
|
1081
|
+
// src/core/mcp-generator.ts
|
|
1082
|
+
var import_node_os = __toESM(require("os"));
|
|
1083
|
+
var import_node_path12 = __toESM(require("path"));
|
|
1084
|
+
|
|
1085
|
+
// src/generators/mcp/index.ts
|
|
1086
|
+
init_claude();
|
|
1087
|
+
init_cline();
|
|
1088
|
+
init_copilot();
|
|
1089
|
+
init_cursor();
|
|
1090
|
+
init_geminicli();
|
|
1091
|
+
init_roo();
|
|
1092
|
+
|
|
1093
|
+
// src/core/mcp-parser.ts
|
|
1094
|
+
var import_node_fs = __toESM(require("fs"));
|
|
1095
|
+
var import_node_path11 = __toESM(require("path"));
|
|
1096
|
+
function parseMcpConfig(projectRoot) {
|
|
1097
|
+
const mcpPath = import_node_path11.default.join(projectRoot, ".rulesync", ".mcp.json");
|
|
1098
|
+
if (!import_node_fs.default.existsSync(mcpPath)) {
|
|
1099
|
+
return null;
|
|
1100
|
+
}
|
|
1101
|
+
try {
|
|
1102
|
+
const content = import_node_fs.default.readFileSync(mcpPath, "utf-8");
|
|
1103
|
+
const rawConfig = JSON.parse(content);
|
|
1104
|
+
if (rawConfig.servers && !rawConfig.mcpServers) {
|
|
1105
|
+
rawConfig.mcpServers = rawConfig.servers;
|
|
1106
|
+
delete rawConfig.servers;
|
|
1107
|
+
}
|
|
1108
|
+
if (!rawConfig.mcpServers || typeof rawConfig.mcpServers !== "object") {
|
|
1109
|
+
throw new Error("Invalid mcp.json: 'mcpServers' field must be an object");
|
|
1110
|
+
}
|
|
1111
|
+
if (rawConfig.tools) {
|
|
1112
|
+
delete rawConfig.tools;
|
|
1113
|
+
}
|
|
1114
|
+
return { mcpServers: rawConfig.mcpServers };
|
|
1115
|
+
} catch (error) {
|
|
1116
|
+
throw new Error(
|
|
1117
|
+
`Failed to parse mcp.json: ${error instanceof Error ? error.message : String(error)}`
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// src/core/mcp-generator.ts
|
|
1123
|
+
async function generateMcpConfigs(projectRoot, baseDir) {
|
|
1124
|
+
const results = [];
|
|
1125
|
+
const targetRoot = baseDir || projectRoot;
|
|
1126
|
+
const config = parseMcpConfig(projectRoot);
|
|
1127
|
+
if (!config) {
|
|
1128
|
+
return results;
|
|
1129
|
+
}
|
|
1130
|
+
const generators = [
|
|
1131
|
+
{
|
|
1132
|
+
tool: "claude-project",
|
|
1133
|
+
path: import_node_path12.default.join(targetRoot, ".mcp.json"),
|
|
1134
|
+
generate: () => generateClaudeMcp(config, "project")
|
|
1135
|
+
},
|
|
1136
|
+
{
|
|
1137
|
+
tool: "copilot-editor",
|
|
1138
|
+
path: import_node_path12.default.join(targetRoot, ".vscode", "mcp.json"),
|
|
1139
|
+
generate: () => generateCopilotMcp(config, "editor")
|
|
1140
|
+
},
|
|
1141
|
+
{
|
|
1142
|
+
tool: "cursor-project",
|
|
1143
|
+
path: import_node_path12.default.join(targetRoot, ".cursor", "mcp.json"),
|
|
1144
|
+
generate: () => generateCursorMcp(config, "project")
|
|
1145
|
+
},
|
|
1146
|
+
{
|
|
1147
|
+
tool: "cline-project",
|
|
1148
|
+
path: import_node_path12.default.join(targetRoot, ".cline", "mcp.json"),
|
|
1149
|
+
generate: () => generateClineMcp(config, "project")
|
|
1150
|
+
},
|
|
1151
|
+
{
|
|
1152
|
+
tool: "gemini-project",
|
|
1153
|
+
path: import_node_path12.default.join(targetRoot, ".gemini", "settings.json"),
|
|
1154
|
+
generate: () => generateGeminiCliMcp(config, "project")
|
|
1155
|
+
},
|
|
1156
|
+
{
|
|
1157
|
+
tool: "roo-project",
|
|
1158
|
+
path: import_node_path12.default.join(targetRoot, ".roo", "mcp.json"),
|
|
1159
|
+
generate: () => generateRooMcp(config, "project")
|
|
1160
|
+
}
|
|
1161
|
+
];
|
|
1162
|
+
if (!baseDir) {
|
|
1163
|
+
generators.push(
|
|
1164
|
+
{
|
|
1165
|
+
tool: "claude-global",
|
|
1166
|
+
path: import_node_path12.default.join(import_node_os.default.homedir(), ".claude", "settings.json"),
|
|
1167
|
+
generate: () => generateClaudeMcp(config, "global")
|
|
1168
|
+
},
|
|
1169
|
+
{
|
|
1170
|
+
tool: "cursor-global",
|
|
1171
|
+
path: import_node_path12.default.join(import_node_os.default.homedir(), ".cursor", "mcp.json"),
|
|
1172
|
+
generate: () => generateCursorMcp(config, "global")
|
|
1173
|
+
},
|
|
1174
|
+
{
|
|
1175
|
+
tool: "gemini-global",
|
|
1176
|
+
path: import_node_path12.default.join(import_node_os.default.homedir(), ".gemini", "settings.json"),
|
|
1177
|
+
generate: () => generateGeminiCliMcp(config, "global")
|
|
1178
|
+
}
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
for (const generator of generators) {
|
|
1182
|
+
try {
|
|
1183
|
+
const content = generator.generate();
|
|
1184
|
+
const parsed = JSON.parse(content);
|
|
1185
|
+
if (generator.tool.includes("claude") || generator.tool.includes("cline") || generator.tool.includes("cursor") || generator.tool.includes("gemini") || generator.tool.includes("roo")) {
|
|
1186
|
+
if (!parsed.mcpServers || Object.keys(parsed.mcpServers).length === 0) {
|
|
1187
|
+
results.push({
|
|
1188
|
+
tool: generator.tool,
|
|
1189
|
+
path: generator.path,
|
|
1190
|
+
status: "skipped"
|
|
1191
|
+
});
|
|
1192
|
+
continue;
|
|
1193
|
+
}
|
|
1194
|
+
} else if (generator.tool.includes("copilot")) {
|
|
1195
|
+
const key = generator.tool.includes("codingAgent") ? "mcpServers" : "servers";
|
|
1196
|
+
if (!parsed[key] || Object.keys(parsed[key]).length === 0) {
|
|
1197
|
+
results.push({
|
|
1198
|
+
tool: generator.tool,
|
|
1199
|
+
path: generator.path,
|
|
1200
|
+
status: "skipped"
|
|
1201
|
+
});
|
|
1202
|
+
continue;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
await writeFileContent(generator.path, content);
|
|
1206
|
+
results.push({
|
|
1207
|
+
tool: generator.tool,
|
|
1208
|
+
path: generator.path,
|
|
1209
|
+
status: "success"
|
|
1210
|
+
});
|
|
1211
|
+
} catch (error) {
|
|
1212
|
+
results.push({
|
|
1213
|
+
tool: generator.tool,
|
|
1214
|
+
path: generator.path,
|
|
1215
|
+
status: "error",
|
|
1216
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
return results;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
525
1223
|
// src/cli/commands/generate.ts
|
|
526
1224
|
async function generateCommand(options = {}) {
|
|
527
1225
|
const config = getDefaultConfig();
|
|
@@ -567,6 +1265,9 @@ async function generateCommand(options = {}) {
|
|
|
567
1265
|
case "roo":
|
|
568
1266
|
deleteTasks.push(removeDirectory(config.outputPaths.roo));
|
|
569
1267
|
break;
|
|
1268
|
+
case "geminicli":
|
|
1269
|
+
deleteTasks.push(removeDirectory(config.outputPaths.geminicli));
|
|
1270
|
+
break;
|
|
570
1271
|
}
|
|
571
1272
|
}
|
|
572
1273
|
await Promise.all(deleteTasks);
|
|
@@ -599,6 +1300,30 @@ Generating configurations for base directory: ${baseDir}`);
|
|
|
599
1300
|
}
|
|
600
1301
|
console.log(`
|
|
601
1302
|
\u{1F389} Successfully generated ${totalOutputs} configuration file(s)!`);
|
|
1303
|
+
if (options.verbose) {
|
|
1304
|
+
console.log("\nGenerating MCP configurations...");
|
|
1305
|
+
}
|
|
1306
|
+
for (const baseDir of baseDirs) {
|
|
1307
|
+
const mcpResults = await generateMcpConfigs(
|
|
1308
|
+
process.cwd(),
|
|
1309
|
+
baseDir === process.cwd() ? void 0 : baseDir
|
|
1310
|
+
);
|
|
1311
|
+
if (mcpResults.length === 0) {
|
|
1312
|
+
if (options.verbose) {
|
|
1313
|
+
console.log(`No MCP configuration found for ${baseDir}`);
|
|
1314
|
+
}
|
|
1315
|
+
continue;
|
|
1316
|
+
}
|
|
1317
|
+
for (const result of mcpResults) {
|
|
1318
|
+
if (result.status === "success") {
|
|
1319
|
+
console.log(`\u2705 Generated ${result.tool} MCP configuration: ${result.path}`);
|
|
1320
|
+
} else if (result.status === "error") {
|
|
1321
|
+
console.error(`\u274C Failed to generate ${result.tool} MCP configuration: ${result.error}`);
|
|
1322
|
+
} else if (options.verbose && result.status === "skipped") {
|
|
1323
|
+
console.log(`\u23ED\uFE0F Skipped ${result.tool} MCP configuration (no servers configured)`);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
602
1327
|
} catch (error) {
|
|
603
1328
|
console.error("\u274C Failed to generate configurations:", error);
|
|
604
1329
|
process.exit(1);
|
|
@@ -606,23 +1331,36 @@ Generating configurations for base directory: ${baseDir}`);
|
|
|
606
1331
|
}
|
|
607
1332
|
|
|
608
1333
|
// src/cli/commands/gitignore.ts
|
|
609
|
-
var
|
|
610
|
-
var
|
|
1334
|
+
var import_node_fs2 = require("fs");
|
|
1335
|
+
var import_node_path13 = require("path");
|
|
611
1336
|
var gitignoreCommand = async () => {
|
|
612
|
-
const gitignorePath = (0,
|
|
1337
|
+
const gitignorePath = (0, import_node_path13.join)(process.cwd(), ".gitignore");
|
|
613
1338
|
const rulesFilesToIgnore = [
|
|
614
1339
|
"# Generated by rulesync - AI tool configuration files",
|
|
615
1340
|
"**/.github/copilot-instructions.md",
|
|
616
1341
|
"**/.github/instructions/",
|
|
617
1342
|
"**/.cursor/rules/",
|
|
1343
|
+
"**/.cursorignore",
|
|
618
1344
|
"**/.clinerules/",
|
|
1345
|
+
"**/.clineignore",
|
|
619
1346
|
"**/CLAUDE.md",
|
|
620
1347
|
"**/.claude/memories/",
|
|
621
|
-
"**/.roo/rules/"
|
|
1348
|
+
"**/.roo/rules/",
|
|
1349
|
+
"**/.rooignore",
|
|
1350
|
+
"**/.copilotignore",
|
|
1351
|
+
"**/GEMINI.md",
|
|
1352
|
+
"**/.gemini/memories/",
|
|
1353
|
+
"**/.aiexclude",
|
|
1354
|
+
"**/.mcp.json",
|
|
1355
|
+
"**/.cursor/mcp.json",
|
|
1356
|
+
"**/.cline/mcp.json",
|
|
1357
|
+
"**/.vscode/mcp.json",
|
|
1358
|
+
"**/.gemini/settings.json",
|
|
1359
|
+
"**/.roo/mcp.json"
|
|
622
1360
|
];
|
|
623
1361
|
let gitignoreContent = "";
|
|
624
|
-
if ((0,
|
|
625
|
-
gitignoreContent = (0,
|
|
1362
|
+
if ((0, import_node_fs2.existsSync)(gitignorePath)) {
|
|
1363
|
+
gitignoreContent = (0, import_node_fs2.readFileSync)(gitignorePath, "utf-8");
|
|
626
1364
|
}
|
|
627
1365
|
const linesToAdd = [];
|
|
628
1366
|
for (const rule of rulesFilesToIgnore) {
|
|
@@ -639,7 +1377,7 @@ var gitignoreCommand = async () => {
|
|
|
639
1377
|
${linesToAdd.join("\n")}
|
|
640
1378
|
` : `${linesToAdd.join("\n")}
|
|
641
1379
|
`;
|
|
642
|
-
(0,
|
|
1380
|
+
(0, import_node_fs2.writeFileSync)(gitignorePath, newContent);
|
|
643
1381
|
console.log(`\u2705 .gitignore\u306B${linesToAdd.length}\u500B\u306E\u30EB\u30FC\u30EB\u3092\u8FFD\u52A0\u3057\u307E\u3057\u305F:`);
|
|
644
1382
|
for (const line of linesToAdd) {
|
|
645
1383
|
if (!line.startsWith("#")) {
|
|
@@ -649,15 +1387,17 @@ ${linesToAdd.join("\n")}
|
|
|
649
1387
|
};
|
|
650
1388
|
|
|
651
1389
|
// src/core/importer.ts
|
|
652
|
-
var
|
|
1390
|
+
var import_node_path20 = require("path");
|
|
653
1391
|
var import_gray_matter4 = __toESM(require("gray-matter"));
|
|
654
1392
|
|
|
655
1393
|
// src/parsers/claudecode.ts
|
|
656
|
-
var
|
|
1394
|
+
var import_node_path14 = require("path");
|
|
657
1395
|
async function parseClaudeConfiguration(baseDir = process.cwd()) {
|
|
658
1396
|
const errors = [];
|
|
659
1397
|
const rules = [];
|
|
660
|
-
|
|
1398
|
+
let ignorePatterns;
|
|
1399
|
+
let mcpServers;
|
|
1400
|
+
const claudeFilePath = (0, import_node_path14.join)(baseDir, "CLAUDE.md");
|
|
661
1401
|
if (!await fileExists(claudeFilePath)) {
|
|
662
1402
|
errors.push("CLAUDE.md file not found");
|
|
663
1403
|
return { rules, errors };
|
|
@@ -668,16 +1408,32 @@ async function parseClaudeConfiguration(baseDir = process.cwd()) {
|
|
|
668
1408
|
if (mainRule) {
|
|
669
1409
|
rules.push(mainRule);
|
|
670
1410
|
}
|
|
671
|
-
const memoryDir = (0,
|
|
1411
|
+
const memoryDir = (0, import_node_path14.join)(baseDir, ".claude", "memories");
|
|
672
1412
|
if (await fileExists(memoryDir)) {
|
|
673
1413
|
const memoryRules = await parseClaudeMemoryFiles(memoryDir);
|
|
674
1414
|
rules.push(...memoryRules);
|
|
675
1415
|
}
|
|
1416
|
+
const settingsPath = (0, import_node_path14.join)(baseDir, ".claude", "settings.json");
|
|
1417
|
+
if (await fileExists(settingsPath)) {
|
|
1418
|
+
const settingsResult = await parseClaudeSettings(settingsPath);
|
|
1419
|
+
if (settingsResult.ignorePatterns) {
|
|
1420
|
+
ignorePatterns = settingsResult.ignorePatterns;
|
|
1421
|
+
}
|
|
1422
|
+
if (settingsResult.mcpServers) {
|
|
1423
|
+
mcpServers = settingsResult.mcpServers;
|
|
1424
|
+
}
|
|
1425
|
+
errors.push(...settingsResult.errors);
|
|
1426
|
+
}
|
|
676
1427
|
} catch (error) {
|
|
677
1428
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
678
1429
|
errors.push(`Failed to parse Claude configuration: ${errorMessage}`);
|
|
679
1430
|
}
|
|
680
|
-
return {
|
|
1431
|
+
return {
|
|
1432
|
+
rules,
|
|
1433
|
+
errors,
|
|
1434
|
+
...ignorePatterns && { ignorePatterns },
|
|
1435
|
+
...mcpServers && { mcpServers }
|
|
1436
|
+
};
|
|
681
1437
|
}
|
|
682
1438
|
function parseClaudeMainFile(content, filepath) {
|
|
683
1439
|
const lines = content.split("\n");
|
|
@@ -714,10 +1470,10 @@ async function parseClaudeMemoryFiles(memoryDir) {
|
|
|
714
1470
|
const files = await readdir2(memoryDir);
|
|
715
1471
|
for (const file of files) {
|
|
716
1472
|
if (file.endsWith(".md")) {
|
|
717
|
-
const filePath = (0,
|
|
1473
|
+
const filePath = (0, import_node_path14.join)(memoryDir, file);
|
|
718
1474
|
const content = await readFileContent(filePath);
|
|
719
1475
|
if (content.trim()) {
|
|
720
|
-
const filename = (0,
|
|
1476
|
+
const filename = (0, import_node_path14.basename)(file, ".md");
|
|
721
1477
|
const frontmatter = {
|
|
722
1478
|
root: false,
|
|
723
1479
|
targets: ["claudecode"],
|
|
@@ -737,47 +1493,113 @@ async function parseClaudeMemoryFiles(memoryDir) {
|
|
|
737
1493
|
}
|
|
738
1494
|
return rules;
|
|
739
1495
|
}
|
|
1496
|
+
async function parseClaudeSettings(settingsPath) {
|
|
1497
|
+
const errors = [];
|
|
1498
|
+
let ignorePatterns;
|
|
1499
|
+
let mcpServers;
|
|
1500
|
+
try {
|
|
1501
|
+
const content = await readFileContent(settingsPath);
|
|
1502
|
+
const settings = JSON.parse(content);
|
|
1503
|
+
if (settings.permissions?.deny) {
|
|
1504
|
+
const readPatterns = settings.permissions.deny.filter((rule) => rule.startsWith("Read(") && rule.endsWith(")")).map((rule) => {
|
|
1505
|
+
const match = rule.match(/^Read\((.+)\)$/);
|
|
1506
|
+
return match ? match[1] : null;
|
|
1507
|
+
}).filter((pattern) => pattern !== null);
|
|
1508
|
+
if (readPatterns.length > 0) {
|
|
1509
|
+
ignorePatterns = readPatterns;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
if (settings.mcpServers && Object.keys(settings.mcpServers).length > 0) {
|
|
1513
|
+
mcpServers = settings.mcpServers;
|
|
1514
|
+
}
|
|
1515
|
+
} catch (error) {
|
|
1516
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1517
|
+
errors.push(`Failed to parse settings.json: ${errorMessage}`);
|
|
1518
|
+
}
|
|
1519
|
+
return {
|
|
1520
|
+
errors,
|
|
1521
|
+
...ignorePatterns && { ignorePatterns },
|
|
1522
|
+
...mcpServers && { mcpServers }
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
740
1525
|
|
|
741
1526
|
// src/parsers/cline.ts
|
|
742
|
-
var
|
|
1527
|
+
var import_node_path15 = require("path");
|
|
743
1528
|
async function parseClineConfiguration(baseDir = process.cwd()) {
|
|
744
1529
|
const errors = [];
|
|
745
1530
|
const rules = [];
|
|
746
|
-
const clineFilePath = (0,
|
|
747
|
-
if (
|
|
748
|
-
|
|
749
|
-
|
|
1531
|
+
const clineFilePath = (0, import_node_path15.join)(baseDir, ".cline", "instructions.md");
|
|
1532
|
+
if (await fileExists(clineFilePath)) {
|
|
1533
|
+
try {
|
|
1534
|
+
const content = await readFileContent(clineFilePath);
|
|
1535
|
+
if (content.trim()) {
|
|
1536
|
+
const frontmatter = {
|
|
1537
|
+
root: false,
|
|
1538
|
+
targets: ["cline"],
|
|
1539
|
+
description: "Cline instructions",
|
|
1540
|
+
globs: ["**/*"]
|
|
1541
|
+
};
|
|
1542
|
+
rules.push({
|
|
1543
|
+
frontmatter,
|
|
1544
|
+
content: content.trim(),
|
|
1545
|
+
filename: "cline-instructions",
|
|
1546
|
+
filepath: clineFilePath
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
} catch (error) {
|
|
1550
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1551
|
+
errors.push(`Failed to parse .cline/instructions.md: ${errorMessage}`);
|
|
1552
|
+
}
|
|
750
1553
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
const
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
1554
|
+
const clinerulesDirPath = (0, import_node_path15.join)(baseDir, ".clinerules");
|
|
1555
|
+
if (await fileExists(clinerulesDirPath)) {
|
|
1556
|
+
try {
|
|
1557
|
+
const { readdir: readdir2 } = await import("fs/promises");
|
|
1558
|
+
const files = await readdir2(clinerulesDirPath);
|
|
1559
|
+
for (const file of files) {
|
|
1560
|
+
if (file.endsWith(".md")) {
|
|
1561
|
+
const filePath = (0, import_node_path15.join)(clinerulesDirPath, file);
|
|
1562
|
+
try {
|
|
1563
|
+
const content = await readFileContent(filePath);
|
|
1564
|
+
if (content.trim()) {
|
|
1565
|
+
const filename = file.replace(".md", "");
|
|
1566
|
+
const frontmatter = {
|
|
1567
|
+
root: false,
|
|
1568
|
+
targets: ["cline"],
|
|
1569
|
+
description: `Cline rule: ${filename}`,
|
|
1570
|
+
globs: ["**/*"]
|
|
1571
|
+
};
|
|
1572
|
+
rules.push({
|
|
1573
|
+
frontmatter,
|
|
1574
|
+
content: content.trim(),
|
|
1575
|
+
filename: `cline-${filename}`,
|
|
1576
|
+
filepath: filePath
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
} catch (error) {
|
|
1580
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1581
|
+
errors.push(`Failed to parse ${filePath}: ${errorMessage}`);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
} catch (error) {
|
|
1586
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1587
|
+
errors.push(`Failed to parse .clinerules files: ${errorMessage}`);
|
|
766
1588
|
}
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
errors.push(
|
|
1589
|
+
}
|
|
1590
|
+
if (rules.length === 0) {
|
|
1591
|
+
errors.push("No Cline configuration files found (.cline/instructions.md or .clinerules/*.md)");
|
|
770
1592
|
}
|
|
771
1593
|
return { rules, errors };
|
|
772
1594
|
}
|
|
773
1595
|
|
|
774
1596
|
// src/parsers/copilot.ts
|
|
775
|
-
var
|
|
1597
|
+
var import_node_path16 = require("path");
|
|
776
1598
|
var import_gray_matter2 = __toESM(require("gray-matter"));
|
|
777
1599
|
async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
778
1600
|
const errors = [];
|
|
779
1601
|
const rules = [];
|
|
780
|
-
const copilotFilePath = (0,
|
|
1602
|
+
const copilotFilePath = (0, import_node_path16.join)(baseDir, ".github", "copilot-instructions.md");
|
|
781
1603
|
if (await fileExists(copilotFilePath)) {
|
|
782
1604
|
try {
|
|
783
1605
|
const rawContent = await readFileContent(copilotFilePath);
|
|
@@ -802,19 +1624,19 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
|
802
1624
|
errors.push(`Failed to parse copilot-instructions.md: ${errorMessage}`);
|
|
803
1625
|
}
|
|
804
1626
|
}
|
|
805
|
-
const instructionsDir = (0,
|
|
1627
|
+
const instructionsDir = (0, import_node_path16.join)(baseDir, ".github", "instructions");
|
|
806
1628
|
if (await fileExists(instructionsDir)) {
|
|
807
1629
|
try {
|
|
808
1630
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
809
1631
|
const files = await readdir2(instructionsDir);
|
|
810
1632
|
for (const file of files) {
|
|
811
1633
|
if (file.endsWith(".instructions.md")) {
|
|
812
|
-
const filePath = (0,
|
|
1634
|
+
const filePath = (0, import_node_path16.join)(instructionsDir, file);
|
|
813
1635
|
const rawContent = await readFileContent(filePath);
|
|
814
1636
|
const parsed = (0, import_gray_matter2.default)(rawContent);
|
|
815
1637
|
const content = parsed.content.trim();
|
|
816
1638
|
if (content) {
|
|
817
|
-
const filename = (0,
|
|
1639
|
+
const filename = (0, import_node_path16.basename)(file, ".instructions.md");
|
|
818
1640
|
const frontmatter = {
|
|
819
1641
|
root: false,
|
|
820
1642
|
targets: ["copilot"],
|
|
@@ -844,7 +1666,7 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
|
844
1666
|
}
|
|
845
1667
|
|
|
846
1668
|
// src/parsers/cursor.ts
|
|
847
|
-
var
|
|
1669
|
+
var import_node_path17 = require("path");
|
|
848
1670
|
var import_gray_matter3 = __toESM(require("gray-matter"));
|
|
849
1671
|
var import_js_yaml = __toESM(require("js-yaml"));
|
|
850
1672
|
var customMatterOptions = {
|
|
@@ -868,7 +1690,9 @@ var customMatterOptions = {
|
|
|
868
1690
|
async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
869
1691
|
const errors = [];
|
|
870
1692
|
const rules = [];
|
|
871
|
-
|
|
1693
|
+
let ignorePatterns;
|
|
1694
|
+
let mcpServers;
|
|
1695
|
+
const cursorFilePath = (0, import_node_path17.join)(baseDir, ".cursorrules");
|
|
872
1696
|
if (await fileExists(cursorFilePath)) {
|
|
873
1697
|
try {
|
|
874
1698
|
const rawContent = await readFileContent(cursorFilePath);
|
|
@@ -893,20 +1717,20 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
893
1717
|
errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
|
|
894
1718
|
}
|
|
895
1719
|
}
|
|
896
|
-
const cursorRulesDir = (0,
|
|
1720
|
+
const cursorRulesDir = (0, import_node_path17.join)(baseDir, ".cursor", "rules");
|
|
897
1721
|
if (await fileExists(cursorRulesDir)) {
|
|
898
1722
|
try {
|
|
899
1723
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
900
1724
|
const files = await readdir2(cursorRulesDir);
|
|
901
1725
|
for (const file of files) {
|
|
902
1726
|
if (file.endsWith(".mdc")) {
|
|
903
|
-
const filePath = (0,
|
|
1727
|
+
const filePath = (0, import_node_path17.join)(cursorRulesDir, file);
|
|
904
1728
|
try {
|
|
905
1729
|
const rawContent = await readFileContent(filePath);
|
|
906
1730
|
const parsed = (0, import_gray_matter3.default)(rawContent, customMatterOptions);
|
|
907
1731
|
const content = parsed.content.trim();
|
|
908
1732
|
if (content) {
|
|
909
|
-
const filename = (0,
|
|
1733
|
+
const filename = (0, import_node_path17.basename)(file, ".mdc");
|
|
910
1734
|
const frontmatter = {
|
|
911
1735
|
root: false,
|
|
912
1736
|
targets: ["cursor"],
|
|
@@ -934,38 +1758,244 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
934
1758
|
if (rules.length === 0) {
|
|
935
1759
|
errors.push("No Cursor configuration files found (.cursorrules or .cursor/rules/*.mdc)");
|
|
936
1760
|
}
|
|
937
|
-
|
|
1761
|
+
const cursorIgnorePath = (0, import_node_path17.join)(baseDir, ".cursorignore");
|
|
1762
|
+
if (await fileExists(cursorIgnorePath)) {
|
|
1763
|
+
try {
|
|
1764
|
+
const content = await readFileContent(cursorIgnorePath);
|
|
1765
|
+
const patterns = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
1766
|
+
if (patterns.length > 0) {
|
|
1767
|
+
ignorePatterns = patterns;
|
|
1768
|
+
}
|
|
1769
|
+
} catch (error) {
|
|
1770
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1771
|
+
errors.push(`Failed to parse .cursorignore: ${errorMessage}`);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
const cursorMcpPath = (0, import_node_path17.join)(baseDir, ".cursor", "mcp.json");
|
|
1775
|
+
if (await fileExists(cursorMcpPath)) {
|
|
1776
|
+
try {
|
|
1777
|
+
const content = await readFileContent(cursorMcpPath);
|
|
1778
|
+
const mcp = JSON.parse(content);
|
|
1779
|
+
if (mcp.mcpServers && Object.keys(mcp.mcpServers).length > 0) {
|
|
1780
|
+
mcpServers = mcp.mcpServers;
|
|
1781
|
+
}
|
|
1782
|
+
} catch (error) {
|
|
1783
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1784
|
+
errors.push(`Failed to parse .cursor/mcp.json: ${errorMessage}`);
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
return {
|
|
1788
|
+
rules,
|
|
1789
|
+
errors,
|
|
1790
|
+
...ignorePatterns && { ignorePatterns },
|
|
1791
|
+
...mcpServers && { mcpServers }
|
|
1792
|
+
};
|
|
938
1793
|
}
|
|
939
1794
|
|
|
940
|
-
// src/parsers/
|
|
941
|
-
var
|
|
942
|
-
async function
|
|
1795
|
+
// src/parsers/geminicli.ts
|
|
1796
|
+
var import_node_path18 = require("path");
|
|
1797
|
+
async function parseGeminiConfiguration(baseDir = process.cwd()) {
|
|
943
1798
|
const errors = [];
|
|
944
1799
|
const rules = [];
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1800
|
+
let ignorePatterns;
|
|
1801
|
+
let mcpServers;
|
|
1802
|
+
const geminiFilePath = (0, import_node_path18.join)(baseDir, "GEMINI.md");
|
|
1803
|
+
if (!await fileExists(geminiFilePath)) {
|
|
1804
|
+
errors.push("GEMINI.md file not found");
|
|
948
1805
|
return { rules, errors };
|
|
949
1806
|
}
|
|
950
1807
|
try {
|
|
951
|
-
const
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
rules.push(
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1808
|
+
const geminiContent = await readFileContent(geminiFilePath);
|
|
1809
|
+
const mainRule = parseGeminiMainFile(geminiContent, geminiFilePath);
|
|
1810
|
+
if (mainRule) {
|
|
1811
|
+
rules.push(mainRule);
|
|
1812
|
+
}
|
|
1813
|
+
const memoryDir = (0, import_node_path18.join)(baseDir, ".gemini", "memories");
|
|
1814
|
+
if (await fileExists(memoryDir)) {
|
|
1815
|
+
const memoryRules = await parseGeminiMemoryFiles(memoryDir);
|
|
1816
|
+
rules.push(...memoryRules);
|
|
1817
|
+
}
|
|
1818
|
+
const settingsPath = (0, import_node_path18.join)(baseDir, ".gemini", "settings.json");
|
|
1819
|
+
if (await fileExists(settingsPath)) {
|
|
1820
|
+
const settingsResult = await parseGeminiSettings(settingsPath);
|
|
1821
|
+
if (settingsResult.ignorePatterns) {
|
|
1822
|
+
ignorePatterns = settingsResult.ignorePatterns;
|
|
1823
|
+
}
|
|
1824
|
+
if (settingsResult.mcpServers) {
|
|
1825
|
+
mcpServers = settingsResult.mcpServers;
|
|
1826
|
+
}
|
|
1827
|
+
errors.push(...settingsResult.errors);
|
|
1828
|
+
}
|
|
1829
|
+
const aiexcludePath = (0, import_node_path18.join)(baseDir, ".aiexclude");
|
|
1830
|
+
if (await fileExists(aiexcludePath)) {
|
|
1831
|
+
const aiexcludePatterns = await parseAiexclude(aiexcludePath);
|
|
1832
|
+
if (aiexcludePatterns.length > 0) {
|
|
1833
|
+
ignorePatterns = ignorePatterns ? [...ignorePatterns, ...aiexcludePatterns] : aiexcludePatterns;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
} catch (error) {
|
|
1837
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1838
|
+
errors.push(`Failed to parse Gemini configuration: ${errorMessage}`);
|
|
1839
|
+
}
|
|
1840
|
+
return {
|
|
1841
|
+
rules,
|
|
1842
|
+
errors,
|
|
1843
|
+
...ignorePatterns && { ignorePatterns },
|
|
1844
|
+
...mcpServers && { mcpServers }
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
function parseGeminiMainFile(content, filepath) {
|
|
1848
|
+
const lines = content.split("\n");
|
|
1849
|
+
let contentStartIndex = 0;
|
|
1850
|
+
if (lines.some((line) => line.includes("| Document | Description | File Patterns |"))) {
|
|
1851
|
+
const tableEndIndex = lines.findIndex(
|
|
1852
|
+
(line, index) => index > 0 && line.trim() === "" && lines[index - 1]?.includes("|") && !lines[index + 1]?.includes("|")
|
|
1853
|
+
);
|
|
1854
|
+
if (tableEndIndex !== -1) {
|
|
1855
|
+
contentStartIndex = tableEndIndex + 1;
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
const mainContent = lines.slice(contentStartIndex).join("\n").trim();
|
|
1859
|
+
if (!mainContent) {
|
|
1860
|
+
return null;
|
|
1861
|
+
}
|
|
1862
|
+
const frontmatter = {
|
|
1863
|
+
root: false,
|
|
1864
|
+
targets: ["geminicli"],
|
|
1865
|
+
description: "Main Gemini CLI configuration",
|
|
1866
|
+
globs: ["**/*"]
|
|
1867
|
+
};
|
|
1868
|
+
return {
|
|
1869
|
+
frontmatter,
|
|
1870
|
+
content: mainContent,
|
|
1871
|
+
filename: "gemini-main",
|
|
1872
|
+
filepath
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1875
|
+
async function parseGeminiMemoryFiles(memoryDir) {
|
|
1876
|
+
const rules = [];
|
|
1877
|
+
try {
|
|
1878
|
+
const { readdir: readdir2 } = await import("fs/promises");
|
|
1879
|
+
const files = await readdir2(memoryDir);
|
|
1880
|
+
for (const file of files) {
|
|
1881
|
+
if (file.endsWith(".md")) {
|
|
1882
|
+
const filePath = (0, import_node_path18.join)(memoryDir, file);
|
|
1883
|
+
const content = await readFileContent(filePath);
|
|
1884
|
+
if (content.trim()) {
|
|
1885
|
+
const filename = (0, import_node_path18.basename)(file, ".md");
|
|
1886
|
+
const frontmatter = {
|
|
1887
|
+
root: false,
|
|
1888
|
+
targets: ["geminicli"],
|
|
1889
|
+
description: `Memory file: ${filename}`,
|
|
1890
|
+
globs: ["**/*"]
|
|
1891
|
+
};
|
|
1892
|
+
rules.push({
|
|
1893
|
+
frontmatter,
|
|
1894
|
+
content: content.trim(),
|
|
1895
|
+
filename: `gemini-memory-${filename}`,
|
|
1896
|
+
filepath: filePath
|
|
1897
|
+
});
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
} catch (_error) {
|
|
1902
|
+
}
|
|
1903
|
+
return rules;
|
|
1904
|
+
}
|
|
1905
|
+
async function parseGeminiSettings(settingsPath) {
|
|
1906
|
+
const errors = [];
|
|
1907
|
+
let mcpServers;
|
|
1908
|
+
try {
|
|
1909
|
+
const content = await readFileContent(settingsPath);
|
|
1910
|
+
const settings = JSON.parse(content);
|
|
1911
|
+
if (settings.mcpServers && Object.keys(settings.mcpServers).length > 0) {
|
|
1912
|
+
mcpServers = settings.mcpServers;
|
|
965
1913
|
}
|
|
966
1914
|
} catch (error) {
|
|
967
1915
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
968
|
-
errors.push(`Failed to parse
|
|
1916
|
+
errors.push(`Failed to parse settings.json: ${errorMessage}`);
|
|
1917
|
+
}
|
|
1918
|
+
return {
|
|
1919
|
+
errors,
|
|
1920
|
+
...mcpServers && { mcpServers }
|
|
1921
|
+
};
|
|
1922
|
+
}
|
|
1923
|
+
async function parseAiexclude(aiexcludePath) {
|
|
1924
|
+
try {
|
|
1925
|
+
const content = await readFileContent(aiexcludePath);
|
|
1926
|
+
const patterns = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
1927
|
+
return patterns;
|
|
1928
|
+
} catch (_error) {
|
|
1929
|
+
return [];
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
// src/parsers/roo.ts
|
|
1934
|
+
var import_node_path19 = require("path");
|
|
1935
|
+
async function parseRooConfiguration(baseDir = process.cwd()) {
|
|
1936
|
+
const errors = [];
|
|
1937
|
+
const rules = [];
|
|
1938
|
+
const rooFilePath = (0, import_node_path19.join)(baseDir, ".roo", "instructions.md");
|
|
1939
|
+
if (await fileExists(rooFilePath)) {
|
|
1940
|
+
try {
|
|
1941
|
+
const content = await readFileContent(rooFilePath);
|
|
1942
|
+
if (content.trim()) {
|
|
1943
|
+
const frontmatter = {
|
|
1944
|
+
root: false,
|
|
1945
|
+
targets: ["roo"],
|
|
1946
|
+
description: "Roo Code instructions",
|
|
1947
|
+
globs: ["**/*"]
|
|
1948
|
+
};
|
|
1949
|
+
rules.push({
|
|
1950
|
+
frontmatter,
|
|
1951
|
+
content: content.trim(),
|
|
1952
|
+
filename: "roo-instructions",
|
|
1953
|
+
filepath: rooFilePath
|
|
1954
|
+
});
|
|
1955
|
+
}
|
|
1956
|
+
} catch (error) {
|
|
1957
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1958
|
+
errors.push(`Failed to parse .roo/instructions.md: ${errorMessage}`);
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
const rooRulesDir = (0, import_node_path19.join)(baseDir, ".roo", "rules");
|
|
1962
|
+
if (await fileExists(rooRulesDir)) {
|
|
1963
|
+
try {
|
|
1964
|
+
const { readdir: readdir2 } = await import("fs/promises");
|
|
1965
|
+
const files = await readdir2(rooRulesDir);
|
|
1966
|
+
for (const file of files) {
|
|
1967
|
+
if (file.endsWith(".md")) {
|
|
1968
|
+
const filePath = (0, import_node_path19.join)(rooRulesDir, file);
|
|
1969
|
+
try {
|
|
1970
|
+
const content = await readFileContent(filePath);
|
|
1971
|
+
if (content.trim()) {
|
|
1972
|
+
const filename = file.replace(".md", "");
|
|
1973
|
+
const frontmatter = {
|
|
1974
|
+
root: false,
|
|
1975
|
+
targets: ["roo"],
|
|
1976
|
+
description: `Roo rule: ${filename}`,
|
|
1977
|
+
globs: ["**/*"]
|
|
1978
|
+
};
|
|
1979
|
+
rules.push({
|
|
1980
|
+
frontmatter,
|
|
1981
|
+
content: content.trim(),
|
|
1982
|
+
filename: `roo-${filename}`,
|
|
1983
|
+
filepath: filePath
|
|
1984
|
+
});
|
|
1985
|
+
}
|
|
1986
|
+
} catch (error) {
|
|
1987
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1988
|
+
errors.push(`Failed to parse ${filePath}: ${errorMessage}`);
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
} catch (error) {
|
|
1993
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1994
|
+
errors.push(`Failed to parse .roo/rules files: ${errorMessage}`);
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
if (rules.length === 0) {
|
|
1998
|
+
errors.push("No Roo Code configuration files found (.roo/instructions.md or .roo/rules/*.md)");
|
|
969
1999
|
}
|
|
970
2000
|
return { rules, errors };
|
|
971
2001
|
}
|
|
@@ -975,6 +2005,8 @@ async function importConfiguration(options) {
|
|
|
975
2005
|
const { tool, baseDir = process.cwd(), rulesDir = ".rulesync", verbose = false } = options;
|
|
976
2006
|
const errors = [];
|
|
977
2007
|
let rules = [];
|
|
2008
|
+
let ignorePatterns;
|
|
2009
|
+
let mcpServers;
|
|
978
2010
|
if (verbose) {
|
|
979
2011
|
console.log(`Importing ${tool} configuration from ${baseDir}...`);
|
|
980
2012
|
}
|
|
@@ -984,12 +2016,16 @@ async function importConfiguration(options) {
|
|
|
984
2016
|
const claudeResult = await parseClaudeConfiguration(baseDir);
|
|
985
2017
|
rules = claudeResult.rules;
|
|
986
2018
|
errors.push(...claudeResult.errors);
|
|
2019
|
+
ignorePatterns = claudeResult.ignorePatterns;
|
|
2020
|
+
mcpServers = claudeResult.mcpServers;
|
|
987
2021
|
break;
|
|
988
2022
|
}
|
|
989
2023
|
case "cursor": {
|
|
990
2024
|
const cursorResult = await parseCursorConfiguration(baseDir);
|
|
991
2025
|
rules = cursorResult.rules;
|
|
992
2026
|
errors.push(...cursorResult.errors);
|
|
2027
|
+
ignorePatterns = cursorResult.ignorePatterns;
|
|
2028
|
+
mcpServers = cursorResult.mcpServers;
|
|
993
2029
|
break;
|
|
994
2030
|
}
|
|
995
2031
|
case "copilot": {
|
|
@@ -1010,6 +2046,14 @@ async function importConfiguration(options) {
|
|
|
1010
2046
|
errors.push(...rooResult.errors);
|
|
1011
2047
|
break;
|
|
1012
2048
|
}
|
|
2049
|
+
case "geminicli": {
|
|
2050
|
+
const geminiResult = await parseGeminiConfiguration(baseDir);
|
|
2051
|
+
rules = geminiResult.rules;
|
|
2052
|
+
errors.push(...geminiResult.errors);
|
|
2053
|
+
ignorePatterns = geminiResult.ignorePatterns;
|
|
2054
|
+
mcpServers = geminiResult.mcpServers;
|
|
2055
|
+
break;
|
|
2056
|
+
}
|
|
1013
2057
|
default:
|
|
1014
2058
|
errors.push(`Unsupported tool: ${tool}`);
|
|
1015
2059
|
return { success: false, rulesCreated: 0, errors };
|
|
@@ -1019,10 +2063,10 @@ async function importConfiguration(options) {
|
|
|
1019
2063
|
errors.push(`Failed to parse ${tool} configuration: ${errorMessage}`);
|
|
1020
2064
|
return { success: false, rulesCreated: 0, errors };
|
|
1021
2065
|
}
|
|
1022
|
-
if (rules.length === 0) {
|
|
2066
|
+
if (rules.length === 0 && !ignorePatterns && !mcpServers) {
|
|
1023
2067
|
return { success: false, rulesCreated: 0, errors };
|
|
1024
2068
|
}
|
|
1025
|
-
const rulesDirPath = (0,
|
|
2069
|
+
const rulesDirPath = (0, import_node_path20.join)(baseDir, rulesDir);
|
|
1026
2070
|
try {
|
|
1027
2071
|
const { mkdir: mkdir3 } = await import("fs/promises");
|
|
1028
2072
|
await mkdir3(rulesDirPath, { recursive: true });
|
|
@@ -1036,7 +2080,7 @@ async function importConfiguration(options) {
|
|
|
1036
2080
|
try {
|
|
1037
2081
|
const baseFilename = `${tool}__${rule.filename}`;
|
|
1038
2082
|
const filename = await generateUniqueFilename(rulesDirPath, baseFilename);
|
|
1039
|
-
const filePath = (0,
|
|
2083
|
+
const filePath = (0, import_node_path20.join)(rulesDirPath, `${filename}.md`);
|
|
1040
2084
|
const content = generateRuleFileContent(rule);
|
|
1041
2085
|
await writeFileContent(filePath, content);
|
|
1042
2086
|
rulesCreated++;
|
|
@@ -1048,10 +2092,44 @@ async function importConfiguration(options) {
|
|
|
1048
2092
|
errors.push(`Failed to create rule file for ${rule.filename}: ${errorMessage}`);
|
|
1049
2093
|
}
|
|
1050
2094
|
}
|
|
2095
|
+
let ignoreFileCreated = false;
|
|
2096
|
+
if (ignorePatterns && ignorePatterns.length > 0) {
|
|
2097
|
+
try {
|
|
2098
|
+
const rulesyncignorePath = (0, import_node_path20.join)(baseDir, ".rulesyncignore");
|
|
2099
|
+
const ignoreContent = `${ignorePatterns.join("\n")}
|
|
2100
|
+
`;
|
|
2101
|
+
await writeFileContent(rulesyncignorePath, ignoreContent);
|
|
2102
|
+
ignoreFileCreated = true;
|
|
2103
|
+
if (verbose) {
|
|
2104
|
+
console.log(`\u2705 Created .rulesyncignore with ${ignorePatterns.length} patterns`);
|
|
2105
|
+
}
|
|
2106
|
+
} catch (error) {
|
|
2107
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2108
|
+
errors.push(`Failed to create .rulesyncignore: ${errorMessage}`);
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
let mcpFileCreated = false;
|
|
2112
|
+
if (mcpServers && Object.keys(mcpServers).length > 0) {
|
|
2113
|
+
try {
|
|
2114
|
+
const mcpPath = (0, import_node_path20.join)(baseDir, rulesDir, ".mcp.json");
|
|
2115
|
+
const mcpContent = `${JSON.stringify({ mcpServers }, null, 2)}
|
|
2116
|
+
`;
|
|
2117
|
+
await writeFileContent(mcpPath, mcpContent);
|
|
2118
|
+
mcpFileCreated = true;
|
|
2119
|
+
if (verbose) {
|
|
2120
|
+
console.log(`\u2705 Created .mcp.json with ${Object.keys(mcpServers).length} servers`);
|
|
2121
|
+
}
|
|
2122
|
+
} catch (error) {
|
|
2123
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2124
|
+
errors.push(`Failed to create .mcp.json: ${errorMessage}`);
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
1051
2127
|
return {
|
|
1052
|
-
success: rulesCreated > 0,
|
|
2128
|
+
success: rulesCreated > 0 || ignoreFileCreated || mcpFileCreated,
|
|
1053
2129
|
rulesCreated,
|
|
1054
|
-
errors
|
|
2130
|
+
errors,
|
|
2131
|
+
ignoreFileCreated,
|
|
2132
|
+
mcpFileCreated
|
|
1055
2133
|
};
|
|
1056
2134
|
}
|
|
1057
2135
|
function generateRuleFileContent(rule) {
|
|
@@ -1061,7 +2139,7 @@ function generateRuleFileContent(rule) {
|
|
|
1061
2139
|
async function generateUniqueFilename(rulesDir, baseFilename) {
|
|
1062
2140
|
let filename = baseFilename;
|
|
1063
2141
|
let counter = 1;
|
|
1064
|
-
while (await fileExists((0,
|
|
2142
|
+
while (await fileExists((0, import_node_path20.join)(rulesDir, `${filename}.md`))) {
|
|
1065
2143
|
filename = `${baseFilename}-${counter}`;
|
|
1066
2144
|
counter++;
|
|
1067
2145
|
}
|
|
@@ -1076,59 +2154,57 @@ async function importCommand(options = {}) {
|
|
|
1076
2154
|
if (options.copilot) tools.push("copilot");
|
|
1077
2155
|
if (options.cline) tools.push("cline");
|
|
1078
2156
|
if (options.roo) tools.push("roo");
|
|
2157
|
+
if (options.geminicli) tools.push("geminicli");
|
|
1079
2158
|
if (tools.length === 0) {
|
|
1080
2159
|
console.error(
|
|
1081
|
-
"\u274C Please specify
|
|
2160
|
+
"\u274C Please specify one tool to import from (--claudecode, --cursor, --copilot, --cline, --roo, --geminicli)"
|
|
1082
2161
|
);
|
|
1083
2162
|
process.exit(1);
|
|
1084
2163
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
for (const tool of tools) {
|
|
1089
|
-
if (options.verbose) {
|
|
1090
|
-
console.log(`
|
|
1091
|
-
Importing from ${tool}...`);
|
|
1092
|
-
}
|
|
1093
|
-
try {
|
|
1094
|
-
const result = await importConfiguration({
|
|
1095
|
-
tool,
|
|
1096
|
-
verbose: options.verbose ?? false
|
|
1097
|
-
});
|
|
1098
|
-
if (result.success) {
|
|
1099
|
-
console.log(`\u2705 Imported ${result.rulesCreated} rule(s) from ${tool}`);
|
|
1100
|
-
totalRulesCreated += result.rulesCreated;
|
|
1101
|
-
} else if (result.errors.length > 0) {
|
|
1102
|
-
console.warn(`\u26A0\uFE0F Failed to import from ${tool}: ${result.errors[0]}`);
|
|
1103
|
-
if (options.verbose) {
|
|
1104
|
-
allErrors.push(...result.errors);
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
} catch (error) {
|
|
1108
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1109
|
-
console.error(`\u274C Error importing from ${tool}: ${errorMessage}`);
|
|
1110
|
-
allErrors.push(`${tool}: ${errorMessage}`);
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
if (totalRulesCreated > 0) {
|
|
1114
|
-
console.log(`
|
|
1115
|
-
\u{1F389} Successfully imported ${totalRulesCreated} rule(s) total!`);
|
|
1116
|
-
console.log("You can now run 'rulesync generate' to create tool-specific configurations.");
|
|
1117
|
-
} else {
|
|
1118
|
-
console.warn(
|
|
1119
|
-
"\n\u26A0\uFE0F No rules were imported. Please check that configuration files exist for the selected tools."
|
|
2164
|
+
if (tools.length > 1) {
|
|
2165
|
+
console.error(
|
|
2166
|
+
"\u274C Only one tool can be specified at a time. Please run the import command separately for each tool."
|
|
1120
2167
|
);
|
|
2168
|
+
process.exit(1);
|
|
1121
2169
|
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
2170
|
+
const tool = tools[0];
|
|
2171
|
+
if (!tool) {
|
|
2172
|
+
console.error("Error: No tool specified");
|
|
2173
|
+
process.exit(1);
|
|
2174
|
+
}
|
|
2175
|
+
console.log(`Importing configuration files from ${tool}...`);
|
|
2176
|
+
try {
|
|
2177
|
+
const result = await importConfiguration({
|
|
2178
|
+
tool,
|
|
2179
|
+
verbose: options.verbose ?? false
|
|
2180
|
+
});
|
|
2181
|
+
if (result.success) {
|
|
2182
|
+
console.log(`\u2705 Imported ${result.rulesCreated} rule(s) from ${tool}`);
|
|
2183
|
+
if (result.ignoreFileCreated) {
|
|
2184
|
+
console.log("\u2705 Created .rulesyncignore file from ignore patterns");
|
|
2185
|
+
}
|
|
2186
|
+
if (result.mcpFileCreated) {
|
|
2187
|
+
console.log("\u2705 Created .rulesync/.mcp.json file from MCP configuration");
|
|
2188
|
+
}
|
|
2189
|
+
console.log("You can now run 'rulesync generate' to create tool-specific configurations.");
|
|
2190
|
+
} else if (result.errors.length > 0) {
|
|
2191
|
+
console.warn(`\u26A0\uFE0F Failed to import from ${tool}: ${result.errors[0]}`);
|
|
2192
|
+
if (options.verbose && result.errors.length > 1) {
|
|
2193
|
+
console.log("\nDetailed errors:");
|
|
2194
|
+
for (const error of result.errors) {
|
|
2195
|
+
console.log(` - ${error}`);
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
1126
2198
|
}
|
|
2199
|
+
} catch (error) {
|
|
2200
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2201
|
+
console.error(`\u274C Error importing from ${tool}: ${errorMessage}`);
|
|
2202
|
+
process.exit(1);
|
|
1127
2203
|
}
|
|
1128
2204
|
}
|
|
1129
2205
|
|
|
1130
2206
|
// src/cli/commands/init.ts
|
|
1131
|
-
var
|
|
2207
|
+
var import_node_path21 = require("path");
|
|
1132
2208
|
async function initCommand() {
|
|
1133
2209
|
const aiRulesDir = ".rulesync";
|
|
1134
2210
|
console.log("Initializing rulesync...");
|
|
@@ -1258,7 +2334,7 @@ globs: ["src/api/**/*.ts", "src/services/**/*.ts", "src/models/**/*.ts"]
|
|
|
1258
2334
|
}
|
|
1259
2335
|
];
|
|
1260
2336
|
for (const file of sampleFiles) {
|
|
1261
|
-
const filepath = (0,
|
|
2337
|
+
const filepath = (0, import_node_path21.join)(aiRulesDir, file.filename);
|
|
1262
2338
|
if (!await fileExists(filepath)) {
|
|
1263
2339
|
await writeFileContent(filepath, file.content);
|
|
1264
2340
|
console.log(`Created ${filepath}`);
|
|
@@ -1371,11 +2447,11 @@ async function watchCommand() {
|
|
|
1371
2447
|
persistent: true
|
|
1372
2448
|
});
|
|
1373
2449
|
let isGenerating = false;
|
|
1374
|
-
const handleChange = async (
|
|
2450
|
+
const handleChange = async (path4) => {
|
|
1375
2451
|
if (isGenerating) return;
|
|
1376
2452
|
isGenerating = true;
|
|
1377
2453
|
console.log(`
|
|
1378
|
-
\u{1F4DD} Detected change in ${
|
|
2454
|
+
\u{1F4DD} Detected change in ${path4}`);
|
|
1379
2455
|
try {
|
|
1380
2456
|
await generateCommand({ verbose: false });
|
|
1381
2457
|
console.log("\u2705 Regenerated configuration files");
|
|
@@ -1385,10 +2461,10 @@ async function watchCommand() {
|
|
|
1385
2461
|
isGenerating = false;
|
|
1386
2462
|
}
|
|
1387
2463
|
};
|
|
1388
|
-
watcher.on("change", handleChange).on("add", handleChange).on("unlink", (
|
|
2464
|
+
watcher.on("change", handleChange).on("add", handleChange).on("unlink", (path4) => {
|
|
1389
2465
|
console.log(`
|
|
1390
|
-
\u{1F5D1}\uFE0F Removed ${
|
|
1391
|
-
handleChange(
|
|
2466
|
+
\u{1F5D1}\uFE0F Removed ${path4}`);
|
|
2467
|
+
handleChange(path4);
|
|
1392
2468
|
}).on("error", (error) => {
|
|
1393
2469
|
console.error("\u274C Watcher error:", error);
|
|
1394
2470
|
});
|
|
@@ -1401,12 +2477,12 @@ async function watchCommand() {
|
|
|
1401
2477
|
|
|
1402
2478
|
// src/cli/index.ts
|
|
1403
2479
|
var program = new import_commander.Command();
|
|
1404
|
-
program.name("rulesync").description("Unified AI rules management CLI tool").version("0.
|
|
2480
|
+
program.name("rulesync").description("Unified AI rules management CLI tool").version("0.36.0");
|
|
1405
2481
|
program.command("init").description("Initialize rulesync in current directory").action(initCommand);
|
|
1406
2482
|
program.command("add <filename>").description("Add a new rule file").action(addCommand);
|
|
1407
2483
|
program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
|
|
1408
|
-
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("-v, --verbose", "Verbose output").action(importCommand);
|
|
1409
|
-
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("--delete", "Delete all existing files in output directories before generating").option(
|
|
2484
|
+
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);
|
|
2485
|
+
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(
|
|
1410
2486
|
"-b, --base-dir <paths>",
|
|
1411
2487
|
"Base directories to generate files (comma-separated for multiple paths)"
|
|
1412
2488
|
).option("-v, --verbose", "Verbose output").action(async (options) => {
|
|
@@ -1416,6 +2492,7 @@ program.command("generate").description("Generate configuration files for AI too
|
|
|
1416
2492
|
if (options.cline) tools.push("cline");
|
|
1417
2493
|
if (options.claudecode) tools.push("claudecode");
|
|
1418
2494
|
if (options.roo) tools.push("roo");
|
|
2495
|
+
if (options.geminicli) tools.push("geminicli");
|
|
1419
2496
|
const generateOptions = {
|
|
1420
2497
|
verbose: options.verbose,
|
|
1421
2498
|
delete: options.delete
|