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