rulesync 0.48.0 → 0.51.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 +8 -2
- package/README.md +95 -2
- package/dist/augmentcode-ICLQ2NEZ.js +9 -0
- package/dist/{chunk-OTCCHS7Q.js → chunk-4MZXYV5H.js} +1 -1
- package/dist/{chunk-BY6RI77W.js → chunk-AREA26HF.js} +1 -1
- package/dist/{chunk-L2JTXZZB.js → chunk-FFW6TGCM.js} +1 -1
- package/dist/{chunk-JWN6GRG6.js → chunk-IXCMY24P.js} +1 -1
- package/dist/chunk-OPZOVKIL.js +111 -0
- package/dist/{chunk-D365OP7N.js → chunk-QUJMXHNR.js} +1 -1
- package/dist/{chunk-7UVBAWYG.js → chunk-TTHBLXOB.js} +1 -1
- package/dist/{chunk-7ZIUEZZQ.js → chunk-USKQYIZ2.js} +13 -3
- package/dist/chunk-Y26DXTAT.js +90 -0
- package/dist/{chunk-P6KQZULZ.js → chunk-Y2XH4E5R.js} +1 -1
- package/dist/{claudecode-Y3GIXDUN.js → claudecode-SRXYHIJE.js} +2 -2
- package/dist/{cline-NS3OPXM2.js → cline-IJW27CUU.js} +2 -2
- package/dist/{copilot-QN2SC7Y2.js → copilot-ARYIWVJ7.js} +2 -2
- package/dist/{cursor-DV2IS7JF.js → cursor-FCS74IAH.js} +2 -2
- package/dist/{geminicli-MRYTLT2T.js → geminicli-VOPV6DXZ.js} +2 -2
- package/dist/index.cjs +2018 -829
- package/dist/index.js +1904 -827
- package/dist/junie-A2Y2WZI4.js +9 -0
- package/dist/{kiro-S5TSM7VW.js → kiro-MHIK4UBV.js} +2 -2
- package/dist/{roo-NWLD3YYN.js → roo-VG4IUNTE.js} +2 -2
- package/package.json +17 -16
package/dist/index.js
CHANGED
|
@@ -1,30 +1,38 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
generateKiroMcp
|
|
4
|
+
} from "./chunk-QUJMXHNR.js";
|
|
5
|
+
import {
|
|
6
|
+
generateRooMcp
|
|
7
|
+
} from "./chunk-FFW6TGCM.js";
|
|
8
|
+
import {
|
|
9
|
+
generateAugmentcodeMcp
|
|
10
|
+
} from "./chunk-OPZOVKIL.js";
|
|
2
11
|
import {
|
|
3
12
|
generateClaudeMcp
|
|
4
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-4MZXYV5H.js";
|
|
5
14
|
import {
|
|
6
15
|
generateClineMcp
|
|
7
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-AREA26HF.js";
|
|
8
17
|
import {
|
|
9
18
|
generateCopilotMcp
|
|
10
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-Y2XH4E5R.js";
|
|
11
20
|
import {
|
|
12
21
|
generateCursorMcp
|
|
13
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-TTHBLXOB.js";
|
|
14
23
|
import {
|
|
15
24
|
generateGeminiCliMcp
|
|
16
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-IXCMY24P.js";
|
|
17
26
|
import {
|
|
18
|
-
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
import {
|
|
21
|
-
generateRooMcp
|
|
22
|
-
} from "./chunk-L2JTXZZB.js";
|
|
27
|
+
generateJunieMcp
|
|
28
|
+
} from "./chunk-Y26DXTAT.js";
|
|
23
29
|
import {
|
|
30
|
+
ALL_TOOL_TARGETS,
|
|
24
31
|
RulesyncTargetsSchema,
|
|
25
32
|
ToolTargetSchema,
|
|
26
|
-
ToolTargetsSchema
|
|
27
|
-
|
|
33
|
+
ToolTargetsSchema,
|
|
34
|
+
isToolTarget
|
|
35
|
+
} from "./chunk-USKQYIZ2.js";
|
|
28
36
|
|
|
29
37
|
// src/cli/index.ts
|
|
30
38
|
import { Command } from "commander";
|
|
@@ -38,16 +46,19 @@ function getDefaultConfig() {
|
|
|
38
46
|
return {
|
|
39
47
|
aiRulesDir: ".rulesync",
|
|
40
48
|
outputPaths: {
|
|
49
|
+
augmentcode: ".",
|
|
50
|
+
"augmentcode-legacy": ".",
|
|
41
51
|
copilot: ".github/instructions",
|
|
42
52
|
cursor: ".cursor/rules",
|
|
43
53
|
cline: ".clinerules",
|
|
44
54
|
claudecode: ".",
|
|
45
55
|
roo: ".roo/rules",
|
|
46
56
|
geminicli: ".gemini/memories",
|
|
47
|
-
kiro: ".kiro/steering"
|
|
57
|
+
kiro: ".kiro/steering",
|
|
58
|
+
junie: "."
|
|
48
59
|
},
|
|
49
60
|
watchEnabled: false,
|
|
50
|
-
defaultTargets:
|
|
61
|
+
defaultTargets: ALL_TOOL_TARGETS.filter((tool) => tool !== "augmentcode-legacy")
|
|
51
62
|
};
|
|
52
63
|
}
|
|
53
64
|
function resolveTargets(targets, config) {
|
|
@@ -94,13 +105,1039 @@ async function addCommand(filename) {
|
|
|
94
105
|
}
|
|
95
106
|
}
|
|
96
107
|
|
|
108
|
+
// src/cli/commands/config.ts
|
|
109
|
+
import { writeFileSync } from "fs";
|
|
110
|
+
import path2 from "path";
|
|
111
|
+
|
|
112
|
+
// src/types/claudecode.ts
|
|
113
|
+
import { z } from "zod/mini";
|
|
114
|
+
var ClaudeSettingsSchema = z.looseObject({
|
|
115
|
+
permissions: z._default(
|
|
116
|
+
z.looseObject({
|
|
117
|
+
deny: z._default(z.array(z.string()), [])
|
|
118
|
+
}),
|
|
119
|
+
{ deny: [] }
|
|
120
|
+
)
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// src/types/config.ts
|
|
124
|
+
import { z as z2 } from "zod/mini";
|
|
125
|
+
var ConfigSchema = z2.object({
|
|
126
|
+
aiRulesDir: z2.string(),
|
|
127
|
+
outputPaths: z2.record(ToolTargetSchema, z2.string()),
|
|
128
|
+
watchEnabled: z2.boolean(),
|
|
129
|
+
defaultTargets: ToolTargetsSchema
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// src/types/config-options.ts
|
|
133
|
+
import { z as z3 } from "zod/mini";
|
|
134
|
+
var OutputPathsSchema = z3.object({
|
|
135
|
+
augmentcode: z3.optional(z3.string()),
|
|
136
|
+
"augmentcode-legacy": z3.optional(z3.string()),
|
|
137
|
+
copilot: z3.optional(z3.string()),
|
|
138
|
+
cursor: z3.optional(z3.string()),
|
|
139
|
+
cline: z3.optional(z3.string()),
|
|
140
|
+
claudecode: z3.optional(z3.string()),
|
|
141
|
+
roo: z3.optional(z3.string()),
|
|
142
|
+
geminicli: z3.optional(z3.string()),
|
|
143
|
+
kiro: z3.optional(z3.string()),
|
|
144
|
+
junie: z3.optional(z3.string())
|
|
145
|
+
});
|
|
146
|
+
var ConfigOptionsSchema = z3.object({
|
|
147
|
+
aiRulesDir: z3.optional(z3.string()),
|
|
148
|
+
outputPaths: z3.optional(OutputPathsSchema),
|
|
149
|
+
watchEnabled: z3.optional(z3.boolean()),
|
|
150
|
+
defaultTargets: z3.optional(ToolTargetsSchema),
|
|
151
|
+
targets: z3.optional(z3.array(ToolTargetSchema)),
|
|
152
|
+
exclude: z3.optional(z3.array(ToolTargetSchema)),
|
|
153
|
+
verbose: z3.optional(z3.boolean()),
|
|
154
|
+
delete: z3.optional(z3.boolean()),
|
|
155
|
+
baseDir: z3.optional(z3.union([z3.string(), z3.array(z3.string())])),
|
|
156
|
+
watch: z3.optional(
|
|
157
|
+
z3.object({
|
|
158
|
+
enabled: z3.optional(z3.boolean()),
|
|
159
|
+
interval: z3.optional(z3.number()),
|
|
160
|
+
ignore: z3.optional(z3.array(z3.string()))
|
|
161
|
+
})
|
|
162
|
+
)
|
|
163
|
+
});
|
|
164
|
+
var MergedConfigSchema = z3.object({
|
|
165
|
+
aiRulesDir: z3.string(),
|
|
166
|
+
outputPaths: z3.record(ToolTargetSchema, z3.string()),
|
|
167
|
+
watchEnabled: z3.boolean(),
|
|
168
|
+
defaultTargets: ToolTargetsSchema,
|
|
169
|
+
targets: z3.optional(z3.array(ToolTargetSchema)),
|
|
170
|
+
exclude: z3.optional(z3.array(ToolTargetSchema)),
|
|
171
|
+
verbose: z3.optional(z3.boolean()),
|
|
172
|
+
delete: z3.optional(z3.boolean()),
|
|
173
|
+
baseDir: z3.optional(z3.union([z3.string(), z3.array(z3.string())])),
|
|
174
|
+
configPath: z3.optional(z3.string()),
|
|
175
|
+
watch: z3.optional(
|
|
176
|
+
z3.object({
|
|
177
|
+
enabled: z3.optional(z3.boolean()),
|
|
178
|
+
interval: z3.optional(z3.number()),
|
|
179
|
+
ignore: z3.optional(z3.array(z3.string()))
|
|
180
|
+
})
|
|
181
|
+
)
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// src/types/mcp.ts
|
|
185
|
+
import { z as z4 } from "zod/mini";
|
|
186
|
+
var McpTransportTypeSchema = z4.enum(["stdio", "sse", "http"]);
|
|
187
|
+
var McpServerBaseSchema = z4.object({
|
|
188
|
+
command: z4.optional(z4.string()),
|
|
189
|
+
args: z4.optional(z4.array(z4.string())),
|
|
190
|
+
url: z4.optional(z4.string()),
|
|
191
|
+
httpUrl: z4.optional(z4.string()),
|
|
192
|
+
env: z4.optional(z4.record(z4.string(), z4.string())),
|
|
193
|
+
disabled: z4.optional(z4.boolean()),
|
|
194
|
+
networkTimeout: z4.optional(z4.number()),
|
|
195
|
+
timeout: z4.optional(z4.number()),
|
|
196
|
+
trust: z4.optional(z4.boolean()),
|
|
197
|
+
cwd: z4.optional(z4.string()),
|
|
198
|
+
transport: z4.optional(McpTransportTypeSchema),
|
|
199
|
+
type: z4.optional(z4.enum(["sse", "streamable-http"])),
|
|
200
|
+
alwaysAllow: z4.optional(z4.array(z4.string())),
|
|
201
|
+
tools: z4.optional(z4.array(z4.string())),
|
|
202
|
+
kiroAutoApprove: z4.optional(z4.array(z4.string())),
|
|
203
|
+
kiroAutoBlock: z4.optional(z4.array(z4.string()))
|
|
204
|
+
});
|
|
205
|
+
var RulesyncMcpServerSchema = z4.extend(McpServerBaseSchema, {
|
|
206
|
+
targets: z4.optional(RulesyncTargetsSchema)
|
|
207
|
+
});
|
|
208
|
+
var McpConfigSchema = z4.object({
|
|
209
|
+
mcpServers: z4.record(z4.string(), McpServerBaseSchema)
|
|
210
|
+
});
|
|
211
|
+
var RulesyncMcpConfigSchema = z4.object({
|
|
212
|
+
mcpServers: z4.record(z4.string(), RulesyncMcpServerSchema)
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// src/types/rules.ts
|
|
216
|
+
import { z as z5 } from "zod/mini";
|
|
217
|
+
var RuleFrontmatterSchema = z5.object({
|
|
218
|
+
root: z5.boolean(),
|
|
219
|
+
targets: RulesyncTargetsSchema,
|
|
220
|
+
description: z5.string(),
|
|
221
|
+
globs: z5.array(z5.string()),
|
|
222
|
+
cursorRuleType: z5.optional(z5.enum(["always", "manual", "specificFiles", "intelligently"])),
|
|
223
|
+
tags: z5.optional(z5.array(z5.string()))
|
|
224
|
+
});
|
|
225
|
+
var ParsedRuleSchema = z5.object({
|
|
226
|
+
frontmatter: RuleFrontmatterSchema,
|
|
227
|
+
content: z5.string(),
|
|
228
|
+
filename: z5.string(),
|
|
229
|
+
filepath: z5.string()
|
|
230
|
+
});
|
|
231
|
+
var GeneratedOutputSchema = z5.object({
|
|
232
|
+
tool: ToolTargetSchema,
|
|
233
|
+
filepath: z5.string(),
|
|
234
|
+
content: z5.string()
|
|
235
|
+
});
|
|
236
|
+
var GenerateOptionsSchema = z5.object({
|
|
237
|
+
targetTools: z5.optional(ToolTargetsSchema),
|
|
238
|
+
outputDir: z5.optional(z5.string()),
|
|
239
|
+
watch: z5.optional(z5.boolean())
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// src/utils/config-loader.ts
|
|
243
|
+
import { loadConfig as loadC12Config } from "c12";
|
|
244
|
+
import { $ZodError } from "zod/v4/core";
|
|
245
|
+
var MODULE_NAME = "rulesync";
|
|
246
|
+
async function loadConfig(options = {}) {
|
|
247
|
+
const defaultConfig = getDefaultConfig();
|
|
248
|
+
if (options.noConfig) {
|
|
249
|
+
return {
|
|
250
|
+
config: defaultConfig,
|
|
251
|
+
isEmpty: true
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
const loadOptions = {
|
|
256
|
+
name: MODULE_NAME,
|
|
257
|
+
cwd: options.cwd || process.cwd(),
|
|
258
|
+
rcFile: false,
|
|
259
|
+
// Disable rc file lookup
|
|
260
|
+
configFile: "rulesync",
|
|
261
|
+
// Will look for rulesync.jsonc, rulesync.ts, etc.
|
|
262
|
+
defaults: defaultConfig
|
|
263
|
+
};
|
|
264
|
+
if (options.configPath) {
|
|
265
|
+
loadOptions.configFile = options.configPath;
|
|
266
|
+
}
|
|
267
|
+
const { config, configFile } = await loadC12Config(loadOptions);
|
|
268
|
+
if (!config || Object.keys(config).length === 0) {
|
|
269
|
+
return {
|
|
270
|
+
config: defaultConfig,
|
|
271
|
+
isEmpty: true
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
ConfigOptionsSchema.parse(config);
|
|
276
|
+
} catch (error) {
|
|
277
|
+
if (error instanceof $ZodError) {
|
|
278
|
+
const issues = error.issues.map((issue) => ` - ${issue.path.join(".")}: ${issue.message}`).join("\n");
|
|
279
|
+
throw new Error(`Invalid configuration in ${configFile}:
|
|
280
|
+
${issues}`);
|
|
281
|
+
}
|
|
282
|
+
throw error;
|
|
283
|
+
}
|
|
284
|
+
const processedConfig = postProcessConfig(config);
|
|
285
|
+
const result = {
|
|
286
|
+
config: processedConfig,
|
|
287
|
+
isEmpty: false
|
|
288
|
+
};
|
|
289
|
+
if (configFile) {
|
|
290
|
+
result.filepath = configFile;
|
|
291
|
+
}
|
|
292
|
+
return result;
|
|
293
|
+
} catch (error) {
|
|
294
|
+
throw new Error(
|
|
295
|
+
`Failed to load configuration: ${error instanceof Error ? error.message : String(error)}`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function postProcessConfig(config) {
|
|
300
|
+
const processed = { ...config };
|
|
301
|
+
if (processed.baseDir && !Array.isArray(processed.baseDir)) {
|
|
302
|
+
processed.baseDir = [processed.baseDir];
|
|
303
|
+
}
|
|
304
|
+
if (config.targets || config.exclude) {
|
|
305
|
+
const baseTargets = config.targets || processed.defaultTargets;
|
|
306
|
+
if (config.exclude && config.exclude.length > 0) {
|
|
307
|
+
processed.defaultTargets = baseTargets.filter(
|
|
308
|
+
(target) => config.exclude && !config.exclude.includes(target)
|
|
309
|
+
);
|
|
310
|
+
} else {
|
|
311
|
+
processed.defaultTargets = baseTargets;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return processed;
|
|
315
|
+
}
|
|
316
|
+
function generateMinimalConfig(options) {
|
|
317
|
+
if (!options || Object.keys(options).length === 0) {
|
|
318
|
+
return generateSampleConfig();
|
|
319
|
+
}
|
|
320
|
+
const lines = ["{"];
|
|
321
|
+
if (options.targets || options.exclude) {
|
|
322
|
+
lines.push(` // Available tools: ${ALL_TOOL_TARGETS.join(", ")}`);
|
|
323
|
+
}
|
|
324
|
+
if (options.targets) {
|
|
325
|
+
lines.push(` "targets": ${JSON.stringify(options.targets)}`);
|
|
326
|
+
}
|
|
327
|
+
if (options.exclude) {
|
|
328
|
+
const comma = lines.length > 1 ? "," : "";
|
|
329
|
+
if (comma) lines[lines.length - 1] += comma;
|
|
330
|
+
lines.push(` "exclude": ${JSON.stringify(options.exclude)}`);
|
|
331
|
+
}
|
|
332
|
+
if (options.aiRulesDir) {
|
|
333
|
+
const comma = lines.length > 1 ? "," : "";
|
|
334
|
+
if (comma) lines[lines.length - 1] += comma;
|
|
335
|
+
lines.push(` "aiRulesDir": "${options.aiRulesDir}"`);
|
|
336
|
+
}
|
|
337
|
+
if (options.outputPaths) {
|
|
338
|
+
const comma = lines.length > 1 ? "," : "";
|
|
339
|
+
if (comma) lines[lines.length - 1] += comma;
|
|
340
|
+
lines.push(
|
|
341
|
+
` "outputPaths": ${JSON.stringify(options.outputPaths, null, 4).split("\n").map((line, i) => i === 0 ? line : ` ${line}`).join("\n")}`
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
if (options.baseDir) {
|
|
345
|
+
const comma = lines.length > 1 ? "," : "";
|
|
346
|
+
if (comma) lines[lines.length - 1] += comma;
|
|
347
|
+
lines.push(` "baseDir": ${JSON.stringify(options.baseDir)}`);
|
|
348
|
+
}
|
|
349
|
+
if (options.delete !== void 0) {
|
|
350
|
+
const comma = lines.length > 1 ? "," : "";
|
|
351
|
+
if (comma) lines[lines.length - 1] += comma;
|
|
352
|
+
lines.push(` "delete": ${options.delete}`);
|
|
353
|
+
}
|
|
354
|
+
if (options.verbose !== void 0) {
|
|
355
|
+
const comma = lines.length > 1 ? "," : "";
|
|
356
|
+
if (comma) lines[lines.length - 1] += comma;
|
|
357
|
+
lines.push(` "verbose": ${options.verbose}`);
|
|
358
|
+
}
|
|
359
|
+
if (options.watch) {
|
|
360
|
+
const comma = lines.length > 1 ? "," : "";
|
|
361
|
+
if (comma) lines[lines.length - 1] += comma;
|
|
362
|
+
lines.push(
|
|
363
|
+
` "watch": ${JSON.stringify(options.watch, null, 4).split("\n").map((line, i) => i === 0 ? line : ` ${line}`).join("\n")}`
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
lines.push("}");
|
|
367
|
+
return lines.join("\n");
|
|
368
|
+
}
|
|
369
|
+
function generateSampleConfig(options) {
|
|
370
|
+
const targets = options?.targets || ALL_TOOL_TARGETS;
|
|
371
|
+
const excludeValue = options?.exclude ? JSON.stringify(options.exclude) : null;
|
|
372
|
+
const aiRulesDir = options?.aiRulesDir || null;
|
|
373
|
+
const baseDir = options?.baseDir || null;
|
|
374
|
+
const deleteFlag = options?.delete || false;
|
|
375
|
+
const verbose = options?.verbose !== void 0 ? options.verbose : true;
|
|
376
|
+
return `{
|
|
377
|
+
// List of tools to generate configurations for
|
|
378
|
+
// Available: ${ALL_TOOL_TARGETS.join(", ")}
|
|
379
|
+
"targets": ${JSON.stringify(targets)},
|
|
380
|
+
|
|
381
|
+
// Tools to exclude from generation (overrides targets)
|
|
382
|
+
${excludeValue ? `"exclude": ${excludeValue},` : '// "exclude": ["roo"],'}
|
|
383
|
+
${aiRulesDir ? `
|
|
384
|
+
// Directory containing AI rule files
|
|
385
|
+
"aiRulesDir": "${aiRulesDir}",` : ""}
|
|
386
|
+
|
|
387
|
+
// Custom output paths for specific tools
|
|
388
|
+
"outputPaths": {
|
|
389
|
+
"copilot": ".github/copilot-instructions.md"
|
|
390
|
+
},
|
|
391
|
+
${baseDir ? `
|
|
392
|
+
// Base directory for generation
|
|
393
|
+
"baseDir": "${baseDir}",` : `
|
|
394
|
+
// Base directory or directories for generation
|
|
395
|
+
// "baseDir": "./packages",
|
|
396
|
+
// "baseDir": ["./packages/frontend", "./packages/backend"],`}
|
|
397
|
+
|
|
398
|
+
// Delete existing files before generating
|
|
399
|
+
"delete": ${deleteFlag},
|
|
400
|
+
|
|
401
|
+
// Enable verbose output
|
|
402
|
+
"verbose": ${verbose},
|
|
403
|
+
|
|
404
|
+
// Watch configuration
|
|
405
|
+
"watch": {
|
|
406
|
+
"enabled": false,
|
|
407
|
+
"interval": 1000,
|
|
408
|
+
"ignore": ["node_modules/**", "dist/**"]
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
`;
|
|
412
|
+
}
|
|
413
|
+
function mergeWithCliOptions(config, cliOptions) {
|
|
414
|
+
const merged = { ...config };
|
|
415
|
+
if (cliOptions.verbose !== void 0) {
|
|
416
|
+
merged.verbose = cliOptions.verbose;
|
|
417
|
+
}
|
|
418
|
+
if (cliOptions.delete !== void 0) {
|
|
419
|
+
merged.delete = cliOptions.delete;
|
|
420
|
+
}
|
|
421
|
+
if (cliOptions.baseDirs && cliOptions.baseDirs.length > 0) {
|
|
422
|
+
merged.baseDir = cliOptions.baseDirs;
|
|
423
|
+
}
|
|
424
|
+
if (cliOptions.tools && cliOptions.tools.length > 0) {
|
|
425
|
+
merged.defaultTargets = cliOptions.tools;
|
|
426
|
+
merged.exclude = void 0;
|
|
427
|
+
}
|
|
428
|
+
return merged;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/utils/file.ts
|
|
432
|
+
import { mkdir as mkdir2, readdir, readFile, rm, stat, writeFile as writeFile2 } from "fs/promises";
|
|
433
|
+
import { dirname, join as join2 } from "path";
|
|
434
|
+
async function ensureDir(dirPath) {
|
|
435
|
+
try {
|
|
436
|
+
await stat(dirPath);
|
|
437
|
+
} catch {
|
|
438
|
+
await mkdir2(dirPath, { recursive: true });
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
async function readFileContent(filepath) {
|
|
442
|
+
return readFile(filepath, "utf-8");
|
|
443
|
+
}
|
|
444
|
+
async function writeFileContent(filepath, content) {
|
|
445
|
+
await ensureDir(dirname(filepath));
|
|
446
|
+
await writeFile2(filepath, content, "utf-8");
|
|
447
|
+
}
|
|
448
|
+
async function fileExists(filepath) {
|
|
449
|
+
try {
|
|
450
|
+
await stat(filepath);
|
|
451
|
+
return true;
|
|
452
|
+
} catch {
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
async function findFiles(dir, extension = ".md") {
|
|
457
|
+
try {
|
|
458
|
+
const files = await readdir(dir);
|
|
459
|
+
return files.filter((file) => file.endsWith(extension)).map((file) => join2(dir, file));
|
|
460
|
+
} catch {
|
|
461
|
+
return [];
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
async function removeDirectory(dirPath) {
|
|
465
|
+
const dangerousPaths = [".", "/", "~", "src", "node_modules"];
|
|
466
|
+
if (dangerousPaths.includes(dirPath) || dirPath === "") {
|
|
467
|
+
console.warn(`Skipping deletion of dangerous path: ${dirPath}`);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
try {
|
|
471
|
+
if (await fileExists(dirPath)) {
|
|
472
|
+
await rm(dirPath, { recursive: true, force: true });
|
|
473
|
+
}
|
|
474
|
+
} catch (error) {
|
|
475
|
+
console.warn(`Failed to remove directory ${dirPath}:`, error);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
async function removeFile(filepath) {
|
|
479
|
+
try {
|
|
480
|
+
if (await fileExists(filepath)) {
|
|
481
|
+
await rm(filepath);
|
|
482
|
+
}
|
|
483
|
+
} catch (error) {
|
|
484
|
+
console.warn(`Failed to remove file ${filepath}:`, error);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
async function removeClaudeGeneratedFiles() {
|
|
488
|
+
const filesToRemove = ["CLAUDE.md", ".claude/memories"];
|
|
489
|
+
for (const fileOrDir of filesToRemove) {
|
|
490
|
+
if (fileOrDir.endsWith("/memories")) {
|
|
491
|
+
await removeDirectory(fileOrDir);
|
|
492
|
+
} else {
|
|
493
|
+
await removeFile(fileOrDir);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// src/utils/rules.ts
|
|
499
|
+
function isToolSpecificRule(rule, targetTool) {
|
|
500
|
+
const filename = rule.filename;
|
|
501
|
+
const toolPatterns = {
|
|
502
|
+
"augmentcode-legacy": /^specification-augmentcode-legacy-/i,
|
|
503
|
+
augmentcode: /^specification-augmentcode-/i,
|
|
504
|
+
copilot: /^specification-copilot-/i,
|
|
505
|
+
cursor: /^specification-cursor-/i,
|
|
506
|
+
cline: /^specification-cline-/i,
|
|
507
|
+
claudecode: /^specification-claudecode-/i,
|
|
508
|
+
roo: /^specification-roo-/i,
|
|
509
|
+
geminicli: /^specification-geminicli-/i,
|
|
510
|
+
kiro: /^specification-kiro-/i
|
|
511
|
+
};
|
|
512
|
+
for (const [tool, pattern] of Object.entries(toolPatterns)) {
|
|
513
|
+
if (pattern.test(filename)) {
|
|
514
|
+
return tool === targetTool;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return true;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// src/cli/commands/config.ts
|
|
521
|
+
async function configCommand(options = {}) {
|
|
522
|
+
if (options.init) {
|
|
523
|
+
await initConfig(options);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
await showConfig();
|
|
527
|
+
}
|
|
528
|
+
async function showConfig() {
|
|
529
|
+
console.log("Loading configuration...\n");
|
|
530
|
+
try {
|
|
531
|
+
const result = await loadConfig();
|
|
532
|
+
if (result.isEmpty) {
|
|
533
|
+
console.log("No configuration file found. Using default configuration.\n");
|
|
534
|
+
} else {
|
|
535
|
+
console.log(`Configuration loaded from: ${result.filepath}
|
|
536
|
+
`);
|
|
537
|
+
}
|
|
538
|
+
console.log("Current configuration:");
|
|
539
|
+
console.log("=====================");
|
|
540
|
+
const config = result.config;
|
|
541
|
+
console.log(`
|
|
542
|
+
AI Rules Directory: ${config.aiRulesDir}`);
|
|
543
|
+
console.log(`
|
|
544
|
+
Default Targets: ${config.defaultTargets.join(", ")}`);
|
|
545
|
+
if (config.exclude && config.exclude.length > 0) {
|
|
546
|
+
console.log(`Excluded Targets: ${config.exclude.join(", ")}`);
|
|
547
|
+
}
|
|
548
|
+
console.log("\nOutput Paths:");
|
|
549
|
+
for (const [tool, outputPath] of Object.entries(config.outputPaths)) {
|
|
550
|
+
console.log(` ${tool}: ${outputPath}`);
|
|
551
|
+
}
|
|
552
|
+
if (config.baseDir) {
|
|
553
|
+
const dirs = Array.isArray(config.baseDir) ? config.baseDir : [config.baseDir];
|
|
554
|
+
console.log(`
|
|
555
|
+
Base Directories: ${dirs.join(", ")}`);
|
|
556
|
+
}
|
|
557
|
+
console.log(`
|
|
558
|
+
Verbose: ${config.verbose || false}`);
|
|
559
|
+
console.log(`Delete before generate: ${config.delete || false}`);
|
|
560
|
+
if (config.watch) {
|
|
561
|
+
console.log("\nWatch Configuration:");
|
|
562
|
+
console.log(` Enabled: ${config.watch.enabled || false}`);
|
|
563
|
+
if (config.watch.interval) {
|
|
564
|
+
console.log(` Interval: ${config.watch.interval}ms`);
|
|
565
|
+
}
|
|
566
|
+
if (config.watch.ignore && config.watch.ignore.length > 0) {
|
|
567
|
+
console.log(` Ignore patterns: ${config.watch.ignore.join(", ")}`);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
console.log("\nTip: Use 'rulesync config init' to create a configuration file.");
|
|
571
|
+
} catch (error) {
|
|
572
|
+
console.error(
|
|
573
|
+
"\u274C Failed to load configuration:",
|
|
574
|
+
error instanceof Error ? error.message : String(error)
|
|
575
|
+
);
|
|
576
|
+
process.exit(1);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
var FORMAT_CONFIG = {
|
|
580
|
+
jsonc: {
|
|
581
|
+
filename: "rulesync.jsonc",
|
|
582
|
+
generator: generateJsoncConfig
|
|
583
|
+
},
|
|
584
|
+
ts: {
|
|
585
|
+
filename: "rulesync.ts",
|
|
586
|
+
generator: generateTsConfig
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
async function initConfig(options) {
|
|
590
|
+
const validFormats = Object.keys(FORMAT_CONFIG);
|
|
591
|
+
const selectedFormat = options.format || "jsonc";
|
|
592
|
+
if (!validFormats.includes(selectedFormat)) {
|
|
593
|
+
console.error(
|
|
594
|
+
`\u274C Invalid format: ${selectedFormat}. Valid formats are: ${validFormats.join(", ")}`
|
|
595
|
+
);
|
|
596
|
+
process.exit(1);
|
|
597
|
+
}
|
|
598
|
+
const formatConfig = FORMAT_CONFIG[selectedFormat];
|
|
599
|
+
const filename = formatConfig.filename;
|
|
600
|
+
const configOptions = {};
|
|
601
|
+
if (options.targets) {
|
|
602
|
+
const targets = options.targets.split(",").map((t) => t.trim());
|
|
603
|
+
const validTargets = [];
|
|
604
|
+
for (const target of targets) {
|
|
605
|
+
const result = ToolTargetSchema.safeParse(target);
|
|
606
|
+
if (result.success) {
|
|
607
|
+
validTargets.push(result.data);
|
|
608
|
+
} else {
|
|
609
|
+
console.error(`\u274C Invalid target: ${target}`);
|
|
610
|
+
process.exit(1);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
configOptions.targets = validTargets;
|
|
614
|
+
}
|
|
615
|
+
if (options.exclude) {
|
|
616
|
+
const excludes = options.exclude.split(",").map((t) => t.trim());
|
|
617
|
+
const validExcludes = [];
|
|
618
|
+
for (const exclude of excludes) {
|
|
619
|
+
const result = ToolTargetSchema.safeParse(exclude);
|
|
620
|
+
if (result.success) {
|
|
621
|
+
validExcludes.push(result.data);
|
|
622
|
+
} else {
|
|
623
|
+
console.error(`\u274C Invalid exclude target: ${exclude}`);
|
|
624
|
+
process.exit(1);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
configOptions.exclude = validExcludes;
|
|
628
|
+
}
|
|
629
|
+
if (options.aiRulesDir) {
|
|
630
|
+
configOptions.aiRulesDir = options.aiRulesDir;
|
|
631
|
+
}
|
|
632
|
+
if (options.baseDir) {
|
|
633
|
+
configOptions.baseDir = options.baseDir;
|
|
634
|
+
}
|
|
635
|
+
if (options.verbose !== void 0) {
|
|
636
|
+
configOptions.verbose = options.verbose;
|
|
637
|
+
}
|
|
638
|
+
if (options.delete !== void 0) {
|
|
639
|
+
configOptions.delete = options.delete;
|
|
640
|
+
}
|
|
641
|
+
const content = formatConfig.generator(configOptions);
|
|
642
|
+
const filepath = path2.join(process.cwd(), filename);
|
|
643
|
+
try {
|
|
644
|
+
const fs2 = await import("fs/promises");
|
|
645
|
+
await fs2.access(filepath);
|
|
646
|
+
console.error(`\u274C Configuration file already exists: ${filepath}`);
|
|
647
|
+
console.log("Remove the existing file or choose a different format.");
|
|
648
|
+
process.exit(1);
|
|
649
|
+
} catch {
|
|
650
|
+
}
|
|
651
|
+
try {
|
|
652
|
+
writeFileSync(filepath, content, "utf-8");
|
|
653
|
+
console.log(`\u2705 Created configuration file: ${filepath}`);
|
|
654
|
+
console.log("\nYou can now customize the configuration to fit your needs.");
|
|
655
|
+
console.log("Run 'rulesync generate' to use the new configuration.");
|
|
656
|
+
} catch (error) {
|
|
657
|
+
console.error(
|
|
658
|
+
`\u274C Failed to create configuration file: ${error instanceof Error ? error.message : String(error)}`
|
|
659
|
+
);
|
|
660
|
+
process.exit(1);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
function generateJsoncConfig(options) {
|
|
664
|
+
if (options && Object.keys(options).length > 0) {
|
|
665
|
+
return generateMinimalConfig(options);
|
|
666
|
+
}
|
|
667
|
+
return generateSampleConfig(options);
|
|
668
|
+
}
|
|
669
|
+
function generateTsConfig(options) {
|
|
670
|
+
if (!options || Object.keys(options).length === 0) {
|
|
671
|
+
return `import type { ConfigOptions } from "rulesync";
|
|
672
|
+
|
|
673
|
+
const config: ConfigOptions = {
|
|
674
|
+
// List of tools to generate configurations for
|
|
675
|
+
// Available: ${ALL_TOOL_TARGETS.join(", ")}
|
|
676
|
+
targets: ${JSON.stringify(ALL_TOOL_TARGETS)},
|
|
677
|
+
|
|
678
|
+
// Custom output paths for specific tools
|
|
679
|
+
// outputPaths: {
|
|
680
|
+
// copilot: ".github/copilot-instructions.md",
|
|
681
|
+
// },
|
|
682
|
+
|
|
683
|
+
// Delete existing files before generating
|
|
684
|
+
// delete: false,
|
|
685
|
+
|
|
686
|
+
// Enable verbose output
|
|
687
|
+
verbose: true,
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
export default config;`;
|
|
691
|
+
}
|
|
692
|
+
const configLines = [];
|
|
693
|
+
if (options.targets) {
|
|
694
|
+
configLines.push(` targets: ${JSON.stringify(options.targets)}`);
|
|
695
|
+
}
|
|
696
|
+
if (options.exclude) {
|
|
697
|
+
configLines.push(` exclude: ${JSON.stringify(options.exclude)}`);
|
|
698
|
+
}
|
|
699
|
+
if (options.aiRulesDir) {
|
|
700
|
+
configLines.push(` aiRulesDir: "${options.aiRulesDir}"`);
|
|
701
|
+
}
|
|
702
|
+
if (options.outputPaths) {
|
|
703
|
+
const pathsStr = JSON.stringify(options.outputPaths, null, 4).split("\n").map((line, i) => i === 0 ? line : ` ${line}`).join("\n");
|
|
704
|
+
configLines.push(` outputPaths: ${pathsStr}`);
|
|
705
|
+
}
|
|
706
|
+
if (options.baseDir) {
|
|
707
|
+
configLines.push(` baseDir: ${JSON.stringify(options.baseDir)}`);
|
|
708
|
+
}
|
|
709
|
+
if (options.delete !== void 0) {
|
|
710
|
+
configLines.push(` delete: ${options.delete}`);
|
|
711
|
+
}
|
|
712
|
+
if (options.verbose !== void 0) {
|
|
713
|
+
configLines.push(` verbose: ${options.verbose}`);
|
|
714
|
+
}
|
|
715
|
+
if (options.watch) {
|
|
716
|
+
const watchStr = JSON.stringify(options.watch, null, 4).split("\n").map((line, i) => i === 0 ? line : ` ${line}`).join("\n");
|
|
717
|
+
configLines.push(` watch: ${watchStr}`);
|
|
718
|
+
}
|
|
719
|
+
const configContent = `import type { ConfigOptions } from "rulesync";
|
|
720
|
+
|
|
721
|
+
const config: ConfigOptions = {
|
|
722
|
+
${configLines.join(",\n")},
|
|
723
|
+
};
|
|
724
|
+
|
|
725
|
+
export default config;
|
|
726
|
+
`;
|
|
727
|
+
return configContent;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// src/cli/commands/generate.ts
|
|
731
|
+
import { join as join15 } from "path";
|
|
732
|
+
|
|
733
|
+
// src/generators/ignore/augmentcode.ts
|
|
734
|
+
import { join as join3 } from "path";
|
|
735
|
+
|
|
736
|
+
// src/generators/ignore/shared-helpers.ts
|
|
737
|
+
function extractIgnorePatternsFromRules(rules) {
|
|
738
|
+
const patterns = [];
|
|
739
|
+
for (const rule of rules) {
|
|
740
|
+
if (rule.frontmatter.globs && rule.frontmatter.globs.length > 0) {
|
|
741
|
+
for (const glob of rule.frontmatter.globs) {
|
|
742
|
+
if (shouldExcludeFromAI(glob)) {
|
|
743
|
+
patterns.push(`# Exclude: ${rule.frontmatter.description}`);
|
|
744
|
+
patterns.push(glob);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
const contentPatterns = extractIgnorePatternsFromContent(rule.content);
|
|
749
|
+
patterns.push(...contentPatterns);
|
|
750
|
+
}
|
|
751
|
+
return patterns;
|
|
752
|
+
}
|
|
753
|
+
function shouldExcludeFromAI(glob) {
|
|
754
|
+
const excludePatterns = [
|
|
755
|
+
// Large generated files that slow indexing
|
|
756
|
+
"**/assets/generated/**",
|
|
757
|
+
"**/public/build/**",
|
|
758
|
+
// Test fixtures with potentially sensitive data
|
|
759
|
+
"**/tests/fixtures/**",
|
|
760
|
+
"**/test/fixtures/**",
|
|
761
|
+
"**/*.fixture.*",
|
|
762
|
+
// Build outputs that provide little value for AI context
|
|
763
|
+
"**/dist/**",
|
|
764
|
+
"**/build/**",
|
|
765
|
+
"**/coverage/**",
|
|
766
|
+
// Configuration that might contain sensitive data
|
|
767
|
+
"**/config/production/**",
|
|
768
|
+
"**/config/secrets/**",
|
|
769
|
+
"**/config/prod/**",
|
|
770
|
+
"**/deploy/prod/**",
|
|
771
|
+
"**/*.prod.*",
|
|
772
|
+
// Internal documentation that might be sensitive
|
|
773
|
+
"**/internal/**",
|
|
774
|
+
"**/internal-docs/**",
|
|
775
|
+
"**/proprietary/**",
|
|
776
|
+
"**/personal-notes/**",
|
|
777
|
+
"**/private/**",
|
|
778
|
+
"**/confidential/**"
|
|
779
|
+
];
|
|
780
|
+
return excludePatterns.some((pattern) => {
|
|
781
|
+
const regex = new RegExp(pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*"));
|
|
782
|
+
return regex.test(glob);
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
function extractIgnorePatternsFromContent(content) {
|
|
786
|
+
const patterns = [];
|
|
787
|
+
const lines = content.split("\n");
|
|
788
|
+
for (const line of lines) {
|
|
789
|
+
const trimmed = line.trim();
|
|
790
|
+
if (trimmed.startsWith("# IGNORE:") || trimmed.startsWith("# aiignore:")) {
|
|
791
|
+
const pattern = trimmed.replace(/^# (IGNORE|aiignore):\s*/, "").trim();
|
|
792
|
+
if (pattern) {
|
|
793
|
+
patterns.push(pattern);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
if (trimmed.startsWith("# AUGMENT_IGNORE:") || trimmed.startsWith("# augmentignore:")) {
|
|
797
|
+
const pattern = trimmed.replace(/^# (AUGMENT_IGNORE|augmentignore):\s*/, "").trim();
|
|
798
|
+
if (pattern) {
|
|
799
|
+
patterns.push(pattern);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
if (trimmed.startsWith("# AUGMENT_INCLUDE:") || trimmed.startsWith("# augmentinclude:")) {
|
|
803
|
+
const pattern = trimmed.replace(/^# (AUGMENT_INCLUDE|augmentinclude):\s*/, "").trim();
|
|
804
|
+
if (pattern) {
|
|
805
|
+
patterns.push(`!${pattern}`);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
if (trimmed.includes("exclude") || trimmed.includes("ignore")) {
|
|
809
|
+
const matches = trimmed.match(/['"`]([^'"`]+\.(log|tmp|cache|temp))['"`]/g);
|
|
810
|
+
if (matches) {
|
|
811
|
+
patterns.push(...matches.map((m) => m.replace(/['"`]/g, "")));
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
return patterns;
|
|
816
|
+
}
|
|
817
|
+
function extractAugmentCodeIgnorePatternsFromContent(content) {
|
|
818
|
+
const patterns = [];
|
|
819
|
+
const lines = content.split("\n");
|
|
820
|
+
for (const line of lines) {
|
|
821
|
+
const trimmed = line.trim();
|
|
822
|
+
if (trimmed.startsWith("# AUGMENT_IGNORE:") || trimmed.startsWith("# augmentignore:")) {
|
|
823
|
+
const pattern = trimmed.replace(/^# (AUGMENT_IGNORE|augmentignore):\s*/, "").trim();
|
|
824
|
+
if (pattern) {
|
|
825
|
+
patterns.push(pattern);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
if (trimmed.startsWith("# AUGMENT_INCLUDE:") || trimmed.startsWith("# augmentinclude:")) {
|
|
829
|
+
const pattern = trimmed.replace(/^# (AUGMENT_INCLUDE|augmentinclude):\s*/, "").trim();
|
|
830
|
+
if (pattern) {
|
|
831
|
+
patterns.push(`!${pattern}`);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
if (trimmed.includes("large file") || trimmed.includes("binary") || trimmed.includes("media")) {
|
|
835
|
+
const regex = /['"`]([^'"`]+\.(mp4|avi|zip|tar\.gz|rar|pdf|doc|xlsx))['"`]/g;
|
|
836
|
+
let match;
|
|
837
|
+
while ((match = regex.exec(trimmed)) !== null) {
|
|
838
|
+
if (match[1]) {
|
|
839
|
+
patterns.push(match[1]);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
return patterns;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// src/generators/ignore/augmentcode.ts
|
|
848
|
+
async function generateAugmentCodeIgnoreFiles(rules, config, baseDir) {
|
|
849
|
+
const outputs = [];
|
|
850
|
+
const augmentignoreContent = generateAugmentignoreContent(rules);
|
|
851
|
+
const outputPath = baseDir || process.cwd();
|
|
852
|
+
const filepath = join3(outputPath, ".augmentignore");
|
|
853
|
+
outputs.push({
|
|
854
|
+
tool: "augmentcode",
|
|
855
|
+
filepath,
|
|
856
|
+
content: augmentignoreContent
|
|
857
|
+
});
|
|
858
|
+
return outputs;
|
|
859
|
+
}
|
|
860
|
+
function generateAugmentignoreContent(rules) {
|
|
861
|
+
const lines = [
|
|
862
|
+
"# Generated by rulesync - AugmentCode ignore patterns",
|
|
863
|
+
"# AugmentCode uses a two-tier approach: .gitignore first, then .augmentignore",
|
|
864
|
+
"# This file provides Augment-specific exclusions and re-inclusions",
|
|
865
|
+
""
|
|
866
|
+
];
|
|
867
|
+
lines.push(
|
|
868
|
+
"# Security and Secrets (critical exclusions)",
|
|
869
|
+
"# Environment files",
|
|
870
|
+
".env*",
|
|
871
|
+
"",
|
|
872
|
+
"# Private keys and certificates",
|
|
873
|
+
"*.pem",
|
|
874
|
+
"*.key",
|
|
875
|
+
"*.p12",
|
|
876
|
+
"*.crt",
|
|
877
|
+
"*.der",
|
|
878
|
+
"",
|
|
879
|
+
"# SSH keys",
|
|
880
|
+
"id_rsa*",
|
|
881
|
+
"id_dsa*",
|
|
882
|
+
"",
|
|
883
|
+
"# AWS credentials",
|
|
884
|
+
".aws/",
|
|
885
|
+
"aws-exports.js",
|
|
886
|
+
"",
|
|
887
|
+
"# API keys and tokens",
|
|
888
|
+
"**/apikeys/",
|
|
889
|
+
"**/*_token*",
|
|
890
|
+
"**/*_secret*",
|
|
891
|
+
""
|
|
892
|
+
);
|
|
893
|
+
lines.push(
|
|
894
|
+
"# Build Artifacts and Dependencies",
|
|
895
|
+
"# Build outputs",
|
|
896
|
+
"dist/",
|
|
897
|
+
"build/",
|
|
898
|
+
"out/",
|
|
899
|
+
"target/",
|
|
900
|
+
"",
|
|
901
|
+
"# Dependencies",
|
|
902
|
+
"node_modules/",
|
|
903
|
+
"venv/",
|
|
904
|
+
"*.egg-info/",
|
|
905
|
+
"",
|
|
906
|
+
"# Logs",
|
|
907
|
+
"*.log",
|
|
908
|
+
"logs/",
|
|
909
|
+
"",
|
|
910
|
+
"# Temporary files",
|
|
911
|
+
"*.tmp",
|
|
912
|
+
"*.swp",
|
|
913
|
+
"*.swo",
|
|
914
|
+
"*~",
|
|
915
|
+
""
|
|
916
|
+
);
|
|
917
|
+
lines.push(
|
|
918
|
+
"# Large Files and Media",
|
|
919
|
+
"# Binary files",
|
|
920
|
+
"*.jar",
|
|
921
|
+
"*.png",
|
|
922
|
+
"*.jpg",
|
|
923
|
+
"*.jpeg",
|
|
924
|
+
"*.gif",
|
|
925
|
+
"*.mp4",
|
|
926
|
+
"*.avi",
|
|
927
|
+
"*.zip",
|
|
928
|
+
"*.tar.gz",
|
|
929
|
+
"*.rar",
|
|
930
|
+
"",
|
|
931
|
+
"# Database files",
|
|
932
|
+
"*.sqlite",
|
|
933
|
+
"*.db",
|
|
934
|
+
"*.mdb",
|
|
935
|
+
"",
|
|
936
|
+
"# Data files",
|
|
937
|
+
"*.csv",
|
|
938
|
+
"*.tsv",
|
|
939
|
+
"*.xlsx",
|
|
940
|
+
""
|
|
941
|
+
);
|
|
942
|
+
lines.push(
|
|
943
|
+
"# Performance Optimization",
|
|
944
|
+
"# Exclude files that are too large for effective AI processing",
|
|
945
|
+
"**/*.{mp4,avi,mov,mkv}",
|
|
946
|
+
"**/*.{zip,tar,gz,rar}",
|
|
947
|
+
"**/*.{pdf,doc,docx}",
|
|
948
|
+
"**/logs/**/*.log",
|
|
949
|
+
"",
|
|
950
|
+
"# But include small configuration files",
|
|
951
|
+
"!**/config.{json,yaml,yml}",
|
|
952
|
+
""
|
|
953
|
+
);
|
|
954
|
+
const rulePatterns = extractIgnorePatternsFromRules(rules);
|
|
955
|
+
const augmentPatterns = [];
|
|
956
|
+
for (const rule of rules) {
|
|
957
|
+
augmentPatterns.push(...extractAugmentCodeIgnorePatternsFromContent(rule.content));
|
|
958
|
+
}
|
|
959
|
+
const allPatterns = [...rulePatterns, ...augmentPatterns];
|
|
960
|
+
if (allPatterns.length > 0) {
|
|
961
|
+
lines.push("# Project-specific patterns from rulesync rules");
|
|
962
|
+
lines.push(...allPatterns);
|
|
963
|
+
lines.push("");
|
|
964
|
+
}
|
|
965
|
+
lines.push(
|
|
966
|
+
"# Team Collaboration",
|
|
967
|
+
"# Exclude personal IDE settings",
|
|
968
|
+
".vscode/settings.json",
|
|
969
|
+
".idea/workspace.xml",
|
|
970
|
+
"",
|
|
971
|
+
"# But include shared team settings",
|
|
972
|
+
"!.vscode/extensions.json",
|
|
973
|
+
"!.idea/codeStyles/",
|
|
974
|
+
"",
|
|
975
|
+
"# Exclude test fixtures with sensitive data",
|
|
976
|
+
"tests/fixtures/real-data/**",
|
|
977
|
+
"",
|
|
978
|
+
"# Re-include important documentation",
|
|
979
|
+
"!vendor/*/README.md",
|
|
980
|
+
"!third-party/*/LICENSE",
|
|
981
|
+
""
|
|
982
|
+
);
|
|
983
|
+
return lines.join("\n");
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// src/generators/ignore/junie.ts
|
|
987
|
+
import { join as join4 } from "path";
|
|
988
|
+
async function generateJunieIgnoreFiles(rules, config, baseDir) {
|
|
989
|
+
const outputs = [];
|
|
990
|
+
const aiignoreContent = generateAiignoreContent(rules);
|
|
991
|
+
const outputPath = baseDir || process.cwd();
|
|
992
|
+
const filepath = join4(outputPath, ".aiignore");
|
|
993
|
+
outputs.push({
|
|
994
|
+
tool: "junie",
|
|
995
|
+
filepath,
|
|
996
|
+
content: aiignoreContent
|
|
997
|
+
});
|
|
998
|
+
return outputs;
|
|
999
|
+
}
|
|
1000
|
+
function generateAiignoreContent(rules) {
|
|
1001
|
+
const lines = [
|
|
1002
|
+
"# Generated by rulesync - JetBrains Junie AI ignore file",
|
|
1003
|
+
"# This file controls which files the AI can access automatically",
|
|
1004
|
+
"# AI must ask before reading or editing matched files/directories",
|
|
1005
|
+
"",
|
|
1006
|
+
"# \u2500\u2500\u2500\u2500\u2500 Source Control Metadata \u2500\u2500\u2500\u2500\u2500",
|
|
1007
|
+
".git/",
|
|
1008
|
+
".svn/",
|
|
1009
|
+
".hg/",
|
|
1010
|
+
".idea/",
|
|
1011
|
+
"*.iml",
|
|
1012
|
+
".vscode/settings.json",
|
|
1013
|
+
"",
|
|
1014
|
+
"# \u2500\u2500\u2500\u2500\u2500 Build Artifacts \u2500\u2500\u2500\u2500\u2500",
|
|
1015
|
+
"/out/",
|
|
1016
|
+
"/dist/",
|
|
1017
|
+
"/target/",
|
|
1018
|
+
"/build/",
|
|
1019
|
+
"*.class",
|
|
1020
|
+
"*.jar",
|
|
1021
|
+
"*.war",
|
|
1022
|
+
"",
|
|
1023
|
+
"# \u2500\u2500\u2500\u2500\u2500 Secrets & Credentials \u2500\u2500\u2500\u2500\u2500",
|
|
1024
|
+
"# Environment files",
|
|
1025
|
+
".env",
|
|
1026
|
+
".env.*",
|
|
1027
|
+
"!.env.example",
|
|
1028
|
+
"",
|
|
1029
|
+
"# Key material",
|
|
1030
|
+
"*.pem",
|
|
1031
|
+
"*.key",
|
|
1032
|
+
"*.crt",
|
|
1033
|
+
"*.p12",
|
|
1034
|
+
"*.pfx",
|
|
1035
|
+
"*.der",
|
|
1036
|
+
"id_rsa*",
|
|
1037
|
+
"id_dsa*",
|
|
1038
|
+
"*.ppk",
|
|
1039
|
+
"",
|
|
1040
|
+
"# Cloud and service configs",
|
|
1041
|
+
"aws-credentials.json",
|
|
1042
|
+
"gcp-service-account*.json",
|
|
1043
|
+
"azure-credentials.json",
|
|
1044
|
+
"secrets/**",
|
|
1045
|
+
"config/secrets/",
|
|
1046
|
+
"**/secrets/",
|
|
1047
|
+
"",
|
|
1048
|
+
"# Database credentials",
|
|
1049
|
+
"database.yml",
|
|
1050
|
+
"**/database/config.*",
|
|
1051
|
+
"",
|
|
1052
|
+
"# API keys and tokens",
|
|
1053
|
+
"**/apikeys/",
|
|
1054
|
+
"**/*_token*",
|
|
1055
|
+
"**/*_secret*",
|
|
1056
|
+
"**/*api_key*",
|
|
1057
|
+
"",
|
|
1058
|
+
"# \u2500\u2500\u2500\u2500\u2500 Infrastructure & Deployment \u2500\u2500\u2500\u2500\u2500",
|
|
1059
|
+
"# Terraform state",
|
|
1060
|
+
"*.tfstate",
|
|
1061
|
+
"*.tfstate.*",
|
|
1062
|
+
".terraform/",
|
|
1063
|
+
"",
|
|
1064
|
+
"# Kubernetes secrets",
|
|
1065
|
+
"**/k8s/**/secret*.yaml",
|
|
1066
|
+
"**/kubernetes/**/secret*.yaml",
|
|
1067
|
+
"",
|
|
1068
|
+
"# Docker secrets",
|
|
1069
|
+
"docker-compose.override.yml",
|
|
1070
|
+
"**/docker/secrets/",
|
|
1071
|
+
"",
|
|
1072
|
+
"# \u2500\u2500\u2500\u2500\u2500 Logs & Runtime Data \u2500\u2500\u2500\u2500\u2500",
|
|
1073
|
+
"*.log",
|
|
1074
|
+
"*.tmp",
|
|
1075
|
+
"*.cache",
|
|
1076
|
+
"logs/",
|
|
1077
|
+
"/var/log/",
|
|
1078
|
+
"coverage/",
|
|
1079
|
+
".nyc_output/",
|
|
1080
|
+
"",
|
|
1081
|
+
"# \u2500\u2500\u2500\u2500\u2500 Large Data Files \u2500\u2500\u2500\u2500\u2500",
|
|
1082
|
+
"*.csv",
|
|
1083
|
+
"*.xlsx",
|
|
1084
|
+
"*.sqlite",
|
|
1085
|
+
"*.db",
|
|
1086
|
+
"*.dump",
|
|
1087
|
+
"data/",
|
|
1088
|
+
"datasets/",
|
|
1089
|
+
"",
|
|
1090
|
+
"# \u2500\u2500\u2500\u2500\u2500 Node.js Specific \u2500\u2500\u2500\u2500\u2500",
|
|
1091
|
+
"node_modules/",
|
|
1092
|
+
".pnpm-store/",
|
|
1093
|
+
".yarn/",
|
|
1094
|
+
".next/",
|
|
1095
|
+
".nuxt/",
|
|
1096
|
+
".cache/",
|
|
1097
|
+
".parcel-cache/",
|
|
1098
|
+
"",
|
|
1099
|
+
"# \u2500\u2500\u2500\u2500\u2500 Python Specific \u2500\u2500\u2500\u2500\u2500",
|
|
1100
|
+
"__pycache__/",
|
|
1101
|
+
"*.pyc",
|
|
1102
|
+
"*.pyo",
|
|
1103
|
+
"*.pyd",
|
|
1104
|
+
".Python",
|
|
1105
|
+
"venv/",
|
|
1106
|
+
".venv/",
|
|
1107
|
+
"env/",
|
|
1108
|
+
".env/",
|
|
1109
|
+
"",
|
|
1110
|
+
"# \u2500\u2500\u2500\u2500\u2500 Java Specific \u2500\u2500\u2500\u2500\u2500",
|
|
1111
|
+
"*.class",
|
|
1112
|
+
"*.jar",
|
|
1113
|
+
"*.war",
|
|
1114
|
+
"target/",
|
|
1115
|
+
""
|
|
1116
|
+
];
|
|
1117
|
+
const rulePatterns = extractIgnorePatternsFromRules(rules);
|
|
1118
|
+
if (rulePatterns.length > 0) {
|
|
1119
|
+
lines.push("# \u2500\u2500\u2500\u2500\u2500 Project-specific exclusions from rulesync rules \u2500\u2500\u2500\u2500\u2500");
|
|
1120
|
+
lines.push(...rulePatterns);
|
|
1121
|
+
lines.push("");
|
|
1122
|
+
}
|
|
1123
|
+
lines.push(
|
|
1124
|
+
"# \u2500\u2500\u2500\u2500\u2500 Allow specific source files (uncomment as needed) \u2500\u2500\u2500\u2500\u2500",
|
|
1125
|
+
"# !src/**/*.ts",
|
|
1126
|
+
"# !src/**/*.js",
|
|
1127
|
+
"# !lib/**/*.py",
|
|
1128
|
+
"# !src/main/**/*.java",
|
|
1129
|
+
""
|
|
1130
|
+
);
|
|
1131
|
+
return lines.join("\n");
|
|
1132
|
+
}
|
|
1133
|
+
|
|
97
1134
|
// src/generators/ignore/kiro.ts
|
|
98
|
-
import { join as
|
|
1135
|
+
import { join as join5 } from "path";
|
|
99
1136
|
async function generateKiroIgnoreFiles(rules, config, baseDir) {
|
|
100
1137
|
const outputs = [];
|
|
101
|
-
const aiignoreContent =
|
|
1138
|
+
const aiignoreContent = generateAiignoreContent2(rules);
|
|
102
1139
|
const outputPath = baseDir || process.cwd();
|
|
103
|
-
const filepath =
|
|
1140
|
+
const filepath = join5(outputPath, ".aiignore");
|
|
104
1141
|
outputs.push({
|
|
105
1142
|
tool: "kiro",
|
|
106
1143
|
filepath,
|
|
@@ -108,7 +1145,7 @@ async function generateKiroIgnoreFiles(rules, config, baseDir) {
|
|
|
108
1145
|
});
|
|
109
1146
|
return outputs;
|
|
110
1147
|
}
|
|
111
|
-
function
|
|
1148
|
+
function generateAiignoreContent2(rules) {
|
|
112
1149
|
const lines = [
|
|
113
1150
|
"# Generated by rulesync - Kiro AI-specific exclusions",
|
|
114
1151
|
"# This file excludes files that can be in Git but shouldn't be read by the AI",
|
|
@@ -148,157 +1185,22 @@ function generateAiignoreContent(rules) {
|
|
|
148
1185
|
}
|
|
149
1186
|
return lines.join("\n");
|
|
150
1187
|
}
|
|
151
|
-
function extractIgnorePatternsFromRules(rules) {
|
|
152
|
-
const patterns = [];
|
|
153
|
-
for (const rule of rules) {
|
|
154
|
-
if (rule.frontmatter.globs && rule.frontmatter.globs.length > 0) {
|
|
155
|
-
for (const glob of rule.frontmatter.globs) {
|
|
156
|
-
if (shouldExcludeFromAI(glob)) {
|
|
157
|
-
patterns.push(`# Exclude: ${rule.frontmatter.description}`);
|
|
158
|
-
patterns.push(glob);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
const contentPatterns = extractIgnorePatternsFromContent(rule.content);
|
|
163
|
-
patterns.push(...contentPatterns);
|
|
164
|
-
}
|
|
165
|
-
return patterns;
|
|
166
|
-
}
|
|
167
|
-
function shouldExcludeFromAI(glob) {
|
|
168
|
-
const excludePatterns = [
|
|
169
|
-
// Test and fixture files that might be large or confusing
|
|
170
|
-
"**/test/fixtures/**",
|
|
171
|
-
"**/tests/fixtures/**",
|
|
172
|
-
"**/*.fixture.*",
|
|
173
|
-
// Build and generated files
|
|
174
|
-
"**/dist/**",
|
|
175
|
-
"**/build/**",
|
|
176
|
-
"**/coverage/**",
|
|
177
|
-
// Configuration that might contain sensitive data
|
|
178
|
-
"**/config/production/**",
|
|
179
|
-
"**/config/prod/**",
|
|
180
|
-
"**/*.prod.*",
|
|
181
|
-
// Documentation that might be sensitive
|
|
182
|
-
"**/internal/**",
|
|
183
|
-
"**/private/**",
|
|
184
|
-
"**/confidential/**"
|
|
185
|
-
];
|
|
186
|
-
return excludePatterns.some((pattern) => {
|
|
187
|
-
const regex = new RegExp(pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*"));
|
|
188
|
-
return regex.test(glob);
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
function extractIgnorePatternsFromContent(content) {
|
|
192
|
-
const patterns = [];
|
|
193
|
-
const lines = content.split("\n");
|
|
194
|
-
for (const line of lines) {
|
|
195
|
-
const trimmed = line.trim();
|
|
196
|
-
if (trimmed.startsWith("# IGNORE:") || trimmed.startsWith("# aiignore:")) {
|
|
197
|
-
const pattern = trimmed.replace(/^# (IGNORE|aiignore):\s*/, "").trim();
|
|
198
|
-
if (pattern) {
|
|
199
|
-
patterns.push(pattern);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
if (trimmed.includes("exclude") || trimmed.includes("ignore")) {
|
|
203
|
-
const matches = trimmed.match(/['"`]([^'"`]+\.(log|tmp|cache|temp))['"`]/g);
|
|
204
|
-
if (matches) {
|
|
205
|
-
patterns.push(...matches.map((m) => m.replace(/['"`]/g, "")));
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
return patterns;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// src/generators/rules/claudecode.ts
|
|
213
|
-
import { join as join5 } from "path";
|
|
214
1188
|
|
|
215
|
-
// src/
|
|
216
|
-
import {
|
|
217
|
-
var ClaudeSettingsSchema = z.looseObject({
|
|
218
|
-
permissions: z._default(
|
|
219
|
-
z.looseObject({
|
|
220
|
-
deny: z._default(z.array(z.string()), [])
|
|
221
|
-
}),
|
|
222
|
-
{ deny: [] }
|
|
223
|
-
)
|
|
224
|
-
});
|
|
1189
|
+
// src/generators/rules/augmentcode.ts
|
|
1190
|
+
import { join as join8 } from "path";
|
|
225
1191
|
|
|
226
|
-
// src/
|
|
227
|
-
import {
|
|
228
|
-
import { dirname, join as join3 } from "path";
|
|
229
|
-
async function ensureDir(dirPath) {
|
|
230
|
-
try {
|
|
231
|
-
await stat(dirPath);
|
|
232
|
-
} catch {
|
|
233
|
-
await mkdir2(dirPath, { recursive: true });
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
async function readFileContent(filepath) {
|
|
237
|
-
return readFile(filepath, "utf-8");
|
|
238
|
-
}
|
|
239
|
-
async function writeFileContent(filepath, content) {
|
|
240
|
-
await ensureDir(dirname(filepath));
|
|
241
|
-
await writeFile2(filepath, content, "utf-8");
|
|
242
|
-
}
|
|
243
|
-
async function fileExists(filepath) {
|
|
244
|
-
try {
|
|
245
|
-
await stat(filepath);
|
|
246
|
-
return true;
|
|
247
|
-
} catch {
|
|
248
|
-
return false;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
async function findFiles(dir, extension = ".md") {
|
|
252
|
-
try {
|
|
253
|
-
const files = await readdir(dir);
|
|
254
|
-
return files.filter((file) => file.endsWith(extension)).map((file) => join3(dir, file));
|
|
255
|
-
} catch {
|
|
256
|
-
return [];
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
async function removeDirectory(dirPath) {
|
|
260
|
-
const dangerousPaths = [".", "/", "~", "src", "node_modules"];
|
|
261
|
-
if (dangerousPaths.includes(dirPath) || dirPath === "") {
|
|
262
|
-
console.warn(`Skipping deletion of dangerous path: ${dirPath}`);
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
try {
|
|
266
|
-
if (await fileExists(dirPath)) {
|
|
267
|
-
await rm(dirPath, { recursive: true, force: true });
|
|
268
|
-
}
|
|
269
|
-
} catch (error) {
|
|
270
|
-
console.warn(`Failed to remove directory ${dirPath}:`, error);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
async function removeFile(filepath) {
|
|
274
|
-
try {
|
|
275
|
-
if (await fileExists(filepath)) {
|
|
276
|
-
await rm(filepath);
|
|
277
|
-
}
|
|
278
|
-
} catch (error) {
|
|
279
|
-
console.warn(`Failed to remove file ${filepath}:`, error);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
async function removeClaudeGeneratedFiles() {
|
|
283
|
-
const filesToRemove = ["CLAUDE.md", ".claude/memories"];
|
|
284
|
-
for (const fileOrDir of filesToRemove) {
|
|
285
|
-
if (fileOrDir.endsWith("/memories")) {
|
|
286
|
-
await removeDirectory(fileOrDir);
|
|
287
|
-
} else {
|
|
288
|
-
await removeFile(fileOrDir);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
1192
|
+
// src/generators/rules/shared-helpers.ts
|
|
1193
|
+
import { join as join7 } from "path";
|
|
292
1194
|
|
|
293
1195
|
// src/utils/ignore.ts
|
|
294
|
-
import { join as
|
|
1196
|
+
import { join as join6 } from "path";
|
|
295
1197
|
import micromatch from "micromatch";
|
|
296
1198
|
var cachedIgnorePatterns = null;
|
|
297
1199
|
async function loadIgnorePatterns(baseDir = process.cwd()) {
|
|
298
1200
|
if (cachedIgnorePatterns) {
|
|
299
1201
|
return cachedIgnorePatterns;
|
|
300
1202
|
}
|
|
301
|
-
const ignorePath =
|
|
1203
|
+
const ignorePath = join6(baseDir, ".rulesyncignore");
|
|
302
1204
|
if (!await fileExists(ignorePath)) {
|
|
303
1205
|
cachedIgnorePatterns = { patterns: [] };
|
|
304
1206
|
return cachedIgnorePatterns;
|
|
@@ -341,29 +1243,198 @@ function filterIgnoredFiles(files, ignorePatterns) {
|
|
|
341
1243
|
return files.filter((file) => !isFileIgnored(file, ignorePatterns));
|
|
342
1244
|
}
|
|
343
1245
|
|
|
1246
|
+
// src/generators/rules/shared-helpers.ts
|
|
1247
|
+
function resolveOutputDir(config, tool, baseDir) {
|
|
1248
|
+
return baseDir ? join7(baseDir, config.outputPaths[tool]) : config.outputPaths[tool];
|
|
1249
|
+
}
|
|
1250
|
+
function createOutputsArray() {
|
|
1251
|
+
return [];
|
|
1252
|
+
}
|
|
1253
|
+
function addOutput(outputs, tool, config, baseDir, relativePath, content) {
|
|
1254
|
+
const outputDir = resolveOutputDir(config, tool, baseDir);
|
|
1255
|
+
outputs.push({
|
|
1256
|
+
tool,
|
|
1257
|
+
filepath: join7(outputDir, relativePath),
|
|
1258
|
+
content
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
async function generateRulesConfig(rules, config, generatorConfig, baseDir) {
|
|
1262
|
+
const outputs = [];
|
|
1263
|
+
for (const rule of rules) {
|
|
1264
|
+
const content = generatorConfig.generateContent(rule);
|
|
1265
|
+
const outputDir = resolveOutputDir(config, generatorConfig.tool, baseDir);
|
|
1266
|
+
const filepath = generatorConfig.pathResolver ? generatorConfig.pathResolver(rule, outputDir) : join7(outputDir, `${rule.filename}${generatorConfig.fileExtension}`);
|
|
1267
|
+
outputs.push({
|
|
1268
|
+
tool: generatorConfig.tool,
|
|
1269
|
+
filepath,
|
|
1270
|
+
content
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
1274
|
+
if (ignorePatterns.patterns.length > 0) {
|
|
1275
|
+
const ignorePath = baseDir ? join7(baseDir, generatorConfig.ignoreFileName) : generatorConfig.ignoreFileName;
|
|
1276
|
+
const ignoreContent = generateIgnoreFile(ignorePatterns.patterns, generatorConfig.tool);
|
|
1277
|
+
outputs.push({
|
|
1278
|
+
tool: generatorConfig.tool,
|
|
1279
|
+
filepath: ignorePath,
|
|
1280
|
+
content: ignoreContent
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
return outputs;
|
|
1284
|
+
}
|
|
1285
|
+
async function generateComplexRules(rules, config, generatorConfig, baseDir) {
|
|
1286
|
+
const outputs = [];
|
|
1287
|
+
const rootRules = rules.filter((r) => r.frontmatter.root === true);
|
|
1288
|
+
const detailRules = rules.filter((r) => r.frontmatter.root === false);
|
|
1289
|
+
const rootRule = rootRules[0];
|
|
1290
|
+
if (generatorConfig.generateDetailContent && generatorConfig.detailSubDir) {
|
|
1291
|
+
for (const rule of detailRules) {
|
|
1292
|
+
const content = generatorConfig.generateDetailContent(rule);
|
|
1293
|
+
const filepath = baseDir ? join7(baseDir, generatorConfig.detailSubDir, `${rule.filename}.md`) : join7(generatorConfig.detailSubDir, `${rule.filename}.md`);
|
|
1294
|
+
outputs.push({
|
|
1295
|
+
tool: generatorConfig.tool,
|
|
1296
|
+
filepath,
|
|
1297
|
+
content
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
if (generatorConfig.generateRootContent && generatorConfig.rootFilePath) {
|
|
1302
|
+
const rootContent = generatorConfig.generateRootContent(rootRule, detailRules, baseDir);
|
|
1303
|
+
const rootFilepath = baseDir ? join7(baseDir, generatorConfig.rootFilePath) : generatorConfig.rootFilePath;
|
|
1304
|
+
outputs.push({
|
|
1305
|
+
tool: generatorConfig.tool,
|
|
1306
|
+
filepath: rootFilepath,
|
|
1307
|
+
content: rootContent
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
1311
|
+
if (ignorePatterns.patterns.length > 0) {
|
|
1312
|
+
const ignorePath = baseDir ? join7(baseDir, generatorConfig.ignoreFileName) : generatorConfig.ignoreFileName;
|
|
1313
|
+
const ignoreContent = generateIgnoreFile(ignorePatterns.patterns, generatorConfig.tool);
|
|
1314
|
+
outputs.push({
|
|
1315
|
+
tool: generatorConfig.tool,
|
|
1316
|
+
filepath: ignorePath,
|
|
1317
|
+
content: ignoreContent
|
|
1318
|
+
});
|
|
1319
|
+
if (generatorConfig.updateAdditionalConfig) {
|
|
1320
|
+
const additionalOutputs = await generatorConfig.updateAdditionalConfig(
|
|
1321
|
+
ignorePatterns.patterns,
|
|
1322
|
+
baseDir
|
|
1323
|
+
);
|
|
1324
|
+
outputs.push(...additionalOutputs);
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
return outputs;
|
|
1328
|
+
}
|
|
1329
|
+
function generateIgnoreFile(patterns, tool) {
|
|
1330
|
+
const lines = [
|
|
1331
|
+
"# Generated by rulesync from .rulesyncignore",
|
|
1332
|
+
"# This file is automatically generated. Do not edit manually."
|
|
1333
|
+
];
|
|
1334
|
+
if (tool === "copilot") {
|
|
1335
|
+
lines.push("# Note: .copilotignore is not officially supported by GitHub Copilot.");
|
|
1336
|
+
lines.push("# This file is for use with community tools like copilotignore-vscode extension.");
|
|
1337
|
+
}
|
|
1338
|
+
lines.push("");
|
|
1339
|
+
lines.push(...patterns);
|
|
1340
|
+
return lines.join("\n");
|
|
1341
|
+
}
|
|
1342
|
+
async function generateComplexRulesConfig(rules, config, generatorConfig, baseDir) {
|
|
1343
|
+
const unifiedConfig = {
|
|
1344
|
+
tool: generatorConfig.tool,
|
|
1345
|
+
fileExtension: generatorConfig.fileExtension,
|
|
1346
|
+
ignoreFileName: generatorConfig.ignoreFileName,
|
|
1347
|
+
generateContent: generatorConfig.generateContent,
|
|
1348
|
+
pathResolver: generatorConfig.getOutputPath
|
|
1349
|
+
};
|
|
1350
|
+
return generateRulesConfig(rules, config, unifiedConfig, baseDir);
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// src/generators/rules/augmentcode.ts
|
|
1354
|
+
async function generateAugmentcodeConfig(rules, config, baseDir) {
|
|
1355
|
+
const outputs = createOutputsArray();
|
|
1356
|
+
rules.forEach((rule) => {
|
|
1357
|
+
addOutput(
|
|
1358
|
+
outputs,
|
|
1359
|
+
"augmentcode",
|
|
1360
|
+
config,
|
|
1361
|
+
baseDir,
|
|
1362
|
+
join8(".augment", "rules", `${rule.filename}.md`),
|
|
1363
|
+
generateRuleFile(rule)
|
|
1364
|
+
);
|
|
1365
|
+
});
|
|
1366
|
+
return outputs;
|
|
1367
|
+
}
|
|
1368
|
+
function generateRuleFile(rule) {
|
|
1369
|
+
const lines = [];
|
|
1370
|
+
lines.push("---");
|
|
1371
|
+
let ruleType = "manual";
|
|
1372
|
+
let description = rule.frontmatter.description;
|
|
1373
|
+
if (rule.filename.endsWith("-always")) {
|
|
1374
|
+
ruleType = "always";
|
|
1375
|
+
description = "";
|
|
1376
|
+
} else if (rule.filename.endsWith("-auto")) {
|
|
1377
|
+
ruleType = "auto";
|
|
1378
|
+
}
|
|
1379
|
+
lines.push(`type: ${ruleType}`);
|
|
1380
|
+
lines.push(`description: "${description}"`);
|
|
1381
|
+
if (rule.frontmatter.tags && Array.isArray(rule.frontmatter.tags) && rule.frontmatter.tags.length > 0) {
|
|
1382
|
+
lines.push(`tags: [${rule.frontmatter.tags.map((tag) => `"${tag}"`).join(", ")}]`);
|
|
1383
|
+
}
|
|
1384
|
+
lines.push("---");
|
|
1385
|
+
lines.push("");
|
|
1386
|
+
lines.push(rule.content.trim());
|
|
1387
|
+
return lines.join("\n");
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
// src/generators/rules/augmentcode-legacy.ts
|
|
1391
|
+
async function generateAugmentcodeLegacyConfig(rules, config, baseDir) {
|
|
1392
|
+
const outputs = createOutputsArray();
|
|
1393
|
+
if (rules.length > 0) {
|
|
1394
|
+
addOutput(
|
|
1395
|
+
outputs,
|
|
1396
|
+
"augmentcode-legacy",
|
|
1397
|
+
config,
|
|
1398
|
+
baseDir,
|
|
1399
|
+
".augment-guidelines",
|
|
1400
|
+
generateLegacyGuidelinesFile(rules)
|
|
1401
|
+
);
|
|
1402
|
+
}
|
|
1403
|
+
return outputs;
|
|
1404
|
+
}
|
|
1405
|
+
function generateLegacyGuidelinesFile(allRules) {
|
|
1406
|
+
const lines = [];
|
|
1407
|
+
for (const rule of allRules) {
|
|
1408
|
+
lines.push(rule.content.trim());
|
|
1409
|
+
lines.push("");
|
|
1410
|
+
}
|
|
1411
|
+
return lines.join("\n").trim();
|
|
1412
|
+
}
|
|
1413
|
+
|
|
344
1414
|
// src/generators/rules/claudecode.ts
|
|
1415
|
+
import { join as join9 } from "path";
|
|
345
1416
|
async function generateClaudecodeConfig(rules, config, baseDir) {
|
|
346
1417
|
const outputs = [];
|
|
347
1418
|
const rootRules = rules.filter((r) => r.frontmatter.root === true);
|
|
348
1419
|
const detailRules = rules.filter((r) => r.frontmatter.root === false);
|
|
349
1420
|
const claudeMdContent = generateClaudeMarkdown(rootRules, detailRules);
|
|
350
|
-
const claudeOutputDir = baseDir ?
|
|
1421
|
+
const claudeOutputDir = baseDir ? join9(baseDir, config.outputPaths.claudecode) : config.outputPaths.claudecode;
|
|
351
1422
|
outputs.push({
|
|
352
1423
|
tool: "claudecode",
|
|
353
|
-
filepath:
|
|
1424
|
+
filepath: join9(claudeOutputDir, "CLAUDE.md"),
|
|
354
1425
|
content: claudeMdContent
|
|
355
1426
|
});
|
|
356
1427
|
for (const rule of detailRules) {
|
|
357
1428
|
const memoryContent = generateMemoryFile(rule);
|
|
358
1429
|
outputs.push({
|
|
359
1430
|
tool: "claudecode",
|
|
360
|
-
filepath:
|
|
1431
|
+
filepath: join9(claudeOutputDir, ".claude", "memories", `${rule.filename}.md`),
|
|
361
1432
|
content: memoryContent
|
|
362
1433
|
});
|
|
363
1434
|
}
|
|
364
1435
|
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
365
1436
|
if (ignorePatterns.patterns.length > 0) {
|
|
366
|
-
const settingsPath = baseDir ?
|
|
1437
|
+
const settingsPath = baseDir ? join9(baseDir, ".claude", "settings.json") : join9(".claude", "settings.json");
|
|
367
1438
|
await updateClaudeSettings(settingsPath, ignorePatterns.patterns);
|
|
368
1439
|
}
|
|
369
1440
|
return outputs;
|
|
@@ -423,74 +1494,42 @@ async function updateClaudeSettings(settingsPath, ignorePatterns) {
|
|
|
423
1494
|
settings.permissions.deny = Array.from(new Set(filteredDeny));
|
|
424
1495
|
const jsonContent = JSON.stringify(settings, null, 2);
|
|
425
1496
|
await writeFileContent(settingsPath, jsonContent);
|
|
426
|
-
console.log(`\u2705 Updated Claude Code settings: ${settingsPath}`);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// src/generators/rules/cline.ts
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
}
|
|
443
|
-
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
444
|
-
if (ignorePatterns.patterns.length > 0) {
|
|
445
|
-
const clineIgnorePath = baseDir ? join6(baseDir, ".clineignore") : ".clineignore";
|
|
446
|
-
const clineIgnoreContent = generateClineIgnore(ignorePatterns.patterns);
|
|
447
|
-
outputs.push({
|
|
448
|
-
tool: "cline",
|
|
449
|
-
filepath: clineIgnorePath,
|
|
450
|
-
content: clineIgnoreContent
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
return outputs;
|
|
454
|
-
}
|
|
455
|
-
function generateClineMarkdown(rule) {
|
|
456
|
-
return rule.content.trim();
|
|
457
|
-
}
|
|
458
|
-
function generateClineIgnore(patterns) {
|
|
459
|
-
const lines = [
|
|
460
|
-
"# Generated by rulesync from .rulesyncignore",
|
|
461
|
-
"# This file is automatically generated. Do not edit manually.",
|
|
462
|
-
"",
|
|
463
|
-
...patterns
|
|
464
|
-
];
|
|
465
|
-
return lines.join("\n");
|
|
1497
|
+
console.log(`\u2705 Updated Claude Code settings: ${settingsPath}`);
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
// src/generators/rules/cline.ts
|
|
1501
|
+
async function generateClineConfig(rules, config, baseDir) {
|
|
1502
|
+
return generateRulesConfig(
|
|
1503
|
+
rules,
|
|
1504
|
+
config,
|
|
1505
|
+
{
|
|
1506
|
+
tool: "cline",
|
|
1507
|
+
fileExtension: ".md",
|
|
1508
|
+
ignoreFileName: ".clineignore",
|
|
1509
|
+
generateContent: (rule) => rule.content.trim()
|
|
1510
|
+
},
|
|
1511
|
+
baseDir
|
|
1512
|
+
);
|
|
466
1513
|
}
|
|
467
1514
|
|
|
468
1515
|
// src/generators/rules/copilot.ts
|
|
469
|
-
import { join as
|
|
1516
|
+
import { join as join10 } from "path";
|
|
470
1517
|
async function generateCopilotConfig(rules, config, baseDir) {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
const outputDir = baseDir ? join7(baseDir, config.outputPaths.copilot) : config.outputPaths.copilot;
|
|
476
|
-
const filepath = join7(outputDir, `${baseFilename}.instructions.md`);
|
|
477
|
-
outputs.push({
|
|
478
|
-
tool: "copilot",
|
|
479
|
-
filepath,
|
|
480
|
-
content
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
484
|
-
if (ignorePatterns.patterns.length > 0) {
|
|
485
|
-
const copilotIgnorePath = baseDir ? join7(baseDir, ".copilotignore") : ".copilotignore";
|
|
486
|
-
const copilotIgnoreContent = generateCopilotIgnore(ignorePatterns.patterns);
|
|
487
|
-
outputs.push({
|
|
1518
|
+
return generateComplexRulesConfig(
|
|
1519
|
+
rules,
|
|
1520
|
+
config,
|
|
1521
|
+
{
|
|
488
1522
|
tool: "copilot",
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
1523
|
+
fileExtension: ".instructions.md",
|
|
1524
|
+
ignoreFileName: ".copilotignore",
|
|
1525
|
+
generateContent: generateCopilotMarkdown,
|
|
1526
|
+
getOutputPath: (rule, outputDir) => {
|
|
1527
|
+
const baseFilename = rule.filename.replace(/\.md$/, "");
|
|
1528
|
+
return join10(outputDir, `${baseFilename}.instructions.md`);
|
|
1529
|
+
}
|
|
1530
|
+
},
|
|
1531
|
+
baseDir
|
|
1532
|
+
);
|
|
494
1533
|
}
|
|
495
1534
|
function generateCopilotMarkdown(rule) {
|
|
496
1535
|
const lines = [];
|
|
@@ -505,43 +1544,24 @@ function generateCopilotMarkdown(rule) {
|
|
|
505
1544
|
lines.push(rule.content);
|
|
506
1545
|
return lines.join("\n");
|
|
507
1546
|
}
|
|
508
|
-
function generateCopilotIgnore(patterns) {
|
|
509
|
-
const lines = [
|
|
510
|
-
"# Generated by rulesync from .rulesyncignore",
|
|
511
|
-
"# This file is automatically generated. Do not edit manually.",
|
|
512
|
-
"# Note: .copilotignore is not officially supported by GitHub Copilot.",
|
|
513
|
-
"# This file is for use with community tools like copilotignore-vscode extension.",
|
|
514
|
-
"",
|
|
515
|
-
...patterns
|
|
516
|
-
];
|
|
517
|
-
return lines.join("\n");
|
|
518
|
-
}
|
|
519
1547
|
|
|
520
1548
|
// src/generators/rules/cursor.ts
|
|
521
|
-
import { join as
|
|
1549
|
+
import { join as join11 } from "path";
|
|
522
1550
|
async function generateCursorConfig(rules, config, baseDir) {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
const filepath = join8(outputDir, `${rule.filename}.mdc`);
|
|
528
|
-
outputs.push({
|
|
529
|
-
tool: "cursor",
|
|
530
|
-
filepath,
|
|
531
|
-
content
|
|
532
|
-
});
|
|
533
|
-
}
|
|
534
|
-
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
535
|
-
if (ignorePatterns.patterns.length > 0) {
|
|
536
|
-
const cursorIgnorePath = baseDir ? join8(baseDir, ".cursorignore") : ".cursorignore";
|
|
537
|
-
const cursorIgnoreContent = generateCursorIgnore(ignorePatterns.patterns);
|
|
538
|
-
outputs.push({
|
|
1551
|
+
return generateComplexRulesConfig(
|
|
1552
|
+
rules,
|
|
1553
|
+
config,
|
|
1554
|
+
{
|
|
539
1555
|
tool: "cursor",
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
1556
|
+
fileExtension: ".mdc",
|
|
1557
|
+
ignoreFileName: ".cursorignore",
|
|
1558
|
+
generateContent: generateCursorMarkdown,
|
|
1559
|
+
getOutputPath: (rule, outputDir) => {
|
|
1560
|
+
return join11(outputDir, `${rule.filename}.mdc`);
|
|
1561
|
+
}
|
|
1562
|
+
},
|
|
1563
|
+
baseDir
|
|
1564
|
+
);
|
|
545
1565
|
}
|
|
546
1566
|
function generateCursorMarkdown(rule) {
|
|
547
1567
|
const lines = [];
|
|
@@ -595,50 +1615,20 @@ function determineCursorRuleType(frontmatter) {
|
|
|
595
1615
|
}
|
|
596
1616
|
return "intelligently";
|
|
597
1617
|
}
|
|
598
|
-
function generateCursorIgnore(patterns) {
|
|
599
|
-
const lines = [
|
|
600
|
-
"# Generated by rulesync from .rulesyncignore",
|
|
601
|
-
"# This file is automatically generated. Do not edit manually.",
|
|
602
|
-
"",
|
|
603
|
-
...patterns
|
|
604
|
-
];
|
|
605
|
-
return lines.join("\n");
|
|
606
|
-
}
|
|
607
1618
|
|
|
608
1619
|
// src/generators/rules/geminicli.ts
|
|
609
|
-
import { join as join9 } from "path";
|
|
610
1620
|
async function generateGeminiConfig(rules, config, baseDir) {
|
|
611
|
-
const
|
|
612
|
-
const rootRule = rules.find((rule) => rule.frontmatter.root === true);
|
|
613
|
-
const memoryRules = rules.filter((rule) => rule.frontmatter.root === false);
|
|
614
|
-
for (const rule of memoryRules) {
|
|
615
|
-
const content = generateGeminiMemoryMarkdown(rule);
|
|
616
|
-
const outputDir = baseDir ? join9(baseDir, config.outputPaths.geminicli) : config.outputPaths.geminicli;
|
|
617
|
-
const filepath = join9(outputDir, `${rule.filename}.md`);
|
|
618
|
-
outputs.push({
|
|
619
|
-
tool: "geminicli",
|
|
620
|
-
filepath,
|
|
621
|
-
content
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
const rootContent = generateGeminiRootMarkdown(rootRule, memoryRules, baseDir);
|
|
625
|
-
const rootFilepath = baseDir ? join9(baseDir, "GEMINI.md") : "GEMINI.md";
|
|
626
|
-
outputs.push({
|
|
1621
|
+
const generatorConfig = {
|
|
627
1622
|
tool: "geminicli",
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
filepath: aiexcludePath,
|
|
638
|
-
content: aiexcludeContent
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
return outputs;
|
|
1623
|
+
fileExtension: ".md",
|
|
1624
|
+
ignoreFileName: ".aiexclude",
|
|
1625
|
+
generateContent: generateGeminiMemoryMarkdown,
|
|
1626
|
+
generateDetailContent: generateGeminiMemoryMarkdown,
|
|
1627
|
+
generateRootContent: generateGeminiRootMarkdown,
|
|
1628
|
+
rootFilePath: "GEMINI.md",
|
|
1629
|
+
detailSubDir: ".gemini/memories"
|
|
1630
|
+
};
|
|
1631
|
+
return generateComplexRules(rules, config, generatorConfig, baseDir);
|
|
642
1632
|
}
|
|
643
1633
|
function generateGeminiMemoryMarkdown(rule) {
|
|
644
1634
|
return rule.content.trim();
|
|
@@ -667,24 +1657,42 @@ function generateGeminiRootMarkdown(rootRule, memoryRules, _baseDir) {
|
|
|
667
1657
|
}
|
|
668
1658
|
return lines.join("\n");
|
|
669
1659
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
"",
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
1660
|
+
|
|
1661
|
+
// src/generators/rules/junie.ts
|
|
1662
|
+
async function generateJunieConfig(rules, config, baseDir) {
|
|
1663
|
+
const generatorConfig = {
|
|
1664
|
+
tool: "junie",
|
|
1665
|
+
fileExtension: ".md",
|
|
1666
|
+
ignoreFileName: ".aiignore",
|
|
1667
|
+
generateContent: (rule) => rule.content.trim(),
|
|
1668
|
+
generateRootContent: generateGuidelinesMarkdown,
|
|
1669
|
+
rootFilePath: ".junie/guidelines.md"
|
|
1670
|
+
};
|
|
1671
|
+
return generateComplexRules(rules, config, generatorConfig, baseDir);
|
|
1672
|
+
}
|
|
1673
|
+
function generateGuidelinesMarkdown(rootRule, detailRules) {
|
|
1674
|
+
const lines = [];
|
|
1675
|
+
if (rootRule) {
|
|
1676
|
+
lines.push(rootRule.content);
|
|
1677
|
+
lines.push("");
|
|
1678
|
+
}
|
|
1679
|
+
if (detailRules.length > 0) {
|
|
1680
|
+
for (const rule of detailRules) {
|
|
1681
|
+
lines.push(rule.content);
|
|
1682
|
+
lines.push("");
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
return lines.join("\n").trim();
|
|
678
1686
|
}
|
|
679
1687
|
|
|
680
1688
|
// src/generators/rules/kiro.ts
|
|
681
|
-
import { join as
|
|
1689
|
+
import { join as join12 } from "path";
|
|
682
1690
|
async function generateKiroConfig(rules, config, baseDir) {
|
|
683
1691
|
const outputs = [];
|
|
684
1692
|
for (const rule of rules) {
|
|
685
1693
|
const content = generateKiroMarkdown(rule);
|
|
686
|
-
const outputDir = baseDir ?
|
|
687
|
-
const filepath =
|
|
1694
|
+
const outputDir = baseDir ? join12(baseDir, config.outputPaths.kiro) : config.outputPaths.kiro;
|
|
1695
|
+
const filepath = join12(outputDir, `${rule.filename}.md`);
|
|
688
1696
|
outputs.push({
|
|
689
1697
|
tool: "kiro",
|
|
690
1698
|
filepath,
|
|
@@ -698,47 +1706,23 @@ function generateKiroMarkdown(rule) {
|
|
|
698
1706
|
}
|
|
699
1707
|
|
|
700
1708
|
// src/generators/rules/roo.ts
|
|
701
|
-
import { join as join11 } from "path";
|
|
702
1709
|
async function generateRooConfig(rules, config, baseDir) {
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
const filepath = join11(outputDir, `${rule.filename}.md`);
|
|
708
|
-
outputs.push({
|
|
709
|
-
tool: "roo",
|
|
710
|
-
filepath,
|
|
711
|
-
content
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
|
-
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
715
|
-
if (ignorePatterns.patterns.length > 0) {
|
|
716
|
-
const rooIgnorePath = baseDir ? join11(baseDir, ".rooignore") : ".rooignore";
|
|
717
|
-
const rooIgnoreContent = generateRooIgnore(ignorePatterns.patterns);
|
|
718
|
-
outputs.push({
|
|
1710
|
+
return generateRulesConfig(
|
|
1711
|
+
rules,
|
|
1712
|
+
config,
|
|
1713
|
+
{
|
|
719
1714
|
tool: "roo",
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
function generateRooMarkdown(rule) {
|
|
727
|
-
return rule.content.trim();
|
|
728
|
-
}
|
|
729
|
-
function generateRooIgnore(patterns) {
|
|
730
|
-
const lines = [
|
|
731
|
-
"# Generated by rulesync from .rulesyncignore",
|
|
732
|
-
"# This file is automatically generated. Do not edit manually.",
|
|
733
|
-
"",
|
|
734
|
-
...patterns
|
|
735
|
-
];
|
|
736
|
-
return lines.join("\n");
|
|
1715
|
+
fileExtension: ".md",
|
|
1716
|
+
ignoreFileName: ".rooignore",
|
|
1717
|
+
generateContent: (rule) => rule.content.trim()
|
|
1718
|
+
},
|
|
1719
|
+
baseDir
|
|
1720
|
+
);
|
|
737
1721
|
}
|
|
738
1722
|
|
|
739
1723
|
// src/core/generator.ts
|
|
740
1724
|
async function generateConfigurations(rules, config, targetTools, baseDir) {
|
|
741
|
-
const outputs =
|
|
1725
|
+
const outputs = createOutputsArray();
|
|
742
1726
|
const toolsToGenerate = targetTools || config.defaultTargets;
|
|
743
1727
|
const rootFiles = rules.filter((rule) => rule.frontmatter.root === true);
|
|
744
1728
|
if (rootFiles.length === 0) {
|
|
@@ -762,11 +1746,28 @@ async function generateConfigurations(rules, config, targetTools, baseDir) {
|
|
|
762
1746
|
function filterRulesForTool(rules, tool, config) {
|
|
763
1747
|
return rules.filter((rule) => {
|
|
764
1748
|
const targets = resolveTargets(rule.frontmatter.targets, config);
|
|
765
|
-
|
|
1749
|
+
if (!targets.includes(tool)) {
|
|
1750
|
+
return false;
|
|
1751
|
+
}
|
|
1752
|
+
return isToolSpecificRule(rule, tool);
|
|
766
1753
|
});
|
|
767
1754
|
}
|
|
768
1755
|
async function generateForTool(tool, rules, config, baseDir) {
|
|
769
1756
|
switch (tool) {
|
|
1757
|
+
case "augmentcode": {
|
|
1758
|
+
const augmentRulesOutputs = await generateAugmentcodeConfig(rules, config, baseDir);
|
|
1759
|
+
const augmentIgnoreOutputs = await generateAugmentCodeIgnoreFiles(rules, config, baseDir);
|
|
1760
|
+
return [...augmentRulesOutputs, ...augmentIgnoreOutputs];
|
|
1761
|
+
}
|
|
1762
|
+
case "augmentcode-legacy": {
|
|
1763
|
+
const augmentLegacyRulesOutputs = await generateAugmentcodeLegacyConfig(
|
|
1764
|
+
rules,
|
|
1765
|
+
config,
|
|
1766
|
+
baseDir
|
|
1767
|
+
);
|
|
1768
|
+
const augmentIgnoreOutputs = await generateAugmentCodeIgnoreFiles(rules, config, baseDir);
|
|
1769
|
+
return [...augmentLegacyRulesOutputs, ...augmentIgnoreOutputs];
|
|
1770
|
+
}
|
|
770
1771
|
case "copilot":
|
|
771
1772
|
return generateCopilotConfig(rules, config, baseDir);
|
|
772
1773
|
case "cursor":
|
|
@@ -779,6 +1780,11 @@ async function generateForTool(tool, rules, config, baseDir) {
|
|
|
779
1780
|
return generateRooConfig(rules, config, baseDir);
|
|
780
1781
|
case "geminicli":
|
|
781
1782
|
return generateGeminiConfig(rules, config, baseDir);
|
|
1783
|
+
case "junie": {
|
|
1784
|
+
const junieRulesOutputs = await generateJunieConfig(rules, config, baseDir);
|
|
1785
|
+
const junieIgnoreOutputs = await generateJunieIgnoreFiles(rules, config, baseDir);
|
|
1786
|
+
return [...junieRulesOutputs, ...junieIgnoreOutputs];
|
|
1787
|
+
}
|
|
782
1788
|
case "kiro": {
|
|
783
1789
|
const kiroRulesOutputs = await generateKiroConfig(rules, config, baseDir);
|
|
784
1790
|
const kiroIgnoreOutputs = await generateKiroIgnoreFiles(rules, config, baseDir);
|
|
@@ -793,74 +1799,6 @@ async function generateForTool(tool, rules, config, baseDir) {
|
|
|
793
1799
|
// src/core/parser.ts
|
|
794
1800
|
import { basename } from "path";
|
|
795
1801
|
import matter from "gray-matter";
|
|
796
|
-
|
|
797
|
-
// src/types/config.ts
|
|
798
|
-
import { z as z2 } from "zod/mini";
|
|
799
|
-
var ConfigSchema = z2.object({
|
|
800
|
-
aiRulesDir: z2.string(),
|
|
801
|
-
outputPaths: z2.record(ToolTargetSchema, z2.string()),
|
|
802
|
-
watchEnabled: z2.boolean(),
|
|
803
|
-
defaultTargets: ToolTargetsSchema
|
|
804
|
-
});
|
|
805
|
-
|
|
806
|
-
// src/types/mcp.ts
|
|
807
|
-
import { z as z3 } from "zod/mini";
|
|
808
|
-
var McpTransportTypeSchema = z3.enum(["stdio", "sse", "http"]);
|
|
809
|
-
var McpServerBaseSchema = z3.object({
|
|
810
|
-
command: z3.optional(z3.string()),
|
|
811
|
-
args: z3.optional(z3.array(z3.string())),
|
|
812
|
-
url: z3.optional(z3.string()),
|
|
813
|
-
httpUrl: z3.optional(z3.string()),
|
|
814
|
-
env: z3.optional(z3.record(z3.string(), z3.string())),
|
|
815
|
-
disabled: z3.optional(z3.boolean()),
|
|
816
|
-
networkTimeout: z3.optional(z3.number()),
|
|
817
|
-
timeout: z3.optional(z3.number()),
|
|
818
|
-
trust: z3.optional(z3.boolean()),
|
|
819
|
-
cwd: z3.optional(z3.string()),
|
|
820
|
-
transport: z3.optional(McpTransportTypeSchema),
|
|
821
|
-
type: z3.optional(z3.enum(["sse", "streamable-http"])),
|
|
822
|
-
alwaysAllow: z3.optional(z3.array(z3.string())),
|
|
823
|
-
tools: z3.optional(z3.array(z3.string())),
|
|
824
|
-
kiroAutoApprove: z3.optional(z3.array(z3.string())),
|
|
825
|
-
kiroAutoBlock: z3.optional(z3.array(z3.string()))
|
|
826
|
-
});
|
|
827
|
-
var RulesyncMcpServerSchema = z3.extend(McpServerBaseSchema, {
|
|
828
|
-
targets: z3.optional(RulesyncTargetsSchema)
|
|
829
|
-
});
|
|
830
|
-
var McpConfigSchema = z3.object({
|
|
831
|
-
mcpServers: z3.record(z3.string(), McpServerBaseSchema)
|
|
832
|
-
});
|
|
833
|
-
var RulesyncMcpConfigSchema = z3.object({
|
|
834
|
-
mcpServers: z3.record(z3.string(), RulesyncMcpServerSchema)
|
|
835
|
-
});
|
|
836
|
-
|
|
837
|
-
// src/types/rules.ts
|
|
838
|
-
import { z as z4 } from "zod/mini";
|
|
839
|
-
var RuleFrontmatterSchema = z4.object({
|
|
840
|
-
root: z4.boolean(),
|
|
841
|
-
targets: RulesyncTargetsSchema,
|
|
842
|
-
description: z4.string(),
|
|
843
|
-
globs: z4.array(z4.string()),
|
|
844
|
-
cursorRuleType: z4.optional(z4.enum(["always", "manual", "specificFiles", "intelligently"]))
|
|
845
|
-
});
|
|
846
|
-
var ParsedRuleSchema = z4.object({
|
|
847
|
-
frontmatter: RuleFrontmatterSchema,
|
|
848
|
-
content: z4.string(),
|
|
849
|
-
filename: z4.string(),
|
|
850
|
-
filepath: z4.string()
|
|
851
|
-
});
|
|
852
|
-
var GeneratedOutputSchema = z4.object({
|
|
853
|
-
tool: ToolTargetSchema,
|
|
854
|
-
filepath: z4.string(),
|
|
855
|
-
content: z4.string()
|
|
856
|
-
});
|
|
857
|
-
var GenerateOptionsSchema = z4.object({
|
|
858
|
-
targetTools: z4.optional(ToolTargetsSchema),
|
|
859
|
-
outputDir: z4.optional(z4.string()),
|
|
860
|
-
watch: z4.optional(z4.boolean())
|
|
861
|
-
});
|
|
862
|
-
|
|
863
|
-
// src/core/parser.ts
|
|
864
1802
|
async function parseRulesFromDirectory(aiRulesDir) {
|
|
865
1803
|
const ignorePatterns = await loadIgnorePatterns();
|
|
866
1804
|
const allRuleFiles = await findFiles(aiRulesDir, ".md");
|
|
@@ -962,13 +1900,13 @@ async function validateRule(rule) {
|
|
|
962
1900
|
}
|
|
963
1901
|
|
|
964
1902
|
// src/core/mcp-generator.ts
|
|
965
|
-
import * as
|
|
1903
|
+
import * as path4 from "path";
|
|
966
1904
|
|
|
967
1905
|
// src/core/mcp-parser.ts
|
|
968
1906
|
import * as fs from "fs";
|
|
969
|
-
import * as
|
|
1907
|
+
import * as path3 from "path";
|
|
970
1908
|
function parseMcpConfig(projectRoot) {
|
|
971
|
-
const mcpPath =
|
|
1909
|
+
const mcpPath = path3.join(projectRoot, ".rulesync", ".mcp.json");
|
|
972
1910
|
if (!fs.existsSync(mcpPath)) {
|
|
973
1911
|
return null;
|
|
974
1912
|
}
|
|
@@ -992,7 +1930,7 @@ function parseMcpConfig(projectRoot) {
|
|
|
992
1930
|
}
|
|
993
1931
|
|
|
994
1932
|
// src/core/mcp-generator.ts
|
|
995
|
-
async function generateMcpConfigs(projectRoot, baseDir) {
|
|
1933
|
+
async function generateMcpConfigs(projectRoot, baseDir, targetTools) {
|
|
996
1934
|
const results = [];
|
|
997
1935
|
const targetRoot = baseDir || projectRoot;
|
|
998
1936
|
const config = parseMcpConfig(projectRoot);
|
|
@@ -1000,47 +1938,72 @@ async function generateMcpConfigs(projectRoot, baseDir) {
|
|
|
1000
1938
|
return results;
|
|
1001
1939
|
}
|
|
1002
1940
|
const generators = [
|
|
1941
|
+
{
|
|
1942
|
+
tool: "augmentcode-project",
|
|
1943
|
+
path: path4.join(targetRoot, ".mcp.json"),
|
|
1944
|
+
generate: () => generateAugmentcodeMcp(config)
|
|
1945
|
+
},
|
|
1946
|
+
{
|
|
1947
|
+
tool: "augmentcode-legacy-project",
|
|
1948
|
+
path: path4.join(targetRoot, ".mcp.json"),
|
|
1949
|
+
generate: () => generateAugmentcodeMcp(config)
|
|
1950
|
+
},
|
|
1003
1951
|
{
|
|
1004
1952
|
tool: "claude-project",
|
|
1005
|
-
path:
|
|
1953
|
+
path: path4.join(targetRoot, ".mcp.json"),
|
|
1006
1954
|
generate: () => generateClaudeMcp(config)
|
|
1007
1955
|
},
|
|
1008
1956
|
{
|
|
1009
1957
|
tool: "copilot-editor",
|
|
1010
|
-
path:
|
|
1958
|
+
path: path4.join(targetRoot, ".vscode", "mcp.json"),
|
|
1011
1959
|
generate: () => generateCopilotMcp(config, "editor")
|
|
1012
1960
|
},
|
|
1013
1961
|
{
|
|
1014
1962
|
tool: "cursor-project",
|
|
1015
|
-
path:
|
|
1963
|
+
path: path4.join(targetRoot, ".cursor", "mcp.json"),
|
|
1016
1964
|
generate: () => generateCursorMcp(config)
|
|
1017
1965
|
},
|
|
1018
1966
|
{
|
|
1019
1967
|
tool: "cline-project",
|
|
1020
|
-
path:
|
|
1968
|
+
path: path4.join(targetRoot, ".cline", "mcp.json"),
|
|
1021
1969
|
generate: () => generateClineMcp(config)
|
|
1022
1970
|
},
|
|
1023
1971
|
{
|
|
1024
1972
|
tool: "gemini-project",
|
|
1025
|
-
path:
|
|
1973
|
+
path: path4.join(targetRoot, ".gemini", "settings.json"),
|
|
1026
1974
|
generate: () => generateGeminiCliMcp(config)
|
|
1027
1975
|
},
|
|
1976
|
+
{
|
|
1977
|
+
tool: "junie-project",
|
|
1978
|
+
path: path4.join(targetRoot, ".junie", "mcp-config.json"),
|
|
1979
|
+
generate: () => generateJunieMcp(config)
|
|
1980
|
+
},
|
|
1028
1981
|
{
|
|
1029
1982
|
tool: "kiro-project",
|
|
1030
|
-
path:
|
|
1983
|
+
path: path4.join(targetRoot, ".kiro", "mcp.json"),
|
|
1031
1984
|
generate: () => generateKiroMcp(config)
|
|
1032
1985
|
},
|
|
1033
1986
|
{
|
|
1034
1987
|
tool: "roo-project",
|
|
1035
|
-
path:
|
|
1988
|
+
path: path4.join(targetRoot, ".roo", "mcp.json"),
|
|
1036
1989
|
generate: () => generateRooMcp(config)
|
|
1037
1990
|
}
|
|
1038
1991
|
];
|
|
1039
|
-
|
|
1992
|
+
const filteredGenerators = targetTools ? generators.filter((g) => {
|
|
1993
|
+
const baseTool = g.tool.split("-")[0];
|
|
1994
|
+
if (!isToolTarget(baseTool)) {
|
|
1995
|
+
return false;
|
|
1996
|
+
}
|
|
1997
|
+
if (baseTool === "augmentcode") {
|
|
1998
|
+
return targetTools.includes("augmentcode") || targetTools.includes("augmentcode-legacy");
|
|
1999
|
+
}
|
|
2000
|
+
return targetTools.includes(baseTool);
|
|
2001
|
+
}) : generators;
|
|
2002
|
+
for (const generator of filteredGenerators) {
|
|
1040
2003
|
try {
|
|
1041
2004
|
const content = generator.generate();
|
|
1042
2005
|
const parsed = JSON.parse(content);
|
|
1043
|
-
if (generator.tool.includes("claude") || generator.tool.includes("cline") || generator.tool.includes("cursor") || generator.tool.includes("gemini") || generator.tool.includes("kiro") || generator.tool.includes("roo")) {
|
|
2006
|
+
if (generator.tool.includes("augmentcode") || generator.tool.includes("claude") || generator.tool.includes("cline") || generator.tool.includes("cursor") || generator.tool.includes("gemini") || generator.tool.includes("junie") || generator.tool.includes("kiro") || generator.tool.includes("roo")) {
|
|
1044
2007
|
if (!parsed.mcpServers || Object.keys(parsed.mcpServers).length === 0) {
|
|
1045
2008
|
results.push({
|
|
1046
2009
|
tool: generator.tool,
|
|
@@ -1080,15 +2043,58 @@ async function generateMcpConfigs(projectRoot, baseDir) {
|
|
|
1080
2043
|
|
|
1081
2044
|
// src/cli/commands/generate.ts
|
|
1082
2045
|
async function generateCommand(options = {}) {
|
|
1083
|
-
const
|
|
1084
|
-
|
|
2046
|
+
const configLoaderOptions = {
|
|
2047
|
+
...options.config !== void 0 && { configPath: options.config },
|
|
2048
|
+
...options.noConfig !== void 0 && { noConfig: options.noConfig }
|
|
2049
|
+
};
|
|
2050
|
+
const configResult = await loadConfig(configLoaderOptions);
|
|
2051
|
+
const cliOptions = {
|
|
2052
|
+
...options.tools !== void 0 && { tools: options.tools },
|
|
2053
|
+
...options.verbose !== void 0 && { verbose: options.verbose },
|
|
2054
|
+
...options.delete !== void 0 && { delete: options.delete },
|
|
2055
|
+
...options.baseDirs !== void 0 && { baseDirs: options.baseDirs }
|
|
2056
|
+
};
|
|
2057
|
+
const config = mergeWithCliOptions(configResult.config, cliOptions);
|
|
2058
|
+
if (options.tools && options.tools.length > 0) {
|
|
2059
|
+
const configTargets = config.defaultTargets;
|
|
2060
|
+
const cliTools = options.tools;
|
|
2061
|
+
const cliToolsSet = new Set(cliTools);
|
|
2062
|
+
const configTargetsSet = new Set(configTargets);
|
|
2063
|
+
const notInConfig = cliTools.filter((tool) => !configTargetsSet.has(tool));
|
|
2064
|
+
const notInCli = configTargets.filter((tool) => !cliToolsSet.has(tool));
|
|
2065
|
+
if (notInConfig.length > 0 || notInCli.length > 0) {
|
|
2066
|
+
console.warn("\u26A0\uFE0F Warning: CLI tool selection differs from configuration!");
|
|
2067
|
+
console.warn(` Config targets: ${configTargets.join(", ")}`);
|
|
2068
|
+
console.warn(` CLI specified: ${cliTools.join(", ")}`);
|
|
2069
|
+
if (notInConfig.length > 0) {
|
|
2070
|
+
console.warn(` Tools specified but not in config: ${notInConfig.join(", ")}`);
|
|
2071
|
+
}
|
|
2072
|
+
if (notInCli.length > 0) {
|
|
2073
|
+
console.warn(` Tools in config but not specified: ${notInCli.join(", ")}`);
|
|
2074
|
+
}
|
|
2075
|
+
console.warn("\n The configuration file targets will be used.");
|
|
2076
|
+
console.warn(" To change targets, update your rulesync config file.");
|
|
2077
|
+
console.warn("");
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
let baseDirs;
|
|
2081
|
+
if (config.baseDir) {
|
|
2082
|
+
baseDirs = Array.isArray(config.baseDir) ? config.baseDir : [config.baseDir];
|
|
2083
|
+
} else if (options.baseDirs) {
|
|
2084
|
+
baseDirs = options.baseDirs;
|
|
2085
|
+
} else {
|
|
2086
|
+
baseDirs = [process.cwd()];
|
|
2087
|
+
}
|
|
2088
|
+
if (config.verbose && configResult.filepath) {
|
|
2089
|
+
console.log(`Loaded configuration from: ${configResult.filepath}`);
|
|
2090
|
+
}
|
|
1085
2091
|
console.log("Generating configuration files...");
|
|
1086
2092
|
if (!await fileExists(config.aiRulesDir)) {
|
|
1087
2093
|
console.error("\u274C .rulesync directory not found. Run 'rulesync init' first.");
|
|
1088
2094
|
process.exit(1);
|
|
1089
2095
|
}
|
|
1090
2096
|
try {
|
|
1091
|
-
if (
|
|
2097
|
+
if (config.verbose) {
|
|
1092
2098
|
console.log(`Parsing rules from ${config.aiRulesDir}...`);
|
|
1093
2099
|
}
|
|
1094
2100
|
const rules = await parseRulesFromDirectory(config.aiRulesDir);
|
|
@@ -1096,18 +2102,26 @@ async function generateCommand(options = {}) {
|
|
|
1096
2102
|
console.warn("\u26A0\uFE0F No rules found in .rulesync directory");
|
|
1097
2103
|
return;
|
|
1098
2104
|
}
|
|
1099
|
-
if (
|
|
2105
|
+
if (config.verbose) {
|
|
1100
2106
|
console.log(`Found ${rules.length} rule(s)`);
|
|
1101
2107
|
console.log(`Base directories: ${baseDirs.join(", ")}`);
|
|
1102
2108
|
}
|
|
1103
|
-
if (
|
|
1104
|
-
if (
|
|
2109
|
+
if (config.delete) {
|
|
2110
|
+
if (config.verbose) {
|
|
1105
2111
|
console.log("Deleting existing output directories...");
|
|
1106
2112
|
}
|
|
1107
|
-
const targetTools =
|
|
2113
|
+
const targetTools = config.defaultTargets;
|
|
1108
2114
|
const deleteTasks = [];
|
|
1109
2115
|
for (const tool of targetTools) {
|
|
1110
2116
|
switch (tool) {
|
|
2117
|
+
case "augmentcode":
|
|
2118
|
+
deleteTasks.push(removeDirectory(join15(".augment", "rules")));
|
|
2119
|
+
deleteTasks.push(removeDirectory(join15(".augment", "ignore")));
|
|
2120
|
+
break;
|
|
2121
|
+
case "augmentcode-legacy":
|
|
2122
|
+
deleteTasks.push(removeClaudeGeneratedFiles());
|
|
2123
|
+
deleteTasks.push(removeDirectory(join15(".augment", "ignore")));
|
|
2124
|
+
break;
|
|
1111
2125
|
case "copilot":
|
|
1112
2126
|
deleteTasks.push(removeDirectory(config.outputPaths.copilot));
|
|
1113
2127
|
break;
|
|
@@ -1132,19 +2146,19 @@ async function generateCommand(options = {}) {
|
|
|
1132
2146
|
}
|
|
1133
2147
|
}
|
|
1134
2148
|
await Promise.all(deleteTasks);
|
|
1135
|
-
if (
|
|
2149
|
+
if (config.verbose) {
|
|
1136
2150
|
console.log("Deleted existing output directories");
|
|
1137
2151
|
}
|
|
1138
2152
|
}
|
|
1139
2153
|
let totalOutputs = 0;
|
|
1140
2154
|
for (const baseDir of baseDirs) {
|
|
1141
|
-
if (
|
|
2155
|
+
if (config.verbose) {
|
|
1142
2156
|
console.log(`
|
|
1143
2157
|
Generating configurations for base directory: ${baseDir}`);
|
|
1144
2158
|
}
|
|
1145
|
-
const outputs = await generateConfigurations(rules, config,
|
|
2159
|
+
const outputs = await generateConfigurations(rules, config, config.defaultTargets, baseDir);
|
|
1146
2160
|
if (outputs.length === 0) {
|
|
1147
|
-
if (
|
|
2161
|
+
if (config.verbose) {
|
|
1148
2162
|
console.warn(`\u26A0\uFE0F No configurations generated for ${baseDir}`);
|
|
1149
2163
|
}
|
|
1150
2164
|
continue;
|
|
@@ -1159,17 +2173,18 @@ Generating configurations for base directory: ${baseDir}`);
|
|
|
1159
2173
|
console.warn("\u26A0\uFE0F No configurations generated");
|
|
1160
2174
|
return;
|
|
1161
2175
|
}
|
|
1162
|
-
if (
|
|
2176
|
+
if (config.verbose) {
|
|
1163
2177
|
console.log("\nGenerating MCP configurations...");
|
|
1164
2178
|
}
|
|
1165
2179
|
let totalMcpOutputs = 0;
|
|
1166
2180
|
for (const baseDir of baseDirs) {
|
|
1167
2181
|
const mcpResults = await generateMcpConfigs(
|
|
1168
2182
|
process.cwd(),
|
|
1169
|
-
baseDir === process.cwd() ? void 0 : baseDir
|
|
2183
|
+
baseDir === process.cwd() ? void 0 : baseDir,
|
|
2184
|
+
config.defaultTargets
|
|
1170
2185
|
);
|
|
1171
2186
|
if (mcpResults.length === 0) {
|
|
1172
|
-
if (
|
|
2187
|
+
if (config.verbose) {
|
|
1173
2188
|
console.log(`No MCP configuration found for ${baseDir}`);
|
|
1174
2189
|
}
|
|
1175
2190
|
continue;
|
|
@@ -1180,7 +2195,7 @@ Generating configurations for base directory: ${baseDir}`);
|
|
|
1180
2195
|
totalMcpOutputs++;
|
|
1181
2196
|
} else if (result.status === "error") {
|
|
1182
2197
|
console.error(`\u274C Failed to generate ${result.tool} MCP configuration: ${result.error}`);
|
|
1183
|
-
} else if (
|
|
2198
|
+
} else if (config.verbose && result.status === "skipped") {
|
|
1184
2199
|
console.log(`\u23ED\uFE0F Skipped ${result.tool} MCP configuration (no servers configured)`);
|
|
1185
2200
|
}
|
|
1186
2201
|
}
|
|
@@ -1199,10 +2214,10 @@ Generating configurations for base directory: ${baseDir}`);
|
|
|
1199
2214
|
}
|
|
1200
2215
|
|
|
1201
2216
|
// src/cli/commands/gitignore.ts
|
|
1202
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
1203
|
-
import { join as
|
|
2217
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
2218
|
+
import { join as join16 } from "path";
|
|
1204
2219
|
var gitignoreCommand = async () => {
|
|
1205
|
-
const gitignorePath =
|
|
2220
|
+
const gitignorePath = join16(process.cwd(), ".gitignore");
|
|
1206
2221
|
const rulesFilesToIgnore = [
|
|
1207
2222
|
"# Generated by rulesync - AI tool configuration files",
|
|
1208
2223
|
"**/.github/copilot-instructions.md",
|
|
@@ -1220,7 +2235,12 @@ var gitignoreCommand = async () => {
|
|
|
1220
2235
|
"**/.gemini/memories/",
|
|
1221
2236
|
"**/.aiexclude",
|
|
1222
2237
|
"**/.aiignore",
|
|
2238
|
+
"**/.augmentignore",
|
|
1223
2239
|
"**/.kiro/steering/",
|
|
2240
|
+
"**/.augment/rules/",
|
|
2241
|
+
"**/.augment-guidelines",
|
|
2242
|
+
"**/.junie/guidelines.md",
|
|
2243
|
+
"**/.noai",
|
|
1224
2244
|
"**/.mcp.json",
|
|
1225
2245
|
"!.rulesync/.mcp.json",
|
|
1226
2246
|
"**/.cursor/mcp.json",
|
|
@@ -1248,45 +2268,283 @@ var gitignoreCommand = async () => {
|
|
|
1248
2268
|
${linesToAdd.join("\n")}
|
|
1249
2269
|
` : `${linesToAdd.join("\n")}
|
|
1250
2270
|
`;
|
|
1251
|
-
|
|
2271
|
+
writeFileSync2(gitignorePath, newContent);
|
|
1252
2272
|
console.log(`\u2705 Added ${linesToAdd.length} rules to .gitignore:`);
|
|
1253
2273
|
for (const line of linesToAdd) {
|
|
1254
2274
|
if (!line.startsWith("#")) {
|
|
1255
2275
|
console.log(` ${line}`);
|
|
1256
2276
|
}
|
|
1257
2277
|
}
|
|
1258
|
-
};
|
|
1259
|
-
|
|
1260
|
-
// src/core/importer.ts
|
|
1261
|
-
import { join as
|
|
1262
|
-
import
|
|
1263
|
-
|
|
1264
|
-
// src/parsers/
|
|
1265
|
-
import { basename as basename2, join as
|
|
1266
|
-
|
|
2278
|
+
};
|
|
2279
|
+
|
|
2280
|
+
// src/core/importer.ts
|
|
2281
|
+
import { join as join22 } from "path";
|
|
2282
|
+
import matter5 from "gray-matter";
|
|
2283
|
+
|
|
2284
|
+
// src/parsers/augmentcode.ts
|
|
2285
|
+
import { basename as basename2, join as join17 } from "path";
|
|
2286
|
+
import matter2 from "gray-matter";
|
|
2287
|
+
|
|
2288
|
+
// src/utils/parser-helpers.ts
|
|
2289
|
+
function createParseResult() {
|
|
2290
|
+
return { rules: [], errors: [] };
|
|
2291
|
+
}
|
|
2292
|
+
function addError(result, error) {
|
|
2293
|
+
result.errors.push(error);
|
|
2294
|
+
}
|
|
2295
|
+
function addRule(result, rule) {
|
|
2296
|
+
if (!result.rules) {
|
|
2297
|
+
result.rules = [];
|
|
2298
|
+
}
|
|
2299
|
+
result.rules.push(rule);
|
|
2300
|
+
}
|
|
2301
|
+
function addRules(result, rules) {
|
|
2302
|
+
if (!result.rules) {
|
|
2303
|
+
result.rules = [];
|
|
2304
|
+
}
|
|
2305
|
+
result.rules.push(...rules);
|
|
2306
|
+
}
|
|
2307
|
+
function handleParseError(error, context) {
|
|
2308
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2309
|
+
return `${context}: ${errorMessage}`;
|
|
2310
|
+
}
|
|
2311
|
+
async function safeReadFile(operation, errorContext) {
|
|
2312
|
+
try {
|
|
2313
|
+
const result = await operation();
|
|
2314
|
+
return { success: true, result };
|
|
2315
|
+
} catch (error) {
|
|
2316
|
+
return {
|
|
2317
|
+
success: false,
|
|
2318
|
+
error: handleParseError(error, errorContext)
|
|
2319
|
+
};
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
// src/parsers/augmentcode.ts
|
|
2324
|
+
async function parseAugmentcodeConfiguration(baseDir = process.cwd()) {
|
|
2325
|
+
const result = createParseResult();
|
|
2326
|
+
const rulesDir = join17(baseDir, ".augment", "rules");
|
|
2327
|
+
if (await fileExists(rulesDir)) {
|
|
2328
|
+
const rulesResult = await parseAugmentRules(rulesDir);
|
|
2329
|
+
addRules(result, rulesResult.rules);
|
|
2330
|
+
result.errors.push(...rulesResult.errors);
|
|
2331
|
+
} else {
|
|
2332
|
+
addError(result, "No AugmentCode configuration found. Expected .augment/rules/ directory.");
|
|
2333
|
+
}
|
|
2334
|
+
return { rules: result.rules || [], errors: result.errors };
|
|
2335
|
+
}
|
|
2336
|
+
async function parseAugmentRules(rulesDir) {
|
|
2337
|
+
const rules = [];
|
|
2338
|
+
const errors = [];
|
|
2339
|
+
try {
|
|
2340
|
+
const { readdir: readdir2 } = await import("fs/promises");
|
|
2341
|
+
const files = await readdir2(rulesDir);
|
|
2342
|
+
for (const file of files) {
|
|
2343
|
+
if (file.endsWith(".md") || file.endsWith(".mdc")) {
|
|
2344
|
+
const filePath = join17(rulesDir, file);
|
|
2345
|
+
try {
|
|
2346
|
+
const rawContent = await readFileContent(filePath);
|
|
2347
|
+
const parsed = matter2(rawContent);
|
|
2348
|
+
const frontmatterData = parsed.data;
|
|
2349
|
+
const ruleType = frontmatterData.type || "manual";
|
|
2350
|
+
const description = frontmatterData.description || "";
|
|
2351
|
+
const tags = Array.isArray(frontmatterData.tags) ? frontmatterData.tags : void 0;
|
|
2352
|
+
const isRoot = ruleType === "always";
|
|
2353
|
+
const filename = basename2(file, file.endsWith(".mdc") ? ".mdc" : ".md");
|
|
2354
|
+
const frontmatter = {
|
|
2355
|
+
root: isRoot,
|
|
2356
|
+
targets: ["augmentcode"],
|
|
2357
|
+
description,
|
|
2358
|
+
globs: ["**/*"],
|
|
2359
|
+
// AugmentCode doesn't use specific globs in the same way
|
|
2360
|
+
...tags && { tags }
|
|
2361
|
+
};
|
|
2362
|
+
rules.push({
|
|
2363
|
+
frontmatter,
|
|
2364
|
+
content: parsed.content.trim(),
|
|
2365
|
+
filename: `augmentcode-${ruleType}-${filename}`,
|
|
2366
|
+
filepath: filePath
|
|
2367
|
+
});
|
|
2368
|
+
} catch (error) {
|
|
2369
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2370
|
+
errors.push(`Failed to parse ${filePath}: ${errorMessage}`);
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
} catch (error) {
|
|
2375
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2376
|
+
errors.push(`Failed to read .augment/rules/ directory: ${errorMessage}`);
|
|
2377
|
+
}
|
|
2378
|
+
return { rules, errors };
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
// src/parsers/augmentcode-legacy.ts
|
|
2382
|
+
import { join as join18 } from "path";
|
|
2383
|
+
async function parseAugmentcodeLegacyConfiguration(baseDir = process.cwd()) {
|
|
2384
|
+
const result = createParseResult();
|
|
2385
|
+
const guidelinesPath = join18(baseDir, ".augment-guidelines");
|
|
2386
|
+
if (await fileExists(guidelinesPath)) {
|
|
2387
|
+
const guidelinesResult = await parseAugmentGuidelines(guidelinesPath);
|
|
2388
|
+
if (guidelinesResult.rule) {
|
|
2389
|
+
addRule(result, guidelinesResult.rule);
|
|
2390
|
+
}
|
|
2391
|
+
result.errors.push(...guidelinesResult.errors);
|
|
2392
|
+
} else {
|
|
2393
|
+
addError(
|
|
2394
|
+
result,
|
|
2395
|
+
"No AugmentCode legacy configuration found. Expected .augment-guidelines file."
|
|
2396
|
+
);
|
|
2397
|
+
}
|
|
2398
|
+
return { rules: result.rules || [], errors: result.errors };
|
|
2399
|
+
}
|
|
2400
|
+
async function parseAugmentGuidelines(guidelinesPath) {
|
|
2401
|
+
const parseResult = await safeReadFile(async () => {
|
|
2402
|
+
const content = await readFileContent(guidelinesPath);
|
|
2403
|
+
if (content.trim()) {
|
|
2404
|
+
const frontmatter = {
|
|
2405
|
+
root: true,
|
|
2406
|
+
// Legacy guidelines become root rules
|
|
2407
|
+
targets: ["augmentcode-legacy"],
|
|
2408
|
+
description: "Legacy AugmentCode guidelines",
|
|
2409
|
+
globs: ["**/*"]
|
|
2410
|
+
};
|
|
2411
|
+
return {
|
|
2412
|
+
frontmatter,
|
|
2413
|
+
content: content.trim(),
|
|
2414
|
+
filename: "augmentcode-legacy-guidelines",
|
|
2415
|
+
filepath: guidelinesPath
|
|
2416
|
+
};
|
|
2417
|
+
}
|
|
2418
|
+
return null;
|
|
2419
|
+
}, "Failed to parse .augment-guidelines");
|
|
2420
|
+
if (parseResult.success) {
|
|
2421
|
+
return { rule: parseResult.result || null, errors: [] };
|
|
2422
|
+
} else {
|
|
2423
|
+
return { rule: null, errors: [parseResult.error || "Unknown error"] };
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
// src/parsers/shared-helpers.ts
|
|
2428
|
+
import { basename as basename3, join as join19 } from "path";
|
|
2429
|
+
import matter3 from "gray-matter";
|
|
2430
|
+
async function parseConfigurationFiles(baseDir = process.cwd(), config) {
|
|
2431
|
+
const errors = [];
|
|
2432
|
+
const rules = [];
|
|
2433
|
+
if (config.mainFile) {
|
|
2434
|
+
const mainFilePath = join19(baseDir, config.mainFile.path);
|
|
2435
|
+
if (await fileExists(mainFilePath)) {
|
|
2436
|
+
try {
|
|
2437
|
+
const rawContent = await readFileContent(mainFilePath);
|
|
2438
|
+
let content;
|
|
2439
|
+
let frontmatter;
|
|
2440
|
+
if (config.mainFile.useFrontmatter) {
|
|
2441
|
+
const parsed = matter3(rawContent);
|
|
2442
|
+
content = parsed.content.trim();
|
|
2443
|
+
frontmatter = {
|
|
2444
|
+
root: false,
|
|
2445
|
+
targets: [config.tool],
|
|
2446
|
+
description: config.mainFile.description,
|
|
2447
|
+
globs: ["**/*"]
|
|
2448
|
+
};
|
|
2449
|
+
} else {
|
|
2450
|
+
content = rawContent.trim();
|
|
2451
|
+
frontmatter = {
|
|
2452
|
+
root: false,
|
|
2453
|
+
targets: [config.tool],
|
|
2454
|
+
description: config.mainFile.description,
|
|
2455
|
+
globs: ["**/*"]
|
|
2456
|
+
};
|
|
2457
|
+
}
|
|
2458
|
+
if (content) {
|
|
2459
|
+
rules.push({
|
|
2460
|
+
frontmatter,
|
|
2461
|
+
content,
|
|
2462
|
+
filename: `${config.tool}-instructions`,
|
|
2463
|
+
filepath: mainFilePath
|
|
2464
|
+
});
|
|
2465
|
+
}
|
|
2466
|
+
} catch (error) {
|
|
2467
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2468
|
+
errors.push(`Failed to parse ${config.mainFile.path}: ${errorMessage}`);
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
if (config.directories) {
|
|
2473
|
+
for (const dirConfig of config.directories) {
|
|
2474
|
+
const dirPath = join19(baseDir, dirConfig.directory);
|
|
2475
|
+
if (await fileExists(dirPath)) {
|
|
2476
|
+
try {
|
|
2477
|
+
const { readdir: readdir2 } = await import("fs/promises");
|
|
2478
|
+
const files = await readdir2(dirPath);
|
|
2479
|
+
for (const file of files) {
|
|
2480
|
+
if (file.endsWith(dirConfig.filePattern)) {
|
|
2481
|
+
const filePath = join19(dirPath, file);
|
|
2482
|
+
try {
|
|
2483
|
+
const rawContent = await readFileContent(filePath);
|
|
2484
|
+
let content;
|
|
2485
|
+
if (dirConfig.filePattern === ".instructions.md") {
|
|
2486
|
+
const parsed = matter3(rawContent);
|
|
2487
|
+
content = parsed.content.trim();
|
|
2488
|
+
} else {
|
|
2489
|
+
content = rawContent.trim();
|
|
2490
|
+
}
|
|
2491
|
+
if (content) {
|
|
2492
|
+
const filename = file.replace(new RegExp(`\\${dirConfig.filePattern}$`), "");
|
|
2493
|
+
const frontmatter = {
|
|
2494
|
+
root: false,
|
|
2495
|
+
targets: [config.tool],
|
|
2496
|
+
description: `${dirConfig.description}: ${filename}`,
|
|
2497
|
+
globs: ["**/*"]
|
|
2498
|
+
};
|
|
2499
|
+
rules.push({
|
|
2500
|
+
frontmatter,
|
|
2501
|
+
content,
|
|
2502
|
+
filename: `${config.tool}-${filename}`,
|
|
2503
|
+
filepath: filePath
|
|
2504
|
+
});
|
|
2505
|
+
}
|
|
2506
|
+
} catch (error) {
|
|
2507
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2508
|
+
errors.push(`Failed to parse ${filePath}: ${errorMessage}`);
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
} catch (error) {
|
|
2513
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2514
|
+
errors.push(`Failed to parse ${dirConfig.directory} files: ${errorMessage}`);
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
if (rules.length === 0) {
|
|
2520
|
+
errors.push(config.errorMessage);
|
|
2521
|
+
}
|
|
2522
|
+
return { rules, errors };
|
|
2523
|
+
}
|
|
2524
|
+
async function parseMemoryBasedConfiguration(baseDir = process.cwd(), config) {
|
|
1267
2525
|
const errors = [];
|
|
1268
2526
|
const rules = [];
|
|
1269
2527
|
let ignorePatterns;
|
|
1270
2528
|
let mcpServers;
|
|
1271
|
-
const
|
|
1272
|
-
if (!await fileExists(
|
|
1273
|
-
errors.push(
|
|
2529
|
+
const mainFilePath = join19(baseDir, config.mainFileName);
|
|
2530
|
+
if (!await fileExists(mainFilePath)) {
|
|
2531
|
+
errors.push(`${config.mainFileName} file not found`);
|
|
1274
2532
|
return { rules, errors };
|
|
1275
2533
|
}
|
|
1276
2534
|
try {
|
|
1277
|
-
const
|
|
1278
|
-
const mainRule =
|
|
2535
|
+
const mainContent = await readFileContent(mainFilePath);
|
|
2536
|
+
const mainRule = parseMainFile(mainContent, mainFilePath, config);
|
|
1279
2537
|
if (mainRule) {
|
|
1280
2538
|
rules.push(mainRule);
|
|
1281
2539
|
}
|
|
1282
|
-
const memoryDir =
|
|
2540
|
+
const memoryDir = join19(baseDir, config.memoryDirPath);
|
|
1283
2541
|
if (await fileExists(memoryDir)) {
|
|
1284
|
-
const memoryRules = await
|
|
2542
|
+
const memoryRules = await parseMemoryFiles(memoryDir, config);
|
|
1285
2543
|
rules.push(...memoryRules);
|
|
1286
2544
|
}
|
|
1287
|
-
const settingsPath =
|
|
2545
|
+
const settingsPath = join19(baseDir, config.settingsPath);
|
|
1288
2546
|
if (await fileExists(settingsPath)) {
|
|
1289
|
-
const settingsResult = await
|
|
2547
|
+
const settingsResult = await parseSettingsFile(settingsPath, config.tool);
|
|
1290
2548
|
if (settingsResult.ignorePatterns) {
|
|
1291
2549
|
ignorePatterns = settingsResult.ignorePatterns;
|
|
1292
2550
|
}
|
|
@@ -1295,9 +2553,18 @@ async function parseClaudeConfiguration(baseDir = process.cwd()) {
|
|
|
1295
2553
|
}
|
|
1296
2554
|
errors.push(...settingsResult.errors);
|
|
1297
2555
|
}
|
|
2556
|
+
if (config.additionalIgnoreFile) {
|
|
2557
|
+
const additionalIgnorePath = join19(baseDir, config.additionalIgnoreFile.path);
|
|
2558
|
+
if (await fileExists(additionalIgnorePath)) {
|
|
2559
|
+
const additionalPatterns = await config.additionalIgnoreFile.parser(additionalIgnorePath);
|
|
2560
|
+
if (additionalPatterns.length > 0) {
|
|
2561
|
+
ignorePatterns = ignorePatterns ? [...ignorePatterns, ...additionalPatterns] : additionalPatterns;
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
1298
2565
|
} catch (error) {
|
|
1299
2566
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1300
|
-
errors.push(`Failed to parse
|
|
2567
|
+
errors.push(`Failed to parse ${config.tool} configuration: ${errorMessage}`);
|
|
1301
2568
|
}
|
|
1302
2569
|
return {
|
|
1303
2570
|
rules,
|
|
@@ -1306,7 +2573,7 @@ async function parseClaudeConfiguration(baseDir = process.cwd()) {
|
|
|
1306
2573
|
...mcpServers && { mcpServers }
|
|
1307
2574
|
};
|
|
1308
2575
|
}
|
|
1309
|
-
function
|
|
2576
|
+
function parseMainFile(content, filepath, config) {
|
|
1310
2577
|
const lines = content.split("\n");
|
|
1311
2578
|
let contentStartIndex = 0;
|
|
1312
2579
|
if (lines.some((line) => line.includes("| Document | Description | File Patterns |"))) {
|
|
@@ -1323,38 +2590,38 @@ function parseClaudeMainFile(content, filepath) {
|
|
|
1323
2590
|
}
|
|
1324
2591
|
const frontmatter = {
|
|
1325
2592
|
root: false,
|
|
1326
|
-
targets: [
|
|
1327
|
-
description:
|
|
2593
|
+
targets: [config.tool],
|
|
2594
|
+
description: config.mainDescription,
|
|
1328
2595
|
globs: ["**/*"]
|
|
1329
2596
|
};
|
|
1330
2597
|
return {
|
|
1331
2598
|
frontmatter,
|
|
1332
2599
|
content: mainContent,
|
|
1333
|
-
filename:
|
|
2600
|
+
filename: `${config.filenamePrefix}-main`,
|
|
1334
2601
|
filepath
|
|
1335
2602
|
};
|
|
1336
2603
|
}
|
|
1337
|
-
async function
|
|
2604
|
+
async function parseMemoryFiles(memoryDir, config) {
|
|
1338
2605
|
const rules = [];
|
|
1339
2606
|
try {
|
|
1340
2607
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
1341
2608
|
const files = await readdir2(memoryDir);
|
|
1342
2609
|
for (const file of files) {
|
|
1343
2610
|
if (file.endsWith(".md")) {
|
|
1344
|
-
const filePath =
|
|
2611
|
+
const filePath = join19(memoryDir, file);
|
|
1345
2612
|
const content = await readFileContent(filePath);
|
|
1346
2613
|
if (content.trim()) {
|
|
1347
|
-
const filename =
|
|
2614
|
+
const filename = basename3(file, ".md");
|
|
1348
2615
|
const frontmatter = {
|
|
1349
2616
|
root: false,
|
|
1350
|
-
targets: [
|
|
1351
|
-
description:
|
|
2617
|
+
targets: [config.tool],
|
|
2618
|
+
description: `${config.memoryDescription}: ${filename}`,
|
|
1352
2619
|
globs: ["**/*"]
|
|
1353
2620
|
};
|
|
1354
2621
|
rules.push({
|
|
1355
2622
|
frontmatter,
|
|
1356
2623
|
content: content.trim(),
|
|
1357
|
-
filename:
|
|
2624
|
+
filename: `${config.filenamePrefix}-memory-${filename}`,
|
|
1358
2625
|
filepath: filePath
|
|
1359
2626
|
});
|
|
1360
2627
|
}
|
|
@@ -1364,14 +2631,14 @@ async function parseClaudeMemoryFiles(memoryDir) {
|
|
|
1364
2631
|
}
|
|
1365
2632
|
return rules;
|
|
1366
2633
|
}
|
|
1367
|
-
async function
|
|
2634
|
+
async function parseSettingsFile(settingsPath, tool) {
|
|
1368
2635
|
const errors = [];
|
|
1369
2636
|
let ignorePatterns;
|
|
1370
2637
|
let mcpServers;
|
|
1371
2638
|
try {
|
|
1372
2639
|
const content = await readFileContent(settingsPath);
|
|
1373
2640
|
const settings = JSON.parse(content);
|
|
1374
|
-
if (typeof settings === "object" && settings !== null && "permissions" in settings) {
|
|
2641
|
+
if (tool === "claudecode" && typeof settings === "object" && settings !== null && "permissions" in settings) {
|
|
1375
2642
|
const permissions = settings.permissions;
|
|
1376
2643
|
if (typeof permissions !== "object" || permissions === null) {
|
|
1377
2644
|
return { ignorePatterns: [], errors: [] };
|
|
@@ -1403,153 +2670,64 @@ async function parseClaudeSettings(settingsPath) {
|
|
|
1403
2670
|
};
|
|
1404
2671
|
}
|
|
1405
2672
|
|
|
2673
|
+
// src/parsers/claudecode.ts
|
|
2674
|
+
async function parseClaudeConfiguration(baseDir = process.cwd()) {
|
|
2675
|
+
return parseMemoryBasedConfiguration(baseDir, {
|
|
2676
|
+
tool: "claudecode",
|
|
2677
|
+
mainFileName: "CLAUDE.md",
|
|
2678
|
+
memoryDirPath: ".claude/memories",
|
|
2679
|
+
settingsPath: ".claude/settings.json",
|
|
2680
|
+
mainDescription: "Main Claude Code configuration",
|
|
2681
|
+
memoryDescription: "Memory file",
|
|
2682
|
+
filenamePrefix: "claude"
|
|
2683
|
+
});
|
|
2684
|
+
}
|
|
2685
|
+
|
|
1406
2686
|
// src/parsers/cline.ts
|
|
1407
|
-
import { join as join16 } from "path";
|
|
1408
2687
|
async function parseClineConfiguration(baseDir = process.cwd()) {
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
};
|
|
1422
|
-
rules.push({
|
|
1423
|
-
frontmatter,
|
|
1424
|
-
content: content.trim(),
|
|
1425
|
-
filename: "cline-instructions",
|
|
1426
|
-
filepath: clineFilePath
|
|
1427
|
-
});
|
|
1428
|
-
}
|
|
1429
|
-
} catch (error) {
|
|
1430
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1431
|
-
errors.push(`Failed to parse .cline/instructions.md: ${errorMessage}`);
|
|
1432
|
-
}
|
|
1433
|
-
}
|
|
1434
|
-
const clinerulesDirPath = join16(baseDir, ".clinerules");
|
|
1435
|
-
if (await fileExists(clinerulesDirPath)) {
|
|
1436
|
-
try {
|
|
1437
|
-
const { readdir: readdir2 } = await import("fs/promises");
|
|
1438
|
-
const files = await readdir2(clinerulesDirPath);
|
|
1439
|
-
for (const file of files) {
|
|
1440
|
-
if (file.endsWith(".md")) {
|
|
1441
|
-
const filePath = join16(clinerulesDirPath, file);
|
|
1442
|
-
try {
|
|
1443
|
-
const content = await readFileContent(filePath);
|
|
1444
|
-
if (content.trim()) {
|
|
1445
|
-
const filename = file.replace(".md", "");
|
|
1446
|
-
const frontmatter = {
|
|
1447
|
-
root: false,
|
|
1448
|
-
targets: ["cline"],
|
|
1449
|
-
description: `Cline rule: ${filename}`,
|
|
1450
|
-
globs: ["**/*"]
|
|
1451
|
-
};
|
|
1452
|
-
rules.push({
|
|
1453
|
-
frontmatter,
|
|
1454
|
-
content: content.trim(),
|
|
1455
|
-
filename: `cline-${filename}`,
|
|
1456
|
-
filepath: filePath
|
|
1457
|
-
});
|
|
1458
|
-
}
|
|
1459
|
-
} catch (error) {
|
|
1460
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1461
|
-
errors.push(`Failed to parse ${filePath}: ${errorMessage}`);
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
2688
|
+
return parseConfigurationFiles(baseDir, {
|
|
2689
|
+
tool: "cline",
|
|
2690
|
+
mainFile: {
|
|
2691
|
+
path: ".cline/instructions.md",
|
|
2692
|
+
useFrontmatter: false,
|
|
2693
|
+
description: "Cline instructions"
|
|
2694
|
+
},
|
|
2695
|
+
directories: [
|
|
2696
|
+
{
|
|
2697
|
+
directory: ".clinerules",
|
|
2698
|
+
filePattern: ".md",
|
|
2699
|
+
description: "Cline rule"
|
|
1464
2700
|
}
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1470
|
-
if (rules.length === 0) {
|
|
1471
|
-
errors.push("No Cline configuration files found (.cline/instructions.md or .clinerules/*.md)");
|
|
1472
|
-
}
|
|
1473
|
-
return { rules, errors };
|
|
2701
|
+
],
|
|
2702
|
+
errorMessage: "No Cline configuration files found (.cline/instructions.md or .clinerules/*.md)"
|
|
2703
|
+
});
|
|
1474
2704
|
}
|
|
1475
2705
|
|
|
1476
2706
|
// src/parsers/copilot.ts
|
|
1477
|
-
import { basename as basename3, join as join17 } from "path";
|
|
1478
|
-
import matter2 from "gray-matter";
|
|
1479
2707
|
async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
description: "GitHub Copilot instructions",
|
|
1493
|
-
globs: ["**/*"]
|
|
1494
|
-
};
|
|
1495
|
-
rules.push({
|
|
1496
|
-
frontmatter,
|
|
1497
|
-
content,
|
|
1498
|
-
filename: "copilot-instructions",
|
|
1499
|
-
filepath: copilotFilePath
|
|
1500
|
-
});
|
|
1501
|
-
}
|
|
1502
|
-
} catch (error) {
|
|
1503
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1504
|
-
errors.push(`Failed to parse copilot-instructions.md: ${errorMessage}`);
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
const instructionsDir = join17(baseDir, ".github", "instructions");
|
|
1508
|
-
if (await fileExists(instructionsDir)) {
|
|
1509
|
-
try {
|
|
1510
|
-
const { readdir: readdir2 } = await import("fs/promises");
|
|
1511
|
-
const files = await readdir2(instructionsDir);
|
|
1512
|
-
for (const file of files) {
|
|
1513
|
-
if (file.endsWith(".instructions.md")) {
|
|
1514
|
-
const filePath = join17(instructionsDir, file);
|
|
1515
|
-
const rawContent = await readFileContent(filePath);
|
|
1516
|
-
const parsed = matter2(rawContent);
|
|
1517
|
-
const content = parsed.content.trim();
|
|
1518
|
-
if (content) {
|
|
1519
|
-
const filename = basename3(file, ".instructions.md");
|
|
1520
|
-
const frontmatter = {
|
|
1521
|
-
root: false,
|
|
1522
|
-
targets: ["copilot"],
|
|
1523
|
-
description: `Copilot instruction: ${filename}`,
|
|
1524
|
-
globs: ["**/*"]
|
|
1525
|
-
};
|
|
1526
|
-
rules.push({
|
|
1527
|
-
frontmatter,
|
|
1528
|
-
content,
|
|
1529
|
-
filename: `copilot-${filename}`,
|
|
1530
|
-
filepath: filePath
|
|
1531
|
-
});
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
2708
|
+
return parseConfigurationFiles(baseDir, {
|
|
2709
|
+
tool: "copilot",
|
|
2710
|
+
mainFile: {
|
|
2711
|
+
path: ".github/copilot-instructions.md",
|
|
2712
|
+
useFrontmatter: true,
|
|
2713
|
+
description: "GitHub Copilot instructions"
|
|
2714
|
+
},
|
|
2715
|
+
directories: [
|
|
2716
|
+
{
|
|
2717
|
+
directory: ".github/instructions",
|
|
2718
|
+
filePattern: ".instructions.md",
|
|
2719
|
+
description: "Copilot instruction"
|
|
1534
2720
|
}
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
}
|
|
1539
|
-
}
|
|
1540
|
-
if (rules.length === 0) {
|
|
1541
|
-
errors.push(
|
|
1542
|
-
"No Copilot configuration files found (.github/copilot-instructions.md or .github/instructions/*.instructions.md)"
|
|
1543
|
-
);
|
|
1544
|
-
}
|
|
1545
|
-
return { rules, errors };
|
|
2721
|
+
],
|
|
2722
|
+
errorMessage: "No Copilot configuration files found (.github/copilot-instructions.md or .github/instructions/*.instructions.md)"
|
|
2723
|
+
});
|
|
1546
2724
|
}
|
|
1547
2725
|
|
|
1548
2726
|
// src/parsers/cursor.ts
|
|
1549
|
-
import { basename as basename4, join as
|
|
1550
|
-
import
|
|
2727
|
+
import { basename as basename4, join as join20 } from "path";
|
|
2728
|
+
import matter4 from "gray-matter";
|
|
1551
2729
|
import { DEFAULT_SCHEMA, FAILSAFE_SCHEMA, load } from "js-yaml";
|
|
1552
|
-
import { z as
|
|
2730
|
+
import { z as z6 } from "zod/mini";
|
|
1553
2731
|
var customMatterOptions = {
|
|
1554
2732
|
engines: {
|
|
1555
2733
|
yaml: {
|
|
@@ -1577,7 +2755,7 @@ var customMatterOptions = {
|
|
|
1577
2755
|
}
|
|
1578
2756
|
};
|
|
1579
2757
|
function convertCursorMdcFrontmatter(cursorFrontmatter, _filename) {
|
|
1580
|
-
const FrontmatterSchema =
|
|
2758
|
+
const FrontmatterSchema = z6.record(z6.string(), z6.unknown());
|
|
1581
2759
|
const parseResult = FrontmatterSchema.safeParse(cursorFrontmatter);
|
|
1582
2760
|
if (!parseResult.success) {
|
|
1583
2761
|
return {
|
|
@@ -1671,11 +2849,11 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
1671
2849
|
const rules = [];
|
|
1672
2850
|
let ignorePatterns;
|
|
1673
2851
|
let mcpServers;
|
|
1674
|
-
const cursorFilePath =
|
|
2852
|
+
const cursorFilePath = join20(baseDir, ".cursorrules");
|
|
1675
2853
|
if (await fileExists(cursorFilePath)) {
|
|
1676
2854
|
try {
|
|
1677
2855
|
const rawContent = await readFileContent(cursorFilePath);
|
|
1678
|
-
const parsed =
|
|
2856
|
+
const parsed = matter4(rawContent, customMatterOptions);
|
|
1679
2857
|
const content = parsed.content.trim();
|
|
1680
2858
|
if (content) {
|
|
1681
2859
|
const frontmatter = convertCursorMdcFrontmatter(parsed.data, "cursorrules");
|
|
@@ -1692,17 +2870,17 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
1692
2870
|
errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
|
|
1693
2871
|
}
|
|
1694
2872
|
}
|
|
1695
|
-
const cursorRulesDir =
|
|
2873
|
+
const cursorRulesDir = join20(baseDir, ".cursor", "rules");
|
|
1696
2874
|
if (await fileExists(cursorRulesDir)) {
|
|
1697
2875
|
try {
|
|
1698
2876
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
1699
2877
|
const files = await readdir2(cursorRulesDir);
|
|
1700
2878
|
for (const file of files) {
|
|
1701
2879
|
if (file.endsWith(".mdc")) {
|
|
1702
|
-
const filePath =
|
|
2880
|
+
const filePath = join20(cursorRulesDir, file);
|
|
1703
2881
|
try {
|
|
1704
2882
|
const rawContent = await readFileContent(filePath);
|
|
1705
|
-
const parsed =
|
|
2883
|
+
const parsed = matter4(rawContent, customMatterOptions);
|
|
1706
2884
|
const content = parsed.content.trim();
|
|
1707
2885
|
if (content) {
|
|
1708
2886
|
const filename = basename4(file, ".mdc");
|
|
@@ -1728,7 +2906,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
1728
2906
|
if (rules.length === 0) {
|
|
1729
2907
|
errors.push("No Cursor configuration files found (.cursorrules or .cursor/rules/*.mdc)");
|
|
1730
2908
|
}
|
|
1731
|
-
const cursorIgnorePath =
|
|
2909
|
+
const cursorIgnorePath = join20(baseDir, ".cursorignore");
|
|
1732
2910
|
if (await fileExists(cursorIgnorePath)) {
|
|
1733
2911
|
try {
|
|
1734
2912
|
const content = await readFileContent(cursorIgnorePath);
|
|
@@ -1741,7 +2919,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
1741
2919
|
errors.push(`Failed to parse .cursorignore: ${errorMessage}`);
|
|
1742
2920
|
}
|
|
1743
2921
|
}
|
|
1744
|
-
const cursorMcpPath =
|
|
2922
|
+
const cursorMcpPath = join20(baseDir, ".cursor", "mcp.json");
|
|
1745
2923
|
if (await fileExists(cursorMcpPath)) {
|
|
1746
2924
|
try {
|
|
1747
2925
|
const content = await readFileContent(cursorMcpPath);
|
|
@@ -1764,134 +2942,6 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
1764
2942
|
}
|
|
1765
2943
|
|
|
1766
2944
|
// src/parsers/geminicli.ts
|
|
1767
|
-
import { basename as basename5, join as join19 } from "path";
|
|
1768
|
-
async function parseGeminiConfiguration(baseDir = process.cwd()) {
|
|
1769
|
-
const errors = [];
|
|
1770
|
-
const rules = [];
|
|
1771
|
-
let ignorePatterns;
|
|
1772
|
-
let mcpServers;
|
|
1773
|
-
const geminiFilePath = join19(baseDir, "GEMINI.md");
|
|
1774
|
-
if (!await fileExists(geminiFilePath)) {
|
|
1775
|
-
errors.push("GEMINI.md file not found");
|
|
1776
|
-
return { rules, errors };
|
|
1777
|
-
}
|
|
1778
|
-
try {
|
|
1779
|
-
const geminiContent = await readFileContent(geminiFilePath);
|
|
1780
|
-
const mainRule = parseGeminiMainFile(geminiContent, geminiFilePath);
|
|
1781
|
-
if (mainRule) {
|
|
1782
|
-
rules.push(mainRule);
|
|
1783
|
-
}
|
|
1784
|
-
const memoryDir = join19(baseDir, ".gemini", "memories");
|
|
1785
|
-
if (await fileExists(memoryDir)) {
|
|
1786
|
-
const memoryRules = await parseGeminiMemoryFiles(memoryDir);
|
|
1787
|
-
rules.push(...memoryRules);
|
|
1788
|
-
}
|
|
1789
|
-
const settingsPath = join19(baseDir, ".gemini", "settings.json");
|
|
1790
|
-
if (await fileExists(settingsPath)) {
|
|
1791
|
-
const settingsResult = await parseGeminiSettings(settingsPath);
|
|
1792
|
-
if (settingsResult.ignorePatterns) {
|
|
1793
|
-
ignorePatterns = settingsResult.ignorePatterns;
|
|
1794
|
-
}
|
|
1795
|
-
if (settingsResult.mcpServers) {
|
|
1796
|
-
mcpServers = settingsResult.mcpServers;
|
|
1797
|
-
}
|
|
1798
|
-
errors.push(...settingsResult.errors);
|
|
1799
|
-
}
|
|
1800
|
-
const aiexcludePath = join19(baseDir, ".aiexclude");
|
|
1801
|
-
if (await fileExists(aiexcludePath)) {
|
|
1802
|
-
const aiexcludePatterns = await parseAiexclude(aiexcludePath);
|
|
1803
|
-
if (aiexcludePatterns.length > 0) {
|
|
1804
|
-
ignorePatterns = ignorePatterns ? [...ignorePatterns, ...aiexcludePatterns] : aiexcludePatterns;
|
|
1805
|
-
}
|
|
1806
|
-
}
|
|
1807
|
-
} catch (error) {
|
|
1808
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1809
|
-
errors.push(`Failed to parse Gemini configuration: ${errorMessage}`);
|
|
1810
|
-
}
|
|
1811
|
-
return {
|
|
1812
|
-
rules,
|
|
1813
|
-
errors,
|
|
1814
|
-
...ignorePatterns && { ignorePatterns },
|
|
1815
|
-
...mcpServers && { mcpServers }
|
|
1816
|
-
};
|
|
1817
|
-
}
|
|
1818
|
-
function parseGeminiMainFile(content, filepath) {
|
|
1819
|
-
const lines = content.split("\n");
|
|
1820
|
-
let contentStartIndex = 0;
|
|
1821
|
-
if (lines.some((line) => line.includes("| Document | Description | File Patterns |"))) {
|
|
1822
|
-
const tableEndIndex = lines.findIndex(
|
|
1823
|
-
(line, index) => index > 0 && line.trim() === "" && lines[index - 1]?.includes("|") && !lines[index + 1]?.includes("|")
|
|
1824
|
-
);
|
|
1825
|
-
if (tableEndIndex !== -1) {
|
|
1826
|
-
contentStartIndex = tableEndIndex + 1;
|
|
1827
|
-
}
|
|
1828
|
-
}
|
|
1829
|
-
const mainContent = lines.slice(contentStartIndex).join("\n").trim();
|
|
1830
|
-
if (!mainContent) {
|
|
1831
|
-
return null;
|
|
1832
|
-
}
|
|
1833
|
-
const frontmatter = {
|
|
1834
|
-
root: false,
|
|
1835
|
-
targets: ["geminicli"],
|
|
1836
|
-
description: "Main Gemini CLI configuration",
|
|
1837
|
-
globs: ["**/*"]
|
|
1838
|
-
};
|
|
1839
|
-
return {
|
|
1840
|
-
frontmatter,
|
|
1841
|
-
content: mainContent,
|
|
1842
|
-
filename: "gemini-main",
|
|
1843
|
-
filepath
|
|
1844
|
-
};
|
|
1845
|
-
}
|
|
1846
|
-
async function parseGeminiMemoryFiles(memoryDir) {
|
|
1847
|
-
const rules = [];
|
|
1848
|
-
try {
|
|
1849
|
-
const { readdir: readdir2 } = await import("fs/promises");
|
|
1850
|
-
const files = await readdir2(memoryDir);
|
|
1851
|
-
for (const file of files) {
|
|
1852
|
-
if (file.endsWith(".md")) {
|
|
1853
|
-
const filePath = join19(memoryDir, file);
|
|
1854
|
-
const content = await readFileContent(filePath);
|
|
1855
|
-
if (content.trim()) {
|
|
1856
|
-
const filename = basename5(file, ".md");
|
|
1857
|
-
const frontmatter = {
|
|
1858
|
-
root: false,
|
|
1859
|
-
targets: ["geminicli"],
|
|
1860
|
-
description: `Memory file: ${filename}`,
|
|
1861
|
-
globs: ["**/*"]
|
|
1862
|
-
};
|
|
1863
|
-
rules.push({
|
|
1864
|
-
frontmatter,
|
|
1865
|
-
content: content.trim(),
|
|
1866
|
-
filename: `gemini-memory-${filename}`,
|
|
1867
|
-
filepath: filePath
|
|
1868
|
-
});
|
|
1869
|
-
}
|
|
1870
|
-
}
|
|
1871
|
-
}
|
|
1872
|
-
} catch {
|
|
1873
|
-
}
|
|
1874
|
-
return rules;
|
|
1875
|
-
}
|
|
1876
|
-
async function parseGeminiSettings(settingsPath) {
|
|
1877
|
-
const errors = [];
|
|
1878
|
-
let mcpServers;
|
|
1879
|
-
try {
|
|
1880
|
-
const content = await readFileContent(settingsPath);
|
|
1881
|
-
const settings = JSON.parse(content);
|
|
1882
|
-
const parseResult = RulesyncMcpConfigSchema.safeParse(settings);
|
|
1883
|
-
if (parseResult.success && Object.keys(parseResult.data.mcpServers).length > 0) {
|
|
1884
|
-
mcpServers = parseResult.data.mcpServers;
|
|
1885
|
-
}
|
|
1886
|
-
} catch (error) {
|
|
1887
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1888
|
-
errors.push(`Failed to parse settings.json: ${errorMessage}`);
|
|
1889
|
-
}
|
|
1890
|
-
return {
|
|
1891
|
-
errors,
|
|
1892
|
-
...mcpServers && { mcpServers }
|
|
1893
|
-
};
|
|
1894
|
-
}
|
|
1895
2945
|
async function parseAiexclude(aiexcludePath) {
|
|
1896
2946
|
try {
|
|
1897
2947
|
const content = await readFileContent(aiexcludePath);
|
|
@@ -1901,77 +2951,78 @@ async function parseAiexclude(aiexcludePath) {
|
|
|
1901
2951
|
return [];
|
|
1902
2952
|
}
|
|
1903
2953
|
}
|
|
2954
|
+
async function parseGeminiConfiguration(baseDir = process.cwd()) {
|
|
2955
|
+
return parseMemoryBasedConfiguration(baseDir, {
|
|
2956
|
+
tool: "geminicli",
|
|
2957
|
+
mainFileName: "GEMINI.md",
|
|
2958
|
+
memoryDirPath: ".gemini/memories",
|
|
2959
|
+
settingsPath: ".gemini/settings.json",
|
|
2960
|
+
mainDescription: "Main Gemini CLI configuration",
|
|
2961
|
+
memoryDescription: "Memory file",
|
|
2962
|
+
filenamePrefix: "gemini",
|
|
2963
|
+
additionalIgnoreFile: {
|
|
2964
|
+
path: ".aiexclude",
|
|
2965
|
+
parser: parseAiexclude
|
|
2966
|
+
}
|
|
2967
|
+
});
|
|
2968
|
+
}
|
|
1904
2969
|
|
|
1905
|
-
// src/parsers/
|
|
1906
|
-
import { join as
|
|
1907
|
-
async function
|
|
2970
|
+
// src/parsers/junie.ts
|
|
2971
|
+
import { join as join21 } from "path";
|
|
2972
|
+
async function parseJunieConfiguration(baseDir = process.cwd()) {
|
|
1908
2973
|
const errors = [];
|
|
1909
2974
|
const rules = [];
|
|
1910
|
-
const
|
|
1911
|
-
if (await fileExists(
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
if (content.trim()) {
|
|
1915
|
-
const frontmatter = {
|
|
1916
|
-
root: false,
|
|
1917
|
-
targets: ["roo"],
|
|
1918
|
-
description: "Roo Code instructions",
|
|
1919
|
-
globs: ["**/*"]
|
|
1920
|
-
};
|
|
1921
|
-
rules.push({
|
|
1922
|
-
frontmatter,
|
|
1923
|
-
content: content.trim(),
|
|
1924
|
-
filename: "roo-instructions",
|
|
1925
|
-
filepath: rooFilePath
|
|
1926
|
-
});
|
|
1927
|
-
}
|
|
1928
|
-
} catch (error) {
|
|
1929
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1930
|
-
errors.push(`Failed to parse .roo/instructions.md: ${errorMessage}`);
|
|
1931
|
-
}
|
|
2975
|
+
const guidelinesPath = join21(baseDir, ".junie", "guidelines.md");
|
|
2976
|
+
if (!await fileExists(guidelinesPath)) {
|
|
2977
|
+
errors.push(".junie/guidelines.md file not found");
|
|
2978
|
+
return { rules, errors };
|
|
1932
2979
|
}
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
const
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
description: `Roo rule: ${filename}`,
|
|
1949
|
-
globs: ["**/*"]
|
|
1950
|
-
};
|
|
1951
|
-
rules.push({
|
|
1952
|
-
frontmatter,
|
|
1953
|
-
content: content.trim(),
|
|
1954
|
-
filename: `roo-${filename}`,
|
|
1955
|
-
filepath: filePath
|
|
1956
|
-
});
|
|
1957
|
-
}
|
|
1958
|
-
} catch (error) {
|
|
1959
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1960
|
-
errors.push(`Failed to parse ${filePath}: ${errorMessage}`);
|
|
1961
|
-
}
|
|
1962
|
-
}
|
|
1963
|
-
}
|
|
1964
|
-
} catch (error) {
|
|
1965
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1966
|
-
errors.push(`Failed to parse .roo/rules files: ${errorMessage}`);
|
|
2980
|
+
try {
|
|
2981
|
+
const content = await readFileContent(guidelinesPath);
|
|
2982
|
+
if (content.trim()) {
|
|
2983
|
+
const frontmatter = {
|
|
2984
|
+
root: false,
|
|
2985
|
+
targets: ["junie"],
|
|
2986
|
+
description: "Junie project guidelines",
|
|
2987
|
+
globs: ["**/*"]
|
|
2988
|
+
};
|
|
2989
|
+
rules.push({
|
|
2990
|
+
frontmatter,
|
|
2991
|
+
content: content.trim(),
|
|
2992
|
+
filename: "junie-guidelines",
|
|
2993
|
+
filepath: guidelinesPath
|
|
2994
|
+
});
|
|
1967
2995
|
}
|
|
2996
|
+
} catch (error) {
|
|
2997
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2998
|
+
errors.push(`Failed to parse .junie/guidelines.md: ${errorMessage}`);
|
|
1968
2999
|
}
|
|
1969
3000
|
if (rules.length === 0) {
|
|
1970
|
-
errors.push("No
|
|
3001
|
+
errors.push("No valid Junie configuration found");
|
|
1971
3002
|
}
|
|
1972
3003
|
return { rules, errors };
|
|
1973
3004
|
}
|
|
1974
3005
|
|
|
3006
|
+
// src/parsers/roo.ts
|
|
3007
|
+
async function parseRooConfiguration(baseDir = process.cwd()) {
|
|
3008
|
+
return parseConfigurationFiles(baseDir, {
|
|
3009
|
+
tool: "roo",
|
|
3010
|
+
mainFile: {
|
|
3011
|
+
path: ".roo/instructions.md",
|
|
3012
|
+
useFrontmatter: false,
|
|
3013
|
+
description: "Roo Code instructions"
|
|
3014
|
+
},
|
|
3015
|
+
directories: [
|
|
3016
|
+
{
|
|
3017
|
+
directory: ".roo/rules",
|
|
3018
|
+
filePattern: ".md",
|
|
3019
|
+
description: "Roo rule"
|
|
3020
|
+
}
|
|
3021
|
+
],
|
|
3022
|
+
errorMessage: "No Roo Code configuration files found (.roo/instructions.md or .roo/rules/*.md)"
|
|
3023
|
+
});
|
|
3024
|
+
}
|
|
3025
|
+
|
|
1975
3026
|
// src/core/importer.ts
|
|
1976
3027
|
async function importConfiguration(options) {
|
|
1977
3028
|
const { tool, baseDir = process.cwd(), rulesDir = ".rulesync", verbose = false } = options;
|
|
@@ -1984,6 +3035,18 @@ async function importConfiguration(options) {
|
|
|
1984
3035
|
}
|
|
1985
3036
|
try {
|
|
1986
3037
|
switch (tool) {
|
|
3038
|
+
case "augmentcode": {
|
|
3039
|
+
const augmentResult = await parseAugmentcodeConfiguration(baseDir);
|
|
3040
|
+
rules = augmentResult.rules;
|
|
3041
|
+
errors.push(...augmentResult.errors);
|
|
3042
|
+
break;
|
|
3043
|
+
}
|
|
3044
|
+
case "augmentcode-legacy": {
|
|
3045
|
+
const augmentLegacyResult = await parseAugmentcodeLegacyConfiguration(baseDir);
|
|
3046
|
+
rules = augmentLegacyResult.rules;
|
|
3047
|
+
errors.push(...augmentLegacyResult.errors);
|
|
3048
|
+
break;
|
|
3049
|
+
}
|
|
1987
3050
|
case "claudecode": {
|
|
1988
3051
|
const claudeResult = await parseClaudeConfiguration(baseDir);
|
|
1989
3052
|
rules = claudeResult.rules;
|
|
@@ -2026,6 +3089,12 @@ async function importConfiguration(options) {
|
|
|
2026
3089
|
mcpServers = geminiResult.mcpServers;
|
|
2027
3090
|
break;
|
|
2028
3091
|
}
|
|
3092
|
+
case "junie": {
|
|
3093
|
+
const junieResult = await parseJunieConfiguration(baseDir);
|
|
3094
|
+
rules = junieResult.rules;
|
|
3095
|
+
errors.push(...junieResult.errors);
|
|
3096
|
+
break;
|
|
3097
|
+
}
|
|
2029
3098
|
default:
|
|
2030
3099
|
errors.push(`Unsupported tool: ${tool}`);
|
|
2031
3100
|
return { success: false, rulesCreated: 0, errors };
|
|
@@ -2038,7 +3107,7 @@ async function importConfiguration(options) {
|
|
|
2038
3107
|
if (rules.length === 0 && !ignorePatterns && !mcpServers) {
|
|
2039
3108
|
return { success: false, rulesCreated: 0, errors };
|
|
2040
3109
|
}
|
|
2041
|
-
const rulesDirPath =
|
|
3110
|
+
const rulesDirPath = join22(baseDir, rulesDir);
|
|
2042
3111
|
try {
|
|
2043
3112
|
const { mkdir: mkdir3 } = await import("fs/promises");
|
|
2044
3113
|
await mkdir3(rulesDirPath, { recursive: true });
|
|
@@ -2052,7 +3121,7 @@ async function importConfiguration(options) {
|
|
|
2052
3121
|
try {
|
|
2053
3122
|
const baseFilename = `${tool}__${rule.filename}`;
|
|
2054
3123
|
const filename = await generateUniqueFilename(rulesDirPath, baseFilename);
|
|
2055
|
-
const filePath =
|
|
3124
|
+
const filePath = join22(rulesDirPath, `${filename}.md`);
|
|
2056
3125
|
const content = generateRuleFileContent(rule);
|
|
2057
3126
|
await writeFileContent(filePath, content);
|
|
2058
3127
|
rulesCreated++;
|
|
@@ -2067,7 +3136,7 @@ async function importConfiguration(options) {
|
|
|
2067
3136
|
let ignoreFileCreated = false;
|
|
2068
3137
|
if (ignorePatterns && ignorePatterns.length > 0) {
|
|
2069
3138
|
try {
|
|
2070
|
-
const rulesyncignorePath =
|
|
3139
|
+
const rulesyncignorePath = join22(baseDir, ".rulesyncignore");
|
|
2071
3140
|
const ignoreContent = `${ignorePatterns.join("\n")}
|
|
2072
3141
|
`;
|
|
2073
3142
|
await writeFileContent(rulesyncignorePath, ignoreContent);
|
|
@@ -2083,7 +3152,7 @@ async function importConfiguration(options) {
|
|
|
2083
3152
|
let mcpFileCreated = false;
|
|
2084
3153
|
if (mcpServers && Object.keys(mcpServers).length > 0) {
|
|
2085
3154
|
try {
|
|
2086
|
-
const mcpPath =
|
|
3155
|
+
const mcpPath = join22(baseDir, rulesDir, ".mcp.json");
|
|
2087
3156
|
const mcpContent = `${JSON.stringify({ mcpServers }, null, 2)}
|
|
2088
3157
|
`;
|
|
2089
3158
|
await writeFileContent(mcpPath, mcpContent);
|
|
@@ -2105,13 +3174,13 @@ async function importConfiguration(options) {
|
|
|
2105
3174
|
};
|
|
2106
3175
|
}
|
|
2107
3176
|
function generateRuleFileContent(rule) {
|
|
2108
|
-
const frontmatter =
|
|
3177
|
+
const frontmatter = matter5.stringify("", rule.frontmatter);
|
|
2109
3178
|
return frontmatter + rule.content;
|
|
2110
3179
|
}
|
|
2111
3180
|
async function generateUniqueFilename(rulesDir, baseFilename) {
|
|
2112
3181
|
let filename = baseFilename;
|
|
2113
3182
|
let counter = 1;
|
|
2114
|
-
while (await fileExists(
|
|
3183
|
+
while (await fileExists(join22(rulesDir, `${filename}.md`))) {
|
|
2115
3184
|
filename = `${baseFilename}-${counter}`;
|
|
2116
3185
|
counter++;
|
|
2117
3186
|
}
|
|
@@ -2121,6 +3190,8 @@ async function generateUniqueFilename(rulesDir, baseFilename) {
|
|
|
2121
3190
|
// src/cli/commands/import.ts
|
|
2122
3191
|
async function importCommand(options = {}) {
|
|
2123
3192
|
const tools = [];
|
|
3193
|
+
if (options.augmentcode) tools.push("augmentcode");
|
|
3194
|
+
if (options["augmentcode-legacy"]) tools.push("augmentcode-legacy");
|
|
2124
3195
|
if (options.claudecode) tools.push("claudecode");
|
|
2125
3196
|
if (options.cursor) tools.push("cursor");
|
|
2126
3197
|
if (options.copilot) tools.push("copilot");
|
|
@@ -2129,7 +3200,7 @@ async function importCommand(options = {}) {
|
|
|
2129
3200
|
if (options.geminicli) tools.push("geminicli");
|
|
2130
3201
|
if (tools.length === 0) {
|
|
2131
3202
|
console.error(
|
|
2132
|
-
"\u274C Please specify one tool to import from (--claudecode, --cursor, --copilot, --cline, --roo, --geminicli)"
|
|
3203
|
+
"\u274C Please specify one tool to import from (--augmentcode, --augmentcode-legacy, --claudecode, --cursor, --copilot, --cline, --roo, --geminicli)"
|
|
2133
3204
|
);
|
|
2134
3205
|
process.exit(1);
|
|
2135
3206
|
}
|
|
@@ -2176,7 +3247,7 @@ async function importCommand(options = {}) {
|
|
|
2176
3247
|
}
|
|
2177
3248
|
|
|
2178
3249
|
// src/cli/commands/init.ts
|
|
2179
|
-
import { join as
|
|
3250
|
+
import { join as join23 } from "path";
|
|
2180
3251
|
async function initCommand() {
|
|
2181
3252
|
const aiRulesDir = ".rulesync";
|
|
2182
3253
|
console.log("Initializing rulesync...");
|
|
@@ -2223,7 +3294,7 @@ globs: ["**/*"]
|
|
|
2223
3294
|
- Follow single responsibility principle
|
|
2224
3295
|
`
|
|
2225
3296
|
};
|
|
2226
|
-
const filepath =
|
|
3297
|
+
const filepath = join23(aiRulesDir, sampleFile.filename);
|
|
2227
3298
|
if (!await fileExists(filepath)) {
|
|
2228
3299
|
await writeFileContent(filepath, sampleFile.content);
|
|
2229
3300
|
console.log(`Created ${filepath}`);
|
|
@@ -2337,11 +3408,11 @@ async function watchCommand() {
|
|
|
2337
3408
|
persistent: true
|
|
2338
3409
|
});
|
|
2339
3410
|
let isGenerating = false;
|
|
2340
|
-
const handleChange = async (
|
|
3411
|
+
const handleChange = async (path5) => {
|
|
2341
3412
|
if (isGenerating) return;
|
|
2342
3413
|
isGenerating = true;
|
|
2343
3414
|
console.log(`
|
|
2344
|
-
\u{1F4DD} Detected change in ${
|
|
3415
|
+
\u{1F4DD} Detected change in ${path5}`);
|
|
2345
3416
|
try {
|
|
2346
3417
|
await generateCommand({ verbose: false });
|
|
2347
3418
|
console.log("\u2705 Regenerated configuration files");
|
|
@@ -2351,10 +3422,10 @@ async function watchCommand() {
|
|
|
2351
3422
|
isGenerating = false;
|
|
2352
3423
|
}
|
|
2353
3424
|
};
|
|
2354
|
-
watcher.on("change", handleChange).on("add", handleChange).on("unlink", (
|
|
3425
|
+
watcher.on("change", handleChange).on("add", handleChange).on("unlink", (path5) => {
|
|
2355
3426
|
console.log(`
|
|
2356
|
-
\u{1F5D1}\uFE0F Removed ${
|
|
2357
|
-
handleChange(
|
|
3427
|
+
\u{1F5D1}\uFE0F Removed ${path5}`);
|
|
3428
|
+
handleChange(path5);
|
|
2358
3429
|
}).on("error", (error) => {
|
|
2359
3430
|
console.error("\u274C Watcher error:", error);
|
|
2360
3431
|
});
|
|
@@ -2367,26 +3438,31 @@ async function watchCommand() {
|
|
|
2367
3438
|
|
|
2368
3439
|
// src/cli/index.ts
|
|
2369
3440
|
var program = new Command();
|
|
2370
|
-
program.name("rulesync").description("Unified AI rules management CLI tool").version("0.
|
|
3441
|
+
program.name("rulesync").description("Unified AI rules management CLI tool").version("0.51.0");
|
|
2371
3442
|
program.command("init").description("Initialize rulesync in current directory").action(initCommand);
|
|
2372
3443
|
program.command("add <filename>").description("Add a new rule file").action(addCommand);
|
|
2373
3444
|
program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
|
|
2374
|
-
program.command("import").description("Import configurations from AI tools to rulesync format").option("--claudecode", "Import from Claude Code (CLAUDE.md)").option("--cursor", "Import from Cursor (.cursorrules)").option("--copilot", "Import from GitHub Copilot (.github/copilot-instructions.md)").option("--cline", "Import from Cline (.cline/instructions.md)").option("--roo", "Import from Roo Code (.roo/instructions.md)").option("--geminicli", "Import from Gemini CLI (GEMINI.md)").option("-v, --verbose", "Verbose output").action(importCommand);
|
|
2375
|
-
program.command("generate").description("Generate configuration files for AI tools").option("--copilot", "Generate only for GitHub Copilot").option("--cursor", "Generate only for Cursor").option("--cline", "Generate only for Cline").option("--claudecode", "Generate only for Claude Code").option("--roo", "Generate only for Roo Code").option("--geminicli", "Generate only for Gemini CLI").option("--kiro", "Generate only for Kiro IDE").option("--delete", "Delete all existing files in output directories before generating").option(
|
|
3445
|
+
program.command("import").description("Import configurations from AI tools to rulesync format").option("--augmentcode", "Import from AugmentCode (.augment/rules/)").option("--augmentcode-legacy", "Import from AugmentCode legacy format (.augment-guidelines)").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("--junie", "Import from JetBrains Junie (.junie/guidelines.md)").option("-v, --verbose", "Verbose output").action(importCommand);
|
|
3446
|
+
program.command("generate").description("Generate configuration files for AI tools").option("--augmentcode", "Generate only for AugmentCode").option("--augmentcode-legacy", "Generate only for AugmentCode legacy format").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("--junie", "Generate only for JetBrains Junie").option("--kiro", "Generate only for Kiro IDE").option("--delete", "Delete all existing files in output directories before generating").option(
|
|
2376
3447
|
"-b, --base-dir <paths>",
|
|
2377
3448
|
"Base directories to generate files (comma-separated for multiple paths)"
|
|
2378
|
-
).option("-v, --verbose", "Verbose output").action(async (options) => {
|
|
3449
|
+
).option("-v, --verbose", "Verbose output").option("-c, --config <path>", "Path to configuration file").option("--no-config", "Disable configuration file loading").action(async (options) => {
|
|
2379
3450
|
const tools = [];
|
|
3451
|
+
if (options.augmentcode) tools.push("augmentcode");
|
|
3452
|
+
if (options["augmentcode-legacy"]) tools.push("augmentcode-legacy");
|
|
2380
3453
|
if (options.copilot) tools.push("copilot");
|
|
2381
3454
|
if (options.cursor) tools.push("cursor");
|
|
2382
3455
|
if (options.cline) tools.push("cline");
|
|
2383
3456
|
if (options.claudecode) tools.push("claudecode");
|
|
2384
3457
|
if (options.roo) tools.push("roo");
|
|
2385
3458
|
if (options.geminicli) tools.push("geminicli");
|
|
3459
|
+
if (options.junie) tools.push("junie");
|
|
2386
3460
|
if (options.kiro) tools.push("kiro");
|
|
2387
3461
|
const generateOptions = {
|
|
2388
3462
|
verbose: options.verbose,
|
|
2389
|
-
delete: options.delete
|
|
3463
|
+
delete: options.delete,
|
|
3464
|
+
config: options.config,
|
|
3465
|
+
noConfig: options.noConfig
|
|
2390
3466
|
};
|
|
2391
3467
|
if (tools.length > 0) {
|
|
2392
3468
|
generateOptions.tools = tools;
|
|
@@ -2399,4 +3475,5 @@ program.command("generate").description("Generate configuration files for AI too
|
|
|
2399
3475
|
program.command("validate").description("Validate rulesync configuration").action(validateCommand);
|
|
2400
3476
|
program.command("status").description("Show current status of rulesync").action(statusCommand);
|
|
2401
3477
|
program.command("watch").description("Watch for changes and auto-generate configurations").action(watchCommand);
|
|
3478
|
+
program.command("config").description("Show or initialize rulesync configuration").option("--init", "Initialize a new configuration file").option("--format <format>", "Configuration file format (jsonc, ts)", "jsonc").option("--targets <tools>", "Comma-separated list of tools to generate for").option("--exclude <tools>", "Comma-separated list of tools to exclude").option("--ai-rules-dir <dir>", "Directory containing AI rule files").option("--base-dir <path>", "Base directory for generation").option("--verbose", "Enable verbose output").option("--delete", "Delete existing files before generating").action(configCommand);
|
|
2402
3479
|
program.parse();
|