rulesync 0.48.0 → 0.51.0

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