cursor-recursive-rag 0.2.0-alpha.2 → 0.2.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.
Files changed (210) hide show
  1. package/README.md +179 -203
  2. package/dist/adapters/llm/anthropic.d.ts +27 -0
  3. package/dist/adapters/llm/anthropic.d.ts.map +1 -0
  4. package/dist/adapters/llm/anthropic.js +287 -0
  5. package/dist/adapters/llm/anthropic.js.map +1 -0
  6. package/dist/adapters/llm/base.d.ts +62 -0
  7. package/dist/adapters/llm/base.d.ts.map +1 -0
  8. package/dist/adapters/llm/base.js +140 -0
  9. package/dist/adapters/llm/base.js.map +1 -0
  10. package/dist/adapters/llm/deepseek.d.ts +24 -0
  11. package/dist/adapters/llm/deepseek.d.ts.map +1 -0
  12. package/dist/adapters/llm/deepseek.js +228 -0
  13. package/dist/adapters/llm/deepseek.js.map +1 -0
  14. package/dist/adapters/llm/groq.d.ts +25 -0
  15. package/dist/adapters/llm/groq.d.ts.map +1 -0
  16. package/dist/adapters/llm/groq.js +265 -0
  17. package/dist/adapters/llm/groq.js.map +1 -0
  18. package/dist/adapters/llm/index.d.ts +62 -0
  19. package/dist/adapters/llm/index.d.ts.map +1 -0
  20. package/dist/adapters/llm/index.js +380 -0
  21. package/dist/adapters/llm/index.js.map +1 -0
  22. package/dist/adapters/llm/ollama.d.ts +23 -0
  23. package/dist/adapters/llm/ollama.d.ts.map +1 -0
  24. package/dist/adapters/llm/ollama.js +261 -0
  25. package/dist/adapters/llm/ollama.js.map +1 -0
  26. package/dist/adapters/llm/openai.d.ts +22 -0
  27. package/dist/adapters/llm/openai.d.ts.map +1 -0
  28. package/dist/adapters/llm/openai.js +232 -0
  29. package/dist/adapters/llm/openai.js.map +1 -0
  30. package/dist/adapters/llm/openrouter.d.ts +27 -0
  31. package/dist/adapters/llm/openrouter.d.ts.map +1 -0
  32. package/dist/adapters/llm/openrouter.js +305 -0
  33. package/dist/adapters/llm/openrouter.js.map +1 -0
  34. package/dist/adapters/vector/index.d.ts.map +1 -1
  35. package/dist/adapters/vector/index.js +8 -0
  36. package/dist/adapters/vector/index.js.map +1 -1
  37. package/dist/adapters/vector/redis-native.d.ts +35 -0
  38. package/dist/adapters/vector/redis-native.d.ts.map +1 -0
  39. package/dist/adapters/vector/redis-native.js +170 -0
  40. package/dist/adapters/vector/redis-native.js.map +1 -0
  41. package/dist/cli/commands/chat.d.ts +4 -0
  42. package/dist/cli/commands/chat.d.ts.map +1 -0
  43. package/dist/cli/commands/chat.js +374 -0
  44. package/dist/cli/commands/chat.js.map +1 -0
  45. package/dist/cli/commands/maintenance.d.ts +4 -0
  46. package/dist/cli/commands/maintenance.d.ts.map +1 -0
  47. package/dist/cli/commands/maintenance.js +237 -0
  48. package/dist/cli/commands/maintenance.js.map +1 -0
  49. package/dist/cli/commands/rules.d.ts +9 -0
  50. package/dist/cli/commands/rules.d.ts.map +1 -0
  51. package/dist/cli/commands/rules.js +639 -0
  52. package/dist/cli/commands/rules.js.map +1 -0
  53. package/dist/cli/commands/setup.js +5 -4
  54. package/dist/cli/commands/setup.js.map +1 -1
  55. package/dist/cli/index.js +6 -0
  56. package/dist/cli/index.js.map +1 -1
  57. package/dist/config/memoryConfig.d.ts +427 -0
  58. package/dist/config/memoryConfig.d.ts.map +1 -0
  59. package/dist/config/memoryConfig.js +258 -0
  60. package/dist/config/memoryConfig.js.map +1 -0
  61. package/dist/config/rulesConfig.d.ts +486 -0
  62. package/dist/config/rulesConfig.d.ts.map +1 -0
  63. package/dist/config/rulesConfig.js +345 -0
  64. package/dist/config/rulesConfig.js.map +1 -0
  65. package/dist/dashboard/coreTools.d.ts +14 -0
  66. package/dist/dashboard/coreTools.d.ts.map +1 -0
  67. package/dist/dashboard/coreTools.js +413 -0
  68. package/dist/dashboard/coreTools.js.map +1 -0
  69. package/dist/dashboard/public/index.html +1982 -13
  70. package/dist/dashboard/server.d.ts +1 -8
  71. package/dist/dashboard/server.d.ts.map +1 -1
  72. package/dist/dashboard/server.js +846 -13
  73. package/dist/dashboard/server.js.map +1 -1
  74. package/dist/dashboard/toolRegistry.d.ts +192 -0
  75. package/dist/dashboard/toolRegistry.d.ts.map +1 -0
  76. package/dist/dashboard/toolRegistry.js +322 -0
  77. package/dist/dashboard/toolRegistry.js.map +1 -0
  78. package/dist/proxy/index.d.ts +1 -1
  79. package/dist/proxy/index.d.ts.map +1 -1
  80. package/dist/proxy/index.js +9 -6
  81. package/dist/proxy/index.js.map +1 -1
  82. package/dist/server/index.js +21 -0
  83. package/dist/server/index.js.map +1 -1
  84. package/dist/server/tools/crawl.d.ts.map +1 -1
  85. package/dist/server/tools/crawl.js +8 -0
  86. package/dist/server/tools/crawl.js.map +1 -1
  87. package/dist/server/tools/index.d.ts.map +1 -1
  88. package/dist/server/tools/index.js +19 -1
  89. package/dist/server/tools/index.js.map +1 -1
  90. package/dist/server/tools/ingest.d.ts.map +1 -1
  91. package/dist/server/tools/ingest.js +5 -0
  92. package/dist/server/tools/ingest.js.map +1 -1
  93. package/dist/server/tools/memory.d.ts +250 -0
  94. package/dist/server/tools/memory.d.ts.map +1 -0
  95. package/dist/server/tools/memory.js +472 -0
  96. package/dist/server/tools/memory.js.map +1 -0
  97. package/dist/server/tools/recursive-query.d.ts.map +1 -1
  98. package/dist/server/tools/recursive-query.js +6 -0
  99. package/dist/server/tools/recursive-query.js.map +1 -1
  100. package/dist/server/tools/search.d.ts.map +1 -1
  101. package/dist/server/tools/search.js +6 -0
  102. package/dist/server/tools/search.js.map +1 -1
  103. package/dist/services/activity-log.d.ts +10 -0
  104. package/dist/services/activity-log.d.ts.map +1 -0
  105. package/dist/services/activity-log.js +53 -0
  106. package/dist/services/activity-log.js.map +1 -0
  107. package/dist/services/categoryManager.d.ts +110 -0
  108. package/dist/services/categoryManager.d.ts.map +1 -0
  109. package/dist/services/categoryManager.js +549 -0
  110. package/dist/services/categoryManager.js.map +1 -0
  111. package/dist/services/contextEnvironment.d.ts +206 -0
  112. package/dist/services/contextEnvironment.d.ts.map +1 -0
  113. package/dist/services/contextEnvironment.js +481 -0
  114. package/dist/services/contextEnvironment.js.map +1 -0
  115. package/dist/services/conversationProcessor.d.ts +99 -0
  116. package/dist/services/conversationProcessor.d.ts.map +1 -0
  117. package/dist/services/conversationProcessor.js +311 -0
  118. package/dist/services/conversationProcessor.js.map +1 -0
  119. package/dist/services/cursorChatReader.d.ts +129 -0
  120. package/dist/services/cursorChatReader.d.ts.map +1 -0
  121. package/dist/services/cursorChatReader.js +419 -0
  122. package/dist/services/cursorChatReader.js.map +1 -0
  123. package/dist/services/decayCalculator.d.ts +85 -0
  124. package/dist/services/decayCalculator.d.ts.map +1 -0
  125. package/dist/services/decayCalculator.js +182 -0
  126. package/dist/services/decayCalculator.js.map +1 -0
  127. package/dist/services/enhancedVectorStore.d.ts +102 -0
  128. package/dist/services/enhancedVectorStore.d.ts.map +1 -0
  129. package/dist/services/enhancedVectorStore.js +245 -0
  130. package/dist/services/enhancedVectorStore.js.map +1 -0
  131. package/dist/services/hybridScorer.d.ts +120 -0
  132. package/dist/services/hybridScorer.d.ts.map +1 -0
  133. package/dist/services/hybridScorer.js +334 -0
  134. package/dist/services/hybridScorer.js.map +1 -0
  135. package/dist/services/knowledgeExtractor.d.ts +45 -0
  136. package/dist/services/knowledgeExtractor.d.ts.map +1 -0
  137. package/dist/services/knowledgeExtractor.js +436 -0
  138. package/dist/services/knowledgeExtractor.js.map +1 -0
  139. package/dist/services/knowledgeStorage.d.ts +102 -0
  140. package/dist/services/knowledgeStorage.d.ts.map +1 -0
  141. package/dist/services/knowledgeStorage.js +383 -0
  142. package/dist/services/knowledgeStorage.js.map +1 -0
  143. package/dist/services/maintenanceScheduler.d.ts +89 -0
  144. package/dist/services/maintenanceScheduler.d.ts.map +1 -0
  145. package/dist/services/maintenanceScheduler.js +479 -0
  146. package/dist/services/maintenanceScheduler.js.map +1 -0
  147. package/dist/services/memoryMetadataStore.d.ts +62 -0
  148. package/dist/services/memoryMetadataStore.d.ts.map +1 -0
  149. package/dist/services/memoryMetadataStore.js +570 -0
  150. package/dist/services/memoryMetadataStore.js.map +1 -0
  151. package/dist/services/recursiveRetrieval.d.ts +122 -0
  152. package/dist/services/recursiveRetrieval.d.ts.map +1 -0
  153. package/dist/services/recursiveRetrieval.js +443 -0
  154. package/dist/services/recursiveRetrieval.js.map +1 -0
  155. package/dist/services/relationshipGraph.d.ts +77 -0
  156. package/dist/services/relationshipGraph.d.ts.map +1 -0
  157. package/dist/services/relationshipGraph.js +411 -0
  158. package/dist/services/relationshipGraph.js.map +1 -0
  159. package/dist/services/rlmSafeguards.d.ts +273 -0
  160. package/dist/services/rlmSafeguards.d.ts.map +1 -0
  161. package/dist/services/rlmSafeguards.js +705 -0
  162. package/dist/services/rlmSafeguards.js.map +1 -0
  163. package/dist/services/rulesAnalyzer.d.ts +119 -0
  164. package/dist/services/rulesAnalyzer.d.ts.map +1 -0
  165. package/dist/services/rulesAnalyzer.js +768 -0
  166. package/dist/services/rulesAnalyzer.js.map +1 -0
  167. package/dist/services/rulesMerger.d.ts +75 -0
  168. package/dist/services/rulesMerger.d.ts.map +1 -0
  169. package/dist/services/rulesMerger.js +404 -0
  170. package/dist/services/rulesMerger.js.map +1 -0
  171. package/dist/services/rulesParser.d.ts +127 -0
  172. package/dist/services/rulesParser.d.ts.map +1 -0
  173. package/dist/services/rulesParser.js +594 -0
  174. package/dist/services/rulesParser.js.map +1 -0
  175. package/dist/services/smartChunker.d.ts +110 -0
  176. package/dist/services/smartChunker.d.ts.map +1 -0
  177. package/dist/services/smartChunker.js +520 -0
  178. package/dist/services/smartChunker.js.map +1 -0
  179. package/dist/types/categories.d.ts +105 -0
  180. package/dist/types/categories.d.ts.map +1 -0
  181. package/dist/types/categories.js +108 -0
  182. package/dist/types/categories.js.map +1 -0
  183. package/dist/types/extractedKnowledge.d.ts +233 -0
  184. package/dist/types/extractedKnowledge.d.ts.map +1 -0
  185. package/dist/types/extractedKnowledge.js +56 -0
  186. package/dist/types/extractedKnowledge.js.map +1 -0
  187. package/dist/types/index.d.ts +9 -2
  188. package/dist/types/index.d.ts.map +1 -1
  189. package/dist/types/index.js +12 -1
  190. package/dist/types/index.js.map +1 -1
  191. package/dist/types/llmProvider.d.ts +282 -0
  192. package/dist/types/llmProvider.d.ts.map +1 -0
  193. package/dist/types/llmProvider.js +48 -0
  194. package/dist/types/llmProvider.js.map +1 -0
  195. package/dist/types/memory.d.ts +227 -0
  196. package/dist/types/memory.d.ts.map +1 -0
  197. package/dist/types/memory.js +76 -0
  198. package/dist/types/memory.js.map +1 -0
  199. package/dist/types/relationships.d.ts +167 -0
  200. package/dist/types/relationships.d.ts.map +1 -0
  201. package/dist/types/relationships.js +106 -0
  202. package/dist/types/relationships.js.map +1 -0
  203. package/dist/types/rulesOptimizer.d.ts +345 -0
  204. package/dist/types/rulesOptimizer.d.ts.map +1 -0
  205. package/dist/types/rulesOptimizer.js +22 -0
  206. package/dist/types/rulesOptimizer.js.map +1 -0
  207. package/docs/cursor-recursive-rag-memory-spec.md +4569 -0
  208. package/docs/cursor-recursive-rag-tasks.md +1355 -0
  209. package/package.json +6 -3
  210. package/restart-rag.sh +16 -0
@@ -0,0 +1,639 @@
1
+ /**
2
+ * Rules Optimizer CLI Commands
3
+ *
4
+ * Commands for analyzing and optimizing Cursor rules and AGENTS.md files.
5
+ */
6
+ import { Command } from 'commander';
7
+ import chalk from 'chalk';
8
+ import ora from 'ora';
9
+ import { resolve, relative, basename } from 'path';
10
+ import { existsSync, mkdirSync, copyFileSync } from 'fs';
11
+ import { getRulesParser, parseRulesDirectory } from '../../services/rulesParser.js';
12
+ import { getRulesAnalyzer } from '../../services/rulesAnalyzer.js';
13
+ import { getRulesMerger } from '../../services/rulesMerger.js';
14
+ import { loadConfig } from '../../services/config.js';
15
+ const rulesCommand = new Command('rules')
16
+ .description('Analyze and optimize Cursor rules and AGENTS.md files');
17
+ rulesCommand
18
+ .command('analyze <folder>')
19
+ .description('Analyze rules without making changes')
20
+ .option('--json', 'Output as JSON')
21
+ .option('--no-llm', 'Disable LLM analysis')
22
+ .option('--threshold <number>', 'Similarity threshold for duplicates (0-1)', '0.7')
23
+ .action(async (folder, options) => {
24
+ const folderPath = resolve(folder);
25
+ if (!existsSync(folderPath)) {
26
+ console.error(chalk.red(`Folder not found: ${folderPath}`));
27
+ process.exit(1);
28
+ }
29
+ const spinner = ora('Parsing rules...').start();
30
+ try {
31
+ const config = await loadConfig();
32
+ const parser = getRulesParser();
33
+ const rules = parser.parseDirectory(folderPath);
34
+ spinner.text = `Found ${rules.length} rules, analyzing...`;
35
+ const analyzer = getRulesAnalyzer(config, {
36
+ useLLM: options.llm !== false,
37
+ duplicateThreshold: parseFloat(options.threshold),
38
+ dryRun: true,
39
+ });
40
+ const report = await analyzer.analyzeRules(rules, folderPath);
41
+ spinner.succeed(`Analysis complete`);
42
+ if (options.json) {
43
+ console.log(JSON.stringify(report, null, 2));
44
+ return;
45
+ }
46
+ printAnalysisReport(report, folderPath);
47
+ }
48
+ catch (error) {
49
+ spinner.fail('Analysis failed');
50
+ console.error(chalk.red(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
51
+ process.exit(1);
52
+ }
53
+ });
54
+ rulesCommand
55
+ .command('duplicates <folder>')
56
+ .description('Show duplicate rules only')
57
+ .option('--threshold <number>', 'Similarity threshold (0-1)', '0.7')
58
+ .option('--json', 'Output as JSON')
59
+ .action(async (folder, options) => {
60
+ const folderPath = resolve(folder);
61
+ if (!existsSync(folderPath)) {
62
+ console.error(chalk.red(`Folder not found: ${folderPath}`));
63
+ process.exit(1);
64
+ }
65
+ const spinner = ora('Finding duplicates...').start();
66
+ try {
67
+ const config = await loadConfig();
68
+ const rules = parseRulesDirectory(folderPath);
69
+ spinner.text = `Found ${rules.length} rules, checking for duplicates...`;
70
+ const analyzer = getRulesAnalyzer(config, {
71
+ useLLM: false,
72
+ duplicateThreshold: parseFloat(options.threshold),
73
+ });
74
+ const report = await analyzer.analyzeRules(rules, folderPath);
75
+ const duplicates = report.findings.duplicates;
76
+ spinner.succeed(`Found ${duplicates.length} duplicate pairs`);
77
+ if (options.json) {
78
+ console.log(JSON.stringify(duplicates, null, 2));
79
+ return;
80
+ }
81
+ if (duplicates.length === 0) {
82
+ console.log(chalk.green('\nāœ“ No duplicates found!\n'));
83
+ return;
84
+ }
85
+ printDuplicates(duplicates, folderPath);
86
+ }
87
+ catch (error) {
88
+ spinner.fail('Duplicate detection failed');
89
+ console.error(chalk.red(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
90
+ process.exit(1);
91
+ }
92
+ });
93
+ rulesCommand
94
+ .command('conflicts <folder>')
95
+ .description('Show conflicting rules')
96
+ .option('--json', 'Output as JSON')
97
+ .action(async (folder, options) => {
98
+ const folderPath = resolve(folder);
99
+ if (!existsSync(folderPath)) {
100
+ console.error(chalk.red(`Folder not found: ${folderPath}`));
101
+ process.exit(1);
102
+ }
103
+ const spinner = ora('Finding conflicts...').start();
104
+ try {
105
+ const config = await loadConfig();
106
+ const rules = parseRulesDirectory(folderPath);
107
+ const analyzer = getRulesAnalyzer(config, {
108
+ useLLM: false,
109
+ detectConflicts: true,
110
+ });
111
+ const report = await analyzer.analyzeRules(rules, folderPath);
112
+ const conflicts = report.findings.conflicts;
113
+ spinner.succeed(`Found ${conflicts.length} conflicts`);
114
+ if (options.json) {
115
+ console.log(JSON.stringify(conflicts, null, 2));
116
+ return;
117
+ }
118
+ if (conflicts.length === 0) {
119
+ console.log(chalk.green('\nāœ“ No conflicts found!\n'));
120
+ return;
121
+ }
122
+ printConflicts(conflicts, folderPath);
123
+ }
124
+ catch (error) {
125
+ spinner.fail('Conflict detection failed');
126
+ console.error(chalk.red(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
127
+ process.exit(1);
128
+ }
129
+ });
130
+ rulesCommand
131
+ .command('outdated <folder>')
132
+ .description('Show outdated rules')
133
+ .option('--json', 'Output as JSON')
134
+ .action(async (folder, options) => {
135
+ const folderPath = resolve(folder);
136
+ if (!existsSync(folderPath)) {
137
+ console.error(chalk.red(`Folder not found: ${folderPath}`));
138
+ process.exit(1);
139
+ }
140
+ const spinner = ora('Finding outdated rules...').start();
141
+ try {
142
+ const config = await loadConfig();
143
+ const rules = parseRulesDirectory(folderPath);
144
+ const analyzer = getRulesAnalyzer(config, {
145
+ useLLM: false,
146
+ detectOutdated: true,
147
+ });
148
+ const report = await analyzer.analyzeRules(rules, folderPath);
149
+ const outdated = report.findings.outdated;
150
+ spinner.succeed(`Found ${outdated.length} potentially outdated rules`);
151
+ if (options.json) {
152
+ console.log(JSON.stringify(outdated, null, 2));
153
+ return;
154
+ }
155
+ if (outdated.length === 0) {
156
+ console.log(chalk.green('\nāœ“ No outdated rules found!\n'));
157
+ return;
158
+ }
159
+ printOutdated(outdated, folderPath);
160
+ }
161
+ catch (error) {
162
+ spinner.fail('Outdated detection failed');
163
+ console.error(chalk.red(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
164
+ process.exit(1);
165
+ }
166
+ });
167
+ rulesCommand
168
+ .command('optimize <folder>')
169
+ .description('Optimize rules (dry-run by default)')
170
+ .option('--dry-run', 'Preview changes without applying (default)', true)
171
+ .option('--apply', 'Apply changes (creates backups)')
172
+ .option('--backup <dir>', 'Backup directory', '.cursor-rag/rules-backup')
173
+ .option('--aggressive', 'More aggressive merging')
174
+ .option('--threshold <number>', 'Similarity threshold (0-1)', '0.7')
175
+ .option('--output <dir>', 'Write optimized rules to different location')
176
+ .option('--json', 'Output as JSON')
177
+ .action(async (folder, options) => {
178
+ const folderPath = resolve(folder);
179
+ if (!existsSync(folderPath)) {
180
+ console.error(chalk.red(`Folder not found: ${folderPath}`));
181
+ process.exit(1);
182
+ }
183
+ const isDryRun = !options.apply;
184
+ const spinner = ora(isDryRun ? 'Analyzing rules (dry-run)...' : 'Optimizing rules...').start();
185
+ try {
186
+ const config = await loadConfig();
187
+ const rules = parseRulesDirectory(folderPath);
188
+ spinner.text = `Found ${rules.length} rules, analyzing...`;
189
+ const analyzer = getRulesAnalyzer(config, {
190
+ useLLM: true,
191
+ duplicateThreshold: parseFloat(options.threshold),
192
+ aggressiveness: options.aggressive ? 'aggressive' : 'balanced',
193
+ dryRun: isDryRun,
194
+ createBackups: !isDryRun,
195
+ backupDir: options.backup,
196
+ });
197
+ const report = await analyzer.analyzeRules(rules, folderPath);
198
+ if (isDryRun) {
199
+ spinner.succeed('Analysis complete (dry-run mode)');
200
+ if (options.json) {
201
+ console.log(JSON.stringify(report, null, 2));
202
+ return;
203
+ }
204
+ printOptimizationPlan(report, folderPath);
205
+ }
206
+ else {
207
+ // Create backups
208
+ const backupDir = resolve(options.backup);
209
+ spinner.text = 'Creating backups...';
210
+ createBackups(rules, backupDir);
211
+ // Apply optimizations
212
+ spinner.text = 'Applying optimizations...';
213
+ const result = applyOptimizations(report, options.output ? resolve(options.output) : folderPath);
214
+ spinner.succeed(`Optimization complete`);
215
+ console.log(chalk.cyan('\nšŸ“Š Results:\n'));
216
+ console.log(` Files modified: ${result.filesModified}`);
217
+ console.log(` Files deleted: ${result.filesDeleted}`);
218
+ console.log(` Tokens saved: ${chalk.green(result.tokensSaved.toLocaleString())}`);
219
+ console.log(` Backup location: ${chalk.gray(backupDir)}`);
220
+ console.log('');
221
+ }
222
+ }
223
+ catch (error) {
224
+ spinner.fail('Optimization failed');
225
+ console.error(chalk.red(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
226
+ process.exit(1);
227
+ }
228
+ });
229
+ rulesCommand
230
+ .command('merge <folder>')
231
+ .description('Merge duplicate rules using LLM')
232
+ .option('--dry-run', 'Preview merges without applying (default)', true)
233
+ .option('--apply', 'Apply merges (creates backups)')
234
+ .option('--backup <dir>', 'Backup directory', '.cursor-rag/rules-backup')
235
+ .option('--aggressive', 'More aggressive merging')
236
+ .option('--conservative', 'Conservative merging (preserve more detail)')
237
+ .option('--threshold <number>', 'Similarity threshold (0-1)', '0.7')
238
+ .option('--json', 'Output as JSON')
239
+ .action(async (folder, options) => {
240
+ const folderPath = resolve(folder);
241
+ if (!existsSync(folderPath)) {
242
+ console.error(chalk.red(`Folder not found: ${folderPath}`));
243
+ process.exit(1);
244
+ }
245
+ const isDryRun = !options.apply;
246
+ const aggressiveness = options.aggressive ? 'aggressive' : (options.conservative ? 'conservative' : 'balanced');
247
+ const spinner = ora(isDryRun ? 'Analyzing rules for merging (dry-run)...' : 'Merging rules...').start();
248
+ try {
249
+ const config = await loadConfig();
250
+ const rules = parseRulesDirectory(folderPath);
251
+ spinner.text = `Found ${rules.length} rules, finding duplicates...`;
252
+ const analyzer = getRulesAnalyzer(config, {
253
+ useLLM: false,
254
+ duplicateThreshold: parseFloat(options.threshold),
255
+ });
256
+ const report = await analyzer.analyzeRules(rules, folderPath);
257
+ const duplicates = report.findings.duplicates.filter(d => d.recommendation === 'merge' || d.matchType === 'semantic' || d.matchType === 'near_exact');
258
+ if (duplicates.length === 0) {
259
+ spinner.succeed('No merge candidates found');
260
+ console.log(chalk.green('\nāœ“ No rules need merging!\n'));
261
+ return;
262
+ }
263
+ spinner.text = `Found ${duplicates.length} merge candidates, generating merges...`;
264
+ const merger = getRulesMerger({
265
+ aggressiveness,
266
+ maxTokensPerRule: 2000,
267
+ dryRun: isDryRun,
268
+ });
269
+ const mergeCandidates = [];
270
+ for (let i = 0; i < duplicates.length; i++) {
271
+ const dup = duplicates[i];
272
+ spinner.text = `Merging ${i + 1}/${duplicates.length}: ${dup.rule1.title} + ${dup.rule2.title}`;
273
+ try {
274
+ const candidate = await merger.mergeDuplicates(dup);
275
+ mergeCandidates.push(candidate);
276
+ }
277
+ catch (error) {
278
+ console.warn(chalk.yellow(`\nWarning: Failed to merge ${dup.rule1.title} + ${dup.rule2.title}: ${error instanceof Error ? error.message : 'Unknown error'}`));
279
+ }
280
+ }
281
+ spinner.succeed(`Generated ${mergeCandidates.length} merge candidates`);
282
+ if (options.json) {
283
+ console.log(JSON.stringify(mergeCandidates, null, 2));
284
+ return;
285
+ }
286
+ printMergeCandidates(mergeCandidates, folderPath);
287
+ if (!isDryRun && mergeCandidates.length > 0) {
288
+ // Create backups
289
+ const backupDir = resolve(options.backup);
290
+ const allRules = mergeCandidates.flatMap(c => c.rules);
291
+ createBackups(allRules, backupDir);
292
+ console.log(chalk.cyan(`\nšŸ“ Backups created at: ${backupDir}`));
293
+ console.log(chalk.yellow('\nāš ļø Actual file writing not yet implemented. Review merge candidates above.\n'));
294
+ }
295
+ }
296
+ catch (error) {
297
+ spinner.fail('Merge failed');
298
+ console.error(chalk.red(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
299
+ process.exit(1);
300
+ }
301
+ });
302
+ rulesCommand
303
+ .command('rewrite <folder>')
304
+ .description('Rewrite rules to be more concise using LLM')
305
+ .option('--min-tokens <number>', 'Only rewrite rules with more than this many tokens', '500')
306
+ .option('--dry-run', 'Preview rewrites without applying (default)', true)
307
+ .option('--apply', 'Apply rewrites (creates backups)')
308
+ .option('--backup <dir>', 'Backup directory', '.cursor-rag/rules-backup')
309
+ .option('--json', 'Output as JSON')
310
+ .action(async (folder, options) => {
311
+ const folderPath = resolve(folder);
312
+ if (!existsSync(folderPath)) {
313
+ console.error(chalk.red(`Folder not found: ${folderPath}`));
314
+ process.exit(1);
315
+ }
316
+ const isDryRun = !options.apply;
317
+ const minTokens = parseInt(options.minTokens, 10);
318
+ const spinner = ora('Finding rules to rewrite...').start();
319
+ try {
320
+ const rules = parseRulesDirectory(folderPath);
321
+ const largeRules = rules.filter(r => r.tokenCount >= minTokens);
322
+ if (largeRules.length === 0) {
323
+ spinner.succeed('No rules need rewriting');
324
+ console.log(chalk.green(`\nāœ“ No rules with ${minTokens}+ tokens found!\n`));
325
+ return;
326
+ }
327
+ spinner.text = `Found ${largeRules.length} rules with ${minTokens}+ tokens, rewriting...`;
328
+ const merger = getRulesMerger({
329
+ aggressiveness: 'balanced',
330
+ maxTokensPerRule: 2000,
331
+ dryRun: isDryRun,
332
+ });
333
+ const rewrites = [];
334
+ for (let i = 0; i < largeRules.length; i++) {
335
+ const rule = largeRules[i];
336
+ spinner.text = `Rewriting ${i + 1}/${largeRules.length}: ${rule.title}`;
337
+ try {
338
+ const result = await merger.rewriteRule(rule);
339
+ if (result.success && result.tokensAfter < result.tokensBefore * 0.9) {
340
+ rewrites.push({
341
+ rule,
342
+ newContent: result.mergedContent,
343
+ newTitle: result.mergedTitle,
344
+ tokensBefore: result.tokensBefore,
345
+ tokensAfter: result.tokensAfter,
346
+ rationale: result.rationale,
347
+ });
348
+ }
349
+ }
350
+ catch (error) {
351
+ console.warn(chalk.yellow(`\nWarning: Failed to rewrite ${rule.title}: ${error instanceof Error ? error.message : 'Unknown error'}`));
352
+ }
353
+ }
354
+ spinner.succeed(`Generated ${rewrites.length} rewrites`);
355
+ if (options.json) {
356
+ console.log(JSON.stringify(rewrites, null, 2));
357
+ return;
358
+ }
359
+ if (rewrites.length === 0) {
360
+ console.log(chalk.green('\nāœ“ No rules could be significantly improved!\n'));
361
+ return;
362
+ }
363
+ console.log(chalk.cyan('\nāœļø Rule Rewrites\n'));
364
+ let totalSaved = 0;
365
+ for (const rewrite of rewrites) {
366
+ const saved = rewrite.tokensBefore - rewrite.tokensAfter;
367
+ const percent = Math.round((saved / rewrite.tokensBefore) * 100);
368
+ totalSaved += saved;
369
+ console.log(chalk.bold(`${rewrite.rule.title}`));
370
+ console.log(` File: ${chalk.gray(relative(folderPath, rewrite.rule.sourceFile.path))}`);
371
+ console.log(` Tokens: ${rewrite.tokensBefore} → ${rewrite.tokensAfter} (${chalk.green(`-${percent}%`)})`);
372
+ console.log(` Rationale: ${chalk.gray(rewrite.rationale)}`);
373
+ console.log('');
374
+ }
375
+ console.log(chalk.bold(`Total savings: ${chalk.green(totalSaved.toLocaleString())} tokens`));
376
+ if (!isDryRun && rewrites.length > 0) {
377
+ const backupDir = resolve(options.backup);
378
+ createBackups(rewrites.map(r => r.rule), backupDir);
379
+ console.log(chalk.cyan(`\nšŸ“ Backups created at: ${backupDir}`));
380
+ console.log(chalk.yellow('\nāš ļø Actual file writing not yet implemented. Review rewrites above.\n'));
381
+ }
382
+ }
383
+ catch (error) {
384
+ spinner.fail('Rewrite failed');
385
+ console.error(chalk.red(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
386
+ process.exit(1);
387
+ }
388
+ });
389
+ rulesCommand
390
+ .command('list <folder>')
391
+ .description('List all rules in a folder')
392
+ .option('--json', 'Output as JSON')
393
+ .option('--sort <field>', 'Sort by: tokens, name, modified', 'name')
394
+ .action(async (folder, options) => {
395
+ const folderPath = resolve(folder);
396
+ if (!existsSync(folderPath)) {
397
+ console.error(chalk.red(`Folder not found: ${folderPath}`));
398
+ process.exit(1);
399
+ }
400
+ const spinner = ora('Scanning rules...').start();
401
+ try {
402
+ const rules = parseRulesDirectory(folderPath);
403
+ spinner.succeed(`Found ${rules.length} rules`);
404
+ // Sort rules
405
+ const sortedRules = [...rules].sort((a, b) => {
406
+ switch (options.sort) {
407
+ case 'tokens':
408
+ return b.tokenCount - a.tokenCount;
409
+ case 'modified':
410
+ return b.sourceFile.lastModified.getTime() - a.sourceFile.lastModified.getTime();
411
+ case 'name':
412
+ default:
413
+ return a.title.localeCompare(b.title);
414
+ }
415
+ });
416
+ if (options.json) {
417
+ console.log(JSON.stringify(sortedRules.map(r => ({
418
+ id: r.id,
419
+ title: r.title,
420
+ file: relative(folderPath, r.sourceFile.path),
421
+ tokens: r.tokenCount,
422
+ tags: r.tags,
423
+ modified: r.sourceFile.lastModified.toISOString(),
424
+ })), null, 2));
425
+ return;
426
+ }
427
+ const totalTokens = rules.reduce((sum, r) => sum + r.tokenCount, 0);
428
+ console.log(chalk.cyan('\nšŸ“‹ Rules List\n'));
429
+ console.log(chalk.gray(` Total: ${rules.length} rules, ${totalTokens.toLocaleString()} tokens\n`));
430
+ for (const rule of sortedRules) {
431
+ const filePath = relative(folderPath, rule.sourceFile.path);
432
+ const tags = rule.tags.length > 0 ? chalk.gray(` [${rule.tags.slice(0, 3).join(', ')}]`) : '';
433
+ console.log(` ${chalk.bold(rule.title)}${tags}`);
434
+ console.log(` ${chalk.gray(filePath)} • ${rule.tokenCount} tokens`);
435
+ }
436
+ console.log('');
437
+ }
438
+ catch (error) {
439
+ spinner.fail('Listing failed');
440
+ console.error(chalk.red(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
441
+ process.exit(1);
442
+ }
443
+ });
444
+ function printAnalysisReport(report, folderPath) {
445
+ console.log(chalk.cyan('\nšŸ“Š Rules Analysis Report\n'));
446
+ // Summary
447
+ console.log(chalk.bold('Summary:'));
448
+ console.log(` Files scanned: ${report.summary.totalFiles}`);
449
+ console.log(` Rules found: ${report.summary.totalRules}`);
450
+ console.log(` Total tokens: ${report.summary.totalTokensBefore.toLocaleString()}`);
451
+ if (report.summary.savingsPercent > 0) {
452
+ console.log(` Potential savings: ${chalk.green(`${report.summary.savingsPercent}%`)} (${(report.summary.totalTokensBefore - report.summary.totalTokensAfter).toLocaleString()} tokens)`);
453
+ }
454
+ // Findings
455
+ console.log(chalk.bold('\nFindings:'));
456
+ console.log(` Duplicates: ${report.summary.duplicatesFound}`);
457
+ console.log(` Conflicts: ${report.summary.conflictsFound}`);
458
+ console.log(` Merge candidates: ${report.summary.mergeCandidates}`);
459
+ console.log(` Outdated rules: ${report.summary.outdatedRules}`);
460
+ // Top duplicates
461
+ if (report.findings.duplicates.length > 0) {
462
+ console.log(chalk.bold('\nTop Duplicates:'));
463
+ for (const dup of report.findings.duplicates.slice(0, 5)) {
464
+ const similarity = Math.round(dup.similarity * 100);
465
+ console.log(` ${chalk.yellow(`${similarity}%`)} ${dup.rule1.title} ↔ ${dup.rule2.title}`);
466
+ console.log(` Type: ${dup.matchType}, Recommendation: ${dup.recommendation}`);
467
+ }
468
+ }
469
+ // Conflicts
470
+ if (report.findings.conflicts.length > 0) {
471
+ console.log(chalk.bold('\nConflicts:'));
472
+ for (const conflict of report.findings.conflicts.slice(0, 3)) {
473
+ console.log(` ${chalk.red('⚠')} ${conflict.rule1.title} vs ${conflict.rule2.title}`);
474
+ console.log(` ${conflict.description}`);
475
+ }
476
+ }
477
+ // Outdated
478
+ if (report.findings.outdated.length > 0) {
479
+ console.log(chalk.bold('\nPotentially Outdated:'));
480
+ for (const out of report.findings.outdated.slice(0, 5)) {
481
+ const confidence = Math.round(out.confidence * 100);
482
+ console.log(` ${chalk.yellow(`${confidence}%`)} ${out.rule.title}`);
483
+ console.log(` ${chalk.gray(out.reason)}`);
484
+ }
485
+ }
486
+ // Plan summary
487
+ if (report.plan.actions.length > 0) {
488
+ console.log(chalk.bold('\nOptimization Plan:'));
489
+ console.log(` Actions: ${report.plan.actions.length}`);
490
+ console.log(` Risk level: ${report.plan.riskLevel}`);
491
+ console.log(` Requires review: ${report.plan.requiresManualReview ? 'Yes' : 'No'}`);
492
+ console.log(` Estimated time: ${report.plan.estimatedDuration}`);
493
+ }
494
+ console.log(chalk.gray('\nRun with --json for full report or use `rules optimize` to apply changes.\n'));
495
+ }
496
+ function printDuplicates(duplicates, folderPath) {
497
+ console.log(chalk.cyan('\nšŸ” Duplicate Rules\n'));
498
+ for (let i = 0; i < duplicates.length; i++) {
499
+ const dup = duplicates[i];
500
+ const similarity = Math.round(dup.similarity * 100);
501
+ console.log(chalk.bold(`${i + 1}. ${dup.matchType.toUpperCase()} (${similarity}% similar)`));
502
+ console.log(` Rule 1: ${dup.rule1.title}`);
503
+ console.log(` File: ${chalk.gray(relative(folderPath, dup.rule1.sourceFile.path))}`);
504
+ console.log(` Rule 2: ${dup.rule2.title}`);
505
+ console.log(` File: ${chalk.gray(relative(folderPath, dup.rule2.sourceFile.path))}`);
506
+ console.log(` Overlap: ${dup.overlappingConcepts.join(', ')}`);
507
+ console.log(` Action: ${chalk.yellow(dup.recommendation)}`);
508
+ console.log('');
509
+ }
510
+ }
511
+ function printConflicts(conflicts, folderPath) {
512
+ console.log(chalk.cyan('\nāš ļø Rule Conflicts\n'));
513
+ for (let i = 0; i < conflicts.length; i++) {
514
+ const conflict = conflicts[i];
515
+ console.log(chalk.bold(`${i + 1}. ${conflict.conflictType}`));
516
+ console.log(` ${conflict.rule1.title} vs ${conflict.rule2.title}`);
517
+ console.log(` ${conflict.description}`);
518
+ for (const stmt of conflict.conflictingStatements.slice(0, 2)) {
519
+ console.log(chalk.red(` - "${stmt.statement1.substring(0, 60)}..."`));
520
+ console.log(chalk.red(` - "${stmt.statement2.substring(0, 60)}..."`));
521
+ }
522
+ if (conflict.resolution) {
523
+ console.log(chalk.green(` Suggestion: ${conflict.resolution.reasoning}`));
524
+ }
525
+ console.log('');
526
+ }
527
+ }
528
+ function printOutdated(outdated, folderPath) {
529
+ console.log(chalk.cyan('\nšŸ“… Potentially Outdated Rules\n'));
530
+ for (let i = 0; i < outdated.length; i++) {
531
+ const out = outdated[i];
532
+ const confidence = Math.round(out.confidence * 100);
533
+ console.log(chalk.bold(`${i + 1}. ${out.rule.title} (${confidence}% confidence)`));
534
+ console.log(` File: ${chalk.gray(relative(folderPath, out.rule.sourceFile.path))}`);
535
+ console.log(` Reason: ${out.reason}`);
536
+ console.log(` Action: ${chalk.yellow(out.action)}`);
537
+ for (const ref of out.outdatedReferences.slice(0, 3)) {
538
+ if (ref.suggestedUpdate) {
539
+ console.log(` - Update "${ref.reference}" → "${ref.suggestedUpdate}"`);
540
+ }
541
+ else {
542
+ console.log(` - ${ref.reference}`);
543
+ }
544
+ }
545
+ console.log('');
546
+ }
547
+ }
548
+ function printMergeCandidates(candidates, folderPath) {
549
+ console.log(chalk.cyan('\nšŸ”€ Merge Candidates\n'));
550
+ let totalTokensBefore = 0;
551
+ let totalTokensAfter = 0;
552
+ for (let i = 0; i < candidates.length; i++) {
553
+ const candidate = candidates[i];
554
+ const saved = candidate.tokensBefore - candidate.tokensAfter;
555
+ const percent = Math.round((saved / candidate.tokensBefore) * 100);
556
+ totalTokensBefore += candidate.tokensBefore;
557
+ totalTokensAfter += candidate.tokensAfter;
558
+ console.log(chalk.bold(`${i + 1}. ${candidate.mergedTitle}`));
559
+ console.log(` Merging ${candidate.rules.length} rules:`);
560
+ for (const rule of candidate.rules) {
561
+ console.log(` - ${rule.title} (${rule.tokenCount} tokens)`);
562
+ }
563
+ console.log(` Tokens: ${candidate.tokensBefore} → ${candidate.tokensAfter} (${chalk.green(`-${percent}%`)})`);
564
+ console.log(` Confidence: ${Math.round(candidate.confidence * 100)}%`);
565
+ console.log(` Rationale: ${chalk.gray(candidate.mergeRationale)}`);
566
+ // Show preview of merged content (first 200 chars)
567
+ const preview = candidate.mergedContent.substring(0, 200).replace(/\n/g, ' ');
568
+ console.log(` Preview: ${chalk.gray(preview)}...`);
569
+ console.log('');
570
+ }
571
+ const totalSaved = totalTokensBefore - totalTokensAfter;
572
+ const totalPercent = Math.round((totalSaved / totalTokensBefore) * 100);
573
+ console.log(chalk.bold('Summary:'));
574
+ console.log(` Total candidates: ${candidates.length}`);
575
+ console.log(` Tokens before: ${totalTokensBefore.toLocaleString()}`);
576
+ console.log(` Tokens after: ${totalTokensAfter.toLocaleString()}`);
577
+ console.log(` Total savings: ${chalk.green(`${totalSaved.toLocaleString()} tokens (${totalPercent}%)`)}`);
578
+ console.log('');
579
+ console.log(chalk.gray('Run with --apply to execute merges (backups will be created).\n'));
580
+ }
581
+ function printOptimizationPlan(report, folderPath) {
582
+ console.log(chalk.cyan('\nšŸ“‹ Optimization Plan (Dry Run)\n'));
583
+ console.log(chalk.bold('Summary:'));
584
+ console.log(` Current tokens: ${report.summary.totalTokensBefore.toLocaleString()}`);
585
+ console.log(` After optimization: ${report.summary.totalTokensAfter.toLocaleString()}`);
586
+ console.log(` Savings: ${chalk.green(`${report.summary.savingsPercent}%`)}`);
587
+ console.log(` Risk level: ${report.plan.riskLevel}`);
588
+ console.log('');
589
+ if (report.plan.actions.length === 0) {
590
+ console.log(chalk.green(' āœ“ No optimizations needed!\n'));
591
+ return;
592
+ }
593
+ console.log(chalk.bold('Planned Actions:'));
594
+ for (const action of report.plan.actions) {
595
+ const autoLabel = action.autoApplyable ? chalk.green('[auto]') : chalk.yellow('[review]');
596
+ const tokenLabel = action.tokenImpact < 0
597
+ ? chalk.green(`${action.tokenImpact} tokens`)
598
+ : `+${action.tokenImpact} tokens`;
599
+ console.log(` ${autoLabel} ${action.type.toUpperCase()}: ${action.description}`);
600
+ console.log(` Files: ${action.affectedFiles.map(f => relative(folderPath, f)).join(', ')}`);
601
+ console.log(` Impact: ${tokenLabel}`);
602
+ console.log('');
603
+ }
604
+ console.log(chalk.gray('Run with --apply to execute these changes (backups will be created).\n'));
605
+ }
606
+ function createBackups(rules, backupDir) {
607
+ if (!existsSync(backupDir)) {
608
+ mkdirSync(backupDir, { recursive: true });
609
+ }
610
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
611
+ const versionDir = resolve(backupDir, timestamp);
612
+ mkdirSync(versionDir, { recursive: true });
613
+ const backedUp = new Set();
614
+ for (const rule of rules) {
615
+ if (backedUp.has(rule.sourceFile.path))
616
+ continue;
617
+ backedUp.add(rule.sourceFile.path);
618
+ const backupPath = resolve(versionDir, basename(rule.sourceFile.path));
619
+ copyFileSync(rule.sourceFile.path, backupPath);
620
+ }
621
+ }
622
+ function applyOptimizations(report, outputDir) {
623
+ let filesModified = 0;
624
+ let filesDeleted = 0;
625
+ const tokensSaved = report.summary.totalTokensBefore - report.summary.totalTokensAfter;
626
+ // For now, just count what would be changed
627
+ // Full implementation would write the merged files
628
+ for (const change of report.fileChanges) {
629
+ if (change.changeType === 'modify') {
630
+ filesModified++;
631
+ }
632
+ else if (change.changeType === 'delete') {
633
+ filesDeleted++;
634
+ }
635
+ }
636
+ return { filesModified, filesDeleted, tokensSaved };
637
+ }
638
+ export { rulesCommand };
639
+ //# sourceMappingURL=rules.js.map