rulesync 0.49.0 → 0.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,33 +1,39 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ generateKiroMcp
4
+ } from "./chunk-QUJMXHNR.js";
2
5
  import {
3
6
  generateRooMcp
4
- } from "./chunk-MCADLVGY.js";
7
+ } from "./chunk-FFW6TGCM.js";
5
8
  import {
6
9
  generateAugmentcodeMcp
7
- } from "./chunk-6TAURCQP.js";
10
+ } from "./chunk-OPZOVKIL.js";
8
11
  import {
9
12
  generateClaudeMcp
10
- } from "./chunk-YVRWBSCK.js";
13
+ } from "./chunk-FNL2KPM3.js";
11
14
  import {
12
15
  generateClineMcp
13
- } from "./chunk-XHNIEO22.js";
16
+ } from "./chunk-HMMTSS5E.js";
17
+ import "./chunk-YPIFCGAP.js";
14
18
  import {
15
19
  generateCopilotMcp
16
- } from "./chunk-G5LLOIO4.js";
20
+ } from "./chunk-Y2XH4E5R.js";
17
21
  import {
18
22
  generateCursorMcp
19
- } from "./chunk-6MFEIYHN.js";
23
+ } from "./chunk-TTHBLXOB.js";
20
24
  import {
21
25
  generateGeminiCliMcp
22
- } from "./chunk-QNHGYRJT.js";
26
+ } from "./chunk-IXCMY24P.js";
23
27
  import {
24
- generateKiroMcp
25
- } from "./chunk-R5HFWJ5L.js";
28
+ generateJunieMcp
29
+ } from "./chunk-Y26DXTAT.js";
26
30
  import {
31
+ ALL_TOOL_TARGETS,
27
32
  RulesyncTargetsSchema,
28
33
  ToolTargetSchema,
29
- ToolTargetsSchema
30
- } from "./chunk-IBJGN3JQ.js";
34
+ ToolTargetsSchema,
35
+ isToolTarget
36
+ } from "./chunk-USKQYIZ2.js";
31
37
 
32
38
  // src/cli/index.ts
33
39
  import { Command } from "commander";
@@ -49,19 +55,11 @@ function getDefaultConfig() {
49
55
  claudecode: ".",
50
56
  roo: ".roo/rules",
51
57
  geminicli: ".gemini/memories",
52
- kiro: ".kiro/steering"
58
+ kiro: ".kiro/steering",
59
+ junie: "."
53
60
  },
54
61
  watchEnabled: false,
55
- defaultTargets: [
56
- "augmentcode",
57
- "copilot",
58
- "cursor",
59
- "cline",
60
- "claudecode",
61
- "roo",
62
- "geminicli",
63
- "kiro"
64
- ]
62
+ defaultTargets: ALL_TOOL_TARGETS.filter((tool) => tool !== "augmentcode-legacy")
65
63
  };
66
64
  }
67
65
  function resolveTargets(targets, config) {
@@ -108,157 +106,652 @@ async function addCommand(filename) {
108
106
  }
109
107
  }
110
108
 
111
- // src/generators/ignore/augmentcode.ts
112
- import { join as join2 } from "path";
113
- async function generateAugmentCodeIgnoreFiles(rules, config, baseDir) {
114
- const outputs = [];
115
- const augmentignoreContent = generateAugmentignoreContent(rules);
116
- const outputPath = baseDir || process.cwd();
117
- const filepath = join2(outputPath, ".augmentignore");
118
- outputs.push({
119
- tool: "augmentcode",
120
- filepath,
121
- content: augmentignoreContent
122
- });
123
- return outputs;
109
+ // src/cli/commands/config.ts
110
+ import { writeFileSync } from "fs";
111
+ import path2 from "path";
112
+
113
+ // src/types/claudecode.ts
114
+ import { z } from "zod/mini";
115
+ var ClaudeSettingsSchema = z.looseObject({
116
+ permissions: z._default(
117
+ z.looseObject({
118
+ deny: z._default(z.array(z.string()), [])
119
+ }),
120
+ { deny: [] }
121
+ )
122
+ });
123
+
124
+ // src/types/config.ts
125
+ import { z as z2 } from "zod/mini";
126
+ var ConfigSchema = z2.object({
127
+ aiRulesDir: z2.string(),
128
+ outputPaths: z2.record(ToolTargetSchema, z2.string()),
129
+ watchEnabled: z2.boolean(),
130
+ defaultTargets: ToolTargetsSchema
131
+ });
132
+
133
+ // src/types/config-options.ts
134
+ import { z as z3 } from "zod/mini";
135
+ var OutputPathsSchema = z3.object({
136
+ augmentcode: z3.optional(z3.string()),
137
+ "augmentcode-legacy": z3.optional(z3.string()),
138
+ copilot: z3.optional(z3.string()),
139
+ cursor: z3.optional(z3.string()),
140
+ cline: z3.optional(z3.string()),
141
+ claudecode: z3.optional(z3.string()),
142
+ roo: z3.optional(z3.string()),
143
+ geminicli: z3.optional(z3.string()),
144
+ kiro: z3.optional(z3.string()),
145
+ junie: z3.optional(z3.string())
146
+ });
147
+ var ConfigOptionsSchema = z3.object({
148
+ aiRulesDir: z3.optional(z3.string()),
149
+ outputPaths: z3.optional(OutputPathsSchema),
150
+ watchEnabled: z3.optional(z3.boolean()),
151
+ defaultTargets: z3.optional(ToolTargetsSchema),
152
+ targets: z3.optional(z3.array(ToolTargetSchema)),
153
+ exclude: z3.optional(z3.array(ToolTargetSchema)),
154
+ verbose: z3.optional(z3.boolean()),
155
+ delete: z3.optional(z3.boolean()),
156
+ baseDir: z3.optional(z3.union([z3.string(), z3.array(z3.string())])),
157
+ watch: z3.optional(
158
+ z3.object({
159
+ enabled: z3.optional(z3.boolean()),
160
+ interval: z3.optional(z3.number()),
161
+ ignore: z3.optional(z3.array(z3.string()))
162
+ })
163
+ )
164
+ });
165
+ var MergedConfigSchema = z3.object({
166
+ aiRulesDir: z3.string(),
167
+ outputPaths: z3.record(ToolTargetSchema, z3.string()),
168
+ watchEnabled: z3.boolean(),
169
+ defaultTargets: ToolTargetsSchema,
170
+ targets: z3.optional(z3.array(ToolTargetSchema)),
171
+ exclude: z3.optional(z3.array(ToolTargetSchema)),
172
+ verbose: z3.optional(z3.boolean()),
173
+ delete: z3.optional(z3.boolean()),
174
+ baseDir: z3.optional(z3.union([z3.string(), z3.array(z3.string())])),
175
+ configPath: z3.optional(z3.string()),
176
+ watch: z3.optional(
177
+ z3.object({
178
+ enabled: z3.optional(z3.boolean()),
179
+ interval: z3.optional(z3.number()),
180
+ ignore: z3.optional(z3.array(z3.string()))
181
+ })
182
+ )
183
+ });
184
+
185
+ // src/types/mcp.ts
186
+ import { z as z4 } from "zod/mini";
187
+ var McpTransportTypeSchema = z4.enum(["stdio", "sse", "http"]);
188
+ var McpServerBaseSchema = z4.object({
189
+ command: z4.optional(z4.string()),
190
+ args: z4.optional(z4.array(z4.string())),
191
+ url: z4.optional(z4.string()),
192
+ httpUrl: z4.optional(z4.string()),
193
+ env: z4.optional(z4.record(z4.string(), z4.string())),
194
+ disabled: z4.optional(z4.boolean()),
195
+ networkTimeout: z4.optional(z4.number()),
196
+ timeout: z4.optional(z4.number()),
197
+ trust: z4.optional(z4.boolean()),
198
+ cwd: z4.optional(z4.string()),
199
+ transport: z4.optional(McpTransportTypeSchema),
200
+ type: z4.optional(z4.enum(["sse", "streamable-http"])),
201
+ alwaysAllow: z4.optional(z4.array(z4.string())),
202
+ tools: z4.optional(z4.array(z4.string())),
203
+ kiroAutoApprove: z4.optional(z4.array(z4.string())),
204
+ kiroAutoBlock: z4.optional(z4.array(z4.string()))
205
+ });
206
+ var RulesyncMcpServerSchema = z4.extend(McpServerBaseSchema, {
207
+ targets: z4.optional(RulesyncTargetsSchema)
208
+ });
209
+ var McpConfigSchema = z4.object({
210
+ mcpServers: z4.record(z4.string(), McpServerBaseSchema)
211
+ });
212
+ var RulesyncMcpConfigSchema = z4.object({
213
+ mcpServers: z4.record(z4.string(), RulesyncMcpServerSchema)
214
+ });
215
+
216
+ // src/types/rules.ts
217
+ import { z as z5 } from "zod/mini";
218
+ var RuleFrontmatterSchema = z5.object({
219
+ root: z5.boolean(),
220
+ targets: RulesyncTargetsSchema,
221
+ description: z5.string(),
222
+ globs: z5.array(z5.string()),
223
+ cursorRuleType: z5.optional(z5.enum(["always", "manual", "specificFiles", "intelligently"])),
224
+ tags: z5.optional(z5.array(z5.string()))
225
+ });
226
+ var ParsedRuleSchema = z5.object({
227
+ frontmatter: RuleFrontmatterSchema,
228
+ content: z5.string(),
229
+ filename: z5.string(),
230
+ filepath: z5.string()
231
+ });
232
+ var GeneratedOutputSchema = z5.object({
233
+ tool: ToolTargetSchema,
234
+ filepath: z5.string(),
235
+ content: z5.string()
236
+ });
237
+ var GenerateOptionsSchema = z5.object({
238
+ targetTools: z5.optional(ToolTargetsSchema),
239
+ outputDir: z5.optional(z5.string()),
240
+ watch: z5.optional(z5.boolean())
241
+ });
242
+
243
+ // src/utils/config-loader.ts
244
+ import { loadConfig as loadC12Config } from "c12";
245
+ import { $ZodError } from "zod/v4/core";
246
+ var MODULE_NAME = "rulesync";
247
+ async function loadConfig(options = {}) {
248
+ const defaultConfig = getDefaultConfig();
249
+ if (options.noConfig) {
250
+ return {
251
+ config: defaultConfig,
252
+ isEmpty: true
253
+ };
254
+ }
255
+ try {
256
+ const loadOptions = {
257
+ name: MODULE_NAME,
258
+ cwd: options.cwd || process.cwd(),
259
+ rcFile: false,
260
+ // Disable rc file lookup
261
+ configFile: "rulesync",
262
+ // Will look for rulesync.jsonc, rulesync.ts, etc.
263
+ defaults: defaultConfig
264
+ };
265
+ if (options.configPath) {
266
+ loadOptions.configFile = options.configPath;
267
+ }
268
+ const { config, configFile } = await loadC12Config(loadOptions);
269
+ if (!config || Object.keys(config).length === 0) {
270
+ return {
271
+ config: defaultConfig,
272
+ isEmpty: true
273
+ };
274
+ }
275
+ try {
276
+ ConfigOptionsSchema.parse(config);
277
+ } catch (error) {
278
+ if (error instanceof $ZodError) {
279
+ const issues = error.issues.map((issue) => ` - ${issue.path.join(".")}: ${issue.message}`).join("\n");
280
+ throw new Error(`Invalid configuration in ${configFile}:
281
+ ${issues}`);
282
+ }
283
+ throw error;
284
+ }
285
+ const processedConfig = postProcessConfig(config);
286
+ const result = {
287
+ config: processedConfig,
288
+ isEmpty: false
289
+ };
290
+ if (configFile) {
291
+ result.filepath = configFile;
292
+ }
293
+ return result;
294
+ } catch (error) {
295
+ throw new Error(
296
+ `Failed to load configuration: ${error instanceof Error ? error.message : String(error)}`
297
+ );
298
+ }
124
299
  }
125
- function generateAugmentignoreContent(rules) {
126
- const lines = [
127
- "# Generated by rulesync - AugmentCode ignore patterns",
128
- "# AugmentCode uses a two-tier approach: .gitignore first, then .augmentignore",
129
- "# This file provides Augment-specific exclusions and re-inclusions",
130
- ""
131
- ];
132
- lines.push(
133
- "# Security and Secrets (critical exclusions)",
134
- "# Environment files",
135
- ".env*",
136
- "",
137
- "# Private keys and certificates",
138
- "*.pem",
139
- "*.key",
140
- "*.p12",
141
- "*.crt",
142
- "*.der",
143
- "",
144
- "# SSH keys",
145
- "id_rsa*",
146
- "id_dsa*",
147
- "",
148
- "# AWS credentials",
149
- ".aws/",
150
- "aws-exports.js",
151
- "",
152
- "# API keys and tokens",
153
- "**/apikeys/",
154
- "**/*_token*",
155
- "**/*_secret*",
156
- ""
157
- );
158
- lines.push(
159
- "# Build Artifacts and Dependencies",
160
- "# Build outputs",
161
- "dist/",
162
- "build/",
163
- "out/",
164
- "target/",
165
- "",
166
- "# Dependencies",
167
- "node_modules/",
168
- "venv/",
169
- "*.egg-info/",
170
- "",
171
- "# Logs",
172
- "*.log",
173
- "logs/",
174
- "",
175
- "# Temporary files",
176
- "*.tmp",
177
- "*.swp",
178
- "*.swo",
179
- "*~",
180
- ""
181
- );
182
- lines.push(
183
- "# Large Files and Media",
184
- "# Binary files",
185
- "*.jar",
186
- "*.png",
187
- "*.jpg",
188
- "*.jpeg",
189
- "*.gif",
190
- "*.mp4",
191
- "*.avi",
192
- "*.zip",
193
- "*.tar.gz",
194
- "*.rar",
195
- "",
196
- "# Database files",
197
- "*.sqlite",
198
- "*.db",
199
- "*.mdb",
200
- "",
201
- "# Data files",
202
- "*.csv",
203
- "*.tsv",
204
- "*.xlsx",
205
- ""
206
- );
207
- lines.push(
208
- "# Performance Optimization",
209
- "# Exclude files that are too large for effective AI processing",
210
- "**/*.{mp4,avi,mov,mkv}",
211
- "**/*.{zip,tar,gz,rar}",
212
- "**/*.{pdf,doc,docx}",
213
- "**/logs/**/*.log",
214
- "",
215
- "# But include small configuration files",
216
- "!**/config.{json,yaml,yml}",
217
- ""
218
- );
219
- const rulePatterns = extractIgnorePatternsFromRules(rules);
220
- if (rulePatterns.length > 0) {
221
- lines.push("# Project-specific patterns from rulesync rules");
222
- lines.push(...rulePatterns);
223
- lines.push("");
300
+ function postProcessConfig(config) {
301
+ const processed = { ...config };
302
+ if (processed.baseDir && !Array.isArray(processed.baseDir)) {
303
+ processed.baseDir = [processed.baseDir];
224
304
  }
225
- lines.push(
226
- "# Team Collaboration",
227
- "# Exclude personal IDE settings",
228
- ".vscode/settings.json",
229
- ".idea/workspace.xml",
230
- "",
231
- "# But include shared team settings",
232
- "!.vscode/extensions.json",
233
- "!.idea/codeStyles/",
234
- "",
235
- "# Exclude test fixtures with sensitive data",
236
- "tests/fixtures/real-data/**",
237
- "",
238
- "# Re-include important documentation",
239
- "!vendor/*/README.md",
240
- "!third-party/*/LICENSE",
241
- ""
242
- );
305
+ if (config.targets || config.exclude) {
306
+ const baseTargets = config.targets || processed.defaultTargets;
307
+ if (config.exclude && config.exclude.length > 0) {
308
+ processed.defaultTargets = baseTargets.filter(
309
+ (target) => config.exclude && !config.exclude.includes(target)
310
+ );
311
+ } else {
312
+ processed.defaultTargets = baseTargets;
313
+ }
314
+ }
315
+ return processed;
316
+ }
317
+ function generateMinimalConfig(options) {
318
+ if (!options || Object.keys(options).length === 0) {
319
+ return generateSampleConfig();
320
+ }
321
+ const lines = ["{"];
322
+ if (options.targets || options.exclude) {
323
+ lines.push(` // Available tools: ${ALL_TOOL_TARGETS.join(", ")}`);
324
+ }
325
+ if (options.targets) {
326
+ lines.push(` "targets": ${JSON.stringify(options.targets)}`);
327
+ }
328
+ if (options.exclude) {
329
+ const comma = lines.length > 1 ? "," : "";
330
+ if (comma) lines[lines.length - 1] += comma;
331
+ lines.push(` "exclude": ${JSON.stringify(options.exclude)}`);
332
+ }
333
+ if (options.aiRulesDir) {
334
+ const comma = lines.length > 1 ? "," : "";
335
+ if (comma) lines[lines.length - 1] += comma;
336
+ lines.push(` "aiRulesDir": "${options.aiRulesDir}"`);
337
+ }
338
+ if (options.outputPaths) {
339
+ const comma = lines.length > 1 ? "," : "";
340
+ if (comma) lines[lines.length - 1] += comma;
341
+ lines.push(
342
+ ` "outputPaths": ${JSON.stringify(options.outputPaths, null, 4).split("\n").map((line, i) => i === 0 ? line : ` ${line}`).join("\n")}`
343
+ );
344
+ }
345
+ if (options.baseDir) {
346
+ const comma = lines.length > 1 ? "," : "";
347
+ if (comma) lines[lines.length - 1] += comma;
348
+ lines.push(` "baseDir": ${JSON.stringify(options.baseDir)}`);
349
+ }
350
+ if (options.delete !== void 0) {
351
+ const comma = lines.length > 1 ? "," : "";
352
+ if (comma) lines[lines.length - 1] += comma;
353
+ lines.push(` "delete": ${options.delete}`);
354
+ }
355
+ if (options.verbose !== void 0) {
356
+ const comma = lines.length > 1 ? "," : "";
357
+ if (comma) lines[lines.length - 1] += comma;
358
+ lines.push(` "verbose": ${options.verbose}`);
359
+ }
360
+ if (options.watch) {
361
+ const comma = lines.length > 1 ? "," : "";
362
+ if (comma) lines[lines.length - 1] += comma;
363
+ lines.push(
364
+ ` "watch": ${JSON.stringify(options.watch, null, 4).split("\n").map((line, i) => i === 0 ? line : ` ${line}`).join("\n")}`
365
+ );
366
+ }
367
+ lines.push("}");
243
368
  return lines.join("\n");
244
369
  }
370
+ function generateSampleConfig(options) {
371
+ const targets = options?.targets || ALL_TOOL_TARGETS;
372
+ const excludeValue = options?.exclude ? JSON.stringify(options.exclude) : null;
373
+ const aiRulesDir = options?.aiRulesDir || null;
374
+ const baseDir = options?.baseDir || null;
375
+ const deleteFlag = options?.delete || false;
376
+ const verbose = options?.verbose !== void 0 ? options.verbose : true;
377
+ return `{
378
+ // List of tools to generate configurations for
379
+ // Available: ${ALL_TOOL_TARGETS.join(", ")}
380
+ "targets": ${JSON.stringify(targets)},
381
+
382
+ // Tools to exclude from generation (overrides targets)
383
+ ${excludeValue ? `"exclude": ${excludeValue},` : '// "exclude": ["roo"],'}
384
+ ${aiRulesDir ? `
385
+ // Directory containing AI rule files
386
+ "aiRulesDir": "${aiRulesDir}",` : ""}
387
+
388
+ // Custom output paths for specific tools
389
+ "outputPaths": {
390
+ "copilot": ".github/copilot-instructions.md"
391
+ },
392
+ ${baseDir ? `
393
+ // Base directory for generation
394
+ "baseDir": "${baseDir}",` : `
395
+ // Base directory or directories for generation
396
+ // "baseDir": "./packages",
397
+ // "baseDir": ["./packages/frontend", "./packages/backend"],`}
398
+
399
+ // Delete existing files before generating
400
+ "delete": ${deleteFlag},
401
+
402
+ // Enable verbose output
403
+ "verbose": ${verbose},
404
+
405
+ // Watch configuration
406
+ "watch": {
407
+ "enabled": false,
408
+ "interval": 1000,
409
+ "ignore": ["node_modules/**", "dist/**"]
410
+ }
411
+ }
412
+ `;
413
+ }
414
+ function mergeWithCliOptions(config, cliOptions) {
415
+ const merged = { ...config };
416
+ if (cliOptions.verbose !== void 0) {
417
+ merged.verbose = cliOptions.verbose;
418
+ }
419
+ if (cliOptions.delete !== void 0) {
420
+ merged.delete = cliOptions.delete;
421
+ }
422
+ if (cliOptions.baseDirs && cliOptions.baseDirs.length > 0) {
423
+ merged.baseDir = cliOptions.baseDirs;
424
+ }
425
+ if (cliOptions.tools && cliOptions.tools.length > 0) {
426
+ merged.defaultTargets = cliOptions.tools;
427
+ merged.exclude = void 0;
428
+ }
429
+ return merged;
430
+ }
431
+
432
+ // src/utils/file.ts
433
+ import { mkdir as mkdir2, readdir, readFile, rm, stat, writeFile as writeFile2 } from "fs/promises";
434
+ import { dirname, join as join2 } from "path";
435
+ async function ensureDir(dirPath) {
436
+ try {
437
+ await stat(dirPath);
438
+ } catch {
439
+ await mkdir2(dirPath, { recursive: true });
440
+ }
441
+ }
442
+ async function readFileContent(filepath) {
443
+ return readFile(filepath, "utf-8");
444
+ }
445
+ async function writeFileContent(filepath, content) {
446
+ await ensureDir(dirname(filepath));
447
+ await writeFile2(filepath, content, "utf-8");
448
+ }
449
+ async function fileExists(filepath) {
450
+ try {
451
+ await stat(filepath);
452
+ return true;
453
+ } catch {
454
+ return false;
455
+ }
456
+ }
457
+ async function findFiles(dir, extension = ".md") {
458
+ try {
459
+ const files = await readdir(dir);
460
+ return files.filter((file) => file.endsWith(extension)).map((file) => join2(dir, file));
461
+ } catch {
462
+ return [];
463
+ }
464
+ }
465
+ async function removeDirectory(dirPath) {
466
+ const dangerousPaths = [".", "/", "~", "src", "node_modules"];
467
+ if (dangerousPaths.includes(dirPath) || dirPath === "") {
468
+ console.warn(`Skipping deletion of dangerous path: ${dirPath}`);
469
+ return;
470
+ }
471
+ try {
472
+ if (await fileExists(dirPath)) {
473
+ await rm(dirPath, { recursive: true, force: true });
474
+ }
475
+ } catch (error) {
476
+ console.warn(`Failed to remove directory ${dirPath}:`, error);
477
+ }
478
+ }
479
+ async function removeFile(filepath) {
480
+ try {
481
+ if (await fileExists(filepath)) {
482
+ await rm(filepath);
483
+ }
484
+ } catch (error) {
485
+ console.warn(`Failed to remove file ${filepath}:`, error);
486
+ }
487
+ }
488
+ async function removeClaudeGeneratedFiles() {
489
+ const filesToRemove = ["CLAUDE.md", ".claude/memories"];
490
+ for (const fileOrDir of filesToRemove) {
491
+ if (fileOrDir.endsWith("/memories")) {
492
+ await removeDirectory(fileOrDir);
493
+ } else {
494
+ await removeFile(fileOrDir);
495
+ }
496
+ }
497
+ }
498
+
499
+ // src/utils/rules.ts
500
+ function isToolSpecificRule(rule, targetTool) {
501
+ const filename = rule.filename;
502
+ const toolPatterns = {
503
+ "augmentcode-legacy": /^specification-augmentcode-legacy-/i,
504
+ augmentcode: /^specification-augmentcode-/i,
505
+ copilot: /^specification-copilot-/i,
506
+ cursor: /^specification-cursor-/i,
507
+ cline: /^specification-cline-/i,
508
+ claudecode: /^specification-claudecode-/i,
509
+ roo: /^specification-roo-/i,
510
+ geminicli: /^specification-geminicli-/i,
511
+ kiro: /^specification-kiro-/i
512
+ };
513
+ for (const [tool, pattern] of Object.entries(toolPatterns)) {
514
+ if (pattern.test(filename)) {
515
+ return tool === targetTool;
516
+ }
517
+ }
518
+ return true;
519
+ }
520
+
521
+ // src/cli/commands/config.ts
522
+ async function configCommand(options = {}) {
523
+ if (options.init) {
524
+ await initConfig(options);
525
+ return;
526
+ }
527
+ await showConfig();
528
+ }
529
+ async function showConfig() {
530
+ console.log("Loading configuration...\n");
531
+ try {
532
+ const result = await loadConfig();
533
+ if (result.isEmpty) {
534
+ console.log("No configuration file found. Using default configuration.\n");
535
+ } else {
536
+ console.log(`Configuration loaded from: ${result.filepath}
537
+ `);
538
+ }
539
+ console.log("Current configuration:");
540
+ console.log("=====================");
541
+ const config = result.config;
542
+ console.log(`
543
+ AI Rules Directory: ${config.aiRulesDir}`);
544
+ console.log(`
545
+ Default Targets: ${config.defaultTargets.join(", ")}`);
546
+ if (config.exclude && config.exclude.length > 0) {
547
+ console.log(`Excluded Targets: ${config.exclude.join(", ")}`);
548
+ }
549
+ console.log("\nOutput Paths:");
550
+ for (const [tool, outputPath] of Object.entries(config.outputPaths)) {
551
+ console.log(` ${tool}: ${outputPath}`);
552
+ }
553
+ if (config.baseDir) {
554
+ const dirs = Array.isArray(config.baseDir) ? config.baseDir : [config.baseDir];
555
+ console.log(`
556
+ Base Directories: ${dirs.join(", ")}`);
557
+ }
558
+ console.log(`
559
+ Verbose: ${config.verbose || false}`);
560
+ console.log(`Delete before generate: ${config.delete || false}`);
561
+ if (config.watch) {
562
+ console.log("\nWatch Configuration:");
563
+ console.log(` Enabled: ${config.watch.enabled || false}`);
564
+ if (config.watch.interval) {
565
+ console.log(` Interval: ${config.watch.interval}ms`);
566
+ }
567
+ if (config.watch.ignore && config.watch.ignore.length > 0) {
568
+ console.log(` Ignore patterns: ${config.watch.ignore.join(", ")}`);
569
+ }
570
+ }
571
+ console.log("\nTip: Use 'rulesync config init' to create a configuration file.");
572
+ } catch (error) {
573
+ console.error(
574
+ "\u274C Failed to load configuration:",
575
+ error instanceof Error ? error.message : String(error)
576
+ );
577
+ process.exit(1);
578
+ }
579
+ }
580
+ var FORMAT_CONFIG = {
581
+ jsonc: {
582
+ filename: "rulesync.jsonc",
583
+ generator: generateJsoncConfig
584
+ },
585
+ ts: {
586
+ filename: "rulesync.ts",
587
+ generator: generateTsConfig
588
+ }
589
+ };
590
+ async function initConfig(options) {
591
+ const validFormats = Object.keys(FORMAT_CONFIG);
592
+ const selectedFormat = options.format || "jsonc";
593
+ if (!validFormats.includes(selectedFormat)) {
594
+ console.error(
595
+ `\u274C Invalid format: ${selectedFormat}. Valid formats are: ${validFormats.join(", ")}`
596
+ );
597
+ process.exit(1);
598
+ }
599
+ const formatConfig = FORMAT_CONFIG[selectedFormat];
600
+ const filename = formatConfig.filename;
601
+ const configOptions = {};
602
+ if (options.targets) {
603
+ const targets = options.targets.split(",").map((t) => t.trim());
604
+ const validTargets = [];
605
+ for (const target of targets) {
606
+ const result = ToolTargetSchema.safeParse(target);
607
+ if (result.success) {
608
+ validTargets.push(result.data);
609
+ } else {
610
+ console.error(`\u274C Invalid target: ${target}`);
611
+ process.exit(1);
612
+ }
613
+ }
614
+ configOptions.targets = validTargets;
615
+ }
616
+ if (options.exclude) {
617
+ const excludes = options.exclude.split(",").map((t) => t.trim());
618
+ const validExcludes = [];
619
+ for (const exclude of excludes) {
620
+ const result = ToolTargetSchema.safeParse(exclude);
621
+ if (result.success) {
622
+ validExcludes.push(result.data);
623
+ } else {
624
+ console.error(`\u274C Invalid exclude target: ${exclude}`);
625
+ process.exit(1);
626
+ }
627
+ }
628
+ configOptions.exclude = validExcludes;
629
+ }
630
+ if (options.aiRulesDir) {
631
+ configOptions.aiRulesDir = options.aiRulesDir;
632
+ }
633
+ if (options.baseDir) {
634
+ configOptions.baseDir = options.baseDir;
635
+ }
636
+ if (options.verbose !== void 0) {
637
+ configOptions.verbose = options.verbose;
638
+ }
639
+ if (options.delete !== void 0) {
640
+ configOptions.delete = options.delete;
641
+ }
642
+ const content = formatConfig.generator(configOptions);
643
+ const filepath = path2.join(process.cwd(), filename);
644
+ try {
645
+ const fs2 = await import("fs/promises");
646
+ await fs2.access(filepath);
647
+ console.error(`\u274C Configuration file already exists: ${filepath}`);
648
+ console.log("Remove the existing file or choose a different format.");
649
+ process.exit(1);
650
+ } catch {
651
+ }
652
+ try {
653
+ writeFileSync(filepath, content, "utf-8");
654
+ console.log(`\u2705 Created configuration file: ${filepath}`);
655
+ console.log("\nYou can now customize the configuration to fit your needs.");
656
+ console.log("Run 'rulesync generate' to use the new configuration.");
657
+ } catch (error) {
658
+ console.error(
659
+ `\u274C Failed to create configuration file: ${error instanceof Error ? error.message : String(error)}`
660
+ );
661
+ process.exit(1);
662
+ }
663
+ }
664
+ function generateJsoncConfig(options) {
665
+ if (options && Object.keys(options).length > 0) {
666
+ return generateMinimalConfig(options);
667
+ }
668
+ return generateSampleConfig(options);
669
+ }
670
+ function generateTsConfig(options) {
671
+ if (!options || Object.keys(options).length === 0) {
672
+ return `import type { ConfigOptions } from "rulesync";
673
+
674
+ const config: ConfigOptions = {
675
+ // List of tools to generate configurations for
676
+ // Available: ${ALL_TOOL_TARGETS.join(", ")}
677
+ targets: ${JSON.stringify(ALL_TOOL_TARGETS)},
678
+
679
+ // Custom output paths for specific tools
680
+ // outputPaths: {
681
+ // copilot: ".github/copilot-instructions.md",
682
+ // },
683
+
684
+ // Delete existing files before generating
685
+ // delete: false,
686
+
687
+ // Enable verbose output
688
+ verbose: true,
689
+ };
690
+
691
+ export default config;`;
692
+ }
693
+ const configLines = [];
694
+ if (options.targets) {
695
+ configLines.push(` targets: ${JSON.stringify(options.targets)}`);
696
+ }
697
+ if (options.exclude) {
698
+ configLines.push(` exclude: ${JSON.stringify(options.exclude)}`);
699
+ }
700
+ if (options.aiRulesDir) {
701
+ configLines.push(` aiRulesDir: "${options.aiRulesDir}"`);
702
+ }
703
+ if (options.outputPaths) {
704
+ const pathsStr = JSON.stringify(options.outputPaths, null, 4).split("\n").map((line, i) => i === 0 ? line : ` ${line}`).join("\n");
705
+ configLines.push(` outputPaths: ${pathsStr}`);
706
+ }
707
+ if (options.baseDir) {
708
+ configLines.push(` baseDir: ${JSON.stringify(options.baseDir)}`);
709
+ }
710
+ if (options.delete !== void 0) {
711
+ configLines.push(` delete: ${options.delete}`);
712
+ }
713
+ if (options.verbose !== void 0) {
714
+ configLines.push(` verbose: ${options.verbose}`);
715
+ }
716
+ if (options.watch) {
717
+ const watchStr = JSON.stringify(options.watch, null, 4).split("\n").map((line, i) => i === 0 ? line : ` ${line}`).join("\n");
718
+ configLines.push(` watch: ${watchStr}`);
719
+ }
720
+ const configContent = `import type { ConfigOptions } from "rulesync";
721
+
722
+ const config: ConfigOptions = {
723
+ ${configLines.join(",\n")},
724
+ };
725
+
726
+ export default config;
727
+ `;
728
+ return configContent;
729
+ }
730
+
731
+ // src/cli/commands/generate.ts
732
+ import { join as join13 } from "path";
733
+
734
+ // src/generators/ignore/shared-factory.ts
735
+ import { join as join3 } from "path";
736
+
737
+ // src/generators/ignore/shared-helpers.ts
245
738
  function extractIgnorePatternsFromRules(rules) {
246
739
  const patterns = [];
247
740
  for (const rule of rules) {
248
741
  if (rule.frontmatter.globs && rule.frontmatter.globs.length > 0) {
249
742
  for (const glob of rule.frontmatter.globs) {
250
- if (shouldExcludeFromAugmentCode(glob)) {
743
+ if (shouldExcludeFromAI(glob)) {
251
744
  patterns.push(`# Exclude: ${rule.frontmatter.description}`);
252
745
  patterns.push(glob);
253
746
  }
254
747
  }
255
748
  }
256
- const contentPatterns = extractAugmentCodeIgnorePatternsFromContent(rule.content);
749
+ const contentPatterns = extractIgnorePatternsFromContent(rule.content);
257
750
  patterns.push(...contentPatterns);
258
751
  }
259
752
  return patterns;
260
753
  }
261
- function shouldExcludeFromAugmentCode(glob) {
754
+ function shouldExcludeFromAI(glob) {
262
755
  const excludePatterns = [
263
756
  // Large generated files that slow indexing
264
757
  "**/assets/generated/**",
@@ -274,8 +767,11 @@ function shouldExcludeFromAugmentCode(glob) {
274
767
  // Configuration that might contain sensitive data
275
768
  "**/config/production/**",
276
769
  "**/config/secrets/**",
770
+ "**/config/prod/**",
277
771
  "**/deploy/prod/**",
278
- // Internal documentation
772
+ "**/*.prod.*",
773
+ // Internal documentation that might be sensitive
774
+ "**/internal/**",
279
775
  "**/internal-docs/**",
280
776
  "**/proprietary/**",
281
777
  "**/personal-notes/**",
@@ -287,11 +783,17 @@ function shouldExcludeFromAugmentCode(glob) {
287
783
  return regex.test(glob);
288
784
  });
289
785
  }
290
- function extractAugmentCodeIgnorePatternsFromContent(content) {
786
+ function extractIgnorePatternsFromContent(content) {
291
787
  const patterns = [];
292
788
  const lines = content.split("\n");
293
789
  for (const line of lines) {
294
790
  const trimmed = line.trim();
791
+ if (trimmed.startsWith("# IGNORE:") || trimmed.startsWith("# aiignore:")) {
792
+ const pattern = trimmed.replace(/^# (IGNORE|aiignore):\s*/, "").trim();
793
+ if (pattern) {
794
+ patterns.push(pattern);
795
+ }
796
+ }
295
797
  if (trimmed.startsWith("# AUGMENT_IGNORE:") || trimmed.startsWith("# augmentignore:")) {
296
798
  const pattern = trimmed.replace(/^# (AUGMENT_IGNORE|augmentignore):\s*/, "").trim();
297
799
  if (pattern) {
@@ -304,8 +806,8 @@ function extractAugmentCodeIgnorePatternsFromContent(content) {
304
806
  patterns.push(`!${pattern}`);
305
807
  }
306
808
  }
307
- if (trimmed.includes("large file") || trimmed.includes("binary") || trimmed.includes("media")) {
308
- const matches = trimmed.match(/['"`]([^'"`]+\.(mp4|avi|zip|tar\.gz|rar|pdf|doc|xlsx))['"`]/g);
809
+ if (trimmed.includes("exclude") || trimmed.includes("ignore")) {
810
+ const matches = trimmed.match(/['"`]([^'"`]+\.(log|tmp|cache|temp))['"`]/g);
309
811
  if (matches) {
310
812
  patterns.push(...matches.map((m) => m.replace(/['"`]/g, "")));
311
813
  }
@@ -313,206 +815,384 @@ function extractAugmentCodeIgnorePatternsFromContent(content) {
313
815
  }
314
816
  return patterns;
315
817
  }
818
+ function extractAugmentCodeIgnorePatternsFromContent(content) {
819
+ const patterns = [];
820
+ const lines = content.split("\n");
821
+ for (const line of lines) {
822
+ const trimmed = line.trim();
823
+ if (trimmed.startsWith("# AUGMENT_IGNORE:") || trimmed.startsWith("# augmentignore:")) {
824
+ const pattern = trimmed.replace(/^# (AUGMENT_IGNORE|augmentignore):\s*/, "").trim();
825
+ if (pattern) {
826
+ patterns.push(pattern);
827
+ }
828
+ }
829
+ if (trimmed.startsWith("# AUGMENT_INCLUDE:") || trimmed.startsWith("# augmentinclude:")) {
830
+ const pattern = trimmed.replace(/^# (AUGMENT_INCLUDE|augmentinclude):\s*/, "").trim();
831
+ if (pattern) {
832
+ patterns.push(`!${pattern}`);
833
+ }
834
+ }
835
+ if (trimmed.includes("large file") || trimmed.includes("binary") || trimmed.includes("media")) {
836
+ const regex = /['"`]([^'"`]+\.(mp4|avi|zip|tar\.gz|rar|pdf|doc|xlsx))['"`]/g;
837
+ let match;
838
+ while ((match = regex.exec(trimmed)) !== null) {
839
+ if (match[1]) {
840
+ patterns.push(match[1]);
841
+ }
842
+ }
843
+ }
844
+ }
845
+ return patterns;
846
+ }
316
847
 
317
- // src/generators/ignore/kiro.ts
318
- import { join as join3 } from "path";
319
- async function generateKiroIgnoreFiles(rules, config, baseDir) {
848
+ // src/generators/ignore/shared-factory.ts
849
+ function generateIgnoreFile(rules, config, ignoreConfig, baseDir) {
320
850
  const outputs = [];
321
- const aiignoreContent = generateAiignoreContent(rules);
851
+ const content = generateIgnoreContent(rules, ignoreConfig);
322
852
  const outputPath = baseDir || process.cwd();
323
- const filepath = join3(outputPath, ".aiignore");
853
+ const filepath = join3(outputPath, ignoreConfig.filename);
324
854
  outputs.push({
325
- tool: "kiro",
855
+ tool: ignoreConfig.tool,
326
856
  filepath,
327
- content: aiignoreContent
857
+ content
328
858
  });
329
859
  return outputs;
330
860
  }
331
- function generateAiignoreContent(rules) {
332
- const lines = [
333
- "# Generated by rulesync - Kiro AI-specific exclusions",
334
- "# This file excludes files that can be in Git but shouldn't be read by the AI",
335
- ""
336
- ];
337
- lines.push(
338
- "# Data files AI shouldn't process",
339
- "*.csv",
340
- "*.tsv",
341
- "*.sqlite",
342
- "*.db",
343
- "",
344
- "# Large binary files",
345
- "*.zip",
346
- "*.tar.gz",
347
- "*.rar",
861
+ function generateIgnoreContent(rules, config) {
862
+ const lines = [];
863
+ lines.push(...config.header);
864
+ lines.push("");
865
+ if (config.includeCommonPatterns) {
866
+ lines.push(...getCommonIgnorePatterns());
867
+ }
868
+ if (config.corePatterns.length > 0) {
869
+ lines.push(...config.corePatterns);
870
+ lines.push("");
871
+ }
872
+ const rulePatterns = extractIgnorePatternsFromRules(rules);
873
+ const customPatterns = config.customPatternProcessor ? config.customPatternProcessor(rules) : [];
874
+ const allPatterns = [...rulePatterns, ...customPatterns];
875
+ if (allPatterns.length > 0) {
876
+ const headerText = config.projectPatternsHeader || "# \u2500\u2500\u2500\u2500\u2500 Project-specific exclusions from rulesync rules \u2500\u2500\u2500\u2500\u2500";
877
+ lines.push(headerText);
878
+ lines.push(...allPatterns);
879
+ lines.push("");
880
+ }
881
+ return lines.join("\n");
882
+ }
883
+ function getCommonIgnorePatterns() {
884
+ return [
885
+ "# \u2500\u2500\u2500\u2500\u2500 Source Control Metadata \u2500\u2500\u2500\u2500\u2500",
886
+ ".git/",
887
+ ".svn/",
888
+ ".hg/",
889
+ ".idea/",
890
+ "*.iml",
891
+ ".vscode/settings.json",
348
892
  "",
349
- "# Sensitive documentation",
350
- "internal-docs/",
351
- "confidential/",
893
+ "# \u2500\u2500\u2500\u2500\u2500 Build Artifacts \u2500\u2500\u2500\u2500\u2500",
894
+ "/out/",
895
+ "/dist/",
896
+ "/target/",
897
+ "/build/",
898
+ "*.class",
899
+ "*.jar",
900
+ "*.war",
352
901
  "",
353
- "# Test data that might confuse AI",
354
- "test/fixtures/large-*.json",
355
- "benchmark-results/",
902
+ "# \u2500\u2500\u2500\u2500\u2500 Secrets & Credentials \u2500\u2500\u2500\u2500\u2500",
903
+ "# Environment files",
904
+ ".env",
905
+ ".env.*",
906
+ "!.env.example",
356
907
  "",
357
- "# Reinforce critical exclusions from .gitignore",
908
+ "# Key material",
358
909
  "*.pem",
359
910
  "*.key",
360
- ".env*",
361
- ""
362
- );
363
- const rulePatterns = extractIgnorePatternsFromRules2(rules);
364
- if (rulePatterns.length > 0) {
365
- lines.push("# Project-specific exclusions from rulesync rules");
366
- lines.push(...rulePatterns);
367
- lines.push("");
368
- }
369
- return lines.join("\n");
370
- }
371
- function extractIgnorePatternsFromRules2(rules) {
372
- const patterns = [];
373
- for (const rule of rules) {
374
- if (rule.frontmatter.globs && rule.frontmatter.globs.length > 0) {
375
- for (const glob of rule.frontmatter.globs) {
376
- if (shouldExcludeFromAI(glob)) {
377
- patterns.push(`# Exclude: ${rule.frontmatter.description}`);
378
- patterns.push(glob);
379
- }
380
- }
381
- }
382
- const contentPatterns = extractIgnorePatternsFromContent(rule.content);
383
- patterns.push(...contentPatterns);
384
- }
385
- return patterns;
386
- }
387
- function shouldExcludeFromAI(glob) {
388
- const excludePatterns = [
389
- // Test and fixture files that might be large or confusing
390
- "**/test/fixtures/**",
391
- "**/tests/fixtures/**",
392
- "**/*.fixture.*",
393
- // Build and generated files
394
- "**/dist/**",
395
- "**/build/**",
396
- "**/coverage/**",
397
- // Configuration that might contain sensitive data
398
- "**/config/production/**",
399
- "**/config/prod/**",
400
- "**/*.prod.*",
401
- // Documentation that might be sensitive
402
- "**/internal/**",
403
- "**/private/**",
404
- "**/confidential/**"
405
- ];
406
- return excludePatterns.some((pattern) => {
407
- const regex = new RegExp(pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*"));
408
- return regex.test(glob);
409
- });
410
- }
411
- function extractIgnorePatternsFromContent(content) {
412
- const patterns = [];
413
- const lines = content.split("\n");
414
- for (const line of lines) {
415
- const trimmed = line.trim();
416
- if (trimmed.startsWith("# IGNORE:") || trimmed.startsWith("# aiignore:")) {
417
- const pattern = trimmed.replace(/^# (IGNORE|aiignore):\s*/, "").trim();
418
- if (pattern) {
419
- patterns.push(pattern);
420
- }
421
- }
422
- if (trimmed.includes("exclude") || trimmed.includes("ignore")) {
423
- const matches = trimmed.match(/['"`]([^'"`]+\.(log|tmp|cache|temp))['"`]/g);
424
- if (matches) {
425
- patterns.push(...matches.map((m) => m.replace(/['"`]/g, "")));
911
+ "*.crt",
912
+ "*.p12",
913
+ "*.pfx",
914
+ "*.der",
915
+ "id_rsa*",
916
+ "id_dsa*",
917
+ "*.ppk",
918
+ "",
919
+ "# Cloud and service configs",
920
+ "aws-credentials.json",
921
+ "gcp-service-account*.json",
922
+ "azure-credentials.json",
923
+ "secrets/**",
924
+ "config/secrets/",
925
+ "**/secrets/",
926
+ "",
927
+ "# Database credentials",
928
+ "database.yml",
929
+ "**/database/config.*",
930
+ "",
931
+ "# API keys and tokens",
932
+ "**/apikeys/",
933
+ "**/*_token*",
934
+ "**/*_secret*",
935
+ "**/*api_key*",
936
+ "",
937
+ "# \u2500\u2500\u2500\u2500\u2500 Infrastructure & Deployment \u2500\u2500\u2500\u2500\u2500",
938
+ "# Terraform state",
939
+ "*.tfstate",
940
+ "*.tfstate.*",
941
+ ".terraform/",
942
+ "",
943
+ "# Kubernetes secrets",
944
+ "**/k8s/**/secret*.yaml",
945
+ "**/kubernetes/**/secret*.yaml",
946
+ "",
947
+ "# Docker secrets",
948
+ "docker-compose.override.yml",
949
+ "**/docker/secrets/",
950
+ "",
951
+ "# \u2500\u2500\u2500\u2500\u2500 Logs & Runtime Data \u2500\u2500\u2500\u2500\u2500",
952
+ "*.log",
953
+ "*.tmp",
954
+ "*.cache",
955
+ "logs/",
956
+ "/var/log/",
957
+ "coverage/",
958
+ ".nyc_output/",
959
+ "",
960
+ "# \u2500\u2500\u2500\u2500\u2500 Large Data Files \u2500\u2500\u2500\u2500\u2500",
961
+ "*.csv",
962
+ "*.xlsx",
963
+ "*.sqlite",
964
+ "*.db",
965
+ "*.dump",
966
+ "data/",
967
+ "datasets/",
968
+ "",
969
+ "# \u2500\u2500\u2500\u2500\u2500 Node.js Specific \u2500\u2500\u2500\u2500\u2500",
970
+ "node_modules/",
971
+ ".pnpm-store/",
972
+ ".yarn/",
973
+ ".next/",
974
+ ".nuxt/",
975
+ ".cache/",
976
+ ".parcel-cache/",
977
+ "",
978
+ "# \u2500\u2500\u2500\u2500\u2500 Python Specific \u2500\u2500\u2500\u2500\u2500",
979
+ "__pycache__/",
980
+ "*.pyc",
981
+ "*.pyo",
982
+ "*.pyd",
983
+ ".Python",
984
+ "venv/",
985
+ ".venv/",
986
+ "env/",
987
+ ".env/",
988
+ "",
989
+ "# \u2500\u2500\u2500\u2500\u2500 Java Specific \u2500\u2500\u2500\u2500\u2500",
990
+ "*.class",
991
+ "*.jar",
992
+ "*.war",
993
+ "target/",
994
+ ""
995
+ ];
996
+ }
997
+ var ignoreConfigs = {
998
+ junie: {
999
+ tool: "junie",
1000
+ filename: ".aiignore",
1001
+ header: [
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
+ corePatterns: [
1007
+ "# \u2500\u2500\u2500\u2500\u2500 Allow specific source files (uncomment as needed) \u2500\u2500\u2500\u2500\u2500",
1008
+ "# !src/**/*.ts",
1009
+ "# !src/**/*.js",
1010
+ "# !lib/**/*.py",
1011
+ "# !src/main/**/*.java"
1012
+ ],
1013
+ includeCommonPatterns: true
1014
+ },
1015
+ kiro: {
1016
+ tool: "kiro",
1017
+ filename: ".aiignore",
1018
+ header: [
1019
+ "# Generated by rulesync - Kiro AI-specific exclusions",
1020
+ "# This file excludes files that can be in Git but shouldn't be read by the AI"
1021
+ ],
1022
+ corePatterns: [
1023
+ "# Data files AI shouldn't process",
1024
+ "*.csv",
1025
+ "*.tsv",
1026
+ "*.sqlite",
1027
+ "*.db",
1028
+ "",
1029
+ "# Large binary files",
1030
+ "*.zip",
1031
+ "*.tar.gz",
1032
+ "*.rar",
1033
+ "",
1034
+ "# Sensitive documentation",
1035
+ "internal-docs/",
1036
+ "confidential/",
1037
+ "",
1038
+ "# Test data that might confuse AI",
1039
+ "test/fixtures/large-*.json",
1040
+ "benchmark-results/",
1041
+ "",
1042
+ "# Reinforce critical exclusions from .gitignore",
1043
+ "*.pem",
1044
+ "*.key",
1045
+ ".env*"
1046
+ ],
1047
+ includeCommonPatterns: false,
1048
+ projectPatternsHeader: "# Project-specific exclusions from rulesync rules"
1049
+ },
1050
+ augmentcode: {
1051
+ tool: "augmentcode",
1052
+ filename: ".augmentignore",
1053
+ header: [
1054
+ "# Generated by rulesync - AugmentCode ignore patterns",
1055
+ "# AugmentCode uses a two-tier approach: .gitignore first, then .augmentignore",
1056
+ "# This file provides Augment-specific exclusions and re-inclusions"
1057
+ ],
1058
+ corePatterns: [
1059
+ "# Security and Secrets (critical exclusions)",
1060
+ "# Environment files",
1061
+ ".env*",
1062
+ "",
1063
+ "# Private keys and certificates",
1064
+ "*.pem",
1065
+ "*.key",
1066
+ "*.p12",
1067
+ "*.crt",
1068
+ "*.der",
1069
+ "",
1070
+ "# SSH keys",
1071
+ "id_rsa*",
1072
+ "id_dsa*",
1073
+ "",
1074
+ "# AWS credentials",
1075
+ ".aws/",
1076
+ "aws-exports.js",
1077
+ "",
1078
+ "# API keys and tokens",
1079
+ "**/apikeys/",
1080
+ "**/*_token*",
1081
+ "**/*_secret*",
1082
+ "",
1083
+ "# Build Artifacts and Dependencies",
1084
+ "# Build outputs",
1085
+ "dist/",
1086
+ "build/",
1087
+ "out/",
1088
+ "target/",
1089
+ "",
1090
+ "# Dependencies",
1091
+ "node_modules/",
1092
+ "venv/",
1093
+ "*.egg-info/",
1094
+ "",
1095
+ "# Logs",
1096
+ "*.log",
1097
+ "logs/",
1098
+ "",
1099
+ "# Temporary files",
1100
+ "*.tmp",
1101
+ "*.swp",
1102
+ "*.swo",
1103
+ "*~",
1104
+ "",
1105
+ "# Large Files and Media",
1106
+ "# Binary files",
1107
+ "*.jar",
1108
+ "*.png",
1109
+ "*.jpg",
1110
+ "*.jpeg",
1111
+ "*.gif",
1112
+ "*.mp4",
1113
+ "*.avi",
1114
+ "*.zip",
1115
+ "*.tar.gz",
1116
+ "*.rar",
1117
+ "",
1118
+ "# Database files",
1119
+ "*.sqlite",
1120
+ "*.db",
1121
+ "*.mdb",
1122
+ "",
1123
+ "# Data files",
1124
+ "*.csv",
1125
+ "*.tsv",
1126
+ "*.xlsx",
1127
+ "",
1128
+ "# Performance Optimization",
1129
+ "# Exclude files that are too large for effective AI processing",
1130
+ "**/*.{mp4,avi,mov,mkv}",
1131
+ "**/*.{zip,tar,gz,rar}",
1132
+ "**/*.{pdf,doc,docx}",
1133
+ "**/logs/**/*.log",
1134
+ "",
1135
+ "# But include small configuration files",
1136
+ "!**/config.{json,yaml,yml}",
1137
+ "",
1138
+ "# Team Collaboration",
1139
+ "# Exclude personal IDE settings",
1140
+ ".vscode/settings.json",
1141
+ ".idea/workspace.xml",
1142
+ "",
1143
+ "# But include shared team settings",
1144
+ "!.vscode/extensions.json",
1145
+ "!.idea/codeStyles/",
1146
+ "",
1147
+ "# Exclude test fixtures with sensitive data",
1148
+ "tests/fixtures/real-data/**",
1149
+ "",
1150
+ "# Re-include important documentation",
1151
+ "!vendor/*/README.md",
1152
+ "!third-party/*/LICENSE"
1153
+ ],
1154
+ includeCommonPatterns: false,
1155
+ projectPatternsHeader: "# Project-specific patterns from rulesync rules",
1156
+ customPatternProcessor: (rules) => {
1157
+ const augmentPatterns = [];
1158
+ for (const rule of rules) {
1159
+ augmentPatterns.push(...extractAugmentCodeIgnorePatternsFromContent(rule.content));
426
1160
  }
1161
+ return augmentPatterns;
427
1162
  }
428
1163
  }
429
- return patterns;
1164
+ };
1165
+
1166
+ // src/generators/ignore/augmentcode.ts
1167
+ async function generateAugmentCodeIgnoreFiles(rules, config, baseDir) {
1168
+ return generateIgnoreFile(rules, config, ignoreConfigs.augmentcode, baseDir);
430
1169
  }
431
1170
 
432
- // src/generators/rules/augmentcode.ts
433
- import { join as join7 } from "path";
1171
+ // src/generators/ignore/junie.ts
1172
+ async function generateJunieIgnoreFiles(rules, config, baseDir) {
1173
+ return generateIgnoreFile(rules, config, ignoreConfigs.junie, baseDir);
1174
+ }
434
1175
 
435
- // src/generators/rules/shared-helpers.ts
1176
+ // src/generators/ignore/kiro.ts
1177
+ async function generateKiroIgnoreFiles(rules, config, baseDir) {
1178
+ return generateIgnoreFile(rules, config, ignoreConfigs.kiro, baseDir);
1179
+ }
1180
+
1181
+ // src/generators/rules/augmentcode.ts
436
1182
  import { join as join6 } from "path";
437
1183
 
438
- // src/utils/ignore.ts
1184
+ // src/generators/rules/shared-helpers.ts
439
1185
  import { join as join5 } from "path";
440
- import micromatch from "micromatch";
441
-
442
- // src/utils/file.ts
443
- import { mkdir as mkdir2, readdir, readFile, rm, stat, writeFile as writeFile2 } from "fs/promises";
444
- import { dirname, join as join4 } from "path";
445
- async function ensureDir(dirPath) {
446
- try {
447
- await stat(dirPath);
448
- } catch {
449
- await mkdir2(dirPath, { recursive: true });
450
- }
451
- }
452
- async function readFileContent(filepath) {
453
- return readFile(filepath, "utf-8");
454
- }
455
- async function writeFileContent(filepath, content) {
456
- await ensureDir(dirname(filepath));
457
- await writeFile2(filepath, content, "utf-8");
458
- }
459
- async function fileExists(filepath) {
460
- try {
461
- await stat(filepath);
462
- return true;
463
- } catch {
464
- return false;
465
- }
466
- }
467
- async function findFiles(dir, extension = ".md") {
468
- try {
469
- const files = await readdir(dir);
470
- return files.filter((file) => file.endsWith(extension)).map((file) => join4(dir, file));
471
- } catch {
472
- return [];
473
- }
474
- }
475
- async function removeDirectory(dirPath) {
476
- const dangerousPaths = [".", "/", "~", "src", "node_modules"];
477
- if (dangerousPaths.includes(dirPath) || dirPath === "") {
478
- console.warn(`Skipping deletion of dangerous path: ${dirPath}`);
479
- return;
480
- }
481
- try {
482
- if (await fileExists(dirPath)) {
483
- await rm(dirPath, { recursive: true, force: true });
484
- }
485
- } catch (error) {
486
- console.warn(`Failed to remove directory ${dirPath}:`, error);
487
- }
488
- }
489
- async function removeFile(filepath) {
490
- try {
491
- if (await fileExists(filepath)) {
492
- await rm(filepath);
493
- }
494
- } catch (error) {
495
- console.warn(`Failed to remove file ${filepath}:`, error);
496
- }
497
- }
498
- async function removeClaudeGeneratedFiles() {
499
- const filesToRemove = ["CLAUDE.md", ".claude/memories"];
500
- for (const fileOrDir of filesToRemove) {
501
- if (fileOrDir.endsWith("/memories")) {
502
- await removeDirectory(fileOrDir);
503
- } else {
504
- await removeFile(fileOrDir);
505
- }
506
- }
507
- }
508
1186
 
509
1187
  // src/utils/ignore.ts
1188
+ import { join as join4 } from "path";
1189
+ import micromatch from "micromatch";
510
1190
  var cachedIgnorePatterns = null;
511
1191
  async function loadIgnorePatterns(baseDir = process.cwd()) {
512
1192
  if (cachedIgnorePatterns) {
513
1193
  return cachedIgnorePatterns;
514
1194
  }
515
- const ignorePath = join5(baseDir, ".rulesyncignore");
1195
+ const ignorePath = join4(baseDir, ".rulesyncignore");
516
1196
  if (!await fileExists(ignorePath)) {
517
1197
  cachedIgnorePatterns = { patterns: [] };
518
1198
  return cachedIgnorePatterns;
@@ -557,7 +1237,7 @@ function filterIgnoredFiles(files, ignorePatterns) {
557
1237
 
558
1238
  // src/generators/rules/shared-helpers.ts
559
1239
  function resolveOutputDir(config, tool, baseDir) {
560
- return baseDir ? join6(baseDir, config.outputPaths[tool]) : config.outputPaths[tool];
1240
+ return baseDir ? join5(baseDir, config.outputPaths[tool]) : config.outputPaths[tool];
561
1241
  }
562
1242
  function createOutputsArray() {
563
1243
  return [];
@@ -566,7 +1246,7 @@ function addOutput(outputs, tool, config, baseDir, relativePath, content) {
566
1246
  const outputDir = resolveOutputDir(config, tool, baseDir);
567
1247
  outputs.push({
568
1248
  tool,
569
- filepath: join6(outputDir, relativePath),
1249
+ filepath: join5(outputDir, relativePath),
570
1250
  content
571
1251
  });
572
1252
  }
@@ -575,7 +1255,7 @@ async function generateRulesConfig(rules, config, generatorConfig, baseDir) {
575
1255
  for (const rule of rules) {
576
1256
  const content = generatorConfig.generateContent(rule);
577
1257
  const outputDir = resolveOutputDir(config, generatorConfig.tool, baseDir);
578
- const filepath = generatorConfig.pathResolver ? generatorConfig.pathResolver(rule, outputDir) : join6(outputDir, `${rule.filename}${generatorConfig.fileExtension}`);
1258
+ const filepath = generatorConfig.pathResolver ? generatorConfig.pathResolver(rule, outputDir) : join5(outputDir, `${rule.filename}${generatorConfig.fileExtension}`);
579
1259
  outputs.push({
580
1260
  tool: generatorConfig.tool,
581
1261
  filepath,
@@ -584,17 +1264,61 @@ async function generateRulesConfig(rules, config, generatorConfig, baseDir) {
584
1264
  }
585
1265
  const ignorePatterns = await loadIgnorePatterns(baseDir);
586
1266
  if (ignorePatterns.patterns.length > 0) {
587
- const ignorePath = baseDir ? join6(baseDir, generatorConfig.ignoreFileName) : generatorConfig.ignoreFileName;
588
- const ignoreContent = generateIgnoreFile(ignorePatterns.patterns, generatorConfig.tool);
1267
+ const ignorePath = baseDir ? join5(baseDir, generatorConfig.ignoreFileName) : generatorConfig.ignoreFileName;
1268
+ const ignoreContent = generateIgnoreFile2(ignorePatterns.patterns, generatorConfig.tool);
1269
+ outputs.push({
1270
+ tool: generatorConfig.tool,
1271
+ filepath: ignorePath,
1272
+ content: ignoreContent
1273
+ });
1274
+ }
1275
+ return outputs;
1276
+ }
1277
+ async function generateComplexRules(rules, config, generatorConfig, baseDir) {
1278
+ const outputs = [];
1279
+ const rootRules = rules.filter((r) => r.frontmatter.root === true);
1280
+ const detailRules = rules.filter((r) => r.frontmatter.root === false);
1281
+ const rootRule = rootRules[0];
1282
+ if (generatorConfig.generateDetailContent && generatorConfig.detailSubDir) {
1283
+ for (const rule of detailRules) {
1284
+ const content = generatorConfig.generateDetailContent(rule);
1285
+ const filepath = baseDir ? join5(baseDir, generatorConfig.detailSubDir, `${rule.filename}.md`) : join5(generatorConfig.detailSubDir, `${rule.filename}.md`);
1286
+ outputs.push({
1287
+ tool: generatorConfig.tool,
1288
+ filepath,
1289
+ content
1290
+ });
1291
+ }
1292
+ }
1293
+ if (generatorConfig.generateRootContent && generatorConfig.rootFilePath) {
1294
+ const rootContent = generatorConfig.generateRootContent(rootRule, detailRules, baseDir);
1295
+ const rootFilepath = baseDir ? join5(baseDir, generatorConfig.rootFilePath) : generatorConfig.rootFilePath;
1296
+ outputs.push({
1297
+ tool: generatorConfig.tool,
1298
+ filepath: rootFilepath,
1299
+ content: rootContent
1300
+ });
1301
+ }
1302
+ const ignorePatterns = await loadIgnorePatterns(baseDir);
1303
+ if (ignorePatterns.patterns.length > 0) {
1304
+ const ignorePath = baseDir ? join5(baseDir, generatorConfig.ignoreFileName) : generatorConfig.ignoreFileName;
1305
+ const ignoreContent = generateIgnoreFile2(ignorePatterns.patterns, generatorConfig.tool);
589
1306
  outputs.push({
590
1307
  tool: generatorConfig.tool,
591
1308
  filepath: ignorePath,
592
1309
  content: ignoreContent
593
1310
  });
1311
+ if (generatorConfig.updateAdditionalConfig) {
1312
+ const additionalOutputs = await generatorConfig.updateAdditionalConfig(
1313
+ ignorePatterns.patterns,
1314
+ baseDir
1315
+ );
1316
+ outputs.push(...additionalOutputs);
1317
+ }
594
1318
  }
595
1319
  return outputs;
596
1320
  }
597
- function generateIgnoreFile(patterns, tool) {
1321
+ function generateIgnoreFile2(patterns, tool) {
598
1322
  const lines = [
599
1323
  "# Generated by rulesync from .rulesyncignore",
600
1324
  "# This file is automatically generated. Do not edit manually."
@@ -627,7 +1351,7 @@ async function generateAugmentcodeConfig(rules, config, baseDir) {
627
1351
  "augmentcode",
628
1352
  config,
629
1353
  baseDir,
630
- join7(".augment", "rules", `${rule.filename}.md`),
1354
+ join6(".augment", "rules", `${rule.filename}.md`),
631
1355
  generateRuleFile(rule)
632
1356
  );
633
1357
  });
@@ -680,42 +1404,29 @@ function generateLegacyGuidelinesFile(allRules) {
680
1404
  }
681
1405
 
682
1406
  // src/generators/rules/claudecode.ts
683
- import { join as join8 } from "path";
684
-
685
- // src/types/claudecode.ts
686
- import { z } from "zod/mini";
687
- var ClaudeSettingsSchema = z.looseObject({
688
- permissions: z._default(
689
- z.looseObject({
690
- deny: z._default(z.array(z.string()), [])
691
- }),
692
- { deny: [] }
693
- )
694
- });
695
-
696
- // src/generators/rules/claudecode.ts
1407
+ import { join as join7 } from "path";
697
1408
  async function generateClaudecodeConfig(rules, config, baseDir) {
698
1409
  const outputs = [];
699
1410
  const rootRules = rules.filter((r) => r.frontmatter.root === true);
700
1411
  const detailRules = rules.filter((r) => r.frontmatter.root === false);
701
1412
  const claudeMdContent = generateClaudeMarkdown(rootRules, detailRules);
702
- const claudeOutputDir = baseDir ? join8(baseDir, config.outputPaths.claudecode) : config.outputPaths.claudecode;
1413
+ const claudeOutputDir = baseDir ? join7(baseDir, config.outputPaths.claudecode) : config.outputPaths.claudecode;
703
1414
  outputs.push({
704
1415
  tool: "claudecode",
705
- filepath: join8(claudeOutputDir, "CLAUDE.md"),
1416
+ filepath: join7(claudeOutputDir, "CLAUDE.md"),
706
1417
  content: claudeMdContent
707
1418
  });
708
1419
  for (const rule of detailRules) {
709
1420
  const memoryContent = generateMemoryFile(rule);
710
1421
  outputs.push({
711
1422
  tool: "claudecode",
712
- filepath: join8(claudeOutputDir, ".claude", "memories", `${rule.filename}.md`),
1423
+ filepath: join7(claudeOutputDir, ".claude", "memories", `${rule.filename}.md`),
713
1424
  content: memoryContent
714
1425
  });
715
1426
  }
716
1427
  const ignorePatterns = await loadIgnorePatterns(baseDir);
717
1428
  if (ignorePatterns.patterns.length > 0) {
718
- const settingsPath = baseDir ? join8(baseDir, ".claude", "settings.json") : join8(".claude", "settings.json");
1429
+ const settingsPath = baseDir ? join7(baseDir, ".claude", "settings.json") : join7(".claude", "settings.json");
719
1430
  await updateClaudeSettings(settingsPath, ignorePatterns.patterns);
720
1431
  }
721
1432
  return outputs;
@@ -794,7 +1505,7 @@ async function generateClineConfig(rules, config, baseDir) {
794
1505
  }
795
1506
 
796
1507
  // src/generators/rules/copilot.ts
797
- import { join as join9 } from "path";
1508
+ import { join as join8 } from "path";
798
1509
  async function generateCopilotConfig(rules, config, baseDir) {
799
1510
  return generateComplexRulesConfig(
800
1511
  rules,
@@ -806,7 +1517,7 @@ async function generateCopilotConfig(rules, config, baseDir) {
806
1517
  generateContent: generateCopilotMarkdown,
807
1518
  getOutputPath: (rule, outputDir) => {
808
1519
  const baseFilename = rule.filename.replace(/\.md$/, "");
809
- return join9(outputDir, `${baseFilename}.instructions.md`);
1520
+ return join8(outputDir, `${baseFilename}.instructions.md`);
810
1521
  }
811
1522
  },
812
1523
  baseDir
@@ -827,7 +1538,7 @@ function generateCopilotMarkdown(rule) {
827
1538
  }
828
1539
 
829
1540
  // src/generators/rules/cursor.ts
830
- import { join as join10 } from "path";
1541
+ import { join as join9 } from "path";
831
1542
  async function generateCursorConfig(rules, config, baseDir) {
832
1543
  return generateComplexRulesConfig(
833
1544
  rules,
@@ -838,7 +1549,7 @@ async function generateCursorConfig(rules, config, baseDir) {
838
1549
  ignoreFileName: ".cursorignore",
839
1550
  generateContent: generateCursorMarkdown,
840
1551
  getOutputPath: (rule, outputDir) => {
841
- return join10(outputDir, `${rule.filename}.mdc`);
1552
+ return join9(outputDir, `${rule.filename}.mdc`);
842
1553
  }
843
1554
  },
844
1555
  baseDir
@@ -898,39 +1609,18 @@ function determineCursorRuleType(frontmatter) {
898
1609
  }
899
1610
 
900
1611
  // src/generators/rules/geminicli.ts
901
- import { join as join11 } from "path";
902
1612
  async function generateGeminiConfig(rules, config, baseDir) {
903
- const outputs = [];
904
- const rootRule = rules.find((rule) => rule.frontmatter.root === true);
905
- const memoryRules = rules.filter((rule) => rule.frontmatter.root === false);
906
- for (const rule of memoryRules) {
907
- const content = generateGeminiMemoryMarkdown(rule);
908
- const outputDir = baseDir ? join11(baseDir, config.outputPaths.geminicli) : config.outputPaths.geminicli;
909
- const filepath = join11(outputDir, `${rule.filename}.md`);
910
- outputs.push({
911
- tool: "geminicli",
912
- filepath,
913
- content
914
- });
915
- }
916
- const rootContent = generateGeminiRootMarkdown(rootRule, memoryRules, baseDir);
917
- const rootFilepath = baseDir ? join11(baseDir, "GEMINI.md") : "GEMINI.md";
918
- outputs.push({
1613
+ const generatorConfig = {
919
1614
  tool: "geminicli",
920
- filepath: rootFilepath,
921
- content: rootContent
922
- });
923
- const ignorePatterns = await loadIgnorePatterns(baseDir);
924
- if (ignorePatterns.patterns.length > 0) {
925
- const aiexcludePath = baseDir ? join11(baseDir, ".aiexclude") : ".aiexclude";
926
- const aiexcludeContent = generateAiexclude(ignorePatterns.patterns);
927
- outputs.push({
928
- tool: "geminicli",
929
- filepath: aiexcludePath,
930
- content: aiexcludeContent
931
- });
932
- }
933
- return outputs;
1615
+ fileExtension: ".md",
1616
+ ignoreFileName: ".aiexclude",
1617
+ generateContent: generateGeminiMemoryMarkdown,
1618
+ generateDetailContent: generateGeminiMemoryMarkdown,
1619
+ generateRootContent: generateGeminiRootMarkdown,
1620
+ rootFilePath: "GEMINI.md",
1621
+ detailSubDir: ".gemini/memories"
1622
+ };
1623
+ return generateComplexRules(rules, config, generatorConfig, baseDir);
934
1624
  }
935
1625
  function generateGeminiMemoryMarkdown(rule) {
936
1626
  return rule.content.trim();
@@ -959,24 +1649,42 @@ function generateGeminiRootMarkdown(rootRule, memoryRules, _baseDir) {
959
1649
  }
960
1650
  return lines.join("\n");
961
1651
  }
962
- function generateAiexclude(patterns) {
963
- const lines = [
964
- "# Generated by rulesync from .rulesyncignore",
965
- "# This file is automatically generated. Do not edit manually.",
966
- "",
967
- ...patterns
968
- ];
969
- return lines.join("\n");
1652
+
1653
+ // src/generators/rules/junie.ts
1654
+ async function generateJunieConfig(rules, config, baseDir) {
1655
+ const generatorConfig = {
1656
+ tool: "junie",
1657
+ fileExtension: ".md",
1658
+ ignoreFileName: ".aiignore",
1659
+ generateContent: (rule) => rule.content.trim(),
1660
+ generateRootContent: generateGuidelinesMarkdown,
1661
+ rootFilePath: ".junie/guidelines.md"
1662
+ };
1663
+ return generateComplexRules(rules, config, generatorConfig, baseDir);
1664
+ }
1665
+ function generateGuidelinesMarkdown(rootRule, detailRules) {
1666
+ const lines = [];
1667
+ if (rootRule) {
1668
+ lines.push(rootRule.content);
1669
+ lines.push("");
1670
+ }
1671
+ if (detailRules.length > 0) {
1672
+ for (const rule of detailRules) {
1673
+ lines.push(rule.content);
1674
+ lines.push("");
1675
+ }
1676
+ }
1677
+ return lines.join("\n").trim();
970
1678
  }
971
1679
 
972
1680
  // src/generators/rules/kiro.ts
973
- import { join as join12 } from "path";
1681
+ import { join as join10 } from "path";
974
1682
  async function generateKiroConfig(rules, config, baseDir) {
975
1683
  const outputs = [];
976
1684
  for (const rule of rules) {
977
1685
  const content = generateKiroMarkdown(rule);
978
- const outputDir = baseDir ? join12(baseDir, config.outputPaths.kiro) : config.outputPaths.kiro;
979
- const filepath = join12(outputDir, `${rule.filename}.md`);
1686
+ const outputDir = baseDir ? join10(baseDir, config.outputPaths.kiro) : config.outputPaths.kiro;
1687
+ const filepath = join10(outputDir, `${rule.filename}.md`);
980
1688
  outputs.push({
981
1689
  tool: "kiro",
982
1690
  filepath,
@@ -1030,7 +1738,10 @@ async function generateConfigurations(rules, config, targetTools, baseDir) {
1030
1738
  function filterRulesForTool(rules, tool, config) {
1031
1739
  return rules.filter((rule) => {
1032
1740
  const targets = resolveTargets(rule.frontmatter.targets, config);
1033
- return targets.includes(tool);
1741
+ if (!targets.includes(tool)) {
1742
+ return false;
1743
+ }
1744
+ return isToolSpecificRule(rule, tool);
1034
1745
  });
1035
1746
  }
1036
1747
  async function generateForTool(tool, rules, config, baseDir) {
@@ -1061,6 +1772,11 @@ async function generateForTool(tool, rules, config, baseDir) {
1061
1772
  return generateRooConfig(rules, config, baseDir);
1062
1773
  case "geminicli":
1063
1774
  return generateGeminiConfig(rules, config, baseDir);
1775
+ case "junie": {
1776
+ const junieRulesOutputs = await generateJunieConfig(rules, config, baseDir);
1777
+ const junieIgnoreOutputs = await generateJunieIgnoreFiles(rules, config, baseDir);
1778
+ return [...junieRulesOutputs, ...junieIgnoreOutputs];
1779
+ }
1064
1780
  case "kiro": {
1065
1781
  const kiroRulesOutputs = await generateKiroConfig(rules, config, baseDir);
1066
1782
  const kiroIgnoreOutputs = await generateKiroIgnoreFiles(rules, config, baseDir);
@@ -1075,75 +1791,6 @@ async function generateForTool(tool, rules, config, baseDir) {
1075
1791
  // src/core/parser.ts
1076
1792
  import { basename } from "path";
1077
1793
  import matter from "gray-matter";
1078
-
1079
- // src/types/config.ts
1080
- import { z as z2 } from "zod/mini";
1081
- var ConfigSchema = z2.object({
1082
- aiRulesDir: z2.string(),
1083
- outputPaths: z2.record(ToolTargetSchema, z2.string()),
1084
- watchEnabled: z2.boolean(),
1085
- defaultTargets: ToolTargetsSchema
1086
- });
1087
-
1088
- // src/types/mcp.ts
1089
- import { z as z3 } from "zod/mini";
1090
- var McpTransportTypeSchema = z3.enum(["stdio", "sse", "http"]);
1091
- var McpServerBaseSchema = z3.object({
1092
- command: z3.optional(z3.string()),
1093
- args: z3.optional(z3.array(z3.string())),
1094
- url: z3.optional(z3.string()),
1095
- httpUrl: z3.optional(z3.string()),
1096
- env: z3.optional(z3.record(z3.string(), z3.string())),
1097
- disabled: z3.optional(z3.boolean()),
1098
- networkTimeout: z3.optional(z3.number()),
1099
- timeout: z3.optional(z3.number()),
1100
- trust: z3.optional(z3.boolean()),
1101
- cwd: z3.optional(z3.string()),
1102
- transport: z3.optional(McpTransportTypeSchema),
1103
- type: z3.optional(z3.enum(["sse", "streamable-http"])),
1104
- alwaysAllow: z3.optional(z3.array(z3.string())),
1105
- tools: z3.optional(z3.array(z3.string())),
1106
- kiroAutoApprove: z3.optional(z3.array(z3.string())),
1107
- kiroAutoBlock: z3.optional(z3.array(z3.string()))
1108
- });
1109
- var RulesyncMcpServerSchema = z3.extend(McpServerBaseSchema, {
1110
- targets: z3.optional(RulesyncTargetsSchema)
1111
- });
1112
- var McpConfigSchema = z3.object({
1113
- mcpServers: z3.record(z3.string(), McpServerBaseSchema)
1114
- });
1115
- var RulesyncMcpConfigSchema = z3.object({
1116
- mcpServers: z3.record(z3.string(), RulesyncMcpServerSchema)
1117
- });
1118
-
1119
- // src/types/rules.ts
1120
- import { z as z4 } from "zod/mini";
1121
- var RuleFrontmatterSchema = z4.object({
1122
- root: z4.boolean(),
1123
- targets: RulesyncTargetsSchema,
1124
- description: z4.string(),
1125
- globs: z4.array(z4.string()),
1126
- cursorRuleType: z4.optional(z4.enum(["always", "manual", "specificFiles", "intelligently"])),
1127
- tags: z4.optional(z4.array(z4.string()))
1128
- });
1129
- var ParsedRuleSchema = z4.object({
1130
- frontmatter: RuleFrontmatterSchema,
1131
- content: z4.string(),
1132
- filename: z4.string(),
1133
- filepath: z4.string()
1134
- });
1135
- var GeneratedOutputSchema = z4.object({
1136
- tool: ToolTargetSchema,
1137
- filepath: z4.string(),
1138
- content: z4.string()
1139
- });
1140
- var GenerateOptionsSchema = z4.object({
1141
- targetTools: z4.optional(ToolTargetsSchema),
1142
- outputDir: z4.optional(z4.string()),
1143
- watch: z4.optional(z4.boolean())
1144
- });
1145
-
1146
- // src/core/parser.ts
1147
1794
  async function parseRulesFromDirectory(aiRulesDir) {
1148
1795
  const ignorePatterns = await loadIgnorePatterns();
1149
1796
  const allRuleFiles = await findFiles(aiRulesDir, ".md");
@@ -1245,13 +1892,13 @@ async function validateRule(rule) {
1245
1892
  }
1246
1893
 
1247
1894
  // src/core/mcp-generator.ts
1248
- import * as path3 from "path";
1895
+ import * as path4 from "path";
1249
1896
 
1250
1897
  // src/core/mcp-parser.ts
1251
1898
  import * as fs from "fs";
1252
- import * as path2 from "path";
1899
+ import * as path3 from "path";
1253
1900
  function parseMcpConfig(projectRoot) {
1254
- const mcpPath = path2.join(projectRoot, ".rulesync", ".mcp.json");
1901
+ const mcpPath = path3.join(projectRoot, ".rulesync", ".mcp.json");
1255
1902
  if (!fs.existsSync(mcpPath)) {
1256
1903
  return null;
1257
1904
  }
@@ -1275,7 +1922,7 @@ function parseMcpConfig(projectRoot) {
1275
1922
  }
1276
1923
 
1277
1924
  // src/core/mcp-generator.ts
1278
- async function generateMcpConfigs(projectRoot, baseDir) {
1925
+ async function generateMcpConfigs(projectRoot, baseDir, targetTools) {
1279
1926
  const results = [];
1280
1927
  const targetRoot = baseDir || projectRoot;
1281
1928
  const config = parseMcpConfig(projectRoot);
@@ -1285,55 +1932,70 @@ async function generateMcpConfigs(projectRoot, baseDir) {
1285
1932
  const generators = [
1286
1933
  {
1287
1934
  tool: "augmentcode-project",
1288
- path: path3.join(targetRoot, ".mcp.json"),
1935
+ path: path4.join(targetRoot, ".mcp.json"),
1289
1936
  generate: () => generateAugmentcodeMcp(config)
1290
1937
  },
1291
1938
  {
1292
1939
  tool: "augmentcode-legacy-project",
1293
- path: path3.join(targetRoot, ".mcp.json"),
1940
+ path: path4.join(targetRoot, ".mcp.json"),
1294
1941
  generate: () => generateAugmentcodeMcp(config)
1295
1942
  },
1296
1943
  {
1297
1944
  tool: "claude-project",
1298
- path: path3.join(targetRoot, ".mcp.json"),
1945
+ path: path4.join(targetRoot, ".mcp.json"),
1299
1946
  generate: () => generateClaudeMcp(config)
1300
1947
  },
1301
1948
  {
1302
1949
  tool: "copilot-editor",
1303
- path: path3.join(targetRoot, ".vscode", "mcp.json"),
1950
+ path: path4.join(targetRoot, ".vscode", "mcp.json"),
1304
1951
  generate: () => generateCopilotMcp(config, "editor")
1305
1952
  },
1306
1953
  {
1307
1954
  tool: "cursor-project",
1308
- path: path3.join(targetRoot, ".cursor", "mcp.json"),
1955
+ path: path4.join(targetRoot, ".cursor", "mcp.json"),
1309
1956
  generate: () => generateCursorMcp(config)
1310
1957
  },
1311
1958
  {
1312
1959
  tool: "cline-project",
1313
- path: path3.join(targetRoot, ".cline", "mcp.json"),
1960
+ path: path4.join(targetRoot, ".cline", "mcp.json"),
1314
1961
  generate: () => generateClineMcp(config)
1315
1962
  },
1316
1963
  {
1317
1964
  tool: "gemini-project",
1318
- path: path3.join(targetRoot, ".gemini", "settings.json"),
1965
+ path: path4.join(targetRoot, ".gemini", "settings.json"),
1319
1966
  generate: () => generateGeminiCliMcp(config)
1320
1967
  },
1968
+ {
1969
+ tool: "junie-project",
1970
+ path: path4.join(targetRoot, ".junie", "mcp-config.json"),
1971
+ generate: () => generateJunieMcp(config)
1972
+ },
1321
1973
  {
1322
1974
  tool: "kiro-project",
1323
- path: path3.join(targetRoot, ".kiro", "mcp.json"),
1975
+ path: path4.join(targetRoot, ".kiro", "mcp.json"),
1324
1976
  generate: () => generateKiroMcp(config)
1325
1977
  },
1326
1978
  {
1327
1979
  tool: "roo-project",
1328
- path: path3.join(targetRoot, ".roo", "mcp.json"),
1980
+ path: path4.join(targetRoot, ".roo", "mcp.json"),
1329
1981
  generate: () => generateRooMcp(config)
1330
1982
  }
1331
1983
  ];
1332
- for (const generator of generators) {
1984
+ const filteredGenerators = targetTools ? generators.filter((g) => {
1985
+ const baseTool = g.tool.split("-")[0];
1986
+ if (!isToolTarget(baseTool)) {
1987
+ return false;
1988
+ }
1989
+ if (baseTool === "augmentcode") {
1990
+ return targetTools.includes("augmentcode") || targetTools.includes("augmentcode-legacy");
1991
+ }
1992
+ return targetTools.includes(baseTool);
1993
+ }) : generators;
1994
+ for (const generator of filteredGenerators) {
1333
1995
  try {
1334
1996
  const content = generator.generate();
1335
1997
  const parsed = JSON.parse(content);
1336
- if (generator.tool.includes("augmentcode") || generator.tool.includes("claude") || generator.tool.includes("cline") || generator.tool.includes("cursor") || generator.tool.includes("gemini") || generator.tool.includes("kiro") || generator.tool.includes("roo")) {
1998
+ if (generator.tool.includes("augmentcode") || generator.tool.includes("claude") || generator.tool.includes("cline") || generator.tool.includes("cursor") || generator.tool.includes("gemini") || generator.tool.includes("junie") || generator.tool.includes("kiro") || generator.tool.includes("roo")) {
1337
1999
  if (!parsed.mcpServers || Object.keys(parsed.mcpServers).length === 0) {
1338
2000
  results.push({
1339
2001
  tool: generator.tool,
@@ -1373,15 +2035,58 @@ async function generateMcpConfigs(projectRoot, baseDir) {
1373
2035
 
1374
2036
  // src/cli/commands/generate.ts
1375
2037
  async function generateCommand(options = {}) {
1376
- const config = getDefaultConfig();
1377
- const baseDirs = options.baseDirs || [process.cwd()];
2038
+ const configLoaderOptions = {
2039
+ ...options.config !== void 0 && { configPath: options.config },
2040
+ ...options.noConfig !== void 0 && { noConfig: options.noConfig }
2041
+ };
2042
+ const configResult = await loadConfig(configLoaderOptions);
2043
+ const cliOptions = {
2044
+ ...options.tools !== void 0 && { tools: options.tools },
2045
+ ...options.verbose !== void 0 && { verbose: options.verbose },
2046
+ ...options.delete !== void 0 && { delete: options.delete },
2047
+ ...options.baseDirs !== void 0 && { baseDirs: options.baseDirs }
2048
+ };
2049
+ const config = mergeWithCliOptions(configResult.config, cliOptions);
2050
+ if (options.tools && options.tools.length > 0) {
2051
+ const configTargets = config.defaultTargets;
2052
+ const cliTools = options.tools;
2053
+ const cliToolsSet = new Set(cliTools);
2054
+ const configTargetsSet = new Set(configTargets);
2055
+ const notInConfig = cliTools.filter((tool) => !configTargetsSet.has(tool));
2056
+ const notInCli = configTargets.filter((tool) => !cliToolsSet.has(tool));
2057
+ if (notInConfig.length > 0 || notInCli.length > 0) {
2058
+ console.warn("\u26A0\uFE0F Warning: CLI tool selection differs from configuration!");
2059
+ console.warn(` Config targets: ${configTargets.join(", ")}`);
2060
+ console.warn(` CLI specified: ${cliTools.join(", ")}`);
2061
+ if (notInConfig.length > 0) {
2062
+ console.warn(` Tools specified but not in config: ${notInConfig.join(", ")}`);
2063
+ }
2064
+ if (notInCli.length > 0) {
2065
+ console.warn(` Tools in config but not specified: ${notInCli.join(", ")}`);
2066
+ }
2067
+ console.warn("\n The configuration file targets will be used.");
2068
+ console.warn(" To change targets, update your rulesync config file.");
2069
+ console.warn("");
2070
+ }
2071
+ }
2072
+ let baseDirs;
2073
+ if (config.baseDir) {
2074
+ baseDirs = Array.isArray(config.baseDir) ? config.baseDir : [config.baseDir];
2075
+ } else if (options.baseDirs) {
2076
+ baseDirs = options.baseDirs;
2077
+ } else {
2078
+ baseDirs = [process.cwd()];
2079
+ }
2080
+ if (config.verbose && configResult.filepath) {
2081
+ console.log(`Loaded configuration from: ${configResult.filepath}`);
2082
+ }
1378
2083
  console.log("Generating configuration files...");
1379
2084
  if (!await fileExists(config.aiRulesDir)) {
1380
2085
  console.error("\u274C .rulesync directory not found. Run 'rulesync init' first.");
1381
2086
  process.exit(1);
1382
2087
  }
1383
2088
  try {
1384
- if (options.verbose) {
2089
+ if (config.verbose) {
1385
2090
  console.log(`Parsing rules from ${config.aiRulesDir}...`);
1386
2091
  }
1387
2092
  const rules = await parseRulesFromDirectory(config.aiRulesDir);
@@ -1389,18 +2094,26 @@ async function generateCommand(options = {}) {
1389
2094
  console.warn("\u26A0\uFE0F No rules found in .rulesync directory");
1390
2095
  return;
1391
2096
  }
1392
- if (options.verbose) {
2097
+ if (config.verbose) {
1393
2098
  console.log(`Found ${rules.length} rule(s)`);
1394
2099
  console.log(`Base directories: ${baseDirs.join(", ")}`);
1395
2100
  }
1396
- if (options.delete) {
1397
- if (options.verbose) {
2101
+ if (config.delete) {
2102
+ if (config.verbose) {
1398
2103
  console.log("Deleting existing output directories...");
1399
2104
  }
1400
- const targetTools = options.tools || config.defaultTargets;
2105
+ const targetTools = config.defaultTargets;
1401
2106
  const deleteTasks = [];
1402
2107
  for (const tool of targetTools) {
1403
2108
  switch (tool) {
2109
+ case "augmentcode":
2110
+ deleteTasks.push(removeDirectory(join13(".augment", "rules")));
2111
+ deleteTasks.push(removeDirectory(join13(".augment", "ignore")));
2112
+ break;
2113
+ case "augmentcode-legacy":
2114
+ deleteTasks.push(removeClaudeGeneratedFiles());
2115
+ deleteTasks.push(removeDirectory(join13(".augment", "ignore")));
2116
+ break;
1404
2117
  case "copilot":
1405
2118
  deleteTasks.push(removeDirectory(config.outputPaths.copilot));
1406
2119
  break;
@@ -1425,19 +2138,19 @@ async function generateCommand(options = {}) {
1425
2138
  }
1426
2139
  }
1427
2140
  await Promise.all(deleteTasks);
1428
- if (options.verbose) {
2141
+ if (config.verbose) {
1429
2142
  console.log("Deleted existing output directories");
1430
2143
  }
1431
2144
  }
1432
2145
  let totalOutputs = 0;
1433
2146
  for (const baseDir of baseDirs) {
1434
- if (options.verbose) {
2147
+ if (config.verbose) {
1435
2148
  console.log(`
1436
2149
  Generating configurations for base directory: ${baseDir}`);
1437
2150
  }
1438
- const outputs = await generateConfigurations(rules, config, options.tools, baseDir);
2151
+ const outputs = await generateConfigurations(rules, config, config.defaultTargets, baseDir);
1439
2152
  if (outputs.length === 0) {
1440
- if (options.verbose) {
2153
+ if (config.verbose) {
1441
2154
  console.warn(`\u26A0\uFE0F No configurations generated for ${baseDir}`);
1442
2155
  }
1443
2156
  continue;
@@ -1452,17 +2165,18 @@ Generating configurations for base directory: ${baseDir}`);
1452
2165
  console.warn("\u26A0\uFE0F No configurations generated");
1453
2166
  return;
1454
2167
  }
1455
- if (options.verbose) {
2168
+ if (config.verbose) {
1456
2169
  console.log("\nGenerating MCP configurations...");
1457
2170
  }
1458
2171
  let totalMcpOutputs = 0;
1459
2172
  for (const baseDir of baseDirs) {
1460
2173
  const mcpResults = await generateMcpConfigs(
1461
2174
  process.cwd(),
1462
- baseDir === process.cwd() ? void 0 : baseDir
2175
+ baseDir === process.cwd() ? void 0 : baseDir,
2176
+ config.defaultTargets
1463
2177
  );
1464
2178
  if (mcpResults.length === 0) {
1465
- if (options.verbose) {
2179
+ if (config.verbose) {
1466
2180
  console.log(`No MCP configuration found for ${baseDir}`);
1467
2181
  }
1468
2182
  continue;
@@ -1473,7 +2187,7 @@ Generating configurations for base directory: ${baseDir}`);
1473
2187
  totalMcpOutputs++;
1474
2188
  } else if (result.status === "error") {
1475
2189
  console.error(`\u274C Failed to generate ${result.tool} MCP configuration: ${result.error}`);
1476
- } else if (options.verbose && result.status === "skipped") {
2190
+ } else if (config.verbose && result.status === "skipped") {
1477
2191
  console.log(`\u23ED\uFE0F Skipped ${result.tool} MCP configuration (no servers configured)`);
1478
2192
  }
1479
2193
  }
@@ -1492,10 +2206,10 @@ Generating configurations for base directory: ${baseDir}`);
1492
2206
  }
1493
2207
 
1494
2208
  // src/cli/commands/gitignore.ts
1495
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
1496
- import { join as join15 } from "path";
2209
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
2210
+ import { join as join14 } from "path";
1497
2211
  var gitignoreCommand = async () => {
1498
- const gitignorePath = join15(process.cwd(), ".gitignore");
2212
+ const gitignorePath = join14(process.cwd(), ".gitignore");
1499
2213
  const rulesFilesToIgnore = [
1500
2214
  "# Generated by rulesync - AI tool configuration files",
1501
2215
  "**/.github/copilot-instructions.md",
@@ -1517,6 +2231,8 @@ var gitignoreCommand = async () => {
1517
2231
  "**/.kiro/steering/",
1518
2232
  "**/.augment/rules/",
1519
2233
  "**/.augment-guidelines",
2234
+ "**/.junie/guidelines.md",
2235
+ "**/.noai",
1520
2236
  "**/.mcp.json",
1521
2237
  "!.rulesync/.mcp.json",
1522
2238
  "**/.cursor/mcp.json",
@@ -1544,7 +2260,7 @@ var gitignoreCommand = async () => {
1544
2260
  ${linesToAdd.join("\n")}
1545
2261
  ` : `${linesToAdd.join("\n")}
1546
2262
  `;
1547
- writeFileSync(gitignorePath, newContent);
2263
+ writeFileSync2(gitignorePath, newContent);
1548
2264
  console.log(`\u2705 Added ${linesToAdd.length} rules to .gitignore:`);
1549
2265
  for (const line of linesToAdd) {
1550
2266
  if (!line.startsWith("#")) {
@@ -1558,7 +2274,7 @@ import { join as join20 } from "path";
1558
2274
  import matter5 from "gray-matter";
1559
2275
 
1560
2276
  // src/parsers/augmentcode.ts
1561
- import { basename as basename2, join as join16 } from "path";
2277
+ import { basename as basename2, join as join15 } from "path";
1562
2278
  import matter2 from "gray-matter";
1563
2279
 
1564
2280
  // src/utils/parser-helpers.ts
@@ -1599,7 +2315,7 @@ async function safeReadFile(operation, errorContext) {
1599
2315
  // src/parsers/augmentcode.ts
1600
2316
  async function parseAugmentcodeConfiguration(baseDir = process.cwd()) {
1601
2317
  const result = createParseResult();
1602
- const rulesDir = join16(baseDir, ".augment", "rules");
2318
+ const rulesDir = join15(baseDir, ".augment", "rules");
1603
2319
  if (await fileExists(rulesDir)) {
1604
2320
  const rulesResult = await parseAugmentRules(rulesDir);
1605
2321
  addRules(result, rulesResult.rules);
@@ -1617,7 +2333,7 @@ async function parseAugmentRules(rulesDir) {
1617
2333
  const files = await readdir2(rulesDir);
1618
2334
  for (const file of files) {
1619
2335
  if (file.endsWith(".md") || file.endsWith(".mdc")) {
1620
- const filePath = join16(rulesDir, file);
2336
+ const filePath = join15(rulesDir, file);
1621
2337
  try {
1622
2338
  const rawContent = await readFileContent(filePath);
1623
2339
  const parsed = matter2(rawContent);
@@ -1655,10 +2371,10 @@ async function parseAugmentRules(rulesDir) {
1655
2371
  }
1656
2372
 
1657
2373
  // src/parsers/augmentcode-legacy.ts
1658
- import { join as join17 } from "path";
2374
+ import { join as join16 } from "path";
1659
2375
  async function parseAugmentcodeLegacyConfiguration(baseDir = process.cwd()) {
1660
2376
  const result = createParseResult();
1661
- const guidelinesPath = join17(baseDir, ".augment-guidelines");
2377
+ const guidelinesPath = join16(baseDir, ".augment-guidelines");
1662
2378
  if (await fileExists(guidelinesPath)) {
1663
2379
  const guidelinesResult = await parseAugmentGuidelines(guidelinesPath);
1664
2380
  if (guidelinesResult.rule) {
@@ -1701,13 +2417,13 @@ async function parseAugmentGuidelines(guidelinesPath) {
1701
2417
  }
1702
2418
 
1703
2419
  // src/parsers/shared-helpers.ts
1704
- import { basename as basename3, join as join18 } from "path";
2420
+ import { basename as basename3, join as join17 } from "path";
1705
2421
  import matter3 from "gray-matter";
1706
2422
  async function parseConfigurationFiles(baseDir = process.cwd(), config) {
1707
2423
  const errors = [];
1708
2424
  const rules = [];
1709
2425
  if (config.mainFile) {
1710
- const mainFilePath = join18(baseDir, config.mainFile.path);
2426
+ const mainFilePath = join17(baseDir, config.mainFile.path);
1711
2427
  if (await fileExists(mainFilePath)) {
1712
2428
  try {
1713
2429
  const rawContent = await readFileContent(mainFilePath);
@@ -1747,14 +2463,14 @@ async function parseConfigurationFiles(baseDir = process.cwd(), config) {
1747
2463
  }
1748
2464
  if (config.directories) {
1749
2465
  for (const dirConfig of config.directories) {
1750
- const dirPath = join18(baseDir, dirConfig.directory);
2466
+ const dirPath = join17(baseDir, dirConfig.directory);
1751
2467
  if (await fileExists(dirPath)) {
1752
2468
  try {
1753
2469
  const { readdir: readdir2 } = await import("fs/promises");
1754
2470
  const files = await readdir2(dirPath);
1755
2471
  for (const file of files) {
1756
2472
  if (file.endsWith(dirConfig.filePattern)) {
1757
- const filePath = join18(dirPath, file);
2473
+ const filePath = join17(dirPath, file);
1758
2474
  try {
1759
2475
  const rawContent = await readFileContent(filePath);
1760
2476
  let content;
@@ -1802,7 +2518,7 @@ async function parseMemoryBasedConfiguration(baseDir = process.cwd(), config) {
1802
2518
  const rules = [];
1803
2519
  let ignorePatterns;
1804
2520
  let mcpServers;
1805
- const mainFilePath = join18(baseDir, config.mainFileName);
2521
+ const mainFilePath = join17(baseDir, config.mainFileName);
1806
2522
  if (!await fileExists(mainFilePath)) {
1807
2523
  errors.push(`${config.mainFileName} file not found`);
1808
2524
  return { rules, errors };
@@ -1813,12 +2529,12 @@ async function parseMemoryBasedConfiguration(baseDir = process.cwd(), config) {
1813
2529
  if (mainRule) {
1814
2530
  rules.push(mainRule);
1815
2531
  }
1816
- const memoryDir = join18(baseDir, config.memoryDirPath);
2532
+ const memoryDir = join17(baseDir, config.memoryDirPath);
1817
2533
  if (await fileExists(memoryDir)) {
1818
2534
  const memoryRules = await parseMemoryFiles(memoryDir, config);
1819
2535
  rules.push(...memoryRules);
1820
2536
  }
1821
- const settingsPath = join18(baseDir, config.settingsPath);
2537
+ const settingsPath = join17(baseDir, config.settingsPath);
1822
2538
  if (await fileExists(settingsPath)) {
1823
2539
  const settingsResult = await parseSettingsFile(settingsPath, config.tool);
1824
2540
  if (settingsResult.ignorePatterns) {
@@ -1830,7 +2546,7 @@ async function parseMemoryBasedConfiguration(baseDir = process.cwd(), config) {
1830
2546
  errors.push(...settingsResult.errors);
1831
2547
  }
1832
2548
  if (config.additionalIgnoreFile) {
1833
- const additionalIgnorePath = join18(baseDir, config.additionalIgnoreFile.path);
2549
+ const additionalIgnorePath = join17(baseDir, config.additionalIgnoreFile.path);
1834
2550
  if (await fileExists(additionalIgnorePath)) {
1835
2551
  const additionalPatterns = await config.additionalIgnoreFile.parser(additionalIgnorePath);
1836
2552
  if (additionalPatterns.length > 0) {
@@ -1884,7 +2600,7 @@ async function parseMemoryFiles(memoryDir, config) {
1884
2600
  const files = await readdir2(memoryDir);
1885
2601
  for (const file of files) {
1886
2602
  if (file.endsWith(".md")) {
1887
- const filePath = join18(memoryDir, file);
2603
+ const filePath = join17(memoryDir, file);
1888
2604
  const content = await readFileContent(filePath);
1889
2605
  if (content.trim()) {
1890
2606
  const filename = basename3(file, ".md");
@@ -2000,10 +2716,10 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
2000
2716
  }
2001
2717
 
2002
2718
  // src/parsers/cursor.ts
2003
- import { basename as basename4, join as join19 } from "path";
2719
+ import { basename as basename4, join as join18 } from "path";
2004
2720
  import matter4 from "gray-matter";
2005
2721
  import { DEFAULT_SCHEMA, FAILSAFE_SCHEMA, load } from "js-yaml";
2006
- import { z as z5 } from "zod/mini";
2722
+ import { z as z6 } from "zod/mini";
2007
2723
  var customMatterOptions = {
2008
2724
  engines: {
2009
2725
  yaml: {
@@ -2031,7 +2747,7 @@ var customMatterOptions = {
2031
2747
  }
2032
2748
  };
2033
2749
  function convertCursorMdcFrontmatter(cursorFrontmatter, _filename) {
2034
- const FrontmatterSchema = z5.record(z5.string(), z5.unknown());
2750
+ const FrontmatterSchema = z6.record(z6.string(), z6.unknown());
2035
2751
  const parseResult = FrontmatterSchema.safeParse(cursorFrontmatter);
2036
2752
  if (!parseResult.success) {
2037
2753
  return {
@@ -2125,7 +2841,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
2125
2841
  const rules = [];
2126
2842
  let ignorePatterns;
2127
2843
  let mcpServers;
2128
- const cursorFilePath = join19(baseDir, ".cursorrules");
2844
+ const cursorFilePath = join18(baseDir, ".cursorrules");
2129
2845
  if (await fileExists(cursorFilePath)) {
2130
2846
  try {
2131
2847
  const rawContent = await readFileContent(cursorFilePath);
@@ -2146,14 +2862,14 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
2146
2862
  errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
2147
2863
  }
2148
2864
  }
2149
- const cursorRulesDir = join19(baseDir, ".cursor", "rules");
2865
+ const cursorRulesDir = join18(baseDir, ".cursor", "rules");
2150
2866
  if (await fileExists(cursorRulesDir)) {
2151
2867
  try {
2152
2868
  const { readdir: readdir2 } = await import("fs/promises");
2153
2869
  const files = await readdir2(cursorRulesDir);
2154
2870
  for (const file of files) {
2155
2871
  if (file.endsWith(".mdc")) {
2156
- const filePath = join19(cursorRulesDir, file);
2872
+ const filePath = join18(cursorRulesDir, file);
2157
2873
  try {
2158
2874
  const rawContent = await readFileContent(filePath);
2159
2875
  const parsed = matter4(rawContent, customMatterOptions);
@@ -2182,7 +2898,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
2182
2898
  if (rules.length === 0) {
2183
2899
  errors.push("No Cursor configuration files found (.cursorrules or .cursor/rules/*.mdc)");
2184
2900
  }
2185
- const cursorIgnorePath = join19(baseDir, ".cursorignore");
2901
+ const cursorIgnorePath = join18(baseDir, ".cursorignore");
2186
2902
  if (await fileExists(cursorIgnorePath)) {
2187
2903
  try {
2188
2904
  const content = await readFileContent(cursorIgnorePath);
@@ -2195,7 +2911,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
2195
2911
  errors.push(`Failed to parse .cursorignore: ${errorMessage}`);
2196
2912
  }
2197
2913
  }
2198
- const cursorMcpPath = join19(baseDir, ".cursor", "mcp.json");
2914
+ const cursorMcpPath = join18(baseDir, ".cursor", "mcp.json");
2199
2915
  if (await fileExists(cursorMcpPath)) {
2200
2916
  try {
2201
2917
  const content = await readFileContent(cursorMcpPath);
@@ -2243,6 +2959,42 @@ async function parseGeminiConfiguration(baseDir = process.cwd()) {
2243
2959
  });
2244
2960
  }
2245
2961
 
2962
+ // src/parsers/junie.ts
2963
+ import { join as join19 } from "path";
2964
+ async function parseJunieConfiguration(baseDir = process.cwd()) {
2965
+ const errors = [];
2966
+ const rules = [];
2967
+ const guidelinesPath = join19(baseDir, ".junie", "guidelines.md");
2968
+ if (!await fileExists(guidelinesPath)) {
2969
+ errors.push(".junie/guidelines.md file not found");
2970
+ return { rules, errors };
2971
+ }
2972
+ try {
2973
+ const content = await readFileContent(guidelinesPath);
2974
+ if (content.trim()) {
2975
+ const frontmatter = {
2976
+ root: false,
2977
+ targets: ["junie"],
2978
+ description: "Junie project guidelines",
2979
+ globs: ["**/*"]
2980
+ };
2981
+ rules.push({
2982
+ frontmatter,
2983
+ content: content.trim(),
2984
+ filename: "junie-guidelines",
2985
+ filepath: guidelinesPath
2986
+ });
2987
+ }
2988
+ } catch (error) {
2989
+ const errorMessage = error instanceof Error ? error.message : String(error);
2990
+ errors.push(`Failed to parse .junie/guidelines.md: ${errorMessage}`);
2991
+ }
2992
+ if (rules.length === 0) {
2993
+ errors.push("No valid Junie configuration found");
2994
+ }
2995
+ return { rules, errors };
2996
+ }
2997
+
2246
2998
  // src/parsers/roo.ts
2247
2999
  async function parseRooConfiguration(baseDir = process.cwd()) {
2248
3000
  return parseConfigurationFiles(baseDir, {
@@ -2329,6 +3081,12 @@ async function importConfiguration(options) {
2329
3081
  mcpServers = geminiResult.mcpServers;
2330
3082
  break;
2331
3083
  }
3084
+ case "junie": {
3085
+ const junieResult = await parseJunieConfiguration(baseDir);
3086
+ rules = junieResult.rules;
3087
+ errors.push(...junieResult.errors);
3088
+ break;
3089
+ }
2332
3090
  default:
2333
3091
  errors.push(`Unsupported tool: ${tool}`);
2334
3092
  return { success: false, rulesCreated: 0, errors };
@@ -2353,7 +3111,7 @@ async function importConfiguration(options) {
2353
3111
  let rulesCreated = 0;
2354
3112
  for (const rule of rules) {
2355
3113
  try {
2356
- const baseFilename = `${tool}__${rule.filename}`;
3114
+ const baseFilename = rule.filename;
2357
3115
  const filename = await generateUniqueFilename(rulesDirPath, baseFilename);
2358
3116
  const filePath = join20(rulesDirPath, `${filename}.md`);
2359
3117
  const content = generateRuleFileContent(rule);
@@ -2425,7 +3183,7 @@ async function generateUniqueFilename(rulesDir, baseFilename) {
2425
3183
  async function importCommand(options = {}) {
2426
3184
  const tools = [];
2427
3185
  if (options.augmentcode) tools.push("augmentcode");
2428
- if (options.augmentcodeLegacy) tools.push("augmentcode-legacy");
3186
+ if (options["augmentcode-legacy"]) tools.push("augmentcode-legacy");
2429
3187
  if (options.claudecode) tools.push("claudecode");
2430
3188
  if (options.cursor) tools.push("cursor");
2431
3189
  if (options.copilot) tools.push("copilot");
@@ -2434,7 +3192,7 @@ async function importCommand(options = {}) {
2434
3192
  if (options.geminicli) tools.push("geminicli");
2435
3193
  if (tools.length === 0) {
2436
3194
  console.error(
2437
- "\u274C Please specify one tool to import from (--augmentcode, --augmentcodeLegacy, --claudecode, --cursor, --copilot, --cline, --roo, --geminicli)"
3195
+ "\u274C Please specify one tool to import from (--augmentcode, --augmentcode-legacy, --claudecode, --cursor, --copilot, --cline, --roo, --geminicli)"
2438
3196
  );
2439
3197
  process.exit(1);
2440
3198
  }
@@ -2642,11 +3400,11 @@ async function watchCommand() {
2642
3400
  persistent: true
2643
3401
  });
2644
3402
  let isGenerating = false;
2645
- const handleChange = async (path4) => {
3403
+ const handleChange = async (path5) => {
2646
3404
  if (isGenerating) return;
2647
3405
  isGenerating = true;
2648
3406
  console.log(`
2649
- \u{1F4DD} Detected change in ${path4}`);
3407
+ \u{1F4DD} Detected change in ${path5}`);
2650
3408
  try {
2651
3409
  await generateCommand({ verbose: false });
2652
3410
  console.log("\u2705 Regenerated configuration files");
@@ -2656,10 +3414,10 @@ async function watchCommand() {
2656
3414
  isGenerating = false;
2657
3415
  }
2658
3416
  };
2659
- watcher.on("change", handleChange).on("add", handleChange).on("unlink", (path4) => {
3417
+ watcher.on("change", handleChange).on("add", handleChange).on("unlink", (path5) => {
2660
3418
  console.log(`
2661
- \u{1F5D1}\uFE0F Removed ${path4}`);
2662
- handleChange(path4);
3419
+ \u{1F5D1}\uFE0F Removed ${path5}`);
3420
+ handleChange(path5);
2663
3421
  }).on("error", (error) => {
2664
3422
  console.error("\u274C Watcher error:", error);
2665
3423
  });
@@ -2672,28 +3430,31 @@ async function watchCommand() {
2672
3430
 
2673
3431
  // src/cli/index.ts
2674
3432
  var program = new Command();
2675
- program.name("rulesync").description("Unified AI rules management CLI tool").version("0.49.0");
3433
+ program.name("rulesync").description("Unified AI rules management CLI tool").version("0.52.0");
2676
3434
  program.command("init").description("Initialize rulesync in current directory").action(initCommand);
2677
3435
  program.command("add <filename>").description("Add a new rule file").action(addCommand);
2678
3436
  program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
2679
- program.command("import").description("Import configurations from AI tools to rulesync format").option("--augmentcode", "Import from AugmentCode (.augment/rules/)").option("--augmentcodeLegacy", "Import from AugmentCode legacy format (.augment-guidelines)").option("--claudecode", "Import from Claude Code (CLAUDE.md)").option("--cursor", "Import from Cursor (.cursorrules)").option("--copilot", "Import from GitHub Copilot (.github/copilot-instructions.md)").option("--cline", "Import from Cline (.cline/instructions.md)").option("--roo", "Import from Roo Code (.roo/instructions.md)").option("--geminicli", "Import from Gemini CLI (GEMINI.md)").option("-v, --verbose", "Verbose output").action(importCommand);
2680
- program.command("generate").description("Generate configuration files for AI tools").option("--augmentcode", "Generate only for AugmentCode").option("--augmentcodeLegacy", "Generate only for AugmentCode legacy format").option("--copilot", "Generate only for GitHub Copilot").option("--cursor", "Generate only for Cursor").option("--cline", "Generate only for Cline").option("--claudecode", "Generate only for Claude Code").option("--roo", "Generate only for Roo Code").option("--geminicli", "Generate only for Gemini CLI").option("--kiro", "Generate only for Kiro IDE").option("--delete", "Delete all existing files in output directories before generating").option(
3437
+ 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);
3438
+ program.command("generate").description("Generate configuration files for AI tools").option("--augmentcode", "Generate only for AugmentCode").option("--augmentcode-legacy", "Generate only for AugmentCode legacy format").option("--copilot", "Generate only for GitHub Copilot").option("--cursor", "Generate only for Cursor").option("--cline", "Generate only for Cline").option("--claudecode", "Generate only for Claude Code").option("--roo", "Generate only for Roo Code").option("--geminicli", "Generate only for Gemini CLI").option("--junie", "Generate only for JetBrains Junie").option("--kiro", "Generate only for Kiro IDE").option("--delete", "Delete all existing files in output directories before generating").option(
2681
3439
  "-b, --base-dir <paths>",
2682
3440
  "Base directories to generate files (comma-separated for multiple paths)"
2683
- ).option("-v, --verbose", "Verbose output").action(async (options) => {
3441
+ ).option("-v, --verbose", "Verbose output").option("-c, --config <path>", "Path to configuration file").option("--no-config", "Disable configuration file loading").action(async (options) => {
2684
3442
  const tools = [];
2685
3443
  if (options.augmentcode) tools.push("augmentcode");
2686
- if (options.augmentcodeLegacy) tools.push("augmentcode-legacy");
3444
+ if (options["augmentcode-legacy"]) tools.push("augmentcode-legacy");
2687
3445
  if (options.copilot) tools.push("copilot");
2688
3446
  if (options.cursor) tools.push("cursor");
2689
3447
  if (options.cline) tools.push("cline");
2690
3448
  if (options.claudecode) tools.push("claudecode");
2691
3449
  if (options.roo) tools.push("roo");
2692
3450
  if (options.geminicli) tools.push("geminicli");
3451
+ if (options.junie) tools.push("junie");
2693
3452
  if (options.kiro) tools.push("kiro");
2694
3453
  const generateOptions = {
2695
3454
  verbose: options.verbose,
2696
- delete: options.delete
3455
+ delete: options.delete,
3456
+ config: options.config,
3457
+ noConfig: options.noConfig
2697
3458
  };
2698
3459
  if (tools.length > 0) {
2699
3460
  generateOptions.tools = tools;
@@ -2706,4 +3467,5 @@ program.command("generate").description("Generate configuration files for AI too
2706
3467
  program.command("validate").description("Validate rulesync configuration").action(validateCommand);
2707
3468
  program.command("status").description("Show current status of rulesync").action(statusCommand);
2708
3469
  program.command("watch").description("Watch for changes and auto-generate configurations").action(watchCommand);
3470
+ program.command("config").description("Show or initialize rulesync configuration").option("--init", "Initialize a new configuration file").option("--format <format>", "Configuration file format (jsonc, ts)", "jsonc").option("--targets <tools>", "Comma-separated list of tools to generate for").option("--exclude <tools>", "Comma-separated list of tools to exclude").option("--ai-rules-dir <dir>", "Directory containing AI rule files").option("--base-dir <path>", "Base directory for generation").option("--verbose", "Enable verbose output").option("--delete", "Delete existing files before generating").action(configCommand);
2709
3471
  program.parse();