rulesync 0.49.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/dist/index.js CHANGED
@@ -1,33 +1,38 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ generateKiroMcp
4
+ } from "./chunk-QUJMXHNR.js";
2
5
  import {
3
6
  generateRooMcp
4
- } from "./chunk-MCADLVGY.js";
7
+ } from "./chunk-FFW6TGCM.js";
5
8
  import {
6
9
  generateAugmentcodeMcp
7
- } from "./chunk-6TAURCQP.js";
10
+ } from "./chunk-OPZOVKIL.js";
8
11
  import {
9
12
  generateClaudeMcp
10
- } from "./chunk-YVRWBSCK.js";
13
+ } from "./chunk-4MZXYV5H.js";
11
14
  import {
12
15
  generateClineMcp
13
- } from "./chunk-XHNIEO22.js";
16
+ } from "./chunk-AREA26HF.js";
14
17
  import {
15
18
  generateCopilotMcp
16
- } from "./chunk-G5LLOIO4.js";
19
+ } from "./chunk-Y2XH4E5R.js";
17
20
  import {
18
21
  generateCursorMcp
19
- } from "./chunk-6MFEIYHN.js";
22
+ } from "./chunk-TTHBLXOB.js";
20
23
  import {
21
24
  generateGeminiCliMcp
22
- } from "./chunk-QNHGYRJT.js";
25
+ } from "./chunk-IXCMY24P.js";
23
26
  import {
24
- generateKiroMcp
25
- } from "./chunk-R5HFWJ5L.js";
27
+ generateJunieMcp
28
+ } from "./chunk-Y26DXTAT.js";
26
29
  import {
30
+ ALL_TOOL_TARGETS,
27
31
  RulesyncTargetsSchema,
28
32
  ToolTargetSchema,
29
- ToolTargetsSchema
30
- } from "./chunk-IBJGN3JQ.js";
33
+ ToolTargetsSchema,
34
+ isToolTarget
35
+ } from "./chunk-USKQYIZ2.js";
31
36
 
32
37
  // src/cli/index.ts
33
38
  import { Command } from "commander";
@@ -49,19 +54,11 @@ function getDefaultConfig() {
49
54
  claudecode: ".",
50
55
  roo: ".roo/rules",
51
56
  geminicli: ".gemini/memories",
52
- kiro: ".kiro/steering"
57
+ kiro: ".kiro/steering",
58
+ junie: "."
53
59
  },
54
60
  watchEnabled: false,
55
- defaultTargets: [
56
- "augmentcode",
57
- "copilot",
58
- "cursor",
59
- "cline",
60
- "claudecode",
61
- "roo",
62
- "geminicli",
63
- "kiro"
64
- ]
61
+ defaultTargets: ALL_TOOL_TARGETS.filter((tool) => tool !== "augmentcode-legacy")
65
62
  };
66
63
  }
67
64
  function resolveTargets(targets, config) {
@@ -89,32 +86,770 @@ globs: []
89
86
  Add your rules here.
90
87
  `;
91
88
  }
92
- async function addCommand(filename) {
93
- try {
94
- const config = getDefaultConfig();
95
- const sanitizedFilename = sanitizeFilename(filename);
96
- const rulesDir = config.aiRulesDir;
97
- const filePath = path.join(rulesDir, `${sanitizedFilename}.md`);
98
- await mkdir(rulesDir, { recursive: true });
99
- const template = generateRuleTemplate(sanitizedFilename);
100
- await writeFile(filePath, template, "utf8");
101
- console.log(`\u2705 Created rule file: ${filePath}`);
102
- console.log(`\u{1F4DD} Edit the file to customize your rules.`);
103
- } catch (error) {
104
- console.error(
105
- `\u274C Failed to create rule file: ${error instanceof Error ? error.message : String(error)}`
106
- );
107
- process.exit(3);
89
+ async function addCommand(filename) {
90
+ try {
91
+ const config = getDefaultConfig();
92
+ const sanitizedFilename = sanitizeFilename(filename);
93
+ const rulesDir = config.aiRulesDir;
94
+ const filePath = path.join(rulesDir, `${sanitizedFilename}.md`);
95
+ await mkdir(rulesDir, { recursive: true });
96
+ const template = generateRuleTemplate(sanitizedFilename);
97
+ await writeFile(filePath, template, "utf8");
98
+ console.log(`\u2705 Created rule file: ${filePath}`);
99
+ console.log(`\u{1F4DD} Edit the file to customize your rules.`);
100
+ } catch (error) {
101
+ console.error(
102
+ `\u274C Failed to create rule file: ${error instanceof Error ? error.message : String(error)}`
103
+ );
104
+ process.exit(3);
105
+ }
106
+ }
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
+ }
108
843
  }
844
+ return patterns;
109
845
  }
110
846
 
111
847
  // src/generators/ignore/augmentcode.ts
112
- import { join as join2 } from "path";
113
848
  async function generateAugmentCodeIgnoreFiles(rules, config, baseDir) {
114
849
  const outputs = [];
115
850
  const augmentignoreContent = generateAugmentignoreContent(rules);
116
851
  const outputPath = baseDir || process.cwd();
117
- const filepath = join2(outputPath, ".augmentignore");
852
+ const filepath = join3(outputPath, ".augmentignore");
118
853
  outputs.push({
119
854
  tool: "augmentcode",
120
855
  filepath,
@@ -217,9 +952,14 @@ function generateAugmentignoreContent(rules) {
217
952
  ""
218
953
  );
219
954
  const rulePatterns = extractIgnorePatternsFromRules(rules);
220
- if (rulePatterns.length > 0) {
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) {
221
961
  lines.push("# Project-specific patterns from rulesync rules");
222
- lines.push(...rulePatterns);
962
+ lines.push(...allPatterns);
223
963
  lines.push("");
224
964
  }
225
965
  lines.push(
@@ -242,85 +982,162 @@ function generateAugmentignoreContent(rules) {
242
982
  );
243
983
  return lines.join("\n");
244
984
  }
245
- function extractIgnorePatternsFromRules(rules) {
246
- const patterns = [];
247
- for (const rule of rules) {
248
- if (rule.frontmatter.globs && rule.frontmatter.globs.length > 0) {
249
- for (const glob of rule.frontmatter.globs) {
250
- if (shouldExcludeFromAugmentCode(glob)) {
251
- patterns.push(`# Exclude: ${rule.frontmatter.description}`);
252
- patterns.push(glob);
253
- }
254
- }
255
- }
256
- const contentPatterns = extractAugmentCodeIgnorePatternsFromContent(rule.content);
257
- patterns.push(...contentPatterns);
258
- }
259
- return patterns;
260
- }
261
- function shouldExcludeFromAugmentCode(glob) {
262
- const excludePatterns = [
263
- // Large generated files that slow indexing
264
- "**/assets/generated/**",
265
- "**/public/build/**",
266
- // Test fixtures with potentially sensitive data
267
- "**/tests/fixtures/**",
268
- "**/test/fixtures/**",
269
- "**/*.fixture.*",
270
- // Build outputs that provide little value for AI context
271
- "**/dist/**",
272
- "**/build/**",
273
- "**/coverage/**",
274
- // Configuration that might contain sensitive data
275
- "**/config/production/**",
276
- "**/config/secrets/**",
277
- "**/deploy/prod/**",
278
- // Internal documentation
279
- "**/internal-docs/**",
280
- "**/proprietary/**",
281
- "**/personal-notes/**",
282
- "**/private/**",
283
- "**/confidential/**"
284
- ];
285
- return excludePatterns.some((pattern) => {
286
- const regex = new RegExp(pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*"));
287
- return regex.test(glob);
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
288
997
  });
998
+ return outputs;
289
999
  }
290
- function extractAugmentCodeIgnorePatternsFromContent(content) {
291
- const patterns = [];
292
- const lines = content.split("\n");
293
- for (const line of lines) {
294
- const trimmed = line.trim();
295
- if (trimmed.startsWith("# AUGMENT_IGNORE:") || trimmed.startsWith("# augmentignore:")) {
296
- const pattern = trimmed.replace(/^# (AUGMENT_IGNORE|augmentignore):\s*/, "").trim();
297
- if (pattern) {
298
- patterns.push(pattern);
299
- }
300
- }
301
- if (trimmed.startsWith("# AUGMENT_INCLUDE:") || trimmed.startsWith("# augmentinclude:")) {
302
- const pattern = trimmed.replace(/^# (AUGMENT_INCLUDE|augmentinclude):\s*/, "").trim();
303
- if (pattern) {
304
- patterns.push(`!${pattern}`);
305
- }
306
- }
307
- if (trimmed.includes("large file") || trimmed.includes("binary") || trimmed.includes("media")) {
308
- const matches = trimmed.match(/['"`]([^'"`]+\.(mp4|avi|zip|tar\.gz|rar|pdf|doc|xlsx))['"`]/g);
309
- if (matches) {
310
- patterns.push(...matches.map((m) => m.replace(/['"`]/g, "")));
311
- }
312
- }
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("");
313
1122
  }
314
- return patterns;
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");
315
1132
  }
316
1133
 
317
1134
  // src/generators/ignore/kiro.ts
318
- import { join as join3 } from "path";
1135
+ import { join as join5 } from "path";
319
1136
  async function generateKiroIgnoreFiles(rules, config, baseDir) {
320
1137
  const outputs = [];
321
- const aiignoreContent = generateAiignoreContent(rules);
1138
+ const aiignoreContent = generateAiignoreContent2(rules);
322
1139
  const outputPath = baseDir || process.cwd();
323
- const filepath = join3(outputPath, ".aiignore");
1140
+ const filepath = join5(outputPath, ".aiignore");
324
1141
  outputs.push({
325
1142
  tool: "kiro",
326
1143
  filepath,
@@ -328,7 +1145,7 @@ async function generateKiroIgnoreFiles(rules, config, baseDir) {
328
1145
  });
329
1146
  return outputs;
330
1147
  }
331
- function generateAiignoreContent(rules) {
1148
+ function generateAiignoreContent2(rules) {
332
1149
  const lines = [
333
1150
  "# Generated by rulesync - Kiro AI-specific exclusions",
334
1151
  "# This file excludes files that can be in Git but shouldn't be read by the AI",
@@ -341,178 +1158,49 @@ function generateAiignoreContent(rules) {
341
1158
  "*.sqlite",
342
1159
  "*.db",
343
1160
  "",
344
- "# Large binary files",
345
- "*.zip",
346
- "*.tar.gz",
347
- "*.rar",
348
- "",
349
- "# Sensitive documentation",
350
- "internal-docs/",
351
- "confidential/",
352
- "",
353
- "# Test data that might confuse AI",
354
- "test/fixtures/large-*.json",
355
- "benchmark-results/",
356
- "",
357
- "# Reinforce critical exclusions from .gitignore",
358
- "*.pem",
359
- "*.key",
360
- ".env*",
361
- ""
362
- );
363
- const rulePatterns = extractIgnorePatternsFromRules2(rules);
364
- if (rulePatterns.length > 0) {
365
- lines.push("# Project-specific exclusions from rulesync rules");
366
- lines.push(...rulePatterns);
367
- lines.push("");
368
- }
369
- return lines.join("\n");
370
- }
371
- function extractIgnorePatternsFromRules2(rules) {
372
- const patterns = [];
373
- for (const rule of rules) {
374
- if (rule.frontmatter.globs && rule.frontmatter.globs.length > 0) {
375
- for (const glob of rule.frontmatter.globs) {
376
- if (shouldExcludeFromAI(glob)) {
377
- patterns.push(`# Exclude: ${rule.frontmatter.description}`);
378
- patterns.push(glob);
379
- }
380
- }
381
- }
382
- const contentPatterns = extractIgnorePatternsFromContent(rule.content);
383
- patterns.push(...contentPatterns);
384
- }
385
- return patterns;
386
- }
387
- function shouldExcludeFromAI(glob) {
388
- const excludePatterns = [
389
- // Test and fixture files that might be large or confusing
390
- "**/test/fixtures/**",
391
- "**/tests/fixtures/**",
392
- "**/*.fixture.*",
393
- // Build and generated files
394
- "**/dist/**",
395
- "**/build/**",
396
- "**/coverage/**",
397
- // Configuration that might contain sensitive data
398
- "**/config/production/**",
399
- "**/config/prod/**",
400
- "**/*.prod.*",
401
- // Documentation that might be sensitive
402
- "**/internal/**",
403
- "**/private/**",
404
- "**/confidential/**"
405
- ];
406
- return excludePatterns.some((pattern) => {
407
- const regex = new RegExp(pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*"));
408
- return regex.test(glob);
409
- });
410
- }
411
- function extractIgnorePatternsFromContent(content) {
412
- const patterns = [];
413
- const lines = content.split("\n");
414
- for (const line of lines) {
415
- const trimmed = line.trim();
416
- if (trimmed.startsWith("# IGNORE:") || trimmed.startsWith("# aiignore:")) {
417
- const pattern = trimmed.replace(/^# (IGNORE|aiignore):\s*/, "").trim();
418
- if (pattern) {
419
- patterns.push(pattern);
420
- }
421
- }
422
- if (trimmed.includes("exclude") || trimmed.includes("ignore")) {
423
- const matches = trimmed.match(/['"`]([^'"`]+\.(log|tmp|cache|temp))['"`]/g);
424
- if (matches) {
425
- patterns.push(...matches.map((m) => m.replace(/['"`]/g, "")));
426
- }
427
- }
1161
+ "# Large binary files",
1162
+ "*.zip",
1163
+ "*.tar.gz",
1164
+ "*.rar",
1165
+ "",
1166
+ "# Sensitive documentation",
1167
+ "internal-docs/",
1168
+ "confidential/",
1169
+ "",
1170
+ "# Test data that might confuse AI",
1171
+ "test/fixtures/large-*.json",
1172
+ "benchmark-results/",
1173
+ "",
1174
+ "# Reinforce critical exclusions from .gitignore",
1175
+ "*.pem",
1176
+ "*.key",
1177
+ ".env*",
1178
+ ""
1179
+ );
1180
+ const rulePatterns = extractIgnorePatternsFromRules(rules);
1181
+ if (rulePatterns.length > 0) {
1182
+ lines.push("# Project-specific exclusions from rulesync rules");
1183
+ lines.push(...rulePatterns);
1184
+ lines.push("");
428
1185
  }
429
- return patterns;
1186
+ return lines.join("\n");
430
1187
  }
431
1188
 
432
1189
  // src/generators/rules/augmentcode.ts
433
- import { join as join7 } from "path";
1190
+ import { join as join8 } from "path";
434
1191
 
435
1192
  // src/generators/rules/shared-helpers.ts
436
- import { join as join6 } from "path";
1193
+ import { join as join7 } from "path";
437
1194
 
438
1195
  // src/utils/ignore.ts
439
- import { join as join5 } from "path";
1196
+ import { join as join6 } from "path";
440
1197
  import micromatch from "micromatch";
441
-
442
- // src/utils/file.ts
443
- import { mkdir as mkdir2, readdir, readFile, rm, stat, writeFile as writeFile2 } from "fs/promises";
444
- import { dirname, join as join4 } from "path";
445
- async function ensureDir(dirPath) {
446
- try {
447
- await stat(dirPath);
448
- } catch {
449
- await mkdir2(dirPath, { recursive: true });
450
- }
451
- }
452
- async function readFileContent(filepath) {
453
- return readFile(filepath, "utf-8");
454
- }
455
- async function writeFileContent(filepath, content) {
456
- await ensureDir(dirname(filepath));
457
- await writeFile2(filepath, content, "utf-8");
458
- }
459
- async function fileExists(filepath) {
460
- try {
461
- await stat(filepath);
462
- return true;
463
- } catch {
464
- return false;
465
- }
466
- }
467
- async function findFiles(dir, extension = ".md") {
468
- try {
469
- const files = await readdir(dir);
470
- return files.filter((file) => file.endsWith(extension)).map((file) => join4(dir, file));
471
- } catch {
472
- return [];
473
- }
474
- }
475
- async function removeDirectory(dirPath) {
476
- const dangerousPaths = [".", "/", "~", "src", "node_modules"];
477
- if (dangerousPaths.includes(dirPath) || dirPath === "") {
478
- console.warn(`Skipping deletion of dangerous path: ${dirPath}`);
479
- return;
480
- }
481
- try {
482
- if (await fileExists(dirPath)) {
483
- await rm(dirPath, { recursive: true, force: true });
484
- }
485
- } catch (error) {
486
- console.warn(`Failed to remove directory ${dirPath}:`, error);
487
- }
488
- }
489
- async function removeFile(filepath) {
490
- try {
491
- if (await fileExists(filepath)) {
492
- await rm(filepath);
493
- }
494
- } catch (error) {
495
- console.warn(`Failed to remove file ${filepath}:`, error);
496
- }
497
- }
498
- async function removeClaudeGeneratedFiles() {
499
- const filesToRemove = ["CLAUDE.md", ".claude/memories"];
500
- for (const fileOrDir of filesToRemove) {
501
- if (fileOrDir.endsWith("/memories")) {
502
- await removeDirectory(fileOrDir);
503
- } else {
504
- await removeFile(fileOrDir);
505
- }
506
- }
507
- }
508
-
509
- // src/utils/ignore.ts
510
1198
  var cachedIgnorePatterns = null;
511
1199
  async function loadIgnorePatterns(baseDir = process.cwd()) {
512
1200
  if (cachedIgnorePatterns) {
513
1201
  return cachedIgnorePatterns;
514
1202
  }
515
- const ignorePath = join5(baseDir, ".rulesyncignore");
1203
+ const ignorePath = join6(baseDir, ".rulesyncignore");
516
1204
  if (!await fileExists(ignorePath)) {
517
1205
  cachedIgnorePatterns = { patterns: [] };
518
1206
  return cachedIgnorePatterns;
@@ -557,7 +1245,7 @@ function filterIgnoredFiles(files, ignorePatterns) {
557
1245
 
558
1246
  // src/generators/rules/shared-helpers.ts
559
1247
  function resolveOutputDir(config, tool, baseDir) {
560
- return baseDir ? join6(baseDir, config.outputPaths[tool]) : config.outputPaths[tool];
1248
+ return baseDir ? join7(baseDir, config.outputPaths[tool]) : config.outputPaths[tool];
561
1249
  }
562
1250
  function createOutputsArray() {
563
1251
  return [];
@@ -566,7 +1254,7 @@ function addOutput(outputs, tool, config, baseDir, relativePath, content) {
566
1254
  const outputDir = resolveOutputDir(config, tool, baseDir);
567
1255
  outputs.push({
568
1256
  tool,
569
- filepath: join6(outputDir, relativePath),
1257
+ filepath: join7(outputDir, relativePath),
570
1258
  content
571
1259
  });
572
1260
  }
@@ -575,7 +1263,7 @@ async function generateRulesConfig(rules, config, generatorConfig, baseDir) {
575
1263
  for (const rule of rules) {
576
1264
  const content = generatorConfig.generateContent(rule);
577
1265
  const outputDir = resolveOutputDir(config, generatorConfig.tool, baseDir);
578
- const filepath = generatorConfig.pathResolver ? generatorConfig.pathResolver(rule, outputDir) : join6(outputDir, `${rule.filename}${generatorConfig.fileExtension}`);
1266
+ const filepath = generatorConfig.pathResolver ? generatorConfig.pathResolver(rule, outputDir) : join7(outputDir, `${rule.filename}${generatorConfig.fileExtension}`);
579
1267
  outputs.push({
580
1268
  tool: generatorConfig.tool,
581
1269
  filepath,
@@ -584,13 +1272,57 @@ async function generateRulesConfig(rules, config, generatorConfig, baseDir) {
584
1272
  }
585
1273
  const ignorePatterns = await loadIgnorePatterns(baseDir);
586
1274
  if (ignorePatterns.patterns.length > 0) {
587
- const ignorePath = baseDir ? join6(baseDir, generatorConfig.ignoreFileName) : generatorConfig.ignoreFileName;
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;
588
1313
  const ignoreContent = generateIgnoreFile(ignorePatterns.patterns, generatorConfig.tool);
589
1314
  outputs.push({
590
1315
  tool: generatorConfig.tool,
591
1316
  filepath: ignorePath,
592
1317
  content: ignoreContent
593
1318
  });
1319
+ if (generatorConfig.updateAdditionalConfig) {
1320
+ const additionalOutputs = await generatorConfig.updateAdditionalConfig(
1321
+ ignorePatterns.patterns,
1322
+ baseDir
1323
+ );
1324
+ outputs.push(...additionalOutputs);
1325
+ }
594
1326
  }
595
1327
  return outputs;
596
1328
  }
@@ -627,7 +1359,7 @@ async function generateAugmentcodeConfig(rules, config, baseDir) {
627
1359
  "augmentcode",
628
1360
  config,
629
1361
  baseDir,
630
- join7(".augment", "rules", `${rule.filename}.md`),
1362
+ join8(".augment", "rules", `${rule.filename}.md`),
631
1363
  generateRuleFile(rule)
632
1364
  );
633
1365
  });
@@ -680,42 +1412,29 @@ function generateLegacyGuidelinesFile(allRules) {
680
1412
  }
681
1413
 
682
1414
  // src/generators/rules/claudecode.ts
683
- import { join as join8 } from "path";
684
-
685
- // src/types/claudecode.ts
686
- import { z } from "zod/mini";
687
- var ClaudeSettingsSchema = z.looseObject({
688
- permissions: z._default(
689
- z.looseObject({
690
- deny: z._default(z.array(z.string()), [])
691
- }),
692
- { deny: [] }
693
- )
694
- });
695
-
696
- // src/generators/rules/claudecode.ts
1415
+ import { join as join9 } from "path";
697
1416
  async function generateClaudecodeConfig(rules, config, baseDir) {
698
1417
  const outputs = [];
699
1418
  const rootRules = rules.filter((r) => r.frontmatter.root === true);
700
1419
  const detailRules = rules.filter((r) => r.frontmatter.root === false);
701
1420
  const claudeMdContent = generateClaudeMarkdown(rootRules, detailRules);
702
- const claudeOutputDir = baseDir ? join8(baseDir, config.outputPaths.claudecode) : config.outputPaths.claudecode;
1421
+ const claudeOutputDir = baseDir ? join9(baseDir, config.outputPaths.claudecode) : config.outputPaths.claudecode;
703
1422
  outputs.push({
704
1423
  tool: "claudecode",
705
- filepath: join8(claudeOutputDir, "CLAUDE.md"),
1424
+ filepath: join9(claudeOutputDir, "CLAUDE.md"),
706
1425
  content: claudeMdContent
707
1426
  });
708
1427
  for (const rule of detailRules) {
709
1428
  const memoryContent = generateMemoryFile(rule);
710
1429
  outputs.push({
711
1430
  tool: "claudecode",
712
- filepath: join8(claudeOutputDir, ".claude", "memories", `${rule.filename}.md`),
1431
+ filepath: join9(claudeOutputDir, ".claude", "memories", `${rule.filename}.md`),
713
1432
  content: memoryContent
714
1433
  });
715
1434
  }
716
1435
  const ignorePatterns = await loadIgnorePatterns(baseDir);
717
1436
  if (ignorePatterns.patterns.length > 0) {
718
- const settingsPath = baseDir ? join8(baseDir, ".claude", "settings.json") : join8(".claude", "settings.json");
1437
+ const settingsPath = baseDir ? join9(baseDir, ".claude", "settings.json") : join9(".claude", "settings.json");
719
1438
  await updateClaudeSettings(settingsPath, ignorePatterns.patterns);
720
1439
  }
721
1440
  return outputs;
@@ -794,7 +1513,7 @@ async function generateClineConfig(rules, config, baseDir) {
794
1513
  }
795
1514
 
796
1515
  // src/generators/rules/copilot.ts
797
- import { join as join9 } from "path";
1516
+ import { join as join10 } from "path";
798
1517
  async function generateCopilotConfig(rules, config, baseDir) {
799
1518
  return generateComplexRulesConfig(
800
1519
  rules,
@@ -806,7 +1525,7 @@ async function generateCopilotConfig(rules, config, baseDir) {
806
1525
  generateContent: generateCopilotMarkdown,
807
1526
  getOutputPath: (rule, outputDir) => {
808
1527
  const baseFilename = rule.filename.replace(/\.md$/, "");
809
- return join9(outputDir, `${baseFilename}.instructions.md`);
1528
+ return join10(outputDir, `${baseFilename}.instructions.md`);
810
1529
  }
811
1530
  },
812
1531
  baseDir
@@ -827,7 +1546,7 @@ function generateCopilotMarkdown(rule) {
827
1546
  }
828
1547
 
829
1548
  // src/generators/rules/cursor.ts
830
- import { join as join10 } from "path";
1549
+ import { join as join11 } from "path";
831
1550
  async function generateCursorConfig(rules, config, baseDir) {
832
1551
  return generateComplexRulesConfig(
833
1552
  rules,
@@ -838,7 +1557,7 @@ async function generateCursorConfig(rules, config, baseDir) {
838
1557
  ignoreFileName: ".cursorignore",
839
1558
  generateContent: generateCursorMarkdown,
840
1559
  getOutputPath: (rule, outputDir) => {
841
- return join10(outputDir, `${rule.filename}.mdc`);
1560
+ return join11(outputDir, `${rule.filename}.mdc`);
842
1561
  }
843
1562
  },
844
1563
  baseDir
@@ -898,39 +1617,18 @@ function determineCursorRuleType(frontmatter) {
898
1617
  }
899
1618
 
900
1619
  // src/generators/rules/geminicli.ts
901
- import { join as join11 } from "path";
902
1620
  async function generateGeminiConfig(rules, config, baseDir) {
903
- const outputs = [];
904
- const rootRule = rules.find((rule) => rule.frontmatter.root === true);
905
- const memoryRules = rules.filter((rule) => rule.frontmatter.root === false);
906
- for (const rule of memoryRules) {
907
- const content = generateGeminiMemoryMarkdown(rule);
908
- const outputDir = baseDir ? join11(baseDir, config.outputPaths.geminicli) : config.outputPaths.geminicli;
909
- const filepath = join11(outputDir, `${rule.filename}.md`);
910
- outputs.push({
911
- tool: "geminicli",
912
- filepath,
913
- content
914
- });
915
- }
916
- const rootContent = generateGeminiRootMarkdown(rootRule, memoryRules, baseDir);
917
- const rootFilepath = baseDir ? join11(baseDir, "GEMINI.md") : "GEMINI.md";
918
- outputs.push({
1621
+ const generatorConfig = {
919
1622
  tool: "geminicli",
920
- filepath: rootFilepath,
921
- content: rootContent
922
- });
923
- const ignorePatterns = await loadIgnorePatterns(baseDir);
924
- if (ignorePatterns.patterns.length > 0) {
925
- const aiexcludePath = baseDir ? join11(baseDir, ".aiexclude") : ".aiexclude";
926
- const aiexcludeContent = generateAiexclude(ignorePatterns.patterns);
927
- outputs.push({
928
- tool: "geminicli",
929
- filepath: aiexcludePath,
930
- content: aiexcludeContent
931
- });
932
- }
933
- 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);
934
1632
  }
935
1633
  function generateGeminiMemoryMarkdown(rule) {
936
1634
  return rule.content.trim();
@@ -959,14 +1657,32 @@ function generateGeminiRootMarkdown(rootRule, memoryRules, _baseDir) {
959
1657
  }
960
1658
  return lines.join("\n");
961
1659
  }
962
- function generateAiexclude(patterns) {
963
- const lines = [
964
- "# Generated by rulesync from .rulesyncignore",
965
- "# This file is automatically generated. Do not edit manually.",
966
- "",
967
- ...patterns
968
- ];
969
- return lines.join("\n");
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();
970
1686
  }
971
1687
 
972
1688
  // src/generators/rules/kiro.ts
@@ -1030,7 +1746,10 @@ async function generateConfigurations(rules, config, targetTools, baseDir) {
1030
1746
  function filterRulesForTool(rules, tool, config) {
1031
1747
  return rules.filter((rule) => {
1032
1748
  const targets = resolveTargets(rule.frontmatter.targets, config);
1033
- return targets.includes(tool);
1749
+ if (!targets.includes(tool)) {
1750
+ return false;
1751
+ }
1752
+ return isToolSpecificRule(rule, tool);
1034
1753
  });
1035
1754
  }
1036
1755
  async function generateForTool(tool, rules, config, baseDir) {
@@ -1061,6 +1780,11 @@ async function generateForTool(tool, rules, config, baseDir) {
1061
1780
  return generateRooConfig(rules, config, baseDir);
1062
1781
  case "geminicli":
1063
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
+ }
1064
1788
  case "kiro": {
1065
1789
  const kiroRulesOutputs = await generateKiroConfig(rules, config, baseDir);
1066
1790
  const kiroIgnoreOutputs = await generateKiroIgnoreFiles(rules, config, baseDir);
@@ -1075,75 +1799,6 @@ async function generateForTool(tool, rules, config, baseDir) {
1075
1799
  // src/core/parser.ts
1076
1800
  import { basename } from "path";
1077
1801
  import matter from "gray-matter";
1078
-
1079
- // src/types/config.ts
1080
- import { z as z2 } from "zod/mini";
1081
- var ConfigSchema = z2.object({
1082
- aiRulesDir: z2.string(),
1083
- outputPaths: z2.record(ToolTargetSchema, z2.string()),
1084
- watchEnabled: z2.boolean(),
1085
- defaultTargets: ToolTargetsSchema
1086
- });
1087
-
1088
- // src/types/mcp.ts
1089
- import { z as z3 } from "zod/mini";
1090
- var McpTransportTypeSchema = z3.enum(["stdio", "sse", "http"]);
1091
- var McpServerBaseSchema = z3.object({
1092
- command: z3.optional(z3.string()),
1093
- args: z3.optional(z3.array(z3.string())),
1094
- url: z3.optional(z3.string()),
1095
- httpUrl: z3.optional(z3.string()),
1096
- env: z3.optional(z3.record(z3.string(), z3.string())),
1097
- disabled: z3.optional(z3.boolean()),
1098
- networkTimeout: z3.optional(z3.number()),
1099
- timeout: z3.optional(z3.number()),
1100
- trust: z3.optional(z3.boolean()),
1101
- cwd: z3.optional(z3.string()),
1102
- transport: z3.optional(McpTransportTypeSchema),
1103
- type: z3.optional(z3.enum(["sse", "streamable-http"])),
1104
- alwaysAllow: z3.optional(z3.array(z3.string())),
1105
- tools: z3.optional(z3.array(z3.string())),
1106
- kiroAutoApprove: z3.optional(z3.array(z3.string())),
1107
- kiroAutoBlock: z3.optional(z3.array(z3.string()))
1108
- });
1109
- var RulesyncMcpServerSchema = z3.extend(McpServerBaseSchema, {
1110
- targets: z3.optional(RulesyncTargetsSchema)
1111
- });
1112
- var McpConfigSchema = z3.object({
1113
- mcpServers: z3.record(z3.string(), McpServerBaseSchema)
1114
- });
1115
- var RulesyncMcpConfigSchema = z3.object({
1116
- mcpServers: z3.record(z3.string(), RulesyncMcpServerSchema)
1117
- });
1118
-
1119
- // src/types/rules.ts
1120
- import { z as z4 } from "zod/mini";
1121
- var RuleFrontmatterSchema = z4.object({
1122
- root: z4.boolean(),
1123
- targets: RulesyncTargetsSchema,
1124
- description: z4.string(),
1125
- globs: z4.array(z4.string()),
1126
- cursorRuleType: z4.optional(z4.enum(["always", "manual", "specificFiles", "intelligently"])),
1127
- tags: z4.optional(z4.array(z4.string()))
1128
- });
1129
- var ParsedRuleSchema = z4.object({
1130
- frontmatter: RuleFrontmatterSchema,
1131
- content: z4.string(),
1132
- filename: z4.string(),
1133
- filepath: z4.string()
1134
- });
1135
- var GeneratedOutputSchema = z4.object({
1136
- tool: ToolTargetSchema,
1137
- filepath: z4.string(),
1138
- content: z4.string()
1139
- });
1140
- var GenerateOptionsSchema = z4.object({
1141
- targetTools: z4.optional(ToolTargetsSchema),
1142
- outputDir: z4.optional(z4.string()),
1143
- watch: z4.optional(z4.boolean())
1144
- });
1145
-
1146
- // src/core/parser.ts
1147
1802
  async function parseRulesFromDirectory(aiRulesDir) {
1148
1803
  const ignorePatterns = await loadIgnorePatterns();
1149
1804
  const allRuleFiles = await findFiles(aiRulesDir, ".md");
@@ -1245,13 +1900,13 @@ async function validateRule(rule) {
1245
1900
  }
1246
1901
 
1247
1902
  // src/core/mcp-generator.ts
1248
- import * as path3 from "path";
1903
+ import * as path4 from "path";
1249
1904
 
1250
1905
  // src/core/mcp-parser.ts
1251
1906
  import * as fs from "fs";
1252
- import * as path2 from "path";
1907
+ import * as path3 from "path";
1253
1908
  function parseMcpConfig(projectRoot) {
1254
- const mcpPath = path2.join(projectRoot, ".rulesync", ".mcp.json");
1909
+ const mcpPath = path3.join(projectRoot, ".rulesync", ".mcp.json");
1255
1910
  if (!fs.existsSync(mcpPath)) {
1256
1911
  return null;
1257
1912
  }
@@ -1275,7 +1930,7 @@ function parseMcpConfig(projectRoot) {
1275
1930
  }
1276
1931
 
1277
1932
  // src/core/mcp-generator.ts
1278
- async function generateMcpConfigs(projectRoot, baseDir) {
1933
+ async function generateMcpConfigs(projectRoot, baseDir, targetTools) {
1279
1934
  const results = [];
1280
1935
  const targetRoot = baseDir || projectRoot;
1281
1936
  const config = parseMcpConfig(projectRoot);
@@ -1285,55 +1940,70 @@ async function generateMcpConfigs(projectRoot, baseDir) {
1285
1940
  const generators = [
1286
1941
  {
1287
1942
  tool: "augmentcode-project",
1288
- path: path3.join(targetRoot, ".mcp.json"),
1943
+ path: path4.join(targetRoot, ".mcp.json"),
1289
1944
  generate: () => generateAugmentcodeMcp(config)
1290
1945
  },
1291
1946
  {
1292
1947
  tool: "augmentcode-legacy-project",
1293
- path: path3.join(targetRoot, ".mcp.json"),
1948
+ path: path4.join(targetRoot, ".mcp.json"),
1294
1949
  generate: () => generateAugmentcodeMcp(config)
1295
1950
  },
1296
1951
  {
1297
1952
  tool: "claude-project",
1298
- path: path3.join(targetRoot, ".mcp.json"),
1953
+ path: path4.join(targetRoot, ".mcp.json"),
1299
1954
  generate: () => generateClaudeMcp(config)
1300
1955
  },
1301
1956
  {
1302
1957
  tool: "copilot-editor",
1303
- path: path3.join(targetRoot, ".vscode", "mcp.json"),
1958
+ path: path4.join(targetRoot, ".vscode", "mcp.json"),
1304
1959
  generate: () => generateCopilotMcp(config, "editor")
1305
1960
  },
1306
1961
  {
1307
1962
  tool: "cursor-project",
1308
- path: path3.join(targetRoot, ".cursor", "mcp.json"),
1963
+ path: path4.join(targetRoot, ".cursor", "mcp.json"),
1309
1964
  generate: () => generateCursorMcp(config)
1310
1965
  },
1311
1966
  {
1312
1967
  tool: "cline-project",
1313
- path: path3.join(targetRoot, ".cline", "mcp.json"),
1968
+ path: path4.join(targetRoot, ".cline", "mcp.json"),
1314
1969
  generate: () => generateClineMcp(config)
1315
1970
  },
1316
1971
  {
1317
1972
  tool: "gemini-project",
1318
- path: path3.join(targetRoot, ".gemini", "settings.json"),
1973
+ path: path4.join(targetRoot, ".gemini", "settings.json"),
1319
1974
  generate: () => generateGeminiCliMcp(config)
1320
1975
  },
1976
+ {
1977
+ tool: "junie-project",
1978
+ path: path4.join(targetRoot, ".junie", "mcp-config.json"),
1979
+ generate: () => generateJunieMcp(config)
1980
+ },
1321
1981
  {
1322
1982
  tool: "kiro-project",
1323
- path: path3.join(targetRoot, ".kiro", "mcp.json"),
1983
+ path: path4.join(targetRoot, ".kiro", "mcp.json"),
1324
1984
  generate: () => generateKiroMcp(config)
1325
1985
  },
1326
1986
  {
1327
1987
  tool: "roo-project",
1328
- path: path3.join(targetRoot, ".roo", "mcp.json"),
1988
+ path: path4.join(targetRoot, ".roo", "mcp.json"),
1329
1989
  generate: () => generateRooMcp(config)
1330
1990
  }
1331
1991
  ];
1332
- for (const generator of generators) {
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) {
1333
2003
  try {
1334
2004
  const content = generator.generate();
1335
2005
  const parsed = JSON.parse(content);
1336
- if (generator.tool.includes("augmentcode") || 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")) {
1337
2007
  if (!parsed.mcpServers || Object.keys(parsed.mcpServers).length === 0) {
1338
2008
  results.push({
1339
2009
  tool: generator.tool,
@@ -1373,15 +2043,58 @@ async function generateMcpConfigs(projectRoot, baseDir) {
1373
2043
 
1374
2044
  // src/cli/commands/generate.ts
1375
2045
  async function generateCommand(options = {}) {
1376
- const config = getDefaultConfig();
1377
- const baseDirs = options.baseDirs || [process.cwd()];
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
+ }
1378
2091
  console.log("Generating configuration files...");
1379
2092
  if (!await fileExists(config.aiRulesDir)) {
1380
2093
  console.error("\u274C .rulesync directory not found. Run 'rulesync init' first.");
1381
2094
  process.exit(1);
1382
2095
  }
1383
2096
  try {
1384
- if (options.verbose) {
2097
+ if (config.verbose) {
1385
2098
  console.log(`Parsing rules from ${config.aiRulesDir}...`);
1386
2099
  }
1387
2100
  const rules = await parseRulesFromDirectory(config.aiRulesDir);
@@ -1389,18 +2102,26 @@ async function generateCommand(options = {}) {
1389
2102
  console.warn("\u26A0\uFE0F No rules found in .rulesync directory");
1390
2103
  return;
1391
2104
  }
1392
- if (options.verbose) {
2105
+ if (config.verbose) {
1393
2106
  console.log(`Found ${rules.length} rule(s)`);
1394
2107
  console.log(`Base directories: ${baseDirs.join(", ")}`);
1395
2108
  }
1396
- if (options.delete) {
1397
- if (options.verbose) {
2109
+ if (config.delete) {
2110
+ if (config.verbose) {
1398
2111
  console.log("Deleting existing output directories...");
1399
2112
  }
1400
- const targetTools = options.tools || config.defaultTargets;
2113
+ const targetTools = config.defaultTargets;
1401
2114
  const deleteTasks = [];
1402
2115
  for (const tool of targetTools) {
1403
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;
1404
2125
  case "copilot":
1405
2126
  deleteTasks.push(removeDirectory(config.outputPaths.copilot));
1406
2127
  break;
@@ -1425,19 +2146,19 @@ async function generateCommand(options = {}) {
1425
2146
  }
1426
2147
  }
1427
2148
  await Promise.all(deleteTasks);
1428
- if (options.verbose) {
2149
+ if (config.verbose) {
1429
2150
  console.log("Deleted existing output directories");
1430
2151
  }
1431
2152
  }
1432
2153
  let totalOutputs = 0;
1433
2154
  for (const baseDir of baseDirs) {
1434
- if (options.verbose) {
2155
+ if (config.verbose) {
1435
2156
  console.log(`
1436
2157
  Generating configurations for base directory: ${baseDir}`);
1437
2158
  }
1438
- const outputs = await generateConfigurations(rules, config, options.tools, baseDir);
2159
+ const outputs = await generateConfigurations(rules, config, config.defaultTargets, baseDir);
1439
2160
  if (outputs.length === 0) {
1440
- if (options.verbose) {
2161
+ if (config.verbose) {
1441
2162
  console.warn(`\u26A0\uFE0F No configurations generated for ${baseDir}`);
1442
2163
  }
1443
2164
  continue;
@@ -1452,17 +2173,18 @@ Generating configurations for base directory: ${baseDir}`);
1452
2173
  console.warn("\u26A0\uFE0F No configurations generated");
1453
2174
  return;
1454
2175
  }
1455
- if (options.verbose) {
2176
+ if (config.verbose) {
1456
2177
  console.log("\nGenerating MCP configurations...");
1457
2178
  }
1458
2179
  let totalMcpOutputs = 0;
1459
2180
  for (const baseDir of baseDirs) {
1460
2181
  const mcpResults = await generateMcpConfigs(
1461
2182
  process.cwd(),
1462
- baseDir === process.cwd() ? void 0 : baseDir
2183
+ baseDir === process.cwd() ? void 0 : baseDir,
2184
+ config.defaultTargets
1463
2185
  );
1464
2186
  if (mcpResults.length === 0) {
1465
- if (options.verbose) {
2187
+ if (config.verbose) {
1466
2188
  console.log(`No MCP configuration found for ${baseDir}`);
1467
2189
  }
1468
2190
  continue;
@@ -1473,7 +2195,7 @@ Generating configurations for base directory: ${baseDir}`);
1473
2195
  totalMcpOutputs++;
1474
2196
  } else if (result.status === "error") {
1475
2197
  console.error(`\u274C Failed to generate ${result.tool} MCP configuration: ${result.error}`);
1476
- } else if (options.verbose && result.status === "skipped") {
2198
+ } else if (config.verbose && result.status === "skipped") {
1477
2199
  console.log(`\u23ED\uFE0F Skipped ${result.tool} MCP configuration (no servers configured)`);
1478
2200
  }
1479
2201
  }
@@ -1492,10 +2214,10 @@ Generating configurations for base directory: ${baseDir}`);
1492
2214
  }
1493
2215
 
1494
2216
  // src/cli/commands/gitignore.ts
1495
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
1496
- import { join as join15 } from "path";
2217
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
2218
+ import { join as join16 } from "path";
1497
2219
  var gitignoreCommand = async () => {
1498
- const gitignorePath = join15(process.cwd(), ".gitignore");
2220
+ const gitignorePath = join16(process.cwd(), ".gitignore");
1499
2221
  const rulesFilesToIgnore = [
1500
2222
  "# Generated by rulesync - AI tool configuration files",
1501
2223
  "**/.github/copilot-instructions.md",
@@ -1517,6 +2239,8 @@ var gitignoreCommand = async () => {
1517
2239
  "**/.kiro/steering/",
1518
2240
  "**/.augment/rules/",
1519
2241
  "**/.augment-guidelines",
2242
+ "**/.junie/guidelines.md",
2243
+ "**/.noai",
1520
2244
  "**/.mcp.json",
1521
2245
  "!.rulesync/.mcp.json",
1522
2246
  "**/.cursor/mcp.json",
@@ -1544,7 +2268,7 @@ var gitignoreCommand = async () => {
1544
2268
  ${linesToAdd.join("\n")}
1545
2269
  ` : `${linesToAdd.join("\n")}
1546
2270
  `;
1547
- writeFileSync(gitignorePath, newContent);
2271
+ writeFileSync2(gitignorePath, newContent);
1548
2272
  console.log(`\u2705 Added ${linesToAdd.length} rules to .gitignore:`);
1549
2273
  for (const line of linesToAdd) {
1550
2274
  if (!line.startsWith("#")) {
@@ -1554,11 +2278,11 @@ ${linesToAdd.join("\n")}
1554
2278
  };
1555
2279
 
1556
2280
  // src/core/importer.ts
1557
- import { join as join20 } from "path";
2281
+ import { join as join22 } from "path";
1558
2282
  import matter5 from "gray-matter";
1559
2283
 
1560
2284
  // src/parsers/augmentcode.ts
1561
- import { basename as basename2, join as join16 } from "path";
2285
+ import { basename as basename2, join as join17 } from "path";
1562
2286
  import matter2 from "gray-matter";
1563
2287
 
1564
2288
  // src/utils/parser-helpers.ts
@@ -1599,7 +2323,7 @@ async function safeReadFile(operation, errorContext) {
1599
2323
  // src/parsers/augmentcode.ts
1600
2324
  async function parseAugmentcodeConfiguration(baseDir = process.cwd()) {
1601
2325
  const result = createParseResult();
1602
- const rulesDir = join16(baseDir, ".augment", "rules");
2326
+ const rulesDir = join17(baseDir, ".augment", "rules");
1603
2327
  if (await fileExists(rulesDir)) {
1604
2328
  const rulesResult = await parseAugmentRules(rulesDir);
1605
2329
  addRules(result, rulesResult.rules);
@@ -1617,7 +2341,7 @@ async function parseAugmentRules(rulesDir) {
1617
2341
  const files = await readdir2(rulesDir);
1618
2342
  for (const file of files) {
1619
2343
  if (file.endsWith(".md") || file.endsWith(".mdc")) {
1620
- const filePath = join16(rulesDir, file);
2344
+ const filePath = join17(rulesDir, file);
1621
2345
  try {
1622
2346
  const rawContent = await readFileContent(filePath);
1623
2347
  const parsed = matter2(rawContent);
@@ -1655,10 +2379,10 @@ async function parseAugmentRules(rulesDir) {
1655
2379
  }
1656
2380
 
1657
2381
  // src/parsers/augmentcode-legacy.ts
1658
- import { join as join17 } from "path";
2382
+ import { join as join18 } from "path";
1659
2383
  async function parseAugmentcodeLegacyConfiguration(baseDir = process.cwd()) {
1660
2384
  const result = createParseResult();
1661
- const guidelinesPath = join17(baseDir, ".augment-guidelines");
2385
+ const guidelinesPath = join18(baseDir, ".augment-guidelines");
1662
2386
  if (await fileExists(guidelinesPath)) {
1663
2387
  const guidelinesResult = await parseAugmentGuidelines(guidelinesPath);
1664
2388
  if (guidelinesResult.rule) {
@@ -1701,13 +2425,13 @@ async function parseAugmentGuidelines(guidelinesPath) {
1701
2425
  }
1702
2426
 
1703
2427
  // src/parsers/shared-helpers.ts
1704
- import { basename as basename3, join as join18 } from "path";
2428
+ import { basename as basename3, join as join19 } from "path";
1705
2429
  import matter3 from "gray-matter";
1706
2430
  async function parseConfigurationFiles(baseDir = process.cwd(), config) {
1707
2431
  const errors = [];
1708
2432
  const rules = [];
1709
2433
  if (config.mainFile) {
1710
- const mainFilePath = join18(baseDir, config.mainFile.path);
2434
+ const mainFilePath = join19(baseDir, config.mainFile.path);
1711
2435
  if (await fileExists(mainFilePath)) {
1712
2436
  try {
1713
2437
  const rawContent = await readFileContent(mainFilePath);
@@ -1747,14 +2471,14 @@ async function parseConfigurationFiles(baseDir = process.cwd(), config) {
1747
2471
  }
1748
2472
  if (config.directories) {
1749
2473
  for (const dirConfig of config.directories) {
1750
- const dirPath = join18(baseDir, dirConfig.directory);
2474
+ const dirPath = join19(baseDir, dirConfig.directory);
1751
2475
  if (await fileExists(dirPath)) {
1752
2476
  try {
1753
2477
  const { readdir: readdir2 } = await import("fs/promises");
1754
2478
  const files = await readdir2(dirPath);
1755
2479
  for (const file of files) {
1756
2480
  if (file.endsWith(dirConfig.filePattern)) {
1757
- const filePath = join18(dirPath, file);
2481
+ const filePath = join19(dirPath, file);
1758
2482
  try {
1759
2483
  const rawContent = await readFileContent(filePath);
1760
2484
  let content;
@@ -1802,7 +2526,7 @@ async function parseMemoryBasedConfiguration(baseDir = process.cwd(), config) {
1802
2526
  const rules = [];
1803
2527
  let ignorePatterns;
1804
2528
  let mcpServers;
1805
- const mainFilePath = join18(baseDir, config.mainFileName);
2529
+ const mainFilePath = join19(baseDir, config.mainFileName);
1806
2530
  if (!await fileExists(mainFilePath)) {
1807
2531
  errors.push(`${config.mainFileName} file not found`);
1808
2532
  return { rules, errors };
@@ -1813,12 +2537,12 @@ async function parseMemoryBasedConfiguration(baseDir = process.cwd(), config) {
1813
2537
  if (mainRule) {
1814
2538
  rules.push(mainRule);
1815
2539
  }
1816
- const memoryDir = join18(baseDir, config.memoryDirPath);
2540
+ const memoryDir = join19(baseDir, config.memoryDirPath);
1817
2541
  if (await fileExists(memoryDir)) {
1818
2542
  const memoryRules = await parseMemoryFiles(memoryDir, config);
1819
2543
  rules.push(...memoryRules);
1820
2544
  }
1821
- const settingsPath = join18(baseDir, config.settingsPath);
2545
+ const settingsPath = join19(baseDir, config.settingsPath);
1822
2546
  if (await fileExists(settingsPath)) {
1823
2547
  const settingsResult = await parseSettingsFile(settingsPath, config.tool);
1824
2548
  if (settingsResult.ignorePatterns) {
@@ -1830,7 +2554,7 @@ async function parseMemoryBasedConfiguration(baseDir = process.cwd(), config) {
1830
2554
  errors.push(...settingsResult.errors);
1831
2555
  }
1832
2556
  if (config.additionalIgnoreFile) {
1833
- const additionalIgnorePath = join18(baseDir, config.additionalIgnoreFile.path);
2557
+ const additionalIgnorePath = join19(baseDir, config.additionalIgnoreFile.path);
1834
2558
  if (await fileExists(additionalIgnorePath)) {
1835
2559
  const additionalPatterns = await config.additionalIgnoreFile.parser(additionalIgnorePath);
1836
2560
  if (additionalPatterns.length > 0) {
@@ -1884,7 +2608,7 @@ async function parseMemoryFiles(memoryDir, config) {
1884
2608
  const files = await readdir2(memoryDir);
1885
2609
  for (const file of files) {
1886
2610
  if (file.endsWith(".md")) {
1887
- const filePath = join18(memoryDir, file);
2611
+ const filePath = join19(memoryDir, file);
1888
2612
  const content = await readFileContent(filePath);
1889
2613
  if (content.trim()) {
1890
2614
  const filename = basename3(file, ".md");
@@ -2000,10 +2724,10 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
2000
2724
  }
2001
2725
 
2002
2726
  // src/parsers/cursor.ts
2003
- import { basename as basename4, join as join19 } from "path";
2727
+ import { basename as basename4, join as join20 } from "path";
2004
2728
  import matter4 from "gray-matter";
2005
2729
  import { DEFAULT_SCHEMA, FAILSAFE_SCHEMA, load } from "js-yaml";
2006
- import { z as z5 } from "zod/mini";
2730
+ import { z as z6 } from "zod/mini";
2007
2731
  var customMatterOptions = {
2008
2732
  engines: {
2009
2733
  yaml: {
@@ -2031,7 +2755,7 @@ var customMatterOptions = {
2031
2755
  }
2032
2756
  };
2033
2757
  function convertCursorMdcFrontmatter(cursorFrontmatter, _filename) {
2034
- const FrontmatterSchema = z5.record(z5.string(), z5.unknown());
2758
+ const FrontmatterSchema = z6.record(z6.string(), z6.unknown());
2035
2759
  const parseResult = FrontmatterSchema.safeParse(cursorFrontmatter);
2036
2760
  if (!parseResult.success) {
2037
2761
  return {
@@ -2125,7 +2849,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
2125
2849
  const rules = [];
2126
2850
  let ignorePatterns;
2127
2851
  let mcpServers;
2128
- const cursorFilePath = join19(baseDir, ".cursorrules");
2852
+ const cursorFilePath = join20(baseDir, ".cursorrules");
2129
2853
  if (await fileExists(cursorFilePath)) {
2130
2854
  try {
2131
2855
  const rawContent = await readFileContent(cursorFilePath);
@@ -2146,14 +2870,14 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
2146
2870
  errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
2147
2871
  }
2148
2872
  }
2149
- const cursorRulesDir = join19(baseDir, ".cursor", "rules");
2873
+ const cursorRulesDir = join20(baseDir, ".cursor", "rules");
2150
2874
  if (await fileExists(cursorRulesDir)) {
2151
2875
  try {
2152
2876
  const { readdir: readdir2 } = await import("fs/promises");
2153
2877
  const files = await readdir2(cursorRulesDir);
2154
2878
  for (const file of files) {
2155
2879
  if (file.endsWith(".mdc")) {
2156
- const filePath = join19(cursorRulesDir, file);
2880
+ const filePath = join20(cursorRulesDir, file);
2157
2881
  try {
2158
2882
  const rawContent = await readFileContent(filePath);
2159
2883
  const parsed = matter4(rawContent, customMatterOptions);
@@ -2182,7 +2906,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
2182
2906
  if (rules.length === 0) {
2183
2907
  errors.push("No Cursor configuration files found (.cursorrules or .cursor/rules/*.mdc)");
2184
2908
  }
2185
- const cursorIgnorePath = join19(baseDir, ".cursorignore");
2909
+ const cursorIgnorePath = join20(baseDir, ".cursorignore");
2186
2910
  if (await fileExists(cursorIgnorePath)) {
2187
2911
  try {
2188
2912
  const content = await readFileContent(cursorIgnorePath);
@@ -2195,7 +2919,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
2195
2919
  errors.push(`Failed to parse .cursorignore: ${errorMessage}`);
2196
2920
  }
2197
2921
  }
2198
- const cursorMcpPath = join19(baseDir, ".cursor", "mcp.json");
2922
+ const cursorMcpPath = join20(baseDir, ".cursor", "mcp.json");
2199
2923
  if (await fileExists(cursorMcpPath)) {
2200
2924
  try {
2201
2925
  const content = await readFileContent(cursorMcpPath);
@@ -2243,6 +2967,42 @@ async function parseGeminiConfiguration(baseDir = process.cwd()) {
2243
2967
  });
2244
2968
  }
2245
2969
 
2970
+ // src/parsers/junie.ts
2971
+ import { join as join21 } from "path";
2972
+ async function parseJunieConfiguration(baseDir = process.cwd()) {
2973
+ const errors = [];
2974
+ const rules = [];
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 };
2979
+ }
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
+ });
2995
+ }
2996
+ } catch (error) {
2997
+ const errorMessage = error instanceof Error ? error.message : String(error);
2998
+ errors.push(`Failed to parse .junie/guidelines.md: ${errorMessage}`);
2999
+ }
3000
+ if (rules.length === 0) {
3001
+ errors.push("No valid Junie configuration found");
3002
+ }
3003
+ return { rules, errors };
3004
+ }
3005
+
2246
3006
  // src/parsers/roo.ts
2247
3007
  async function parseRooConfiguration(baseDir = process.cwd()) {
2248
3008
  return parseConfigurationFiles(baseDir, {
@@ -2329,6 +3089,12 @@ async function importConfiguration(options) {
2329
3089
  mcpServers = geminiResult.mcpServers;
2330
3090
  break;
2331
3091
  }
3092
+ case "junie": {
3093
+ const junieResult = await parseJunieConfiguration(baseDir);
3094
+ rules = junieResult.rules;
3095
+ errors.push(...junieResult.errors);
3096
+ break;
3097
+ }
2332
3098
  default:
2333
3099
  errors.push(`Unsupported tool: ${tool}`);
2334
3100
  return { success: false, rulesCreated: 0, errors };
@@ -2341,7 +3107,7 @@ async function importConfiguration(options) {
2341
3107
  if (rules.length === 0 && !ignorePatterns && !mcpServers) {
2342
3108
  return { success: false, rulesCreated: 0, errors };
2343
3109
  }
2344
- const rulesDirPath = join20(baseDir, rulesDir);
3110
+ const rulesDirPath = join22(baseDir, rulesDir);
2345
3111
  try {
2346
3112
  const { mkdir: mkdir3 } = await import("fs/promises");
2347
3113
  await mkdir3(rulesDirPath, { recursive: true });
@@ -2355,7 +3121,7 @@ async function importConfiguration(options) {
2355
3121
  try {
2356
3122
  const baseFilename = `${tool}__${rule.filename}`;
2357
3123
  const filename = await generateUniqueFilename(rulesDirPath, baseFilename);
2358
- const filePath = join20(rulesDirPath, `${filename}.md`);
3124
+ const filePath = join22(rulesDirPath, `${filename}.md`);
2359
3125
  const content = generateRuleFileContent(rule);
2360
3126
  await writeFileContent(filePath, content);
2361
3127
  rulesCreated++;
@@ -2370,7 +3136,7 @@ async function importConfiguration(options) {
2370
3136
  let ignoreFileCreated = false;
2371
3137
  if (ignorePatterns && ignorePatterns.length > 0) {
2372
3138
  try {
2373
- const rulesyncignorePath = join20(baseDir, ".rulesyncignore");
3139
+ const rulesyncignorePath = join22(baseDir, ".rulesyncignore");
2374
3140
  const ignoreContent = `${ignorePatterns.join("\n")}
2375
3141
  `;
2376
3142
  await writeFileContent(rulesyncignorePath, ignoreContent);
@@ -2386,7 +3152,7 @@ async function importConfiguration(options) {
2386
3152
  let mcpFileCreated = false;
2387
3153
  if (mcpServers && Object.keys(mcpServers).length > 0) {
2388
3154
  try {
2389
- const mcpPath = join20(baseDir, rulesDir, ".mcp.json");
3155
+ const mcpPath = join22(baseDir, rulesDir, ".mcp.json");
2390
3156
  const mcpContent = `${JSON.stringify({ mcpServers }, null, 2)}
2391
3157
  `;
2392
3158
  await writeFileContent(mcpPath, mcpContent);
@@ -2414,7 +3180,7 @@ function generateRuleFileContent(rule) {
2414
3180
  async function generateUniqueFilename(rulesDir, baseFilename) {
2415
3181
  let filename = baseFilename;
2416
3182
  let counter = 1;
2417
- while (await fileExists(join20(rulesDir, `${filename}.md`))) {
3183
+ while (await fileExists(join22(rulesDir, `${filename}.md`))) {
2418
3184
  filename = `${baseFilename}-${counter}`;
2419
3185
  counter++;
2420
3186
  }
@@ -2425,7 +3191,7 @@ async function generateUniqueFilename(rulesDir, baseFilename) {
2425
3191
  async function importCommand(options = {}) {
2426
3192
  const tools = [];
2427
3193
  if (options.augmentcode) tools.push("augmentcode");
2428
- if (options.augmentcodeLegacy) tools.push("augmentcode-legacy");
3194
+ if (options["augmentcode-legacy"]) tools.push("augmentcode-legacy");
2429
3195
  if (options.claudecode) tools.push("claudecode");
2430
3196
  if (options.cursor) tools.push("cursor");
2431
3197
  if (options.copilot) tools.push("copilot");
@@ -2434,7 +3200,7 @@ async function importCommand(options = {}) {
2434
3200
  if (options.geminicli) tools.push("geminicli");
2435
3201
  if (tools.length === 0) {
2436
3202
  console.error(
2437
- "\u274C Please specify one tool to import from (--augmentcode, --augmentcodeLegacy, --claudecode, --cursor, --copilot, --cline, --roo, --geminicli)"
3203
+ "\u274C Please specify one tool to import from (--augmentcode, --augmentcode-legacy, --claudecode, --cursor, --copilot, --cline, --roo, --geminicli)"
2438
3204
  );
2439
3205
  process.exit(1);
2440
3206
  }
@@ -2481,7 +3247,7 @@ async function importCommand(options = {}) {
2481
3247
  }
2482
3248
 
2483
3249
  // src/cli/commands/init.ts
2484
- import { join as join21 } from "path";
3250
+ import { join as join23 } from "path";
2485
3251
  async function initCommand() {
2486
3252
  const aiRulesDir = ".rulesync";
2487
3253
  console.log("Initializing rulesync...");
@@ -2528,7 +3294,7 @@ globs: ["**/*"]
2528
3294
  - Follow single responsibility principle
2529
3295
  `
2530
3296
  };
2531
- const filepath = join21(aiRulesDir, sampleFile.filename);
3297
+ const filepath = join23(aiRulesDir, sampleFile.filename);
2532
3298
  if (!await fileExists(filepath)) {
2533
3299
  await writeFileContent(filepath, sampleFile.content);
2534
3300
  console.log(`Created ${filepath}`);
@@ -2642,11 +3408,11 @@ async function watchCommand() {
2642
3408
  persistent: true
2643
3409
  });
2644
3410
  let isGenerating = false;
2645
- const handleChange = async (path4) => {
3411
+ const handleChange = async (path5) => {
2646
3412
  if (isGenerating) return;
2647
3413
  isGenerating = true;
2648
3414
  console.log(`
2649
- \u{1F4DD} Detected change in ${path4}`);
3415
+ \u{1F4DD} Detected change in ${path5}`);
2650
3416
  try {
2651
3417
  await generateCommand({ verbose: false });
2652
3418
  console.log("\u2705 Regenerated configuration files");
@@ -2656,10 +3422,10 @@ async function watchCommand() {
2656
3422
  isGenerating = false;
2657
3423
  }
2658
3424
  };
2659
- watcher.on("change", handleChange).on("add", handleChange).on("unlink", (path4) => {
3425
+ watcher.on("change", handleChange).on("add", handleChange).on("unlink", (path5) => {
2660
3426
  console.log(`
2661
- \u{1F5D1}\uFE0F Removed ${path4}`);
2662
- handleChange(path4);
3427
+ \u{1F5D1}\uFE0F Removed ${path5}`);
3428
+ handleChange(path5);
2663
3429
  }).on("error", (error) => {
2664
3430
  console.error("\u274C Watcher error:", error);
2665
3431
  });
@@ -2672,28 +3438,31 @@ async function watchCommand() {
2672
3438
 
2673
3439
  // src/cli/index.ts
2674
3440
  var program = new Command();
2675
- program.name("rulesync").description("Unified AI rules management CLI tool").version("0.49.0");
3441
+ program.name("rulesync").description("Unified AI rules management CLI tool").version("0.51.0");
2676
3442
  program.command("init").description("Initialize rulesync in current directory").action(initCommand);
2677
3443
  program.command("add <filename>").description("Add a new rule file").action(addCommand);
2678
3444
  program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
2679
- program.command("import").description("Import configurations from AI tools to rulesync format").option("--augmentcode", "Import from AugmentCode (.augment/rules/)").option("--augmentcodeLegacy", "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("-v, --verbose", "Verbose output").action(importCommand);
2680
- program.command("generate").description("Generate configuration files for AI tools").option("--augmentcode", "Generate only for AugmentCode").option("--augmentcodeLegacy", "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("--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(
2681
3447
  "-b, --base-dir <paths>",
2682
3448
  "Base directories to generate files (comma-separated for multiple paths)"
2683
- ).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) => {
2684
3450
  const tools = [];
2685
3451
  if (options.augmentcode) tools.push("augmentcode");
2686
- if (options.augmentcodeLegacy) tools.push("augmentcode-legacy");
3452
+ if (options["augmentcode-legacy"]) tools.push("augmentcode-legacy");
2687
3453
  if (options.copilot) tools.push("copilot");
2688
3454
  if (options.cursor) tools.push("cursor");
2689
3455
  if (options.cline) tools.push("cline");
2690
3456
  if (options.claudecode) tools.push("claudecode");
2691
3457
  if (options.roo) tools.push("roo");
2692
3458
  if (options.geminicli) tools.push("geminicli");
3459
+ if (options.junie) tools.push("junie");
2693
3460
  if (options.kiro) tools.push("kiro");
2694
3461
  const generateOptions = {
2695
3462
  verbose: options.verbose,
2696
- delete: options.delete
3463
+ delete: options.delete,
3464
+ config: options.config,
3465
+ noConfig: options.noConfig
2697
3466
  };
2698
3467
  if (tools.length > 0) {
2699
3468
  generateOptions.tools = tools;
@@ -2706,4 +3475,5 @@ program.command("generate").description("Generate configuration files for AI too
2706
3475
  program.command("validate").description("Validate rulesync configuration").action(validateCommand);
2707
3476
  program.command("status").description("Show current status of rulesync").action(statusCommand);
2708
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);
2709
3479
  program.parse();