rulesync 0.1.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.mjs ADDED
@@ -0,0 +1,681 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/generators/cline.ts
7
+ import { join } from "path";
8
+ async function generateClineConfig(rules, config) {
9
+ const sortedRules = rules.sort((a, b) => {
10
+ if (a.frontmatter.priority !== b.frontmatter.priority) {
11
+ return a.frontmatter.priority === "high" ? -1 : 1;
12
+ }
13
+ return a.filename.localeCompare(b.filename);
14
+ });
15
+ const content = generateClineMarkdown(sortedRules);
16
+ const filepath = join(config.outputPaths.cline, "01-ai-rules.md");
17
+ return {
18
+ tool: "cline",
19
+ filepath,
20
+ content
21
+ };
22
+ }
23
+ function generateClineMarkdown(rules) {
24
+ const lines = [];
25
+ lines.push("# Cline AI Assistant Rules");
26
+ lines.push("");
27
+ lines.push("Configuration rules for Cline AI Assistant. Generated from ai-rules configuration.");
28
+ lines.push("");
29
+ lines.push("These rules provide project-specific guidance for AI-assisted development.");
30
+ lines.push("");
31
+ const highPriorityRules = rules.filter((r) => r.frontmatter.priority === "high");
32
+ const lowPriorityRules = rules.filter((r) => r.frontmatter.priority === "low");
33
+ if (highPriorityRules.length > 0) {
34
+ lines.push("## High Priority Guidelines");
35
+ lines.push("");
36
+ lines.push("These are critical rules that should always be followed:");
37
+ lines.push("");
38
+ for (const rule of highPriorityRules) {
39
+ lines.push(...formatRuleForCline(rule));
40
+ }
41
+ }
42
+ if (lowPriorityRules.length > 0) {
43
+ lines.push("## Standard Guidelines");
44
+ lines.push("");
45
+ lines.push("These are recommended practices for this project:");
46
+ lines.push("");
47
+ for (const rule of lowPriorityRules) {
48
+ lines.push(...formatRuleForCline(rule));
49
+ }
50
+ }
51
+ return lines.join("\n");
52
+ }
53
+ function formatRuleForCline(rule) {
54
+ const lines = [];
55
+ lines.push(`### ${rule.filename}`);
56
+ lines.push("");
57
+ lines.push(`**Description:** ${rule.frontmatter.description}`);
58
+ lines.push("");
59
+ if (rule.frontmatter.globs.length > 0) {
60
+ lines.push(`**Applies to files:** ${rule.frontmatter.globs.join(", ")}`);
61
+ lines.push("");
62
+ }
63
+ lines.push("**Guidelines:**");
64
+ lines.push("");
65
+ lines.push(rule.content);
66
+ lines.push("");
67
+ lines.push("---");
68
+ lines.push("");
69
+ return lines;
70
+ }
71
+
72
+ // src/generators/copilot.ts
73
+ import { join as join2 } from "path";
74
+ async function generateCopilotConfig(rules, config) {
75
+ const sortedRules = rules.sort((a, b) => {
76
+ if (a.frontmatter.priority !== b.frontmatter.priority) {
77
+ return a.frontmatter.priority === "high" ? -1 : 1;
78
+ }
79
+ return a.filename.localeCompare(b.filename);
80
+ });
81
+ const content = generateCopilotMarkdown(sortedRules);
82
+ const filepath = join2(config.outputPaths.copilot, "ai-rules.instructions.md");
83
+ return {
84
+ tool: "copilot",
85
+ filepath,
86
+ content
87
+ };
88
+ }
89
+ function generateCopilotMarkdown(rules) {
90
+ const lines = [];
91
+ lines.push("---");
92
+ lines.push('description: "AI rules configuration for GitHub Copilot"');
93
+ lines.push('applyTo: "**"');
94
+ lines.push("---");
95
+ lines.push("");
96
+ lines.push("# GitHub Copilot Instructions");
97
+ lines.push("");
98
+ lines.push(
99
+ "Generated from ai-rules configuration. These instructions guide GitHub Copilot's code suggestions."
100
+ );
101
+ lines.push("");
102
+ const highPriorityRules = rules.filter((r) => r.frontmatter.priority === "high");
103
+ const lowPriorityRules = rules.filter((r) => r.frontmatter.priority === "low");
104
+ if (highPriorityRules.length > 0) {
105
+ lines.push("## High Priority Rules");
106
+ lines.push("");
107
+ for (const rule of highPriorityRules) {
108
+ lines.push(...formatRuleForCopilot(rule));
109
+ }
110
+ }
111
+ if (lowPriorityRules.length > 0) {
112
+ lines.push("## Standard Rules");
113
+ lines.push("");
114
+ for (const rule of lowPriorityRules) {
115
+ lines.push(...formatRuleForCopilot(rule));
116
+ }
117
+ }
118
+ return lines.join("\n");
119
+ }
120
+ function formatRuleForCopilot(rule) {
121
+ const lines = [];
122
+ lines.push(`### ${rule.filename}`);
123
+ lines.push("");
124
+ lines.push(`**Description:** ${rule.frontmatter.description}`);
125
+ lines.push("");
126
+ if (rule.frontmatter.globs.length > 0) {
127
+ lines.push(`**Applies to:** ${rule.frontmatter.globs.join(", ")}`);
128
+ lines.push("");
129
+ }
130
+ lines.push(rule.content);
131
+ lines.push("");
132
+ return lines;
133
+ }
134
+
135
+ // src/generators/cursor.ts
136
+ import { join as join3 } from "path";
137
+ async function generateCursorConfig(rules, config) {
138
+ const sortedRules = rules.sort((a, b) => {
139
+ if (a.frontmatter.priority !== b.frontmatter.priority) {
140
+ return a.frontmatter.priority === "high" ? -1 : 1;
141
+ }
142
+ return a.filename.localeCompare(b.filename);
143
+ });
144
+ const content = generateCursorMarkdown(sortedRules);
145
+ const filepath = join3(config.outputPaths.cursor, "ai-rules.md");
146
+ return {
147
+ tool: "cursor",
148
+ filepath,
149
+ content
150
+ };
151
+ }
152
+ function generateCursorMarkdown(rules) {
153
+ const lines = [];
154
+ lines.push("---");
155
+ lines.push("description: AI rules configuration for Cursor IDE");
156
+ lines.push("alwaysApply: true");
157
+ lines.push("---");
158
+ lines.push("");
159
+ lines.push("# Cursor IDE Rules");
160
+ lines.push("");
161
+ lines.push("These rules configure Cursor IDE's AI assistant behavior.");
162
+ lines.push("");
163
+ for (const rule of rules) {
164
+ lines.push(...formatRuleForCursor(rule));
165
+ }
166
+ return lines.join("\n");
167
+ }
168
+ function formatRuleForCursor(rule) {
169
+ const lines = [];
170
+ const priorityBadge = rule.frontmatter.priority === "high" ? "\u{1F534} HIGH" : "\u{1F7E1} STANDARD";
171
+ lines.push("---");
172
+ lines.push(`description: ${rule.frontmatter.description}`);
173
+ if (rule.frontmatter.globs.length > 0) {
174
+ lines.push(`globs: [${rule.frontmatter.globs.map((g) => `"${g}"`).join(", ")}]`);
175
+ }
176
+ lines.push(`alwaysApply: ${rule.frontmatter.priority === "high"}`);
177
+ lines.push("---");
178
+ lines.push("");
179
+ lines.push(`## ${rule.filename} ${priorityBadge}`);
180
+ lines.push("");
181
+ lines.push(`**Priority:** ${rule.frontmatter.priority.toUpperCase()}`);
182
+ lines.push("");
183
+ if (rule.frontmatter.globs.length > 0) {
184
+ lines.push("**File Patterns:**");
185
+ for (const glob of rule.frontmatter.globs) {
186
+ lines.push(`- \`${glob}\``);
187
+ }
188
+ lines.push("");
189
+ }
190
+ lines.push("**Rule:**");
191
+ lines.push(rule.content);
192
+ lines.push("");
193
+ lines.push("---");
194
+ lines.push("");
195
+ return lines;
196
+ }
197
+
198
+ // src/utils/config.ts
199
+ function getDefaultConfig() {
200
+ return {
201
+ aiRulesDir: ".rulesync",
202
+ outputPaths: {
203
+ copilot: ".github/instructions",
204
+ cursor: ".cursor/rules",
205
+ cline: ".clinerules"
206
+ },
207
+ watchEnabled: false,
208
+ defaultTargets: ["copilot", "cursor", "cline"]
209
+ };
210
+ }
211
+ function resolveTargets(targets, config) {
212
+ if (targets.includes("*")) {
213
+ return config.defaultTargets;
214
+ }
215
+ return targets;
216
+ }
217
+
218
+ // src/utils/file.ts
219
+ import { mkdir, readdir, readFile, stat, writeFile } from "fs/promises";
220
+ import { dirname, join as join4 } from "path";
221
+ async function ensureDir(dirPath) {
222
+ try {
223
+ await stat(dirPath);
224
+ } catch {
225
+ await mkdir(dirPath, { recursive: true });
226
+ }
227
+ }
228
+ async function readFileContent(filepath) {
229
+ return readFile(filepath, "utf-8");
230
+ }
231
+ async function writeFileContent(filepath, content) {
232
+ await ensureDir(dirname(filepath));
233
+ await writeFile(filepath, content, "utf-8");
234
+ }
235
+ async function findFiles(dir, extension = ".md") {
236
+ try {
237
+ const files = await readdir(dir);
238
+ return files.filter((file) => file.endsWith(extension)).map((file) => join4(dir, file));
239
+ } catch {
240
+ return [];
241
+ }
242
+ }
243
+ async function fileExists(filepath) {
244
+ try {
245
+ await stat(filepath);
246
+ return true;
247
+ } catch {
248
+ return false;
249
+ }
250
+ }
251
+
252
+ // src/core/generator.ts
253
+ async function generateConfigurations(rules, config, targetTools) {
254
+ const outputs = [];
255
+ const toolsToGenerate = targetTools || config.defaultTargets;
256
+ for (const tool of toolsToGenerate) {
257
+ const relevantRules = filterRulesForTool(rules, tool, config);
258
+ if (relevantRules.length === 0) {
259
+ console.warn(`No rules found for tool: ${tool}`);
260
+ continue;
261
+ }
262
+ const output = await generateForTool(tool, relevantRules, config);
263
+ if (output) {
264
+ outputs.push(output);
265
+ }
266
+ }
267
+ return outputs;
268
+ }
269
+ function filterRulesForTool(rules, tool, config) {
270
+ return rules.filter((rule) => {
271
+ const targets = resolveTargets(rule.frontmatter.targets, config);
272
+ return targets.includes(tool);
273
+ });
274
+ }
275
+ async function generateForTool(tool, rules, config) {
276
+ switch (tool) {
277
+ case "copilot":
278
+ return generateCopilotConfig(rules, config);
279
+ case "cursor":
280
+ return generateCursorConfig(rules, config);
281
+ case "cline":
282
+ return generateClineConfig(rules, config);
283
+ default:
284
+ console.warn(`Unknown tool: ${tool}`);
285
+ return null;
286
+ }
287
+ }
288
+
289
+ // src/core/parser.ts
290
+ import { basename } from "path";
291
+ import matter from "gray-matter";
292
+ async function parseRulesFromDirectory(aiRulesDir) {
293
+ const ruleFiles = await findFiles(aiRulesDir);
294
+ const rules = [];
295
+ for (const filepath of ruleFiles) {
296
+ try {
297
+ const rule = await parseRuleFile(filepath);
298
+ rules.push(rule);
299
+ } catch (error) {
300
+ console.warn(`Failed to parse rule file ${filepath}:`, error);
301
+ }
302
+ }
303
+ return rules;
304
+ }
305
+ async function parseRuleFile(filepath) {
306
+ const content = await readFileContent(filepath);
307
+ const parsed = matter(content);
308
+ validateFrontmatter(parsed.data, filepath);
309
+ const frontmatter = parsed.data;
310
+ const filename = basename(filepath, ".md");
311
+ return {
312
+ frontmatter,
313
+ content: parsed.content,
314
+ filename,
315
+ filepath
316
+ };
317
+ }
318
+ function validateFrontmatter(data, filepath) {
319
+ if (!data || typeof data !== "object") {
320
+ throw new Error(`Invalid frontmatter in ${filepath}: must be an object`);
321
+ }
322
+ const obj = data;
323
+ if (!obj.priority || !["high", "low"].includes(obj.priority)) {
324
+ throw new Error(`Invalid priority in ${filepath}: must be "high" or "low"`);
325
+ }
326
+ if (!Array.isArray(obj.targets)) {
327
+ throw new Error(`Invalid targets in ${filepath}: must be an array`);
328
+ }
329
+ const validTargets = ["copilot", "cursor", "cline", "*"];
330
+ for (const target of obj.targets) {
331
+ if (typeof target !== "string" || !validTargets.includes(target)) {
332
+ throw new Error(
333
+ `Invalid target "${target}" in ${filepath}: must be one of ${validTargets.join(", ")}`
334
+ );
335
+ }
336
+ }
337
+ if (!obj.description || typeof obj.description !== "string") {
338
+ throw new Error(`Invalid description in ${filepath}: must be a non-empty string`);
339
+ }
340
+ if (!Array.isArray(obj.globs)) {
341
+ throw new Error(`Invalid globs in ${filepath}: must be an array`);
342
+ }
343
+ for (const glob of obj.globs) {
344
+ if (typeof glob !== "string") {
345
+ throw new Error(`Invalid glob in ${filepath}: all globs must be strings`);
346
+ }
347
+ }
348
+ }
349
+
350
+ // src/core/validator.ts
351
+ async function validateRules(rules) {
352
+ const errors = [];
353
+ const warnings = [];
354
+ const filenames = /* @__PURE__ */ new Set();
355
+ for (const rule of rules) {
356
+ if (filenames.has(rule.filename)) {
357
+ errors.push(`Duplicate rule filename: ${rule.filename}`);
358
+ }
359
+ filenames.add(rule.filename);
360
+ }
361
+ for (const rule of rules) {
362
+ const ruleValidation = await validateRule(rule);
363
+ errors.push(...ruleValidation.errors);
364
+ warnings.push(...ruleValidation.warnings);
365
+ }
366
+ return {
367
+ isValid: errors.length === 0,
368
+ errors,
369
+ warnings
370
+ };
371
+ }
372
+ async function validateRule(rule) {
373
+ const errors = [];
374
+ const warnings = [];
375
+ if (!rule.content.trim()) {
376
+ warnings.push(`Rule ${rule.filename} has empty content`);
377
+ }
378
+ if (rule.frontmatter.description.length < 10) {
379
+ warnings.push(`Rule ${rule.filename} has a very short description`);
380
+ }
381
+ if (rule.frontmatter.globs.length === 0) {
382
+ warnings.push(`Rule ${rule.filename} has no glob patterns specified`);
383
+ }
384
+ if (!await fileExists(rule.filepath)) {
385
+ errors.push(`Rule file ${rule.filepath} does not exist`);
386
+ }
387
+ return {
388
+ isValid: errors.length === 0,
389
+ errors,
390
+ warnings
391
+ };
392
+ }
393
+
394
+ // src/cli/commands/generate.ts
395
+ async function generateCommand(options = {}) {
396
+ const config = getDefaultConfig();
397
+ console.log("Generating configuration files...");
398
+ if (!await fileExists(config.aiRulesDir)) {
399
+ console.error("\u274C .rulesync directory not found. Run 'rulesync init' first.");
400
+ process.exit(1);
401
+ }
402
+ try {
403
+ if (options.verbose) {
404
+ console.log(`Parsing rules from ${config.aiRulesDir}...`);
405
+ }
406
+ const rules = await parseRulesFromDirectory(config.aiRulesDir);
407
+ if (rules.length === 0) {
408
+ console.warn("\u26A0\uFE0F No rules found in .rulesync directory");
409
+ return;
410
+ }
411
+ if (options.verbose) {
412
+ console.log(`Found ${rules.length} rule(s)`);
413
+ }
414
+ const outputs = await generateConfigurations(rules, config, options.tools);
415
+ if (outputs.length === 0) {
416
+ console.warn("\u26A0\uFE0F No configurations generated");
417
+ return;
418
+ }
419
+ for (const output of outputs) {
420
+ await writeFileContent(output.filepath, output.content);
421
+ console.log(`\u2705 Generated ${output.tool} configuration: ${output.filepath}`);
422
+ }
423
+ console.log(`
424
+ \u{1F389} Successfully generated ${outputs.length} configuration file(s)!`);
425
+ } catch (error) {
426
+ console.error("\u274C Failed to generate configurations:", error);
427
+ process.exit(1);
428
+ }
429
+ }
430
+
431
+ // src/cli/commands/init.ts
432
+ import { join as join5 } from "path";
433
+ async function initCommand() {
434
+ const aiRulesDir = ".rulesync";
435
+ console.log("Initializing ai-rules...");
436
+ await ensureDir(aiRulesDir);
437
+ await createSampleFiles(aiRulesDir);
438
+ console.log("\u2705 ai-rules initialized successfully!");
439
+ console.log("\nNext steps:");
440
+ console.log("1. Edit rule files in .rulesync/");
441
+ console.log("2. Run 'ai-rules generate' to create configuration files");
442
+ }
443
+ async function createSampleFiles(aiRulesDir) {
444
+ const sampleFiles = [
445
+ {
446
+ filename: "coding-rules.md",
447
+ content: `---
448
+ priority: high
449
+ targets: ["*"]
450
+ description: "General coding standards and best practices"
451
+ globs: ["**/*.ts", "**/*.js", "**/*.tsx", "**/*.jsx"]
452
+ ---
453
+
454
+ # Coding Rules
455
+
456
+ ## General Guidelines
457
+
458
+ - Use TypeScript for all new code
459
+ - Follow consistent naming conventions
460
+ - Write self-documenting code with clear variable and function names
461
+ - Prefer composition over inheritance
462
+ - Use meaningful comments for complex business logic
463
+
464
+ ## Code Style
465
+
466
+ - Use 2 spaces for indentation
467
+ - Use semicolons
468
+ - Use double quotes for strings
469
+ - Use trailing commas in multi-line objects and arrays
470
+ `
471
+ },
472
+ {
473
+ filename: "naming-conventions.md",
474
+ content: `---
475
+ priority: high
476
+ targets: ["*"]
477
+ description: "Naming conventions for variables, functions, and files"
478
+ globs: ["**/*.ts", "**/*.js"]
479
+ ---
480
+
481
+ # Naming Conventions
482
+
483
+ ## Variables and Functions
484
+ - Use camelCase for variables and functions
485
+ - Use descriptive names that explain the purpose
486
+ - Avoid abbreviations unless they are well-known
487
+
488
+ ## Files and Directories
489
+ - Use kebab-case for file names
490
+ - Use PascalCase for component files
491
+ - Use lowercase for directory names
492
+
493
+ ## Constants
494
+ - Use SCREAMING_SNAKE_CASE for constants
495
+ - Group related constants in enums or const objects
496
+ `
497
+ },
498
+ {
499
+ filename: "architecture.md",
500
+ content: `---
501
+ priority: low
502
+ targets: [copilot, cursor]
503
+ description: "Architectural patterns and project structure guidelines"
504
+ globs: ["src/**/*.ts"]
505
+ ---
506
+
507
+ # Architecture Guidelines
508
+
509
+ ## Project Structure
510
+ - Organize code by feature, not by file type
511
+ - Keep related files close together
512
+ - Use index files for clean imports
513
+
514
+ ## Design Patterns
515
+ - Use dependency injection for better testability
516
+ - Implement proper error handling
517
+ - Follow single responsibility principle
518
+ `
519
+ }
520
+ ];
521
+ for (const file of sampleFiles) {
522
+ const filepath = join5(aiRulesDir, file.filename);
523
+ if (!await fileExists(filepath)) {
524
+ await writeFileContent(filepath, file.content);
525
+ console.log(`Created ${filepath}`);
526
+ } else {
527
+ console.log(`Skipped ${filepath} (already exists)`);
528
+ }
529
+ }
530
+ }
531
+
532
+ // src/cli/commands/status.ts
533
+ async function statusCommand() {
534
+ const config = getDefaultConfig();
535
+ console.log("rulesync Status");
536
+ console.log("===============");
537
+ const aiRulesExists = await fileExists(config.aiRulesDir);
538
+ console.log(`
539
+ \u{1F4C1} .rulesync directory: ${aiRulesExists ? "\u2705 Found" : "\u274C Not found"}`);
540
+ if (!aiRulesExists) {
541
+ console.log("\n\u{1F4A1} Run 'rulesync init' to get started");
542
+ return;
543
+ }
544
+ try {
545
+ const rules = await parseRulesFromDirectory(config.aiRulesDir);
546
+ console.log(`
547
+ \u{1F4CB} Rules: ${rules.length} total`);
548
+ if (rules.length > 0) {
549
+ const highPriority = rules.filter((r) => r.frontmatter.priority === "high").length;
550
+ const lowPriority = rules.filter((r) => r.frontmatter.priority === "low").length;
551
+ console.log(` - High priority: ${highPriority}`);
552
+ console.log(` - Low priority: ${lowPriority}`);
553
+ const targetCounts = { copilot: 0, cursor: 0, cline: 0 };
554
+ for (const rule of rules) {
555
+ const targets = rule.frontmatter.targets.includes("*") ? config.defaultTargets : rule.frontmatter.targets;
556
+ for (const target of targets) {
557
+ if (target in targetCounts) {
558
+ targetCounts[target]++;
559
+ }
560
+ }
561
+ }
562
+ console.log("\n\u{1F3AF} Target tool coverage:");
563
+ console.log(` - Copilot: ${targetCounts.copilot} rules`);
564
+ console.log(` - Cursor: ${targetCounts.cursor} rules`);
565
+ console.log(` - Cline: ${targetCounts.cline} rules`);
566
+ }
567
+ console.log("\n\u{1F4E4} Generated files:");
568
+ for (const [tool, outputPath] of Object.entries(config.outputPaths)) {
569
+ const outputExists = await fileExists(outputPath);
570
+ console.log(` - ${tool}: ${outputExists ? "\u2705 Generated" : "\u274C Not found"}`);
571
+ }
572
+ if (rules.length > 0) {
573
+ console.log("\n\u{1F4A1} Run 'rulesync generate' to update configuration files");
574
+ }
575
+ } catch (error) {
576
+ console.error("\n\u274C Failed to get status:", error);
577
+ }
578
+ }
579
+
580
+ // src/cli/commands/validate.ts
581
+ async function validateCommand() {
582
+ const config = getDefaultConfig();
583
+ console.log("Validating ai-rules configuration...");
584
+ if (!await fileExists(config.aiRulesDir)) {
585
+ console.error("\u274C .rulesync directory not found. Run 'rulesync init' first.");
586
+ process.exit(1);
587
+ }
588
+ try {
589
+ const rules = await parseRulesFromDirectory(config.aiRulesDir);
590
+ if (rules.length === 0) {
591
+ console.warn("\u26A0\uFE0F No rules found in .ai-rules directory");
592
+ return;
593
+ }
594
+ console.log(`Found ${rules.length} rule(s), validating...`);
595
+ const validation = await validateRules(rules);
596
+ if (validation.warnings.length > 0) {
597
+ console.log("\n\u26A0\uFE0F Warnings:");
598
+ for (const warning of validation.warnings) {
599
+ console.log(` - ${warning}`);
600
+ }
601
+ }
602
+ if (validation.errors.length > 0) {
603
+ console.log("\n\u274C Errors:");
604
+ for (const error of validation.errors) {
605
+ console.log(` - ${error}`);
606
+ }
607
+ }
608
+ if (validation.isValid) {
609
+ console.log("\n\u2705 All rules are valid!");
610
+ } else {
611
+ console.log(`
612
+ \u274C Validation failed with ${validation.errors.length} error(s)`);
613
+ process.exit(1);
614
+ }
615
+ } catch (error) {
616
+ console.error("\u274C Failed to validate rules:", error);
617
+ process.exit(1);
618
+ }
619
+ }
620
+
621
+ // src/cli/commands/watch.ts
622
+ import chokidar from "chokidar";
623
+ async function watchCommand() {
624
+ const config = getDefaultConfig();
625
+ console.log("\u{1F440} Watching for changes in .rulesync directory...");
626
+ console.log("Press Ctrl+C to stop watching");
627
+ await generateCommand({ verbose: false });
628
+ const watcher = chokidar.watch(`${config.aiRulesDir}/**/*.md`, {
629
+ ignoreInitial: true,
630
+ persistent: true
631
+ });
632
+ let isGenerating = false;
633
+ const handleChange = async (path) => {
634
+ if (isGenerating) return;
635
+ isGenerating = true;
636
+ console.log(`
637
+ \u{1F4DD} Detected change in ${path}`);
638
+ try {
639
+ await generateCommand({ verbose: false });
640
+ console.log("\u2705 Regenerated configuration files");
641
+ } catch (error) {
642
+ console.error("\u274C Failed to regenerate:", error);
643
+ } finally {
644
+ isGenerating = false;
645
+ }
646
+ };
647
+ watcher.on("change", handleChange).on("add", handleChange).on("unlink", (path) => {
648
+ console.log(`
649
+ \u{1F5D1}\uFE0F Removed ${path}`);
650
+ handleChange(path);
651
+ }).on("error", (error) => {
652
+ console.error("\u274C Watcher error:", error);
653
+ });
654
+ process.on("SIGINT", () => {
655
+ console.log("\n\n\u{1F44B} Stopping watcher...");
656
+ watcher.close();
657
+ process.exit(0);
658
+ });
659
+ }
660
+
661
+ // src/cli/index.ts
662
+ var program = new Command();
663
+ program.name("ai-rules").description("Unified AI rules management CLI tool").version("0.1.0");
664
+ program.command("init").description("Initialize ai-rules in current directory").action(initCommand);
665
+ program.command("generate").description("Generate configuration files for AI tools").option("--copilot", "Generate only for GitHub Copilot").option("--cursor", "Generate only for Cursor").option("--cline", "Generate only for Cline").option("-v, --verbose", "Verbose output").action(async (options) => {
666
+ const tools = [];
667
+ if (options.copilot) tools.push("copilot");
668
+ if (options.cursor) tools.push("cursor");
669
+ if (options.cline) tools.push("cline");
670
+ const generateOptions = {
671
+ verbose: options.verbose
672
+ };
673
+ if (tools.length > 0) {
674
+ generateOptions.tools = tools;
675
+ }
676
+ await generateCommand(generateOptions);
677
+ });
678
+ program.command("validate").description("Validate ai-rules configuration").action(validateCommand);
679
+ program.command("status").description("Show current status of ai-rules").action(statusCommand);
680
+ program.command("watch").description("Watch for changes and auto-generate configurations").action(watchCommand);
681
+ program.parse();