rulesync 0.34.0 → 0.37.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 +95 -6
- package/README.md +95 -6
- package/dist/chunk-2BYTQ4LL.mjs +67 -0
- package/dist/chunk-2CDVII2R.mjs +62 -0
- package/dist/chunk-3GVVX3G5.mjs +78 -0
- package/dist/chunk-KZATM2CQ.mjs +65 -0
- package/dist/chunk-PBOKMNYU.mjs +67 -0
- package/dist/chunk-QQN5GTOV.mjs +56 -0
- package/dist/chunk-TKNVMYAC.mjs +78 -0
- package/dist/claude-2NLZ2CDE.mjs +9 -0
- package/dist/cline-HUXPTQP7.mjs +9 -0
- package/dist/copilot-376H5OXX.mjs +9 -0
- package/dist/cursor-XWLBQYWY.mjs +9 -0
- package/dist/geminicli-EREPFSDP.mjs +9 -0
- package/dist/index.js +1295 -224
- package/dist/index.mjs +955 -215
- package/dist/roo-YS23AEWJ.mjs +9 -0
- package/package.json +33 -12
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,345 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
26
|
mod
|
|
24
27
|
));
|
|
25
28
|
|
|
29
|
+
// src/schemas/mcp.ts
|
|
30
|
+
var import_zod, ToolTargetSchema, WildcardTargetSchema, SpecificTargetsSchema, RulesyncTargetsSchema, McpTransportTypeSchema, McpServerBaseSchema, RulesyncMcpServerSchema;
|
|
31
|
+
var init_mcp = __esm({
|
|
32
|
+
"src/schemas/mcp.ts"() {
|
|
33
|
+
"use strict";
|
|
34
|
+
import_zod = require("zod");
|
|
35
|
+
ToolTargetSchema = import_zod.z.enum([
|
|
36
|
+
"copilot",
|
|
37
|
+
"cursor",
|
|
38
|
+
"cline",
|
|
39
|
+
"claudecode",
|
|
40
|
+
"claude",
|
|
41
|
+
"roo",
|
|
42
|
+
"geminicli"
|
|
43
|
+
]);
|
|
44
|
+
WildcardTargetSchema = import_zod.z.tuple([import_zod.z.literal("*")]);
|
|
45
|
+
SpecificTargetsSchema = import_zod.z.array(ToolTargetSchema);
|
|
46
|
+
RulesyncTargetsSchema = import_zod.z.union([SpecificTargetsSchema, WildcardTargetSchema]);
|
|
47
|
+
McpTransportTypeSchema = import_zod.z.enum(["stdio", "sse", "http"]);
|
|
48
|
+
McpServerBaseSchema = import_zod.z.object({
|
|
49
|
+
command: import_zod.z.string().optional(),
|
|
50
|
+
args: import_zod.z.array(import_zod.z.string()).optional(),
|
|
51
|
+
url: import_zod.z.string().optional(),
|
|
52
|
+
httpUrl: import_zod.z.string().optional(),
|
|
53
|
+
env: import_zod.z.record(import_zod.z.string()).optional(),
|
|
54
|
+
disabled: import_zod.z.boolean().optional(),
|
|
55
|
+
networkTimeout: import_zod.z.number().optional(),
|
|
56
|
+
timeout: import_zod.z.number().optional(),
|
|
57
|
+
trust: import_zod.z.boolean().optional(),
|
|
58
|
+
cwd: import_zod.z.string().optional(),
|
|
59
|
+
transport: McpTransportTypeSchema.optional(),
|
|
60
|
+
type: import_zod.z.enum(["sse", "streamable-http"]).optional(),
|
|
61
|
+
alwaysAllow: import_zod.z.array(import_zod.z.string()).optional(),
|
|
62
|
+
tools: import_zod.z.array(import_zod.z.string()).optional()
|
|
63
|
+
});
|
|
64
|
+
RulesyncMcpServerSchema = McpServerBaseSchema.extend({
|
|
65
|
+
rulesyncTargets: RulesyncTargetsSchema.optional()
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// src/utils/mcp-helpers.ts
|
|
71
|
+
function shouldIncludeServer(server, targetTool) {
|
|
72
|
+
if (!server.rulesyncTargets || server.rulesyncTargets.length === 0) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
const parsedTargets = RulesyncTargetsSchema.parse(server.rulesyncTargets);
|
|
76
|
+
if (parsedTargets.length === 1 && parsedTargets[0] === "*") {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
const validatedTool = ToolTargetSchema.parse(targetTool);
|
|
80
|
+
for (const target of parsedTargets) {
|
|
81
|
+
if (target === validatedTool) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
var init_mcp_helpers = __esm({
|
|
88
|
+
"src/utils/mcp-helpers.ts"() {
|
|
89
|
+
"use strict";
|
|
90
|
+
init_mcp();
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// src/generators/mcp/claude.ts
|
|
95
|
+
function generateClaudeMcp(config, _target) {
|
|
96
|
+
const claudeSettings = {
|
|
97
|
+
mcpServers: {}
|
|
98
|
+
};
|
|
99
|
+
const shouldInclude = (server) => {
|
|
100
|
+
return shouldIncludeServer(server, "claude");
|
|
101
|
+
};
|
|
102
|
+
for (const [serverName, server] of Object.entries(config.mcpServers)) {
|
|
103
|
+
if (!shouldInclude(server)) continue;
|
|
104
|
+
const claudeServer = {};
|
|
105
|
+
if (server.command) {
|
|
106
|
+
claudeServer.command = server.command;
|
|
107
|
+
if (server.args) claudeServer.args = server.args;
|
|
108
|
+
} else if (server.url || server.httpUrl) {
|
|
109
|
+
const url = server.httpUrl || server.url;
|
|
110
|
+
if (url) {
|
|
111
|
+
claudeServer.url = url;
|
|
112
|
+
}
|
|
113
|
+
if (server.httpUrl) {
|
|
114
|
+
claudeServer.transport = "http";
|
|
115
|
+
} else if (server.transport === "sse") {
|
|
116
|
+
claudeServer.transport = "sse";
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (server.env) {
|
|
120
|
+
claudeServer.env = server.env;
|
|
121
|
+
}
|
|
122
|
+
claudeSettings.mcpServers[serverName] = claudeServer;
|
|
123
|
+
}
|
|
124
|
+
return JSON.stringify(claudeSettings, null, 2);
|
|
125
|
+
}
|
|
126
|
+
var init_claude = __esm({
|
|
127
|
+
"src/generators/mcp/claude.ts"() {
|
|
128
|
+
"use strict";
|
|
129
|
+
init_mcp_helpers();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// src/generators/mcp/cline.ts
|
|
134
|
+
function generateClineMcp(config, _target) {
|
|
135
|
+
const clineConfig = {
|
|
136
|
+
mcpServers: {}
|
|
137
|
+
};
|
|
138
|
+
const shouldInclude = (server) => {
|
|
139
|
+
return shouldIncludeServer(server, "cline");
|
|
140
|
+
};
|
|
141
|
+
for (const [serverName, server] of Object.entries(config.mcpServers)) {
|
|
142
|
+
if (!shouldInclude(server)) continue;
|
|
143
|
+
const clineServer = {};
|
|
144
|
+
if (server.command) {
|
|
145
|
+
clineServer.command = server.command;
|
|
146
|
+
if (server.args) clineServer.args = server.args;
|
|
147
|
+
} else if (server.url) {
|
|
148
|
+
clineServer.url = server.url;
|
|
149
|
+
}
|
|
150
|
+
if (server.env) {
|
|
151
|
+
clineServer.env = server.env;
|
|
152
|
+
}
|
|
153
|
+
if (server.disabled !== void 0) {
|
|
154
|
+
clineServer.disabled = server.disabled;
|
|
155
|
+
}
|
|
156
|
+
if (server.alwaysAllow) {
|
|
157
|
+
clineServer.alwaysAllow = server.alwaysAllow;
|
|
158
|
+
}
|
|
159
|
+
if (server.networkTimeout !== void 0) {
|
|
160
|
+
clineServer.networkTimeout = server.networkTimeout;
|
|
161
|
+
}
|
|
162
|
+
clineConfig.mcpServers[serverName] = clineServer;
|
|
163
|
+
}
|
|
164
|
+
return JSON.stringify(clineConfig, null, 2);
|
|
165
|
+
}
|
|
166
|
+
var init_cline = __esm({
|
|
167
|
+
"src/generators/mcp/cline.ts"() {
|
|
168
|
+
"use strict";
|
|
169
|
+
init_mcp_helpers();
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// src/generators/mcp/copilot.ts
|
|
174
|
+
function generateCopilotMcp(config, target) {
|
|
175
|
+
const servers = {};
|
|
176
|
+
const inputs = [];
|
|
177
|
+
const inputMap = /* @__PURE__ */ new Map();
|
|
178
|
+
for (const [serverName, server] of Object.entries(config.mcpServers)) {
|
|
179
|
+
if (!shouldIncludeServer(server, "copilot")) continue;
|
|
180
|
+
const copilotServer = {};
|
|
181
|
+
if (server.command) {
|
|
182
|
+
copilotServer.command = server.command;
|
|
183
|
+
if (server.args) copilotServer.args = server.args;
|
|
184
|
+
} else if (server.url || server.httpUrl) {
|
|
185
|
+
const url = server.httpUrl || server.url;
|
|
186
|
+
if (url) {
|
|
187
|
+
copilotServer.url = url;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (server.env) {
|
|
191
|
+
copilotServer.env = {};
|
|
192
|
+
for (const [key, value] of Object.entries(server.env)) {
|
|
193
|
+
if (target === "editor" && value.includes("SECRET")) {
|
|
194
|
+
const inputId = `${serverName}_${key}`;
|
|
195
|
+
inputMap.set(inputId, value);
|
|
196
|
+
copilotServer.env[key] = `\${input:${inputId}}`;
|
|
197
|
+
inputs.push({
|
|
198
|
+
id: inputId,
|
|
199
|
+
type: "password",
|
|
200
|
+
description: `${key} for ${serverName}`
|
|
201
|
+
});
|
|
202
|
+
} else {
|
|
203
|
+
copilotServer.env[key] = value;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (server.tools) {
|
|
208
|
+
copilotServer.tools = server.tools;
|
|
209
|
+
} else if (server.alwaysAllow) {
|
|
210
|
+
copilotServer.tools = server.alwaysAllow;
|
|
211
|
+
}
|
|
212
|
+
servers[serverName] = copilotServer;
|
|
213
|
+
}
|
|
214
|
+
if (target === "codingAgent") {
|
|
215
|
+
const config2 = { mcpServers: servers };
|
|
216
|
+
return JSON.stringify(config2, null, 2);
|
|
217
|
+
} else {
|
|
218
|
+
const config2 = { servers };
|
|
219
|
+
if (inputs.length > 0) {
|
|
220
|
+
config2.inputs = inputs;
|
|
221
|
+
}
|
|
222
|
+
return JSON.stringify(config2, null, 2);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
var init_copilot = __esm({
|
|
226
|
+
"src/generators/mcp/copilot.ts"() {
|
|
227
|
+
"use strict";
|
|
228
|
+
init_mcp_helpers();
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// src/generators/mcp/cursor.ts
|
|
233
|
+
function generateCursorMcp(config, _target) {
|
|
234
|
+
const cursorConfig = {
|
|
235
|
+
mcpServers: {}
|
|
236
|
+
};
|
|
237
|
+
for (const [serverName, server] of Object.entries(config.mcpServers)) {
|
|
238
|
+
if (!shouldIncludeServer(server, "cursor")) continue;
|
|
239
|
+
const cursorServer = {};
|
|
240
|
+
if (server.command) {
|
|
241
|
+
cursorServer.command = server.command;
|
|
242
|
+
if (server.args) cursorServer.args = server.args;
|
|
243
|
+
} else if (server.url || server.httpUrl) {
|
|
244
|
+
const url = server.httpUrl || server.url;
|
|
245
|
+
if (url) {
|
|
246
|
+
cursorServer.url = url;
|
|
247
|
+
}
|
|
248
|
+
if (server.httpUrl || server.transport === "http") {
|
|
249
|
+
cursorServer.type = "streamable-http";
|
|
250
|
+
} else if (server.transport === "sse" || server.type === "sse") {
|
|
251
|
+
cursorServer.type = "sse";
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (server.env) {
|
|
255
|
+
cursorServer.env = server.env;
|
|
256
|
+
}
|
|
257
|
+
if (server.cwd) {
|
|
258
|
+
cursorServer.cwd = server.cwd;
|
|
259
|
+
}
|
|
260
|
+
cursorConfig.mcpServers[serverName] = cursorServer;
|
|
261
|
+
}
|
|
262
|
+
return JSON.stringify(cursorConfig, null, 2);
|
|
263
|
+
}
|
|
264
|
+
var init_cursor = __esm({
|
|
265
|
+
"src/generators/mcp/cursor.ts"() {
|
|
266
|
+
"use strict";
|
|
267
|
+
init_mcp_helpers();
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// src/generators/mcp/geminicli.ts
|
|
272
|
+
function generateGeminiCliMcp(config, _target) {
|
|
273
|
+
const geminiSettings = {
|
|
274
|
+
mcpServers: {}
|
|
275
|
+
};
|
|
276
|
+
for (const [serverName, server] of Object.entries(config.mcpServers)) {
|
|
277
|
+
if (!shouldIncludeServer(server, "geminicli")) continue;
|
|
278
|
+
const geminiServer = {};
|
|
279
|
+
if (server.command) {
|
|
280
|
+
geminiServer.command = server.command;
|
|
281
|
+
if (server.args) geminiServer.args = server.args;
|
|
282
|
+
} else if (server.url || server.httpUrl) {
|
|
283
|
+
if (server.httpUrl) {
|
|
284
|
+
geminiServer.httpUrl = server.httpUrl;
|
|
285
|
+
} else if (server.url) {
|
|
286
|
+
geminiServer.url = server.url;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (server.env) {
|
|
290
|
+
geminiServer.env = {};
|
|
291
|
+
for (const [key, value] of Object.entries(server.env)) {
|
|
292
|
+
if (value.startsWith("${") && value.endsWith("}")) {
|
|
293
|
+
geminiServer.env[key] = value;
|
|
294
|
+
} else {
|
|
295
|
+
geminiServer.env[key] = `\${${value}}`;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (server.timeout !== void 0) {
|
|
300
|
+
geminiServer.timeout = server.timeout;
|
|
301
|
+
}
|
|
302
|
+
if (server.trust !== void 0) {
|
|
303
|
+
geminiServer.trust = server.trust;
|
|
304
|
+
}
|
|
305
|
+
geminiSettings.mcpServers[serverName] = geminiServer;
|
|
306
|
+
}
|
|
307
|
+
return JSON.stringify(geminiSettings, null, 2);
|
|
308
|
+
}
|
|
309
|
+
var init_geminicli = __esm({
|
|
310
|
+
"src/generators/mcp/geminicli.ts"() {
|
|
311
|
+
"use strict";
|
|
312
|
+
init_mcp_helpers();
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// src/generators/mcp/roo.ts
|
|
317
|
+
function generateRooMcp(config, _target) {
|
|
318
|
+
const rooConfig = {
|
|
319
|
+
mcpServers: {}
|
|
320
|
+
};
|
|
321
|
+
for (const [serverName, server] of Object.entries(config.mcpServers)) {
|
|
322
|
+
if (!shouldIncludeServer(server, "roo")) continue;
|
|
323
|
+
const rooServer = {};
|
|
324
|
+
if (server.command) {
|
|
325
|
+
rooServer.command = server.command;
|
|
326
|
+
if (server.args) rooServer.args = server.args;
|
|
327
|
+
} else if (server.url || server.httpUrl) {
|
|
328
|
+
const url = server.httpUrl || server.url;
|
|
329
|
+
if (url) {
|
|
330
|
+
rooServer.url = url;
|
|
331
|
+
}
|
|
332
|
+
if (server.httpUrl || server.transport === "http") {
|
|
333
|
+
rooServer.type = "streamable-http";
|
|
334
|
+
} else if (server.transport === "sse" || server.type === "sse") {
|
|
335
|
+
rooServer.type = "sse";
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (server.env) {
|
|
339
|
+
rooServer.env = {};
|
|
340
|
+
for (const [key, value] of Object.entries(server.env)) {
|
|
341
|
+
if (value.startsWith("${env:") && value.endsWith("}")) {
|
|
342
|
+
rooServer.env[key] = value;
|
|
343
|
+
} else {
|
|
344
|
+
rooServer.env[key] = `\${env:${value}}`;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (server.disabled !== void 0) {
|
|
349
|
+
rooServer.disabled = server.disabled;
|
|
350
|
+
}
|
|
351
|
+
if (server.alwaysAllow) {
|
|
352
|
+
rooServer.alwaysAllow = server.alwaysAllow;
|
|
353
|
+
}
|
|
354
|
+
if (server.networkTimeout !== void 0) {
|
|
355
|
+
rooServer.networkTimeout = Math.max(3e4, Math.min(3e5, server.networkTimeout));
|
|
356
|
+
}
|
|
357
|
+
rooConfig.mcpServers[serverName] = rooServer;
|
|
358
|
+
}
|
|
359
|
+
return JSON.stringify(rooConfig, null, 2);
|
|
360
|
+
}
|
|
361
|
+
var init_roo = __esm({
|
|
362
|
+
"src/generators/mcp/roo.ts"() {
|
|
363
|
+
"use strict";
|
|
364
|
+
init_mcp_helpers();
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
26
368
|
// src/cli/index.ts
|
|
27
369
|
var import_commander = require("commander");
|
|
28
370
|
|
|
@@ -39,11 +381,12 @@ function getDefaultConfig() {
|
|
|
39
381
|
cursor: ".cursor/rules",
|
|
40
382
|
cline: ".clinerules",
|
|
41
383
|
claudecode: ".",
|
|
384
|
+
claude: ".",
|
|
42
385
|
roo: ".roo/rules",
|
|
43
386
|
geminicli: ".gemini/memories"
|
|
44
387
|
},
|
|
45
388
|
watchEnabled: false,
|
|
46
|
-
defaultTargets: ["copilot", "cursor", "cline", "claudecode", "roo", "geminicli"]
|
|
389
|
+
defaultTargets: ["copilot", "cursor", "cline", "claudecode", "claude", "roo", "geminicli"]
|
|
47
390
|
};
|
|
48
391
|
}
|
|
49
392
|
function resolveTargets(targets, config) {
|
|
@@ -89,27 +432,162 @@ async function addCommand(filename) {
|
|
|
89
432
|
}
|
|
90
433
|
}
|
|
91
434
|
|
|
92
|
-
// src/generators/claudecode.ts
|
|
435
|
+
// src/generators/rules/claudecode.ts
|
|
436
|
+
var import_node_path5 = require("path");
|
|
437
|
+
|
|
438
|
+
// src/utils/file.ts
|
|
439
|
+
var import_promises3 = require("fs/promises");
|
|
440
|
+
var import_node_path4 = require("path");
|
|
441
|
+
|
|
442
|
+
// src/utils/file-ops.ts
|
|
443
|
+
var import_promises2 = require("fs/promises");
|
|
93
444
|
var import_node_path2 = require("path");
|
|
445
|
+
async function ensureDir(dirPath) {
|
|
446
|
+
try {
|
|
447
|
+
await (0, import_promises2.stat)(dirPath);
|
|
448
|
+
} catch {
|
|
449
|
+
await (0, import_promises2.mkdir)(dirPath, { recursive: true });
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
async function readFileContent(filepath) {
|
|
453
|
+
return (0, import_promises2.readFile)(filepath, "utf-8");
|
|
454
|
+
}
|
|
455
|
+
async function writeFileContent(filepath, content) {
|
|
456
|
+
await ensureDir((0, import_node_path2.dirname)(filepath));
|
|
457
|
+
await (0, import_promises2.writeFile)(filepath, content, "utf-8");
|
|
458
|
+
}
|
|
459
|
+
async function fileExists(filepath) {
|
|
460
|
+
try {
|
|
461
|
+
await (0, import_promises2.stat)(filepath);
|
|
462
|
+
return true;
|
|
463
|
+
} catch {
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// src/utils/ignore.ts
|
|
469
|
+
var import_node_path3 = require("path");
|
|
470
|
+
var import_micromatch = __toESM(require("micromatch"));
|
|
471
|
+
var cachedIgnorePatterns = null;
|
|
472
|
+
async function loadIgnorePatterns(baseDir = process.cwd()) {
|
|
473
|
+
if (cachedIgnorePatterns) {
|
|
474
|
+
return cachedIgnorePatterns;
|
|
475
|
+
}
|
|
476
|
+
const ignorePath = (0, import_node_path3.join)(baseDir, ".rulesyncignore");
|
|
477
|
+
if (!await fileExists(ignorePath)) {
|
|
478
|
+
cachedIgnorePatterns = { patterns: [] };
|
|
479
|
+
return cachedIgnorePatterns;
|
|
480
|
+
}
|
|
481
|
+
try {
|
|
482
|
+
const content = await readFileContent(ignorePath);
|
|
483
|
+
const patterns = parseIgnoreFile(content);
|
|
484
|
+
cachedIgnorePatterns = { patterns };
|
|
485
|
+
return cachedIgnorePatterns;
|
|
486
|
+
} catch (error) {
|
|
487
|
+
console.warn(`Failed to read .rulesyncignore: ${error}`);
|
|
488
|
+
cachedIgnorePatterns = { patterns: [] };
|
|
489
|
+
return cachedIgnorePatterns;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
function parseIgnoreFile(content) {
|
|
493
|
+
return content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
494
|
+
}
|
|
495
|
+
function isFileIgnored(filepath, ignorePatterns) {
|
|
496
|
+
if (ignorePatterns.length === 0) {
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
const negationPatterns = ignorePatterns.filter((p) => p.startsWith("!"));
|
|
500
|
+
const positivePatterns = ignorePatterns.filter((p) => !p.startsWith("!"));
|
|
501
|
+
const isIgnored = positivePatterns.length > 0 && import_micromatch.default.isMatch(filepath, positivePatterns, {
|
|
502
|
+
dot: true
|
|
503
|
+
});
|
|
504
|
+
if (isIgnored && negationPatterns.length > 0) {
|
|
505
|
+
const negationPatternsWithoutPrefix = negationPatterns.map((p) => p.substring(1));
|
|
506
|
+
return !import_micromatch.default.isMatch(filepath, negationPatternsWithoutPrefix, {
|
|
507
|
+
dot: true
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
return isIgnored;
|
|
511
|
+
}
|
|
512
|
+
function filterIgnoredFiles(files, ignorePatterns) {
|
|
513
|
+
if (ignorePatterns.length === 0) {
|
|
514
|
+
return files;
|
|
515
|
+
}
|
|
516
|
+
return files.filter((file) => !isFileIgnored(file, ignorePatterns));
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// src/utils/file.ts
|
|
520
|
+
async function findFiles(dir, extension = ".md", ignorePatterns) {
|
|
521
|
+
try {
|
|
522
|
+
const files = await (0, import_promises3.readdir)(dir);
|
|
523
|
+
const filtered = files.filter((file) => file.endsWith(extension)).map((file) => (0, import_node_path4.join)(dir, file));
|
|
524
|
+
if (ignorePatterns && ignorePatterns.length > 0) {
|
|
525
|
+
return filterIgnoredFiles(filtered, ignorePatterns);
|
|
526
|
+
}
|
|
527
|
+
return filtered;
|
|
528
|
+
} catch {
|
|
529
|
+
return [];
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
async function removeDirectory(dirPath) {
|
|
533
|
+
const dangerousPaths = [".", "/", "~", "src", "node_modules"];
|
|
534
|
+
if (dangerousPaths.includes(dirPath) || dirPath === "") {
|
|
535
|
+
console.warn(`Skipping deletion of dangerous path: ${dirPath}`);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
try {
|
|
539
|
+
if (await fileExists(dirPath)) {
|
|
540
|
+
await (0, import_promises3.rm)(dirPath, { recursive: true, force: true });
|
|
541
|
+
}
|
|
542
|
+
} catch (error) {
|
|
543
|
+
console.warn(`Failed to remove directory ${dirPath}:`, error);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
async function removeFile(filepath) {
|
|
547
|
+
try {
|
|
548
|
+
if (await fileExists(filepath)) {
|
|
549
|
+
await (0, import_promises3.rm)(filepath);
|
|
550
|
+
}
|
|
551
|
+
} catch (error) {
|
|
552
|
+
console.warn(`Failed to remove file ${filepath}:`, error);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
async function removeClaudeGeneratedFiles() {
|
|
556
|
+
const filesToRemove = ["CLAUDE.md", ".claude/memories"];
|
|
557
|
+
for (const fileOrDir of filesToRemove) {
|
|
558
|
+
if (fileOrDir.endsWith("/memories")) {
|
|
559
|
+
await removeDirectory(fileOrDir);
|
|
560
|
+
} else {
|
|
561
|
+
await removeFile(fileOrDir);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// src/generators/rules/claudecode.ts
|
|
94
567
|
async function generateClaudecodeConfig(rules, config, baseDir) {
|
|
95
568
|
const outputs = [];
|
|
96
569
|
const rootRules = rules.filter((r) => r.frontmatter.root === true);
|
|
97
570
|
const detailRules = rules.filter((r) => r.frontmatter.root === false);
|
|
98
571
|
const claudeMdContent = generateClaudeMarkdown(rootRules, detailRules);
|
|
99
|
-
const claudeOutputDir = baseDir ? (0,
|
|
572
|
+
const claudeOutputDir = baseDir ? (0, import_node_path5.join)(baseDir, config.outputPaths.claudecode) : config.outputPaths.claudecode;
|
|
100
573
|
outputs.push({
|
|
101
574
|
tool: "claudecode",
|
|
102
|
-
filepath: (0,
|
|
575
|
+
filepath: (0, import_node_path5.join)(claudeOutputDir, "CLAUDE.md"),
|
|
103
576
|
content: claudeMdContent
|
|
104
577
|
});
|
|
105
578
|
for (const rule of detailRules) {
|
|
106
579
|
const memoryContent = generateMemoryFile(rule);
|
|
107
580
|
outputs.push({
|
|
108
581
|
tool: "claudecode",
|
|
109
|
-
filepath: (0,
|
|
582
|
+
filepath: (0, import_node_path5.join)(claudeOutputDir, ".claude", "memories", `${rule.filename}.md`),
|
|
110
583
|
content: memoryContent
|
|
111
584
|
});
|
|
112
585
|
}
|
|
586
|
+
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
587
|
+
if (ignorePatterns.patterns.length > 0) {
|
|
588
|
+
const settingsPath = baseDir ? (0, import_node_path5.join)(baseDir, ".claude", "settings.json") : (0, import_node_path5.join)(".claude", "settings.json");
|
|
589
|
+
await updateClaudeSettings(settingsPath, ignorePatterns.patterns);
|
|
590
|
+
}
|
|
113
591
|
return outputs;
|
|
114
592
|
}
|
|
115
593
|
function generateClaudeMarkdown(rootRules, detailRules) {
|
|
@@ -138,42 +616,108 @@ function generateClaudeMarkdown(rootRules, detailRules) {
|
|
|
138
616
|
function generateMemoryFile(rule) {
|
|
139
617
|
return rule.content.trim();
|
|
140
618
|
}
|
|
619
|
+
async function updateClaudeSettings(settingsPath, ignorePatterns) {
|
|
620
|
+
let settings = {};
|
|
621
|
+
if (await fileExists(settingsPath)) {
|
|
622
|
+
try {
|
|
623
|
+
const content = await readFileContent(settingsPath);
|
|
624
|
+
settings = JSON.parse(content);
|
|
625
|
+
} catch {
|
|
626
|
+
console.warn(`Failed to parse existing ${settingsPath}, creating new settings`);
|
|
627
|
+
settings = {};
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
if (typeof settings !== "object" || settings === null) {
|
|
631
|
+
settings = {};
|
|
632
|
+
}
|
|
633
|
+
const settingsObj = settings;
|
|
634
|
+
if (!settingsObj.permissions || typeof settingsObj.permissions !== "object" || settingsObj.permissions === null) {
|
|
635
|
+
settingsObj.permissions = {};
|
|
636
|
+
}
|
|
637
|
+
const permissions = settingsObj.permissions;
|
|
638
|
+
if (!Array.isArray(permissions.deny)) {
|
|
639
|
+
permissions.deny = [];
|
|
640
|
+
}
|
|
641
|
+
const readDenyRules = ignorePatterns.map((pattern) => `Read(${pattern})`);
|
|
642
|
+
const denyArray = permissions.deny;
|
|
643
|
+
const filteredDeny = denyArray.filter((rule) => {
|
|
644
|
+
if (typeof rule !== "string") return false;
|
|
645
|
+
if (!rule.startsWith("Read(")) return true;
|
|
646
|
+
const match = rule.match(/^Read\((.*)\)$/);
|
|
647
|
+
if (!match) return true;
|
|
648
|
+
return !ignorePatterns.includes(match[1] ?? "");
|
|
649
|
+
});
|
|
650
|
+
filteredDeny.push(...readDenyRules);
|
|
651
|
+
permissions.deny = [...new Set(filteredDeny)];
|
|
652
|
+
const jsonContent = JSON.stringify(settingsObj, null, 2);
|
|
653
|
+
await writeFileContent(settingsPath, jsonContent);
|
|
654
|
+
console.log(`\u2705 Updated Claude Code settings: ${settingsPath}`);
|
|
655
|
+
}
|
|
141
656
|
|
|
142
|
-
// src/generators/cline.ts
|
|
143
|
-
var
|
|
657
|
+
// src/generators/rules/cline.ts
|
|
658
|
+
var import_node_path6 = require("path");
|
|
144
659
|
async function generateClineConfig(rules, config, baseDir) {
|
|
145
660
|
const outputs = [];
|
|
146
661
|
for (const rule of rules) {
|
|
147
662
|
const content = generateClineMarkdown(rule);
|
|
148
|
-
const outputDir = baseDir ? (0,
|
|
149
|
-
const filepath = (0,
|
|
663
|
+
const outputDir = baseDir ? (0, import_node_path6.join)(baseDir, config.outputPaths.cline) : config.outputPaths.cline;
|
|
664
|
+
const filepath = (0, import_node_path6.join)(outputDir, `${rule.filename}.md`);
|
|
150
665
|
outputs.push({
|
|
151
666
|
tool: "cline",
|
|
152
667
|
filepath,
|
|
153
668
|
content
|
|
154
669
|
});
|
|
155
670
|
}
|
|
671
|
+
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
672
|
+
if (ignorePatterns.patterns.length > 0) {
|
|
673
|
+
const clineIgnorePath = baseDir ? (0, import_node_path6.join)(baseDir, ".clineignore") : ".clineignore";
|
|
674
|
+
const clineIgnoreContent = generateClineIgnore(ignorePatterns.patterns);
|
|
675
|
+
outputs.push({
|
|
676
|
+
tool: "cline",
|
|
677
|
+
filepath: clineIgnorePath,
|
|
678
|
+
content: clineIgnoreContent
|
|
679
|
+
});
|
|
680
|
+
}
|
|
156
681
|
return outputs;
|
|
157
682
|
}
|
|
158
683
|
function generateClineMarkdown(rule) {
|
|
159
684
|
return rule.content.trim();
|
|
160
685
|
}
|
|
686
|
+
function generateClineIgnore(patterns) {
|
|
687
|
+
const lines = [
|
|
688
|
+
"# Generated by rulesync from .rulesyncignore",
|
|
689
|
+
"# This file is automatically generated. Do not edit manually.",
|
|
690
|
+
"",
|
|
691
|
+
...patterns
|
|
692
|
+
];
|
|
693
|
+
return lines.join("\n");
|
|
694
|
+
}
|
|
161
695
|
|
|
162
|
-
// src/generators/copilot.ts
|
|
163
|
-
var
|
|
696
|
+
// src/generators/rules/copilot.ts
|
|
697
|
+
var import_node_path7 = require("path");
|
|
164
698
|
async function generateCopilotConfig(rules, config, baseDir) {
|
|
165
699
|
const outputs = [];
|
|
166
700
|
for (const rule of rules) {
|
|
167
701
|
const content = generateCopilotMarkdown(rule);
|
|
168
702
|
const baseFilename = rule.filename.replace(/\.md$/, "");
|
|
169
|
-
const outputDir = baseDir ? (0,
|
|
170
|
-
const filepath = (0,
|
|
703
|
+
const outputDir = baseDir ? (0, import_node_path7.join)(baseDir, config.outputPaths.copilot) : config.outputPaths.copilot;
|
|
704
|
+
const filepath = (0, import_node_path7.join)(outputDir, `${baseFilename}.instructions.md`);
|
|
171
705
|
outputs.push({
|
|
172
706
|
tool: "copilot",
|
|
173
707
|
filepath,
|
|
174
708
|
content
|
|
175
709
|
});
|
|
176
710
|
}
|
|
711
|
+
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
712
|
+
if (ignorePatterns.patterns.length > 0) {
|
|
713
|
+
const copilotIgnorePath = baseDir ? (0, import_node_path7.join)(baseDir, ".copilotignore") : ".copilotignore";
|
|
714
|
+
const copilotIgnoreContent = generateCopilotIgnore(ignorePatterns.patterns);
|
|
715
|
+
outputs.push({
|
|
716
|
+
tool: "copilot",
|
|
717
|
+
filepath: copilotIgnorePath,
|
|
718
|
+
content: copilotIgnoreContent
|
|
719
|
+
});
|
|
720
|
+
}
|
|
177
721
|
return outputs;
|
|
178
722
|
}
|
|
179
723
|
function generateCopilotMarkdown(rule) {
|
|
@@ -189,21 +733,42 @@ function generateCopilotMarkdown(rule) {
|
|
|
189
733
|
lines.push(rule.content);
|
|
190
734
|
return lines.join("\n");
|
|
191
735
|
}
|
|
736
|
+
function generateCopilotIgnore(patterns) {
|
|
737
|
+
const lines = [
|
|
738
|
+
"# Generated by rulesync from .rulesyncignore",
|
|
739
|
+
"# This file is automatically generated. Do not edit manually.",
|
|
740
|
+
"# Note: .copilotignore is not officially supported by GitHub Copilot.",
|
|
741
|
+
"# This file is for use with community tools like copilotignore-vscode extension.",
|
|
742
|
+
"",
|
|
743
|
+
...patterns
|
|
744
|
+
];
|
|
745
|
+
return lines.join("\n");
|
|
746
|
+
}
|
|
192
747
|
|
|
193
|
-
// src/generators/cursor.ts
|
|
194
|
-
var
|
|
748
|
+
// src/generators/rules/cursor.ts
|
|
749
|
+
var import_node_path8 = require("path");
|
|
195
750
|
async function generateCursorConfig(rules, config, baseDir) {
|
|
196
751
|
const outputs = [];
|
|
197
752
|
for (const rule of rules) {
|
|
198
753
|
const content = generateCursorMarkdown(rule);
|
|
199
|
-
const outputDir = baseDir ? (0,
|
|
200
|
-
const filepath = (0,
|
|
754
|
+
const outputDir = baseDir ? (0, import_node_path8.join)(baseDir, config.outputPaths.cursor) : config.outputPaths.cursor;
|
|
755
|
+
const filepath = (0, import_node_path8.join)(outputDir, `${rule.filename}.mdc`);
|
|
201
756
|
outputs.push({
|
|
202
757
|
tool: "cursor",
|
|
203
758
|
filepath,
|
|
204
759
|
content
|
|
205
760
|
});
|
|
206
761
|
}
|
|
762
|
+
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
763
|
+
if (ignorePatterns.patterns.length > 0) {
|
|
764
|
+
const cursorIgnorePath = baseDir ? (0, import_node_path8.join)(baseDir, ".cursorignore") : ".cursorignore";
|
|
765
|
+
const cursorIgnoreContent = generateCursorIgnore(ignorePatterns.patterns);
|
|
766
|
+
outputs.push({
|
|
767
|
+
tool: "cursor",
|
|
768
|
+
filepath: cursorIgnorePath,
|
|
769
|
+
content: cursorIgnoreContent
|
|
770
|
+
});
|
|
771
|
+
}
|
|
207
772
|
return outputs;
|
|
208
773
|
}
|
|
209
774
|
function generateCursorMarkdown(rule) {
|
|
@@ -226,17 +791,26 @@ function generateCursorMarkdown(rule) {
|
|
|
226
791
|
lines.push(rule.content);
|
|
227
792
|
return lines.join("\n");
|
|
228
793
|
}
|
|
794
|
+
function generateCursorIgnore(patterns) {
|
|
795
|
+
const lines = [
|
|
796
|
+
"# Generated by rulesync from .rulesyncignore",
|
|
797
|
+
"# This file is automatically generated. Do not edit manually.",
|
|
798
|
+
"",
|
|
799
|
+
...patterns
|
|
800
|
+
];
|
|
801
|
+
return lines.join("\n");
|
|
802
|
+
}
|
|
229
803
|
|
|
230
|
-
// src/generators/geminicli.ts
|
|
231
|
-
var
|
|
804
|
+
// src/generators/rules/geminicli.ts
|
|
805
|
+
var import_node_path9 = require("path");
|
|
232
806
|
async function generateGeminiConfig(rules, config, baseDir) {
|
|
233
807
|
const outputs = [];
|
|
234
808
|
const rootRule = rules.find((rule) => rule.frontmatter.root === true);
|
|
235
809
|
const memoryRules = rules.filter((rule) => rule.frontmatter.root === false);
|
|
236
810
|
for (const rule of memoryRules) {
|
|
237
811
|
const content = generateGeminiMemoryMarkdown(rule);
|
|
238
|
-
const outputDir = baseDir ? (0,
|
|
239
|
-
const filepath = (0,
|
|
812
|
+
const outputDir = baseDir ? (0, import_node_path9.join)(baseDir, config.outputPaths.geminicli) : config.outputPaths.geminicli;
|
|
813
|
+
const filepath = (0, import_node_path9.join)(outputDir, `${rule.filename}.md`);
|
|
240
814
|
outputs.push({
|
|
241
815
|
tool: "geminicli",
|
|
242
816
|
filepath,
|
|
@@ -244,12 +818,22 @@ async function generateGeminiConfig(rules, config, baseDir) {
|
|
|
244
818
|
});
|
|
245
819
|
}
|
|
246
820
|
const rootContent = generateGeminiRootMarkdown(rootRule, memoryRules, baseDir);
|
|
247
|
-
const rootFilepath = baseDir ? (0,
|
|
821
|
+
const rootFilepath = baseDir ? (0, import_node_path9.join)(baseDir, "GEMINI.md") : "GEMINI.md";
|
|
248
822
|
outputs.push({
|
|
249
823
|
tool: "geminicli",
|
|
250
824
|
filepath: rootFilepath,
|
|
251
825
|
content: rootContent
|
|
252
826
|
});
|
|
827
|
+
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
828
|
+
if (ignorePatterns.patterns.length > 0) {
|
|
829
|
+
const aiexcludePath = baseDir ? (0, import_node_path9.join)(baseDir, ".aiexclude") : ".aiexclude";
|
|
830
|
+
const aiexcludeContent = generateAiexclude(ignorePatterns.patterns);
|
|
831
|
+
outputs.push({
|
|
832
|
+
tool: "geminicli",
|
|
833
|
+
filepath: aiexcludePath,
|
|
834
|
+
content: aiexcludeContent
|
|
835
|
+
});
|
|
836
|
+
}
|
|
253
837
|
return outputs;
|
|
254
838
|
}
|
|
255
839
|
function generateGeminiMemoryMarkdown(rule) {
|
|
@@ -279,92 +863,53 @@ function generateGeminiRootMarkdown(rootRule, memoryRules, _baseDir) {
|
|
|
279
863
|
}
|
|
280
864
|
return lines.join("\n");
|
|
281
865
|
}
|
|
866
|
+
function generateAiexclude(patterns) {
|
|
867
|
+
const lines = [
|
|
868
|
+
"# Generated by rulesync from .rulesyncignore",
|
|
869
|
+
"# This file is automatically generated. Do not edit manually.",
|
|
870
|
+
"",
|
|
871
|
+
...patterns
|
|
872
|
+
];
|
|
873
|
+
return lines.join("\n");
|
|
874
|
+
}
|
|
282
875
|
|
|
283
|
-
// src/generators/roo.ts
|
|
284
|
-
var
|
|
876
|
+
// src/generators/rules/roo.ts
|
|
877
|
+
var import_node_path10 = require("path");
|
|
285
878
|
async function generateRooConfig(rules, config, baseDir) {
|
|
286
879
|
const outputs = [];
|
|
287
880
|
for (const rule of rules) {
|
|
288
881
|
const content = generateRooMarkdown(rule);
|
|
289
|
-
const outputDir = baseDir ? (0,
|
|
290
|
-
const filepath = (0,
|
|
882
|
+
const outputDir = baseDir ? (0, import_node_path10.join)(baseDir, config.outputPaths.roo) : config.outputPaths.roo;
|
|
883
|
+
const filepath = (0, import_node_path10.join)(outputDir, `${rule.filename}.md`);
|
|
291
884
|
outputs.push({
|
|
292
885
|
tool: "roo",
|
|
293
886
|
filepath,
|
|
294
887
|
content
|
|
295
888
|
});
|
|
296
889
|
}
|
|
890
|
+
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
891
|
+
if (ignorePatterns.patterns.length > 0) {
|
|
892
|
+
const rooIgnorePath = baseDir ? (0, import_node_path10.join)(baseDir, ".rooignore") : ".rooignore";
|
|
893
|
+
const rooIgnoreContent = generateRooIgnore(ignorePatterns.patterns);
|
|
894
|
+
outputs.push({
|
|
895
|
+
tool: "roo",
|
|
896
|
+
filepath: rooIgnorePath,
|
|
897
|
+
content: rooIgnoreContent
|
|
898
|
+
});
|
|
899
|
+
}
|
|
297
900
|
return outputs;
|
|
298
901
|
}
|
|
299
902
|
function generateRooMarkdown(rule) {
|
|
300
903
|
return rule.content.trim();
|
|
301
904
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
await (0, import_promises2.mkdir)(dirPath, { recursive: true });
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
async function readFileContent(filepath) {
|
|
314
|
-
return (0, import_promises2.readFile)(filepath, "utf-8");
|
|
315
|
-
}
|
|
316
|
-
async function writeFileContent(filepath, content) {
|
|
317
|
-
await ensureDir((0, import_node_path8.dirname)(filepath));
|
|
318
|
-
await (0, import_promises2.writeFile)(filepath, content, "utf-8");
|
|
319
|
-
}
|
|
320
|
-
async function findFiles(dir, extension = ".md") {
|
|
321
|
-
try {
|
|
322
|
-
const files = await (0, import_promises2.readdir)(dir);
|
|
323
|
-
return files.filter((file) => file.endsWith(extension)).map((file) => (0, import_node_path8.join)(dir, file));
|
|
324
|
-
} catch {
|
|
325
|
-
return [];
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
async function fileExists(filepath) {
|
|
329
|
-
try {
|
|
330
|
-
await (0, import_promises2.stat)(filepath);
|
|
331
|
-
return true;
|
|
332
|
-
} catch {
|
|
333
|
-
return false;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
async function removeDirectory(dirPath) {
|
|
337
|
-
const dangerousPaths = [".", "/", "~", "src", "node_modules"];
|
|
338
|
-
if (dangerousPaths.includes(dirPath) || dirPath === "") {
|
|
339
|
-
console.warn(`Skipping deletion of dangerous path: ${dirPath}`);
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
try {
|
|
343
|
-
if (await fileExists(dirPath)) {
|
|
344
|
-
await (0, import_promises2.rm)(dirPath, { recursive: true, force: true });
|
|
345
|
-
}
|
|
346
|
-
} catch (error) {
|
|
347
|
-
console.warn(`Failed to remove directory ${dirPath}:`, error);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
async function removeFile(filepath) {
|
|
351
|
-
try {
|
|
352
|
-
if (await fileExists(filepath)) {
|
|
353
|
-
await (0, import_promises2.rm)(filepath);
|
|
354
|
-
}
|
|
355
|
-
} catch (error) {
|
|
356
|
-
console.warn(`Failed to remove file ${filepath}:`, error);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
async function removeClaudeGeneratedFiles() {
|
|
360
|
-
const filesToRemove = ["CLAUDE.md", ".claude/memories"];
|
|
361
|
-
for (const fileOrDir of filesToRemove) {
|
|
362
|
-
if (fileOrDir.endsWith("/memories")) {
|
|
363
|
-
await removeDirectory(fileOrDir);
|
|
364
|
-
} else {
|
|
365
|
-
await removeFile(fileOrDir);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
905
|
+
function generateRooIgnore(patterns) {
|
|
906
|
+
const lines = [
|
|
907
|
+
"# Generated by rulesync from .rulesyncignore",
|
|
908
|
+
"# This file is automatically generated. Do not edit manually.",
|
|
909
|
+
"",
|
|
910
|
+
...patterns
|
|
911
|
+
];
|
|
912
|
+
return lines.join("\n");
|
|
368
913
|
}
|
|
369
914
|
|
|
370
915
|
// src/core/generator.ts
|
|
@@ -417,12 +962,16 @@ async function generateForTool(tool, rules, config, baseDir) {
|
|
|
417
962
|
}
|
|
418
963
|
|
|
419
964
|
// src/core/parser.ts
|
|
420
|
-
var
|
|
965
|
+
var import_node_path11 = require("path");
|
|
421
966
|
var import_gray_matter = __toESM(require("gray-matter"));
|
|
422
967
|
async function parseRulesFromDirectory(aiRulesDir) {
|
|
423
|
-
const
|
|
968
|
+
const ignorePatterns = await loadIgnorePatterns();
|
|
969
|
+
const ruleFiles = await findFiles(aiRulesDir, ".md", ignorePatterns.patterns);
|
|
424
970
|
const rules = [];
|
|
425
971
|
const errors = [];
|
|
972
|
+
if (ignorePatterns.patterns.length > 0) {
|
|
973
|
+
console.log(`Loaded ${ignorePatterns.patterns.length} ignore patterns from .rulesyncignore`);
|
|
974
|
+
}
|
|
426
975
|
for (const filepath of ruleFiles) {
|
|
427
976
|
try {
|
|
428
977
|
const rule = await parseRuleFile(filepath);
|
|
@@ -450,7 +999,7 @@ async function parseRuleFile(filepath) {
|
|
|
450
999
|
const parsed = (0, import_gray_matter.default)(content);
|
|
451
1000
|
validateFrontmatter(parsed.data, filepath);
|
|
452
1001
|
const frontmatter = parsed.data;
|
|
453
|
-
const filename = (0,
|
|
1002
|
+
const filename = (0, import_node_path11.basename)(filepath, ".md");
|
|
454
1003
|
return {
|
|
455
1004
|
frontmatter,
|
|
456
1005
|
content: parsed.content,
|
|
@@ -578,6 +1127,148 @@ async function validateRule(rule) {
|
|
|
578
1127
|
};
|
|
579
1128
|
}
|
|
580
1129
|
|
|
1130
|
+
// src/core/mcp-generator.ts
|
|
1131
|
+
var import_node_os = __toESM(require("os"));
|
|
1132
|
+
var import_node_path13 = __toESM(require("path"));
|
|
1133
|
+
|
|
1134
|
+
// src/generators/mcp/index.ts
|
|
1135
|
+
init_claude();
|
|
1136
|
+
init_cline();
|
|
1137
|
+
init_copilot();
|
|
1138
|
+
init_cursor();
|
|
1139
|
+
init_geminicli();
|
|
1140
|
+
init_roo();
|
|
1141
|
+
|
|
1142
|
+
// src/core/mcp-parser.ts
|
|
1143
|
+
var import_node_fs = __toESM(require("fs"));
|
|
1144
|
+
var import_node_path12 = __toESM(require("path"));
|
|
1145
|
+
function parseMcpConfig(projectRoot) {
|
|
1146
|
+
const mcpPath = import_node_path12.default.join(projectRoot, ".rulesync", ".mcp.json");
|
|
1147
|
+
if (!import_node_fs.default.existsSync(mcpPath)) {
|
|
1148
|
+
return null;
|
|
1149
|
+
}
|
|
1150
|
+
try {
|
|
1151
|
+
const content = import_node_fs.default.readFileSync(mcpPath, "utf-8");
|
|
1152
|
+
const rawConfig = JSON.parse(content);
|
|
1153
|
+
if (rawConfig.servers && !rawConfig.mcpServers) {
|
|
1154
|
+
rawConfig.mcpServers = rawConfig.servers;
|
|
1155
|
+
delete rawConfig.servers;
|
|
1156
|
+
}
|
|
1157
|
+
if (!rawConfig.mcpServers || typeof rawConfig.mcpServers !== "object") {
|
|
1158
|
+
throw new Error("Invalid mcp.json: 'mcpServers' field must be an object");
|
|
1159
|
+
}
|
|
1160
|
+
if (rawConfig.tools) {
|
|
1161
|
+
delete rawConfig.tools;
|
|
1162
|
+
}
|
|
1163
|
+
return { mcpServers: rawConfig.mcpServers };
|
|
1164
|
+
} catch (error) {
|
|
1165
|
+
throw new Error(
|
|
1166
|
+
`Failed to parse mcp.json: ${error instanceof Error ? error.message : String(error)}`
|
|
1167
|
+
);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// src/core/mcp-generator.ts
|
|
1172
|
+
async function generateMcpConfigs(projectRoot, baseDir) {
|
|
1173
|
+
const results = [];
|
|
1174
|
+
const targetRoot = baseDir || projectRoot;
|
|
1175
|
+
const config = parseMcpConfig(projectRoot);
|
|
1176
|
+
if (!config) {
|
|
1177
|
+
return results;
|
|
1178
|
+
}
|
|
1179
|
+
const generators = [
|
|
1180
|
+
{
|
|
1181
|
+
tool: "claude-project",
|
|
1182
|
+
path: import_node_path13.default.join(targetRoot, ".mcp.json"),
|
|
1183
|
+
generate: () => generateClaudeMcp(config, "project")
|
|
1184
|
+
},
|
|
1185
|
+
{
|
|
1186
|
+
tool: "copilot-editor",
|
|
1187
|
+
path: import_node_path13.default.join(targetRoot, ".vscode", "mcp.json"),
|
|
1188
|
+
generate: () => generateCopilotMcp(config, "editor")
|
|
1189
|
+
},
|
|
1190
|
+
{
|
|
1191
|
+
tool: "cursor-project",
|
|
1192
|
+
path: import_node_path13.default.join(targetRoot, ".cursor", "mcp.json"),
|
|
1193
|
+
generate: () => generateCursorMcp(config, "project")
|
|
1194
|
+
},
|
|
1195
|
+
{
|
|
1196
|
+
tool: "cline-project",
|
|
1197
|
+
path: import_node_path13.default.join(targetRoot, ".cline", "mcp.json"),
|
|
1198
|
+
generate: () => generateClineMcp(config, "project")
|
|
1199
|
+
},
|
|
1200
|
+
{
|
|
1201
|
+
tool: "gemini-project",
|
|
1202
|
+
path: import_node_path13.default.join(targetRoot, ".gemini", "settings.json"),
|
|
1203
|
+
generate: () => generateGeminiCliMcp(config, "project")
|
|
1204
|
+
},
|
|
1205
|
+
{
|
|
1206
|
+
tool: "roo-project",
|
|
1207
|
+
path: import_node_path13.default.join(targetRoot, ".roo", "mcp.json"),
|
|
1208
|
+
generate: () => generateRooMcp(config, "project")
|
|
1209
|
+
}
|
|
1210
|
+
];
|
|
1211
|
+
if (!baseDir) {
|
|
1212
|
+
generators.push(
|
|
1213
|
+
{
|
|
1214
|
+
tool: "claude-global",
|
|
1215
|
+
path: import_node_path13.default.join(import_node_os.default.homedir(), ".claude", "settings.json"),
|
|
1216
|
+
generate: () => generateClaudeMcp(config, "global")
|
|
1217
|
+
},
|
|
1218
|
+
{
|
|
1219
|
+
tool: "cursor-global",
|
|
1220
|
+
path: import_node_path13.default.join(import_node_os.default.homedir(), ".cursor", "mcp.json"),
|
|
1221
|
+
generate: () => generateCursorMcp(config, "global")
|
|
1222
|
+
},
|
|
1223
|
+
{
|
|
1224
|
+
tool: "gemini-global",
|
|
1225
|
+
path: import_node_path13.default.join(import_node_os.default.homedir(), ".gemini", "settings.json"),
|
|
1226
|
+
generate: () => generateGeminiCliMcp(config, "global")
|
|
1227
|
+
}
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
for (const generator of generators) {
|
|
1231
|
+
try {
|
|
1232
|
+
const content = generator.generate();
|
|
1233
|
+
const parsed = JSON.parse(content);
|
|
1234
|
+
if (generator.tool.includes("claude") || generator.tool.includes("cline") || generator.tool.includes("cursor") || generator.tool.includes("gemini") || generator.tool.includes("roo")) {
|
|
1235
|
+
if (!parsed.mcpServers || Object.keys(parsed.mcpServers).length === 0) {
|
|
1236
|
+
results.push({
|
|
1237
|
+
tool: generator.tool,
|
|
1238
|
+
path: generator.path,
|
|
1239
|
+
status: "skipped"
|
|
1240
|
+
});
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1243
|
+
} else if (generator.tool.includes("copilot")) {
|
|
1244
|
+
const key = generator.tool.includes("codingAgent") ? "mcpServers" : "servers";
|
|
1245
|
+
if (!parsed[key] || Object.keys(parsed[key]).length === 0) {
|
|
1246
|
+
results.push({
|
|
1247
|
+
tool: generator.tool,
|
|
1248
|
+
path: generator.path,
|
|
1249
|
+
status: "skipped"
|
|
1250
|
+
});
|
|
1251
|
+
continue;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
await writeFileContent(generator.path, content);
|
|
1255
|
+
results.push({
|
|
1256
|
+
tool: generator.tool,
|
|
1257
|
+
path: generator.path,
|
|
1258
|
+
status: "success"
|
|
1259
|
+
});
|
|
1260
|
+
} catch (error) {
|
|
1261
|
+
results.push({
|
|
1262
|
+
tool: generator.tool,
|
|
1263
|
+
path: generator.path,
|
|
1264
|
+
status: "error",
|
|
1265
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
return results;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
581
1272
|
// src/cli/commands/generate.ts
|
|
582
1273
|
async function generateCommand(options = {}) {
|
|
583
1274
|
const config = getDefaultConfig();
|
|
@@ -658,6 +1349,30 @@ Generating configurations for base directory: ${baseDir}`);
|
|
|
658
1349
|
}
|
|
659
1350
|
console.log(`
|
|
660
1351
|
\u{1F389} Successfully generated ${totalOutputs} configuration file(s)!`);
|
|
1352
|
+
if (options.verbose) {
|
|
1353
|
+
console.log("\nGenerating MCP configurations...");
|
|
1354
|
+
}
|
|
1355
|
+
for (const baseDir of baseDirs) {
|
|
1356
|
+
const mcpResults = await generateMcpConfigs(
|
|
1357
|
+
process.cwd(),
|
|
1358
|
+
baseDir === process.cwd() ? void 0 : baseDir
|
|
1359
|
+
);
|
|
1360
|
+
if (mcpResults.length === 0) {
|
|
1361
|
+
if (options.verbose) {
|
|
1362
|
+
console.log(`No MCP configuration found for ${baseDir}`);
|
|
1363
|
+
}
|
|
1364
|
+
continue;
|
|
1365
|
+
}
|
|
1366
|
+
for (const result of mcpResults) {
|
|
1367
|
+
if (result.status === "success") {
|
|
1368
|
+
console.log(`\u2705 Generated ${result.tool} MCP configuration: ${result.path}`);
|
|
1369
|
+
} else if (result.status === "error") {
|
|
1370
|
+
console.error(`\u274C Failed to generate ${result.tool} MCP configuration: ${result.error}`);
|
|
1371
|
+
} else if (options.verbose && result.status === "skipped") {
|
|
1372
|
+
console.log(`\u23ED\uFE0F Skipped ${result.tool} MCP configuration (no servers configured)`);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
661
1376
|
} catch (error) {
|
|
662
1377
|
console.error("\u274C Failed to generate configurations:", error);
|
|
663
1378
|
process.exit(1);
|
|
@@ -665,25 +1380,36 @@ Generating configurations for base directory: ${baseDir}`);
|
|
|
665
1380
|
}
|
|
666
1381
|
|
|
667
1382
|
// src/cli/commands/gitignore.ts
|
|
668
|
-
var
|
|
669
|
-
var
|
|
1383
|
+
var import_node_fs2 = require("fs");
|
|
1384
|
+
var import_node_path14 = require("path");
|
|
670
1385
|
var gitignoreCommand = async () => {
|
|
671
|
-
const gitignorePath = (0,
|
|
1386
|
+
const gitignorePath = (0, import_node_path14.join)(process.cwd(), ".gitignore");
|
|
672
1387
|
const rulesFilesToIgnore = [
|
|
673
1388
|
"# Generated by rulesync - AI tool configuration files",
|
|
674
1389
|
"**/.github/copilot-instructions.md",
|
|
675
1390
|
"**/.github/instructions/",
|
|
676
1391
|
"**/.cursor/rules/",
|
|
1392
|
+
"**/.cursorignore",
|
|
677
1393
|
"**/.clinerules/",
|
|
1394
|
+
"**/.clineignore",
|
|
678
1395
|
"**/CLAUDE.md",
|
|
679
1396
|
"**/.claude/memories/",
|
|
680
1397
|
"**/.roo/rules/",
|
|
1398
|
+
"**/.rooignore",
|
|
1399
|
+
"**/.copilotignore",
|
|
681
1400
|
"**/GEMINI.md",
|
|
682
|
-
"**/.gemini/memories/"
|
|
1401
|
+
"**/.gemini/memories/",
|
|
1402
|
+
"**/.aiexclude",
|
|
1403
|
+
"**/.mcp.json",
|
|
1404
|
+
"**/.cursor/mcp.json",
|
|
1405
|
+
"**/.cline/mcp.json",
|
|
1406
|
+
"**/.vscode/mcp.json",
|
|
1407
|
+
"**/.gemini/settings.json",
|
|
1408
|
+
"**/.roo/mcp.json"
|
|
683
1409
|
];
|
|
684
1410
|
let gitignoreContent = "";
|
|
685
|
-
if ((0,
|
|
686
|
-
gitignoreContent = (0,
|
|
1411
|
+
if ((0, import_node_fs2.existsSync)(gitignorePath)) {
|
|
1412
|
+
gitignoreContent = (0, import_node_fs2.readFileSync)(gitignorePath, "utf-8");
|
|
687
1413
|
}
|
|
688
1414
|
const linesToAdd = [];
|
|
689
1415
|
for (const rule of rulesFilesToIgnore) {
|
|
@@ -700,7 +1426,7 @@ var gitignoreCommand = async () => {
|
|
|
700
1426
|
${linesToAdd.join("\n")}
|
|
701
1427
|
` : `${linesToAdd.join("\n")}
|
|
702
1428
|
`;
|
|
703
|
-
(0,
|
|
1429
|
+
(0, import_node_fs2.writeFileSync)(gitignorePath, newContent);
|
|
704
1430
|
console.log(`\u2705 .gitignore\u306B${linesToAdd.length}\u500B\u306E\u30EB\u30FC\u30EB\u3092\u8FFD\u52A0\u3057\u307E\u3057\u305F:`);
|
|
705
1431
|
for (const line of linesToAdd) {
|
|
706
1432
|
if (!line.startsWith("#")) {
|
|
@@ -710,15 +1436,17 @@ ${linesToAdd.join("\n")}
|
|
|
710
1436
|
};
|
|
711
1437
|
|
|
712
1438
|
// src/core/importer.ts
|
|
713
|
-
var
|
|
1439
|
+
var import_node_path21 = require("path");
|
|
714
1440
|
var import_gray_matter4 = __toESM(require("gray-matter"));
|
|
715
1441
|
|
|
716
1442
|
// src/parsers/claudecode.ts
|
|
717
|
-
var
|
|
1443
|
+
var import_node_path15 = require("path");
|
|
718
1444
|
async function parseClaudeConfiguration(baseDir = process.cwd()) {
|
|
719
1445
|
const errors = [];
|
|
720
1446
|
const rules = [];
|
|
721
|
-
|
|
1447
|
+
let ignorePatterns;
|
|
1448
|
+
let mcpServers;
|
|
1449
|
+
const claudeFilePath = (0, import_node_path15.join)(baseDir, "CLAUDE.md");
|
|
722
1450
|
if (!await fileExists(claudeFilePath)) {
|
|
723
1451
|
errors.push("CLAUDE.md file not found");
|
|
724
1452
|
return { rules, errors };
|
|
@@ -729,16 +1457,32 @@ async function parseClaudeConfiguration(baseDir = process.cwd()) {
|
|
|
729
1457
|
if (mainRule) {
|
|
730
1458
|
rules.push(mainRule);
|
|
731
1459
|
}
|
|
732
|
-
const memoryDir = (0,
|
|
1460
|
+
const memoryDir = (0, import_node_path15.join)(baseDir, ".claude", "memories");
|
|
733
1461
|
if (await fileExists(memoryDir)) {
|
|
734
1462
|
const memoryRules = await parseClaudeMemoryFiles(memoryDir);
|
|
735
1463
|
rules.push(...memoryRules);
|
|
736
1464
|
}
|
|
1465
|
+
const settingsPath = (0, import_node_path15.join)(baseDir, ".claude", "settings.json");
|
|
1466
|
+
if (await fileExists(settingsPath)) {
|
|
1467
|
+
const settingsResult = await parseClaudeSettings(settingsPath);
|
|
1468
|
+
if (settingsResult.ignorePatterns) {
|
|
1469
|
+
ignorePatterns = settingsResult.ignorePatterns;
|
|
1470
|
+
}
|
|
1471
|
+
if (settingsResult.mcpServers) {
|
|
1472
|
+
mcpServers = settingsResult.mcpServers;
|
|
1473
|
+
}
|
|
1474
|
+
errors.push(...settingsResult.errors);
|
|
1475
|
+
}
|
|
737
1476
|
} catch (error) {
|
|
738
1477
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
739
1478
|
errors.push(`Failed to parse Claude configuration: ${errorMessage}`);
|
|
740
1479
|
}
|
|
741
|
-
return {
|
|
1480
|
+
return {
|
|
1481
|
+
rules,
|
|
1482
|
+
errors,
|
|
1483
|
+
...ignorePatterns && { ignorePatterns },
|
|
1484
|
+
...mcpServers && { mcpServers }
|
|
1485
|
+
};
|
|
742
1486
|
}
|
|
743
1487
|
function parseClaudeMainFile(content, filepath) {
|
|
744
1488
|
const lines = content.split("\n");
|
|
@@ -775,10 +1519,10 @@ async function parseClaudeMemoryFiles(memoryDir) {
|
|
|
775
1519
|
const files = await readdir2(memoryDir);
|
|
776
1520
|
for (const file of files) {
|
|
777
1521
|
if (file.endsWith(".md")) {
|
|
778
|
-
const filePath = (0,
|
|
1522
|
+
const filePath = (0, import_node_path15.join)(memoryDir, file);
|
|
779
1523
|
const content = await readFileContent(filePath);
|
|
780
1524
|
if (content.trim()) {
|
|
781
|
-
const filename = (0,
|
|
1525
|
+
const filename = (0, import_node_path15.basename)(file, ".md");
|
|
782
1526
|
const frontmatter = {
|
|
783
1527
|
root: false,
|
|
784
1528
|
targets: ["claudecode"],
|
|
@@ -794,51 +1538,125 @@ async function parseClaudeMemoryFiles(memoryDir) {
|
|
|
794
1538
|
}
|
|
795
1539
|
}
|
|
796
1540
|
}
|
|
797
|
-
} catch
|
|
1541
|
+
} catch {
|
|
798
1542
|
}
|
|
799
1543
|
return rules;
|
|
800
1544
|
}
|
|
1545
|
+
async function parseClaudeSettings(settingsPath) {
|
|
1546
|
+
const errors = [];
|
|
1547
|
+
let ignorePatterns;
|
|
1548
|
+
let mcpServers;
|
|
1549
|
+
try {
|
|
1550
|
+
const content = await readFileContent(settingsPath);
|
|
1551
|
+
const settings = JSON.parse(content);
|
|
1552
|
+
if (typeof settings === "object" && settings !== null && "permissions" in settings) {
|
|
1553
|
+
const permissions = settings.permissions;
|
|
1554
|
+
if (permissions && "deny" in permissions && Array.isArray(permissions.deny)) {
|
|
1555
|
+
const readPatterns = permissions.deny.filter(
|
|
1556
|
+
(rule) => typeof rule === "string" && rule.startsWith("Read(") && rule.endsWith(")")
|
|
1557
|
+
).map((rule) => {
|
|
1558
|
+
const match = rule.match(/^Read\((.+)\)$/);
|
|
1559
|
+
return match ? match[1] : null;
|
|
1560
|
+
}).filter((pattern) => pattern !== null);
|
|
1561
|
+
if (readPatterns.length > 0) {
|
|
1562
|
+
ignorePatterns = readPatterns;
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
if (typeof settings === "object" && settings !== null && "mcpServers" in settings) {
|
|
1567
|
+
const servers = settings.mcpServers;
|
|
1568
|
+
if (servers && typeof servers === "object" && Object.keys(servers).length > 0) {
|
|
1569
|
+
mcpServers = servers;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
} catch (error) {
|
|
1573
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1574
|
+
errors.push(`Failed to parse settings.json: ${errorMessage}`);
|
|
1575
|
+
}
|
|
1576
|
+
return {
|
|
1577
|
+
errors,
|
|
1578
|
+
...ignorePatterns && { ignorePatterns },
|
|
1579
|
+
...mcpServers && { mcpServers }
|
|
1580
|
+
};
|
|
1581
|
+
}
|
|
801
1582
|
|
|
802
1583
|
// src/parsers/cline.ts
|
|
803
|
-
var
|
|
1584
|
+
var import_node_path16 = require("path");
|
|
804
1585
|
async function parseClineConfiguration(baseDir = process.cwd()) {
|
|
805
1586
|
const errors = [];
|
|
806
1587
|
const rules = [];
|
|
807
|
-
const clineFilePath = (0,
|
|
808
|
-
if (
|
|
809
|
-
|
|
810
|
-
|
|
1588
|
+
const clineFilePath = (0, import_node_path16.join)(baseDir, ".cline", "instructions.md");
|
|
1589
|
+
if (await fileExists(clineFilePath)) {
|
|
1590
|
+
try {
|
|
1591
|
+
const content = await readFileContent(clineFilePath);
|
|
1592
|
+
if (content.trim()) {
|
|
1593
|
+
const frontmatter = {
|
|
1594
|
+
root: false,
|
|
1595
|
+
targets: ["cline"],
|
|
1596
|
+
description: "Cline instructions",
|
|
1597
|
+
globs: ["**/*"]
|
|
1598
|
+
};
|
|
1599
|
+
rules.push({
|
|
1600
|
+
frontmatter,
|
|
1601
|
+
content: content.trim(),
|
|
1602
|
+
filename: "cline-instructions",
|
|
1603
|
+
filepath: clineFilePath
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1606
|
+
} catch (error) {
|
|
1607
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1608
|
+
errors.push(`Failed to parse .cline/instructions.md: ${errorMessage}`);
|
|
1609
|
+
}
|
|
811
1610
|
}
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
const
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
1611
|
+
const clinerulesDirPath = (0, import_node_path16.join)(baseDir, ".clinerules");
|
|
1612
|
+
if (await fileExists(clinerulesDirPath)) {
|
|
1613
|
+
try {
|
|
1614
|
+
const { readdir: readdir2 } = await import("fs/promises");
|
|
1615
|
+
const files = await readdir2(clinerulesDirPath);
|
|
1616
|
+
for (const file of files) {
|
|
1617
|
+
if (file.endsWith(".md")) {
|
|
1618
|
+
const filePath = (0, import_node_path16.join)(clinerulesDirPath, file);
|
|
1619
|
+
try {
|
|
1620
|
+
const content = await readFileContent(filePath);
|
|
1621
|
+
if (content.trim()) {
|
|
1622
|
+
const filename = file.replace(".md", "");
|
|
1623
|
+
const frontmatter = {
|
|
1624
|
+
root: false,
|
|
1625
|
+
targets: ["cline"],
|
|
1626
|
+
description: `Cline rule: ${filename}`,
|
|
1627
|
+
globs: ["**/*"]
|
|
1628
|
+
};
|
|
1629
|
+
rules.push({
|
|
1630
|
+
frontmatter,
|
|
1631
|
+
content: content.trim(),
|
|
1632
|
+
filename: `cline-${filename}`,
|
|
1633
|
+
filepath: filePath
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
} catch (error) {
|
|
1637
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1638
|
+
errors.push(`Failed to parse ${filePath}: ${errorMessage}`);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
} catch (error) {
|
|
1643
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1644
|
+
errors.push(`Failed to parse .clinerules files: ${errorMessage}`);
|
|
827
1645
|
}
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
errors.push(
|
|
1646
|
+
}
|
|
1647
|
+
if (rules.length === 0) {
|
|
1648
|
+
errors.push("No Cline configuration files found (.cline/instructions.md or .clinerules/*.md)");
|
|
831
1649
|
}
|
|
832
1650
|
return { rules, errors };
|
|
833
1651
|
}
|
|
834
1652
|
|
|
835
1653
|
// src/parsers/copilot.ts
|
|
836
|
-
var
|
|
1654
|
+
var import_node_path17 = require("path");
|
|
837
1655
|
var import_gray_matter2 = __toESM(require("gray-matter"));
|
|
838
1656
|
async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
839
1657
|
const errors = [];
|
|
840
1658
|
const rules = [];
|
|
841
|
-
const copilotFilePath = (0,
|
|
1659
|
+
const copilotFilePath = (0, import_node_path17.join)(baseDir, ".github", "copilot-instructions.md");
|
|
842
1660
|
if (await fileExists(copilotFilePath)) {
|
|
843
1661
|
try {
|
|
844
1662
|
const rawContent = await readFileContent(copilotFilePath);
|
|
@@ -863,19 +1681,19 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
|
863
1681
|
errors.push(`Failed to parse copilot-instructions.md: ${errorMessage}`);
|
|
864
1682
|
}
|
|
865
1683
|
}
|
|
866
|
-
const instructionsDir = (0,
|
|
1684
|
+
const instructionsDir = (0, import_node_path17.join)(baseDir, ".github", "instructions");
|
|
867
1685
|
if (await fileExists(instructionsDir)) {
|
|
868
1686
|
try {
|
|
869
1687
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
870
1688
|
const files = await readdir2(instructionsDir);
|
|
871
1689
|
for (const file of files) {
|
|
872
1690
|
if (file.endsWith(".instructions.md")) {
|
|
873
|
-
const filePath = (0,
|
|
1691
|
+
const filePath = (0, import_node_path17.join)(instructionsDir, file);
|
|
874
1692
|
const rawContent = await readFileContent(filePath);
|
|
875
1693
|
const parsed = (0, import_gray_matter2.default)(rawContent);
|
|
876
1694
|
const content = parsed.content.trim();
|
|
877
1695
|
if (content) {
|
|
878
|
-
const filename = (0,
|
|
1696
|
+
const filename = (0, import_node_path17.basename)(file, ".instructions.md");
|
|
879
1697
|
const frontmatter = {
|
|
880
1698
|
root: false,
|
|
881
1699
|
targets: ["copilot"],
|
|
@@ -905,19 +1723,19 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
|
905
1723
|
}
|
|
906
1724
|
|
|
907
1725
|
// src/parsers/cursor.ts
|
|
908
|
-
var
|
|
1726
|
+
var import_node_path18 = require("path");
|
|
909
1727
|
var import_gray_matter3 = __toESM(require("gray-matter"));
|
|
910
|
-
var import_js_yaml =
|
|
1728
|
+
var import_js_yaml = require("js-yaml");
|
|
911
1729
|
var customMatterOptions = {
|
|
912
1730
|
engines: {
|
|
913
1731
|
yaml: {
|
|
914
1732
|
parse: (str) => {
|
|
915
1733
|
try {
|
|
916
1734
|
const preprocessed = str.replace(/^(\s*globs:\s*)\*\s*$/gm, '$1"*"');
|
|
917
|
-
return import_js_yaml.
|
|
1735
|
+
return (0, import_js_yaml.load)(preprocessed, { schema: import_js_yaml.DEFAULT_SCHEMA });
|
|
918
1736
|
} catch (error) {
|
|
919
1737
|
try {
|
|
920
|
-
return import_js_yaml.
|
|
1738
|
+
return (0, import_js_yaml.load)(str, { schema: import_js_yaml.FAILSAFE_SCHEMA });
|
|
921
1739
|
} catch {
|
|
922
1740
|
throw error;
|
|
923
1741
|
}
|
|
@@ -929,7 +1747,9 @@ var customMatterOptions = {
|
|
|
929
1747
|
async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
930
1748
|
const errors = [];
|
|
931
1749
|
const rules = [];
|
|
932
|
-
|
|
1750
|
+
let ignorePatterns;
|
|
1751
|
+
let mcpServers;
|
|
1752
|
+
const cursorFilePath = (0, import_node_path18.join)(baseDir, ".cursorrules");
|
|
933
1753
|
if (await fileExists(cursorFilePath)) {
|
|
934
1754
|
try {
|
|
935
1755
|
const rawContent = await readFileContent(cursorFilePath);
|
|
@@ -954,20 +1774,20 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
954
1774
|
errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
|
|
955
1775
|
}
|
|
956
1776
|
}
|
|
957
|
-
const cursorRulesDir = (0,
|
|
1777
|
+
const cursorRulesDir = (0, import_node_path18.join)(baseDir, ".cursor", "rules");
|
|
958
1778
|
if (await fileExists(cursorRulesDir)) {
|
|
959
1779
|
try {
|
|
960
1780
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
961
1781
|
const files = await readdir2(cursorRulesDir);
|
|
962
1782
|
for (const file of files) {
|
|
963
1783
|
if (file.endsWith(".mdc")) {
|
|
964
|
-
const filePath = (0,
|
|
1784
|
+
const filePath = (0, import_node_path18.join)(cursorRulesDir, file);
|
|
965
1785
|
try {
|
|
966
1786
|
const rawContent = await readFileContent(filePath);
|
|
967
1787
|
const parsed = (0, import_gray_matter3.default)(rawContent, customMatterOptions);
|
|
968
1788
|
const content = parsed.content.trim();
|
|
969
1789
|
if (content) {
|
|
970
|
-
const filename = (0,
|
|
1790
|
+
const filename = (0, import_node_path18.basename)(file, ".mdc");
|
|
971
1791
|
const frontmatter = {
|
|
972
1792
|
root: false,
|
|
973
1793
|
targets: ["cursor"],
|
|
@@ -995,38 +1815,244 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
995
1815
|
if (rules.length === 0) {
|
|
996
1816
|
errors.push("No Cursor configuration files found (.cursorrules or .cursor/rules/*.mdc)");
|
|
997
1817
|
}
|
|
998
|
-
|
|
1818
|
+
const cursorIgnorePath = (0, import_node_path18.join)(baseDir, ".cursorignore");
|
|
1819
|
+
if (await fileExists(cursorIgnorePath)) {
|
|
1820
|
+
try {
|
|
1821
|
+
const content = await readFileContent(cursorIgnorePath);
|
|
1822
|
+
const patterns = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
1823
|
+
if (patterns.length > 0) {
|
|
1824
|
+
ignorePatterns = patterns;
|
|
1825
|
+
}
|
|
1826
|
+
} catch (error) {
|
|
1827
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1828
|
+
errors.push(`Failed to parse .cursorignore: ${errorMessage}`);
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
const cursorMcpPath = (0, import_node_path18.join)(baseDir, ".cursor", "mcp.json");
|
|
1832
|
+
if (await fileExists(cursorMcpPath)) {
|
|
1833
|
+
try {
|
|
1834
|
+
const content = await readFileContent(cursorMcpPath);
|
|
1835
|
+
const mcp = JSON.parse(content);
|
|
1836
|
+
if (mcp.mcpServers && Object.keys(mcp.mcpServers).length > 0) {
|
|
1837
|
+
mcpServers = mcp.mcpServers;
|
|
1838
|
+
}
|
|
1839
|
+
} catch (error) {
|
|
1840
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1841
|
+
errors.push(`Failed to parse .cursor/mcp.json: ${errorMessage}`);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
return {
|
|
1845
|
+
rules,
|
|
1846
|
+
errors,
|
|
1847
|
+
...ignorePatterns && { ignorePatterns },
|
|
1848
|
+
...mcpServers && { mcpServers }
|
|
1849
|
+
};
|
|
999
1850
|
}
|
|
1000
1851
|
|
|
1001
|
-
// src/parsers/
|
|
1002
|
-
var
|
|
1003
|
-
async function
|
|
1852
|
+
// src/parsers/geminicli.ts
|
|
1853
|
+
var import_node_path19 = require("path");
|
|
1854
|
+
async function parseGeminiConfiguration(baseDir = process.cwd()) {
|
|
1004
1855
|
const errors = [];
|
|
1005
1856
|
const rules = [];
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1857
|
+
let ignorePatterns;
|
|
1858
|
+
let mcpServers;
|
|
1859
|
+
const geminiFilePath = (0, import_node_path19.join)(baseDir, "GEMINI.md");
|
|
1860
|
+
if (!await fileExists(geminiFilePath)) {
|
|
1861
|
+
errors.push("GEMINI.md file not found");
|
|
1009
1862
|
return { rules, errors };
|
|
1010
1863
|
}
|
|
1011
1864
|
try {
|
|
1012
|
-
const
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
rules.push(
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1865
|
+
const geminiContent = await readFileContent(geminiFilePath);
|
|
1866
|
+
const mainRule = parseGeminiMainFile(geminiContent, geminiFilePath);
|
|
1867
|
+
if (mainRule) {
|
|
1868
|
+
rules.push(mainRule);
|
|
1869
|
+
}
|
|
1870
|
+
const memoryDir = (0, import_node_path19.join)(baseDir, ".gemini", "memories");
|
|
1871
|
+
if (await fileExists(memoryDir)) {
|
|
1872
|
+
const memoryRules = await parseGeminiMemoryFiles(memoryDir);
|
|
1873
|
+
rules.push(...memoryRules);
|
|
1874
|
+
}
|
|
1875
|
+
const settingsPath = (0, import_node_path19.join)(baseDir, ".gemini", "settings.json");
|
|
1876
|
+
if (await fileExists(settingsPath)) {
|
|
1877
|
+
const settingsResult = await parseGeminiSettings(settingsPath);
|
|
1878
|
+
if (settingsResult.ignorePatterns) {
|
|
1879
|
+
ignorePatterns = settingsResult.ignorePatterns;
|
|
1880
|
+
}
|
|
1881
|
+
if (settingsResult.mcpServers) {
|
|
1882
|
+
mcpServers = settingsResult.mcpServers;
|
|
1883
|
+
}
|
|
1884
|
+
errors.push(...settingsResult.errors);
|
|
1885
|
+
}
|
|
1886
|
+
const aiexcludePath = (0, import_node_path19.join)(baseDir, ".aiexclude");
|
|
1887
|
+
if (await fileExists(aiexcludePath)) {
|
|
1888
|
+
const aiexcludePatterns = await parseAiexclude(aiexcludePath);
|
|
1889
|
+
if (aiexcludePatterns.length > 0) {
|
|
1890
|
+
ignorePatterns = ignorePatterns ? [...ignorePatterns, ...aiexcludePatterns] : aiexcludePatterns;
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
} catch (error) {
|
|
1894
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1895
|
+
errors.push(`Failed to parse Gemini configuration: ${errorMessage}`);
|
|
1896
|
+
}
|
|
1897
|
+
return {
|
|
1898
|
+
rules,
|
|
1899
|
+
errors,
|
|
1900
|
+
...ignorePatterns && { ignorePatterns },
|
|
1901
|
+
...mcpServers && { mcpServers }
|
|
1902
|
+
};
|
|
1903
|
+
}
|
|
1904
|
+
function parseGeminiMainFile(content, filepath) {
|
|
1905
|
+
const lines = content.split("\n");
|
|
1906
|
+
let contentStartIndex = 0;
|
|
1907
|
+
if (lines.some((line) => line.includes("| Document | Description | File Patterns |"))) {
|
|
1908
|
+
const tableEndIndex = lines.findIndex(
|
|
1909
|
+
(line, index) => index > 0 && line.trim() === "" && lines[index - 1]?.includes("|") && !lines[index + 1]?.includes("|")
|
|
1910
|
+
);
|
|
1911
|
+
if (tableEndIndex !== -1) {
|
|
1912
|
+
contentStartIndex = tableEndIndex + 1;
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
const mainContent = lines.slice(contentStartIndex).join("\n").trim();
|
|
1916
|
+
if (!mainContent) {
|
|
1917
|
+
return null;
|
|
1918
|
+
}
|
|
1919
|
+
const frontmatter = {
|
|
1920
|
+
root: false,
|
|
1921
|
+
targets: ["geminicli"],
|
|
1922
|
+
description: "Main Gemini CLI configuration",
|
|
1923
|
+
globs: ["**/*"]
|
|
1924
|
+
};
|
|
1925
|
+
return {
|
|
1926
|
+
frontmatter,
|
|
1927
|
+
content: mainContent,
|
|
1928
|
+
filename: "gemini-main",
|
|
1929
|
+
filepath
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1932
|
+
async function parseGeminiMemoryFiles(memoryDir) {
|
|
1933
|
+
const rules = [];
|
|
1934
|
+
try {
|
|
1935
|
+
const { readdir: readdir2 } = await import("fs/promises");
|
|
1936
|
+
const files = await readdir2(memoryDir);
|
|
1937
|
+
for (const file of files) {
|
|
1938
|
+
if (file.endsWith(".md")) {
|
|
1939
|
+
const filePath = (0, import_node_path19.join)(memoryDir, file);
|
|
1940
|
+
const content = await readFileContent(filePath);
|
|
1941
|
+
if (content.trim()) {
|
|
1942
|
+
const filename = (0, import_node_path19.basename)(file, ".md");
|
|
1943
|
+
const frontmatter = {
|
|
1944
|
+
root: false,
|
|
1945
|
+
targets: ["geminicli"],
|
|
1946
|
+
description: `Memory file: ${filename}`,
|
|
1947
|
+
globs: ["**/*"]
|
|
1948
|
+
};
|
|
1949
|
+
rules.push({
|
|
1950
|
+
frontmatter,
|
|
1951
|
+
content: content.trim(),
|
|
1952
|
+
filename: `gemini-memory-${filename}`,
|
|
1953
|
+
filepath: filePath
|
|
1954
|
+
});
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
} catch {
|
|
1959
|
+
}
|
|
1960
|
+
return rules;
|
|
1961
|
+
}
|
|
1962
|
+
async function parseGeminiSettings(settingsPath) {
|
|
1963
|
+
const errors = [];
|
|
1964
|
+
let mcpServers;
|
|
1965
|
+
try {
|
|
1966
|
+
const content = await readFileContent(settingsPath);
|
|
1967
|
+
const settings = JSON.parse(content);
|
|
1968
|
+
if (settings.mcpServers && Object.keys(settings.mcpServers).length > 0) {
|
|
1969
|
+
mcpServers = settings.mcpServers;
|
|
1026
1970
|
}
|
|
1027
1971
|
} catch (error) {
|
|
1028
1972
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1029
|
-
errors.push(`Failed to parse
|
|
1973
|
+
errors.push(`Failed to parse settings.json: ${errorMessage}`);
|
|
1974
|
+
}
|
|
1975
|
+
return {
|
|
1976
|
+
errors,
|
|
1977
|
+
...mcpServers && { mcpServers }
|
|
1978
|
+
};
|
|
1979
|
+
}
|
|
1980
|
+
async function parseAiexclude(aiexcludePath) {
|
|
1981
|
+
try {
|
|
1982
|
+
const content = await readFileContent(aiexcludePath);
|
|
1983
|
+
const patterns = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
1984
|
+
return patterns;
|
|
1985
|
+
} catch {
|
|
1986
|
+
return [];
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
// src/parsers/roo.ts
|
|
1991
|
+
var import_node_path20 = require("path");
|
|
1992
|
+
async function parseRooConfiguration(baseDir = process.cwd()) {
|
|
1993
|
+
const errors = [];
|
|
1994
|
+
const rules = [];
|
|
1995
|
+
const rooFilePath = (0, import_node_path20.join)(baseDir, ".roo", "instructions.md");
|
|
1996
|
+
if (await fileExists(rooFilePath)) {
|
|
1997
|
+
try {
|
|
1998
|
+
const content = await readFileContent(rooFilePath);
|
|
1999
|
+
if (content.trim()) {
|
|
2000
|
+
const frontmatter = {
|
|
2001
|
+
root: false,
|
|
2002
|
+
targets: ["roo"],
|
|
2003
|
+
description: "Roo Code instructions",
|
|
2004
|
+
globs: ["**/*"]
|
|
2005
|
+
};
|
|
2006
|
+
rules.push({
|
|
2007
|
+
frontmatter,
|
|
2008
|
+
content: content.trim(),
|
|
2009
|
+
filename: "roo-instructions",
|
|
2010
|
+
filepath: rooFilePath
|
|
2011
|
+
});
|
|
2012
|
+
}
|
|
2013
|
+
} catch (error) {
|
|
2014
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2015
|
+
errors.push(`Failed to parse .roo/instructions.md: ${errorMessage}`);
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
const rooRulesDir = (0, import_node_path20.join)(baseDir, ".roo", "rules");
|
|
2019
|
+
if (await fileExists(rooRulesDir)) {
|
|
2020
|
+
try {
|
|
2021
|
+
const { readdir: readdir2 } = await import("fs/promises");
|
|
2022
|
+
const files = await readdir2(rooRulesDir);
|
|
2023
|
+
for (const file of files) {
|
|
2024
|
+
if (file.endsWith(".md")) {
|
|
2025
|
+
const filePath = (0, import_node_path20.join)(rooRulesDir, file);
|
|
2026
|
+
try {
|
|
2027
|
+
const content = await readFileContent(filePath);
|
|
2028
|
+
if (content.trim()) {
|
|
2029
|
+
const filename = file.replace(".md", "");
|
|
2030
|
+
const frontmatter = {
|
|
2031
|
+
root: false,
|
|
2032
|
+
targets: ["roo"],
|
|
2033
|
+
description: `Roo rule: ${filename}`,
|
|
2034
|
+
globs: ["**/*"]
|
|
2035
|
+
};
|
|
2036
|
+
rules.push({
|
|
2037
|
+
frontmatter,
|
|
2038
|
+
content: content.trim(),
|
|
2039
|
+
filename: `roo-${filename}`,
|
|
2040
|
+
filepath: filePath
|
|
2041
|
+
});
|
|
2042
|
+
}
|
|
2043
|
+
} catch (error) {
|
|
2044
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2045
|
+
errors.push(`Failed to parse ${filePath}: ${errorMessage}`);
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
} catch (error) {
|
|
2050
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2051
|
+
errors.push(`Failed to parse .roo/rules files: ${errorMessage}`);
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
if (rules.length === 0) {
|
|
2055
|
+
errors.push("No Roo Code configuration files found (.roo/instructions.md or .roo/rules/*.md)");
|
|
1030
2056
|
}
|
|
1031
2057
|
return { rules, errors };
|
|
1032
2058
|
}
|
|
@@ -1036,6 +2062,8 @@ async function importConfiguration(options) {
|
|
|
1036
2062
|
const { tool, baseDir = process.cwd(), rulesDir = ".rulesync", verbose = false } = options;
|
|
1037
2063
|
const errors = [];
|
|
1038
2064
|
let rules = [];
|
|
2065
|
+
let ignorePatterns;
|
|
2066
|
+
let mcpServers;
|
|
1039
2067
|
if (verbose) {
|
|
1040
2068
|
console.log(`Importing ${tool} configuration from ${baseDir}...`);
|
|
1041
2069
|
}
|
|
@@ -1045,12 +2073,16 @@ async function importConfiguration(options) {
|
|
|
1045
2073
|
const claudeResult = await parseClaudeConfiguration(baseDir);
|
|
1046
2074
|
rules = claudeResult.rules;
|
|
1047
2075
|
errors.push(...claudeResult.errors);
|
|
2076
|
+
ignorePatterns = claudeResult.ignorePatterns;
|
|
2077
|
+
mcpServers = claudeResult.mcpServers;
|
|
1048
2078
|
break;
|
|
1049
2079
|
}
|
|
1050
2080
|
case "cursor": {
|
|
1051
2081
|
const cursorResult = await parseCursorConfiguration(baseDir);
|
|
1052
2082
|
rules = cursorResult.rules;
|
|
1053
2083
|
errors.push(...cursorResult.errors);
|
|
2084
|
+
ignorePatterns = cursorResult.ignorePatterns;
|
|
2085
|
+
mcpServers = cursorResult.mcpServers;
|
|
1054
2086
|
break;
|
|
1055
2087
|
}
|
|
1056
2088
|
case "copilot": {
|
|
@@ -1071,6 +2103,14 @@ async function importConfiguration(options) {
|
|
|
1071
2103
|
errors.push(...rooResult.errors);
|
|
1072
2104
|
break;
|
|
1073
2105
|
}
|
|
2106
|
+
case "geminicli": {
|
|
2107
|
+
const geminiResult = await parseGeminiConfiguration(baseDir);
|
|
2108
|
+
rules = geminiResult.rules;
|
|
2109
|
+
errors.push(...geminiResult.errors);
|
|
2110
|
+
ignorePatterns = geminiResult.ignorePatterns;
|
|
2111
|
+
mcpServers = geminiResult.mcpServers;
|
|
2112
|
+
break;
|
|
2113
|
+
}
|
|
1074
2114
|
default:
|
|
1075
2115
|
errors.push(`Unsupported tool: ${tool}`);
|
|
1076
2116
|
return { success: false, rulesCreated: 0, errors };
|
|
@@ -1080,10 +2120,10 @@ async function importConfiguration(options) {
|
|
|
1080
2120
|
errors.push(`Failed to parse ${tool} configuration: ${errorMessage}`);
|
|
1081
2121
|
return { success: false, rulesCreated: 0, errors };
|
|
1082
2122
|
}
|
|
1083
|
-
if (rules.length === 0) {
|
|
2123
|
+
if (rules.length === 0 && !ignorePatterns && !mcpServers) {
|
|
1084
2124
|
return { success: false, rulesCreated: 0, errors };
|
|
1085
2125
|
}
|
|
1086
|
-
const rulesDirPath = (0,
|
|
2126
|
+
const rulesDirPath = (0, import_node_path21.join)(baseDir, rulesDir);
|
|
1087
2127
|
try {
|
|
1088
2128
|
const { mkdir: mkdir3 } = await import("fs/promises");
|
|
1089
2129
|
await mkdir3(rulesDirPath, { recursive: true });
|
|
@@ -1097,7 +2137,7 @@ async function importConfiguration(options) {
|
|
|
1097
2137
|
try {
|
|
1098
2138
|
const baseFilename = `${tool}__${rule.filename}`;
|
|
1099
2139
|
const filename = await generateUniqueFilename(rulesDirPath, baseFilename);
|
|
1100
|
-
const filePath = (0,
|
|
2140
|
+
const filePath = (0, import_node_path21.join)(rulesDirPath, `${filename}.md`);
|
|
1101
2141
|
const content = generateRuleFileContent(rule);
|
|
1102
2142
|
await writeFileContent(filePath, content);
|
|
1103
2143
|
rulesCreated++;
|
|
@@ -1109,10 +2149,44 @@ async function importConfiguration(options) {
|
|
|
1109
2149
|
errors.push(`Failed to create rule file for ${rule.filename}: ${errorMessage}`);
|
|
1110
2150
|
}
|
|
1111
2151
|
}
|
|
2152
|
+
let ignoreFileCreated = false;
|
|
2153
|
+
if (ignorePatterns && ignorePatterns.length > 0) {
|
|
2154
|
+
try {
|
|
2155
|
+
const rulesyncignorePath = (0, import_node_path21.join)(baseDir, ".rulesyncignore");
|
|
2156
|
+
const ignoreContent = `${ignorePatterns.join("\n")}
|
|
2157
|
+
`;
|
|
2158
|
+
await writeFileContent(rulesyncignorePath, ignoreContent);
|
|
2159
|
+
ignoreFileCreated = true;
|
|
2160
|
+
if (verbose) {
|
|
2161
|
+
console.log(`\u2705 Created .rulesyncignore with ${ignorePatterns.length} patterns`);
|
|
2162
|
+
}
|
|
2163
|
+
} catch (error) {
|
|
2164
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2165
|
+
errors.push(`Failed to create .rulesyncignore: ${errorMessage}`);
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
let mcpFileCreated = false;
|
|
2169
|
+
if (mcpServers && Object.keys(mcpServers).length > 0) {
|
|
2170
|
+
try {
|
|
2171
|
+
const mcpPath = (0, import_node_path21.join)(baseDir, rulesDir, ".mcp.json");
|
|
2172
|
+
const mcpContent = `${JSON.stringify({ mcpServers }, null, 2)}
|
|
2173
|
+
`;
|
|
2174
|
+
await writeFileContent(mcpPath, mcpContent);
|
|
2175
|
+
mcpFileCreated = true;
|
|
2176
|
+
if (verbose) {
|
|
2177
|
+
console.log(`\u2705 Created .mcp.json with ${Object.keys(mcpServers).length} servers`);
|
|
2178
|
+
}
|
|
2179
|
+
} catch (error) {
|
|
2180
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2181
|
+
errors.push(`Failed to create .mcp.json: ${errorMessage}`);
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
1112
2184
|
return {
|
|
1113
|
-
success: rulesCreated > 0,
|
|
2185
|
+
success: rulesCreated > 0 || ignoreFileCreated || mcpFileCreated,
|
|
1114
2186
|
rulesCreated,
|
|
1115
|
-
errors
|
|
2187
|
+
errors,
|
|
2188
|
+
ignoreFileCreated,
|
|
2189
|
+
mcpFileCreated
|
|
1116
2190
|
};
|
|
1117
2191
|
}
|
|
1118
2192
|
function generateRuleFileContent(rule) {
|
|
@@ -1122,7 +2196,7 @@ function generateRuleFileContent(rule) {
|
|
|
1122
2196
|
async function generateUniqueFilename(rulesDir, baseFilename) {
|
|
1123
2197
|
let filename = baseFilename;
|
|
1124
2198
|
let counter = 1;
|
|
1125
|
-
while (await fileExists((0,
|
|
2199
|
+
while (await fileExists((0, import_node_path21.join)(rulesDir, `${filename}.md`))) {
|
|
1126
2200
|
filename = `${baseFilename}-${counter}`;
|
|
1127
2201
|
counter++;
|
|
1128
2202
|
}
|
|
@@ -1140,57 +2214,54 @@ async function importCommand(options = {}) {
|
|
|
1140
2214
|
if (options.geminicli) tools.push("geminicli");
|
|
1141
2215
|
if (tools.length === 0) {
|
|
1142
2216
|
console.error(
|
|
1143
|
-
"\u274C Please specify
|
|
2217
|
+
"\u274C Please specify one tool to import from (--claudecode, --cursor, --copilot, --cline, --roo, --geminicli)"
|
|
1144
2218
|
);
|
|
1145
2219
|
process.exit(1);
|
|
1146
2220
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
for (const tool of tools) {
|
|
1151
|
-
if (options.verbose) {
|
|
1152
|
-
console.log(`
|
|
1153
|
-
Importing from ${tool}...`);
|
|
1154
|
-
}
|
|
1155
|
-
try {
|
|
1156
|
-
const result = await importConfiguration({
|
|
1157
|
-
tool,
|
|
1158
|
-
verbose: options.verbose ?? false
|
|
1159
|
-
});
|
|
1160
|
-
if (result.success) {
|
|
1161
|
-
console.log(`\u2705 Imported ${result.rulesCreated} rule(s) from ${tool}`);
|
|
1162
|
-
totalRulesCreated += result.rulesCreated;
|
|
1163
|
-
} else if (result.errors.length > 0) {
|
|
1164
|
-
console.warn(`\u26A0\uFE0F Failed to import from ${tool}: ${result.errors[0]}`);
|
|
1165
|
-
if (options.verbose) {
|
|
1166
|
-
allErrors.push(...result.errors);
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
} catch (error) {
|
|
1170
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1171
|
-
console.error(`\u274C Error importing from ${tool}: ${errorMessage}`);
|
|
1172
|
-
allErrors.push(`${tool}: ${errorMessage}`);
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
if (totalRulesCreated > 0) {
|
|
1176
|
-
console.log(`
|
|
1177
|
-
\u{1F389} Successfully imported ${totalRulesCreated} rule(s) total!`);
|
|
1178
|
-
console.log("You can now run 'rulesync generate' to create tool-specific configurations.");
|
|
1179
|
-
} else {
|
|
1180
|
-
console.warn(
|
|
1181
|
-
"\n\u26A0\uFE0F No rules were imported. Please check that configuration files exist for the selected tools."
|
|
2221
|
+
if (tools.length > 1) {
|
|
2222
|
+
console.error(
|
|
2223
|
+
"\u274C Only one tool can be specified at a time. Please run the import command separately for each tool."
|
|
1182
2224
|
);
|
|
2225
|
+
process.exit(1);
|
|
1183
2226
|
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
2227
|
+
const tool = tools[0];
|
|
2228
|
+
if (!tool) {
|
|
2229
|
+
console.error("Error: No tool specified");
|
|
2230
|
+
process.exit(1);
|
|
2231
|
+
}
|
|
2232
|
+
console.log(`Importing configuration files from ${tool}...`);
|
|
2233
|
+
try {
|
|
2234
|
+
const result = await importConfiguration({
|
|
2235
|
+
tool,
|
|
2236
|
+
verbose: options.verbose ?? false
|
|
2237
|
+
});
|
|
2238
|
+
if (result.success) {
|
|
2239
|
+
console.log(`\u2705 Imported ${result.rulesCreated} rule(s) from ${tool}`);
|
|
2240
|
+
if (result.ignoreFileCreated) {
|
|
2241
|
+
console.log("\u2705 Created .rulesyncignore file from ignore patterns");
|
|
2242
|
+
}
|
|
2243
|
+
if (result.mcpFileCreated) {
|
|
2244
|
+
console.log("\u2705 Created .rulesync/.mcp.json file from MCP configuration");
|
|
2245
|
+
}
|
|
2246
|
+
console.log("You can now run 'rulesync generate' to create tool-specific configurations.");
|
|
2247
|
+
} else if (result.errors.length > 0) {
|
|
2248
|
+
console.warn(`\u26A0\uFE0F Failed to import from ${tool}: ${result.errors[0]}`);
|
|
2249
|
+
if (options.verbose && result.errors.length > 1) {
|
|
2250
|
+
console.log("\nDetailed errors:");
|
|
2251
|
+
for (const error of result.errors) {
|
|
2252
|
+
console.log(` - ${error}`);
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
1188
2255
|
}
|
|
2256
|
+
} catch (error) {
|
|
2257
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2258
|
+
console.error(`\u274C Error importing from ${tool}: ${errorMessage}`);
|
|
2259
|
+
process.exit(1);
|
|
1189
2260
|
}
|
|
1190
2261
|
}
|
|
1191
2262
|
|
|
1192
2263
|
// src/cli/commands/init.ts
|
|
1193
|
-
var
|
|
2264
|
+
var import_node_path22 = require("path");
|
|
1194
2265
|
async function initCommand() {
|
|
1195
2266
|
const aiRulesDir = ".rulesync";
|
|
1196
2267
|
console.log("Initializing rulesync...");
|
|
@@ -1320,7 +2391,7 @@ globs: ["src/api/**/*.ts", "src/services/**/*.ts", "src/models/**/*.ts"]
|
|
|
1320
2391
|
}
|
|
1321
2392
|
];
|
|
1322
2393
|
for (const file of sampleFiles) {
|
|
1323
|
-
const filepath = (0,
|
|
2394
|
+
const filepath = (0, import_node_path22.join)(aiRulesDir, file.filename);
|
|
1324
2395
|
if (!await fileExists(filepath)) {
|
|
1325
2396
|
await writeFileContent(filepath, file.content);
|
|
1326
2397
|
console.log(`Created ${filepath}`);
|
|
@@ -1422,22 +2493,22 @@ async function validateCommand() {
|
|
|
1422
2493
|
}
|
|
1423
2494
|
|
|
1424
2495
|
// src/cli/commands/watch.ts
|
|
1425
|
-
var import_chokidar =
|
|
2496
|
+
var import_chokidar = require("chokidar");
|
|
1426
2497
|
async function watchCommand() {
|
|
1427
2498
|
const config = getDefaultConfig();
|
|
1428
2499
|
console.log("\u{1F440} Watching for changes in .rulesync directory...");
|
|
1429
2500
|
console.log("Press Ctrl+C to stop watching");
|
|
1430
2501
|
await generateCommand({ verbose: false });
|
|
1431
|
-
const watcher = import_chokidar.
|
|
2502
|
+
const watcher = (0, import_chokidar.watch)(`${config.aiRulesDir}/**/*.md`, {
|
|
1432
2503
|
ignoreInitial: true,
|
|
1433
2504
|
persistent: true
|
|
1434
2505
|
});
|
|
1435
2506
|
let isGenerating = false;
|
|
1436
|
-
const handleChange = async (
|
|
2507
|
+
const handleChange = async (path4) => {
|
|
1437
2508
|
if (isGenerating) return;
|
|
1438
2509
|
isGenerating = true;
|
|
1439
2510
|
console.log(`
|
|
1440
|
-
\u{1F4DD} Detected change in ${
|
|
2511
|
+
\u{1F4DD} Detected change in ${path4}`);
|
|
1441
2512
|
try {
|
|
1442
2513
|
await generateCommand({ verbose: false });
|
|
1443
2514
|
console.log("\u2705 Regenerated configuration files");
|
|
@@ -1447,10 +2518,10 @@ async function watchCommand() {
|
|
|
1447
2518
|
isGenerating = false;
|
|
1448
2519
|
}
|
|
1449
2520
|
};
|
|
1450
|
-
watcher.on("change", handleChange).on("add", handleChange).on("unlink", (
|
|
2521
|
+
watcher.on("change", handleChange).on("add", handleChange).on("unlink", (path4) => {
|
|
1451
2522
|
console.log(`
|
|
1452
|
-
\u{1F5D1}\uFE0F Removed ${
|
|
1453
|
-
handleChange(
|
|
2523
|
+
\u{1F5D1}\uFE0F Removed ${path4}`);
|
|
2524
|
+
handleChange(path4);
|
|
1454
2525
|
}).on("error", (error) => {
|
|
1455
2526
|
console.error("\u274C Watcher error:", error);
|
|
1456
2527
|
});
|
|
@@ -1463,7 +2534,7 @@ async function watchCommand() {
|
|
|
1463
2534
|
|
|
1464
2535
|
// src/cli/index.ts
|
|
1465
2536
|
var program = new import_commander.Command();
|
|
1466
|
-
program.name("rulesync").description("Unified AI rules management CLI tool").version("0.
|
|
2537
|
+
program.name("rulesync").description("Unified AI rules management CLI tool").version("0.37.0");
|
|
1467
2538
|
program.command("init").description("Initialize rulesync in current directory").action(initCommand);
|
|
1468
2539
|
program.command("add <filename>").description("Add a new rule file").action(addCommand);
|
|
1469
2540
|
program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
|