vibecodingmachine-cli 2026.2.20-438 → 2026.2.26-1739

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 (101) hide show
  1. package/bin/auth/auth-compliance.js +126 -0
  2. package/bin/cli-program.js +104 -0
  3. package/bin/cli-setup.js +52 -0
  4. package/bin/commands/agent-commands.js +310 -0
  5. package/bin/commands/auto-commands.js +70 -0
  6. package/bin/commands/command-aliases.js +118 -0
  7. package/bin/commands/repo-commands.js +39 -0
  8. package/bin/commands/rui-commands.js +152 -0
  9. package/bin/config/cli-config.js +394 -0
  10. package/bin/init/environment-setup.js +84 -0
  11. package/bin/update/update-checker.js +126 -0
  12. package/bin/vibecodingmachine-new.js +50 -0
  13. package/bin/vibecodingmachine.js +29 -663
  14. package/package.json +8 -2
  15. package/src/commands/agents/add.js +277 -0
  16. package/src/commands/agents/check.js +380 -0
  17. package/src/commands/agents/list.js +471 -0
  18. package/src/commands/agents/remove.js +351 -0
  19. package/src/commands/analyze-file-sizes.js +428 -0
  20. package/src/commands/auto-direct/code-processor.js +282 -0
  21. package/src/commands/auto-direct/file-scanner.js +266 -0
  22. package/src/commands/auto-direct/provider-config.js +178 -0
  23. package/src/commands/auto-direct/provider-manager.js +219 -0
  24. package/src/commands/auto-direct/requirement-manager.js +172 -0
  25. package/src/commands/auto-direct/status-display.js +91 -0
  26. package/src/commands/auto-direct/utils.js +106 -0
  27. package/src/commands/auto-direct.js +875 -488
  28. package/src/commands/auto-execution.js +342 -0
  29. package/src/commands/auto-provider-management.js +102 -0
  30. package/src/commands/auto-requirement-management.js +161 -0
  31. package/src/commands/auto-status-helpers.js +141 -0
  32. package/src/commands/auto.js +105 -5155
  33. package/src/commands/check-compliance.js +536 -0
  34. package/src/commands/continuous-scan.js +119 -0
  35. package/src/commands/ide.js +16 -4
  36. package/src/commands/refactor-file.js +486 -0
  37. package/src/commands/requirements.js +301 -2
  38. package/src/commands/timeout.js +290 -0
  39. package/src/trui/TruiInterface.js +108 -0
  40. package/src/trui/agents/AgentInterface.js +580 -0
  41. package/src/utils/antigravity-installer.js +60 -6
  42. package/src/utils/clarification-actions.js +290 -0
  43. package/src/utils/config.js +123 -2
  44. package/src/utils/first-run.js +5 -5
  45. package/src/utils/ide-handlers.js +212 -0
  46. package/src/utils/interactive/clarification-actions.js +348 -0
  47. package/src/utils/interactive/core-ui.js +265 -0
  48. package/src/utils/interactive/file-backup.js +237 -0
  49. package/src/utils/interactive/file-import-export.js +305 -0
  50. package/src/utils/interactive/file-operations.js +49 -0
  51. package/src/utils/interactive/file-validation.js +276 -0
  52. package/src/utils/interactive/interactive-prompts.js +480 -0
  53. package/src/utils/interactive/requirement-actions.js +127 -0
  54. package/src/utils/interactive/requirement-crud.js +356 -0
  55. package/src/utils/interactive/requirements-navigation.js +286 -0
  56. package/src/utils/interactive.js +390 -3459
  57. package/src/utils/provider-checker/agent-checker.js +250 -0
  58. package/src/utils/provider-checker/agent-runner.js +450 -0
  59. package/src/utils/provider-checker/cli-installer.js +123 -0
  60. package/src/utils/provider-checker/cli-utils.js +15 -0
  61. package/src/utils/provider-checker/format-utils.js +32 -0
  62. package/src/utils/provider-checker/ide-manager.js +72 -0
  63. package/src/utils/provider-checker/ide-utils.js +71 -0
  64. package/src/utils/provider-checker/node-detector.js +56 -0
  65. package/src/utils/provider-checker/node-utils.js +61 -0
  66. package/src/utils/provider-checker/process-spawn.js +22 -0
  67. package/src/utils/provider-checker/process-utils.js +37 -0
  68. package/src/utils/provider-checker/provider-validator.js +160 -0
  69. package/src/utils/provider-checker/quota-checker.js +54 -0
  70. package/src/utils/provider-checker/quota-detector.js +44 -0
  71. package/src/utils/provider-checker/requirements-manager.js +94 -0
  72. package/src/utils/provider-checker/test-requirements.js +95 -0
  73. package/src/utils/provider-checker/time-formatter.js +18 -0
  74. package/src/utils/provider-checker-new.js +14 -0
  75. package/src/utils/provider-checker.js +12 -407
  76. package/src/utils/provider-checkers/ide-manager.js +128 -0
  77. package/src/utils/provider-checkers/node-executable-finder.js +51 -0
  78. package/src/utils/provider-checkers/provider-checker-core.js +172 -0
  79. package/src/utils/provider-checkers/provider-checker-main.js +107 -0
  80. package/src/utils/provider-manager.js +60 -4
  81. package/src/utils/provider-registry.js +26 -3
  82. package/src/utils/provider-utils.js +173 -0
  83. package/src/utils/quota-detectors.js +212 -0
  84. package/src/utils/requirement-action-handlers.js +288 -0
  85. package/src/utils/requirement-actions/clarification-actions.js +229 -0
  86. package/src/utils/requirement-actions/confirmation-prompts.js +93 -0
  87. package/src/utils/requirement-actions/file-operations.js +92 -0
  88. package/src/utils/requirement-actions/helpers.js +40 -0
  89. package/src/utils/requirement-actions/requirement-operations.js +335 -0
  90. package/src/utils/requirement-actions.js +46 -856
  91. package/src/utils/requirement-file-operations.js +259 -0
  92. package/src/utils/requirement-helpers.js +128 -0
  93. package/src/utils/requirement-management.js +279 -0
  94. package/src/utils/requirement-navigation.js +146 -0
  95. package/src/utils/requirement-organization.js +271 -0
  96. package/src/utils/simple-trui.js +75 -1
  97. package/src/utils/trui-navigation.js +28 -2
  98. package/src/utils/trui-req-tree.js +196 -11
  99. package/src/utils/trui-specifications.js +31 -1
  100. package/src/utils/interactive-backup.js +0 -5664
  101. package/src/utils/trui-provider-manager.js +0 -182
@@ -0,0 +1,428 @@
1
+ /**
2
+ * CLI Command: Analyze File Sizes
3
+ *
4
+ * Command-line interface for analyzing file sizes across the codebase.
5
+ * Provides comprehensive analysis and refactoring recommendations.
6
+ */
7
+
8
+ const { Command } = require('commander');
9
+ const chalk = require('chalk');
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+
13
+ // Import analysis components
14
+ const { CodebaseScanner } = require('@vibecodingmachine/core/src/analysis/codebase-scanner');
15
+ const { LineCounter } = require('@vibecodingmachine/core/src/analysis/line-counter');
16
+ const { FileCategorizer } = require('@vibecodingmachine/core/src/analysis/categorizer');
17
+ const { BoundaryDetector } = require('@vibecodingmachine/core/src/analysis/boundary-detector');
18
+ const { StrategyGenerator } = require('@vibecodingmachine/core/src/analysis/strategy-generator');
19
+ const { PriorityCalculator } = require('@vibecodingmachine/core/src/analysis/priority-calculator');
20
+ const { AnalysisReporter } = require('@vibecodingmachine/core/src/analysis/analysis-reporter');
21
+ const { FileAnalysis } = require('@vibecodingmachine/core/src/models/file-analysis');
22
+ const { FileAnalysisCollection } = require('@vibecodingmachine/core/src/models/file-analysis');
23
+
24
+ /**
25
+ * Analyze file sizes command
26
+ */
27
+ class AnalyzeFileSizesCommand {
28
+ constructor() {
29
+ this.command = new Command('analyze-file-sizes');
30
+ this.setupCommand();
31
+ }
32
+
33
+ setupCommand() {
34
+ this.command
35
+ .description('Analyze file sizes across the codebase and generate refactoring recommendations')
36
+ .option('-p, --path <path>', 'Path to analyze (default: current directory)', process.cwd())
37
+ .option('-o, --output <path>', 'Output file for the report')
38
+ .option('-f, --format <format>', 'Report format (json, markdown, html)', 'json')
39
+ .option('--include-tests', 'Include test files in analysis', false)
40
+ .option('--include-vendor', 'Include vendor/third-party files', false)
41
+ .option('--max-depth <depth>', 'Maximum directory depth to scan', '10')
42
+ .option('--min-lines <lines>', 'Minimum lines to include in detailed analysis', '10')
43
+ .option('--limit <lines>', 'File size limit to check against', '555')
44
+ .option('--verbose', 'Verbose output', false)
45
+ .option('--quiet', 'Minimal output', false)
46
+ .action(async (options) => {
47
+ try {
48
+ await this.execute(options);
49
+ } catch (error) {
50
+ console.error(chalk.red('Error:'), error.message);
51
+ if (options.verbose) {
52
+ console.error(error.stack);
53
+ }
54
+ process.exit(1);
55
+ }
56
+ });
57
+ }
58
+
59
+ async execute(options) {
60
+ const startTime = Date.now();
61
+
62
+ if (!options.quiet) {
63
+ console.log(chalk.blue('🔍 Starting file size analysis...'));
64
+ console.log(chalk.gray(`Path: ${options.path}`));
65
+ console.log(chalk.gray(`Format: ${options.format}`));
66
+ if (options.output) {
67
+ console.log(chalk.gray(`Output: ${options.output}`));
68
+ }
69
+ console.log();
70
+ }
71
+
72
+ // Step 1: Scan codebase
73
+ const scanResults = await this.scanCodebase(options);
74
+
75
+ // Step 2: Count lines
76
+ const lineResults = await this.countLines(scanResults, options);
77
+
78
+ // Step 3: Categorize files
79
+ const categorizationResults = await this.categorizeFiles(lineResults, options);
80
+
81
+ // Step 4: Detect boundaries
82
+ const boundaryResults = await this.detectBoundaries(lineResults, options);
83
+
84
+ // Step 5: Calculate priorities
85
+ const priorityResults = await this.calculatePriorities(lineResults, boundaryResults, options);
86
+
87
+ // Step 6: Generate strategies
88
+ const strategyResults = await this.generateStrategies(lineResults, priorityResults, options);
89
+
90
+ // Step 7: Generate report
91
+ const reportResults = await this.generateReport(
92
+ lineResults,
93
+ categorizationResults,
94
+ priorityResults,
95
+ strategyResults,
96
+ options
97
+ );
98
+
99
+ const duration = Date.now() - startTime;
100
+
101
+ if (!options.quiet) {
102
+ this.printSummary(reportResults, duration);
103
+ }
104
+
105
+ if (options.output) {
106
+ await this.saveReport(reportResults, options);
107
+ }
108
+
109
+ return reportResults;
110
+ }
111
+
112
+ async scanCodebase(options) {
113
+ if (!options.quiet) {
114
+ console.log(chalk.yellow('📁 Scanning codebase...'));
115
+ }
116
+
117
+ const scanner = new CodebaseScanner();
118
+ const results = await scanner.scan(options.path);
119
+
120
+ if (options.verbose) {
121
+ console.log(chalk.gray(`Found ${results.length} files to analyze`));
122
+ }
123
+
124
+ return results;
125
+ }
126
+
127
+ async countLines(scanResults, options) {
128
+ if (!options.quiet) {
129
+ console.log(chalk.yellow('📏 Counting lines...'));
130
+ }
131
+
132
+ const lineCounter = new LineCounter();
133
+ const filePaths = scanResults.map(result => result.path);
134
+ const lineResults = lineCounter.countFiles(filePaths);
135
+
136
+ // Create file analysis objects
137
+ const fileAnalyses = [];
138
+ for (let i = 0; i < scanResults.length; i++) {
139
+ const scanResult = scanResults[i];
140
+ const lineResult = lineResults[i];
141
+
142
+ const analysis = new FileAnalysis(scanResult.path);
143
+ analysis.setLineCountMetrics(
144
+ lineResult.totalLines,
145
+ lineResult.codeLines,
146
+ lineResult.commentLines,
147
+ lineResult.blankLines
148
+ );
149
+ analysis.setFileTypeFlags(
150
+ lineResult.extension.includes('test') || scanResult.relativePath.includes('test'),
151
+ scanResult.basename.includes('config') || scanResult.basename.includes('package'),
152
+ scanResult.basename.includes('generated') || scanResult.basename.includes('auto')
153
+ );
154
+ analysis.setTimestamps(
155
+ scanResult.modified,
156
+ scanResult.created
157
+ );
158
+
159
+ fileAnalyses.push(analysis);
160
+ }
161
+
162
+ if (options.verbose) {
163
+ const totalLines = fileAnalyses.reduce((sum, f) => sum + f.lineCount, 0);
164
+ console.log(chalk.gray(`Analyzed ${fileAnalyses.length} files with ${totalLines} total lines`));
165
+ }
166
+
167
+ return fileAnalyses;
168
+ }
169
+
170
+ async categorizeFiles(lineResults, options) {
171
+ if (!options.quiet) {
172
+ console.log(chalk.yellow('📂 Categorizing files...'));
173
+ }
174
+
175
+ const categorizer = new FileCategorizer();
176
+ const fileData = lineResults.map(analysis => ({
177
+ filePath: analysis.filePath,
178
+ lineCount: analysis.lineCount,
179
+ fileType: analysis.extension,
180
+ package: analysis.package
181
+ }));
182
+
183
+ const categorizationResults = categorizer.categorizeFiles(fileData);
184
+
185
+ // Update file analyses with categorization
186
+ for (let i = 0; i < lineResults.length; i++) {
187
+ const analysis = lineResults[i];
188
+ const categorization = categorizationResults.files[i];
189
+
190
+ if (categorization && categorization.categorization) {
191
+ analysis.setClassification(
192
+ categorization.categorization.size,
193
+ categorization.categorization.priority,
194
+ categorization.categorization.difficulty,
195
+ categorization.categorization.needsRefactoring,
196
+ categorization.categorization.urgentRefactoring
197
+ );
198
+ }
199
+ }
200
+
201
+ return categorizationResults;
202
+ }
203
+
204
+ async detectBoundaries(lineResults, options) {
205
+ if (!options.quiet) {
206
+ console.log(chalk.yellow('🔍 Detecting code boundaries...'));
207
+ }
208
+
209
+ const boundaryDetector = new BoundaryDetector();
210
+ const boundaryResults = new Map();
211
+
212
+ // Only analyze JavaScript/TypeScript files for boundaries
213
+ const jsFiles = lineResults.filter(analysis =>
214
+ ['.js', '.jsx', '.ts', '.tsx'].includes(analysis.extension)
215
+ );
216
+
217
+ for (let i = 0; i < jsFiles.length; i++) {
218
+ const analysis = jsFiles[i];
219
+
220
+ try {
221
+ const boundaryResult = await boundaryDetector.detectBoundaries(analysis.filePath);
222
+ boundaryResults.set(analysis.filePath, boundaryResult);
223
+
224
+ // Update analysis with boundary data
225
+ analysis.setBoundaries(boundaryResult);
226
+
227
+ if (options.verbose && i % 10 === 0) {
228
+ console.log(chalk.gray(`Analyzed boundaries for ${i + 1}/${jsFiles.length} files`));
229
+ }
230
+ } catch (error) {
231
+ analysis.addError(`Boundary detection failed: ${error.message}`);
232
+ }
233
+ }
234
+
235
+ if (options.verbose) {
236
+ console.log(chalk.gray(`Detected boundaries in ${boundaryResults.size} files`));
237
+ }
238
+
239
+ return boundaryResults;
240
+ }
241
+
242
+ async calculatePriorities(lineResults, boundaryResults, options) {
243
+ if (!options.quiet) {
244
+ console.log(chalk.yellow('🎯 Calculating priorities...'));
245
+ }
246
+
247
+ const priorityCalculator = new PriorityCalculator();
248
+ const priorityResults = priorityCalculator.calculatePriorities(lineResults);
249
+
250
+ // Update file analyses with priority data
251
+ for (let i = 0; i < lineResults.length; i++) {
252
+ const analysis = lineResults[i];
253
+ const priorityResult = priorityResults[i];
254
+
255
+ if (priorityResult) {
256
+ analysis.priority = priorityResult.priority;
257
+ // Add priority reasons as warnings
258
+ for (const reason of priorityResult.reasons) {
259
+ analysis.addWarning(reason);
260
+ }
261
+ }
262
+ }
263
+
264
+ return priorityResults;
265
+ }
266
+
267
+ async generateStrategies(lineResults, priorityResults, options) {
268
+ if (!options.quiet) {
269
+ console.log(chalk.yellow('📋 Generating refactoring strategies...'));
270
+ }
271
+
272
+ // Create analysis collection
273
+ const collection = new FileAnalysisCollection();
274
+ for (const analysis of lineResults) {
275
+ collection.add(analysis);
276
+ }
277
+
278
+ const strategyGenerator = new StrategyGenerator();
279
+ const analysisData = {
280
+ files: lineResults,
281
+ summary: collection.getSummary()
282
+ };
283
+
284
+ const strategies = strategyGenerator.generateStrategies(analysisData);
285
+
286
+ if (options.verbose) {
287
+ console.log(chalk.gray(`Generated ${strategies.length} refactoring strategies`));
288
+ }
289
+
290
+ return strategies;
291
+ }
292
+
293
+ async generateReport(lineResults, categorizationResults, priorityResults, strategies, options) {
294
+ if (!options.quiet) {
295
+ console.log(chalk.yellow('📊 Generating analysis report...'));
296
+ }
297
+
298
+ const reporter = new AnalysisReporter();
299
+
300
+ // Create analysis data for reporter
301
+ const analysisData = {
302
+ files: lineResults,
303
+ summary: {
304
+ totalFiles: lineResults.length,
305
+ filesNeedingRefactoring: lineResults.filter(f => f.needsRefactoring).length,
306
+ urgentRefactoring: lineResults.filter(f => f.urgentRefactoring).length,
307
+ totalLines: lineResults.reduce((sum, f) => sum + f.lineCount, 0)
308
+ }
309
+ };
310
+
311
+ const reportOptions = {
312
+ format: options.format,
313
+ outputPath: options.output,
314
+ scope: 'full_codebase',
315
+ packages: [...new Set(lineResults.map(f => f.package))]
316
+ };
317
+
318
+ const reportResult = await reporter.generateReport(analysisData, reportOptions);
319
+
320
+ return {
321
+ reportData: reporter.report,
322
+ exportResult: reportResult,
323
+ strategies,
324
+ summary: analysisData.summary
325
+ };
326
+ }
327
+
328
+ printSummary(results, duration) {
329
+ const { reportData, summary } = results;
330
+
331
+ console.log();
332
+ console.log(chalk.blue('📈 Analysis Summary'));
333
+ console.log(chalk.gray('─'.repeat(50)));
334
+ console.log(`📁 Total Files: ${chalk.bold(summary.totalFiles)}`);
335
+ console.log(`📏 Total Lines: ${chalk.bold(summary.totalLines.toLocaleString())}`);
336
+ console.log(`🔧 Files Needing Refactoring: ${chalk.bold(summary.filesNeedingRefactoring)}`);
337
+ console.log(`🚨 Urgent Refactoring: ${chalk.bold(summary.urgentRefactoring)}`);
338
+ console.log(`✅ Compliance Rate: ${chalk.bold(reportData.summary.complianceRate + '%')}`);
339
+ console.log(`🎯 Average Complexity: ${chalk.bold(reportData.summary.averageComplexity)}`);
340
+ console.log(`⏱️ Duration: ${chalk.bold((duration / 1000).toFixed(2) + 's')}`);
341
+
342
+ // Show violations
343
+ if (reportData.violations.length > 0) {
344
+ console.log();
345
+ console.log(chalk.red('⚠️ Violations Found'));
346
+ console.log(chalk.gray('─'.repeat(50)));
347
+
348
+ const criticalViolations = reportData.violations.filter(v => v.severity === 'critical');
349
+ const majorViolations = reportData.violations.filter(v => v.severity === 'major');
350
+
351
+ if (criticalViolations.length > 0) {
352
+ console.log(`${chalk.red('🚨 Critical:')} ${criticalViolations.length} files`);
353
+ }
354
+ if (majorViolations.length > 0) {
355
+ console.log(`${chalk.yellow('⚠️ Major:')} ${majorViolations.length} files`);
356
+ }
357
+ }
358
+
359
+ // Show top recommendations
360
+ if (reportData.recommendations.length > 0) {
361
+ console.log();
362
+ console.log(chalk.blue('💡 Top Recommendations'));
363
+ console.log(chalk.gray('─'.repeat(50)));
364
+
365
+ for (const rec of reportData.recommendations.slice(0, 3)) {
366
+ const priorityColor = rec.priority === 'critical' ? 'red' :
367
+ rec.priority === 'high' ? 'yellow' :
368
+ rec.priority === 'medium' ? 'blue' : 'gray';
369
+
370
+ console.log(`${chalk[priorityColor]('●')} ${rec.title}`);
371
+ console.log(` ${rec.description}`);
372
+ console.log(` Priority: ${rec.priority} | Effort: ${rec.estimatedEffort} units`);
373
+ console.log();
374
+ }
375
+ }
376
+
377
+ // Package summary
378
+ if (Object.keys(reportData.packageAnalysis).length > 1) {
379
+ console.log(chalk.blue('📦 Package Summary'));
380
+ console.log(chalk.gray('─'.repeat(50)));
381
+
382
+ for (const [packageName, packageData] of Object.entries(reportData.packageAnalysis)) {
383
+ const complianceColor = packageData.complianceRate >= 90 ? 'green' :
384
+ packageData.complianceRate >= 70 ? 'yellow' : 'red';
385
+
386
+ console.log(`${packageName}: ${chalk[complianceColor](packageData.complianceRate + '%')} compliant (${packageData.files.length} files)`);
387
+ }
388
+ }
389
+ }
390
+
391
+ async saveReport(results, options) {
392
+ try {
393
+ const { exportResult } = results;
394
+
395
+ if (exportResult.success) {
396
+ console.log();
397
+ console.log(chalk.green('✅ Report saved successfully'));
398
+ console.log(chalk.gray(`📄 ${exportResult.path}`));
399
+
400
+ // Show file size
401
+ const stats = fs.statSync(exportResult.path);
402
+ const fileSize = (stats.size / 1024).toFixed(2);
403
+ console.log(chalk.gray(`📊 Size: ${fileSize} KB`));
404
+ } else {
405
+ console.log(chalk.red('❌ Failed to save report'));
406
+ }
407
+ } catch (error) {
408
+ console.error(chalk.red('Error saving report:'), error.message);
409
+ }
410
+ }
411
+ }
412
+
413
+ /**
414
+ * Register command with CLI
415
+ */
416
+ function registerCommand(program) {
417
+ const command = new AnalyzeFileSizesCommand();
418
+ program.addCommand(command.command);
419
+ return command;
420
+ }
421
+
422
+ /**
423
+ * Export for direct use
424
+ */
425
+ module.exports = {
426
+ AnalyzeFileSizesCommand,
427
+ registerCommand
428
+ };
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Code processing functions for auto-direct command
3
+ * Extracted from auto-direct.js to reduce file size
4
+ */
5
+
6
+ const fs = require('fs-extra');
7
+ const path = require('path');
8
+ const chalk = require('chalk');
9
+
10
+ /**
11
+ * Parse search/replace blocks from LLM response
12
+ */
13
+ function parseSearchReplaceBlocks(response) {
14
+ const changes = [];
15
+
16
+ // Match FILE: path SEARCH: ``` old ``` REPLACE: ``` new ``` format
17
+ const fileBlockRegex = /FILE:\s*(.+?)\s*SEARCH:\s*```[\s\S]*?```[\s\S]*?REPLACE:\s*```[\s\S]*?```/g;
18
+
19
+ let match;
20
+ while ((match = fileBlockRegex.exec(response)) !== null) {
21
+ const fileBlock = match[0];
22
+ const filePath = match[1].trim();
23
+
24
+ // Extract search block
25
+ const searchMatch = fileBlock.match(/SEARCH:\s*```([\s\S]*?)```/);
26
+ // Extract replace block
27
+ const replaceMatch = fileBlock.match(/REPLACE:\s*```([\s\S]*?)```/);
28
+
29
+ if (searchMatch && replaceMatch) {
30
+ changes.push({
31
+ file: filePath,
32
+ search: searchMatch[1].trim(),
33
+ replace: replaceMatch[1].trim()
34
+ });
35
+ }
36
+ }
37
+
38
+ return changes;
39
+ }
40
+
41
+ /**
42
+ * Normalize whitespace for comparison (convert all whitespace to single spaces)
43
+ */
44
+ function normalizeWhitespace(str) {
45
+ return str.replace(/\s+/g, ' ').trim();
46
+ }
47
+
48
+ /**
49
+ * Extract key identifiers from code (variable names, function names, strings)
50
+ */
51
+ function extractIdentifiers(code) {
52
+ const identifiers = new Set();
53
+
54
+ // Extract quoted strings
55
+ const stringMatches = code.match(/'([^']+)'/g);
56
+ if (stringMatches) {
57
+ stringMatches.forEach(match => {
58
+ identifiers.add(match.slice(1, -1)); // Remove quotes
59
+ });
60
+ }
61
+
62
+ const doubleStringMatches = code.match(/"([^"]+)"/g);
63
+ if (doubleStringMatches) {
64
+ doubleStringMatches.forEach(match => {
65
+ identifiers.add(match.slice(1, -1)); // Remove quotes
66
+ });
67
+ }
68
+
69
+ // Extract variable/function names (words followed by : or =)
70
+ const nameMatches = code.match(/\b([a-zA-Z_$][a-zA-Z0-9_$]*)\s*[:=]/g);
71
+ if (nameMatches) {
72
+ nameMatches.forEach(match => {
73
+ const name = match.replace(/\s*[:=]/, '');
74
+ identifiers.add(name);
75
+ });
76
+ }
77
+
78
+ // Extract common patterns like 'type:', 'name:', 'value:'
79
+ const patternMatches = code.match(/(type|name|value|file|path|function|const|let|var)\s*:/gi);
80
+ if (patternMatches) {
81
+ patternMatches.forEach(match => {
82
+ identifiers.add(match.toLowerCase().replace(/\s*:/, ''));
83
+ });
84
+ }
85
+
86
+ return Array.from(identifiers);
87
+ }
88
+
89
+ /**
90
+ * Extract structural pattern from code (ignoring values)
91
+ */
92
+ function extractPattern(code) {
93
+ // Replace strings with placeholders
94
+ let pattern = code.replace(/'[^']+'|"[^"]+"/g, '"..."');
95
+ // Replace numbers with placeholders
96
+ pattern = pattern.replace(/\b\d+\.?\d*\b/g, '...');
97
+ // Replace whitespace with single spaces
98
+ pattern = normalizeWhitespace(pattern);
99
+ return pattern;
100
+ }
101
+
102
+ /**
103
+ * Apply a search/replace change to a file with fuzzy matching fallback
104
+ */
105
+ async function applyFileChange(change, repoPath) {
106
+ try {
107
+ const fullPath = path.join(repoPath, change.file);
108
+
109
+ if (!await fs.pathExists(fullPath)) {
110
+ console.log(chalk.yellow(`⚠️ File not found: ${change.file}`));
111
+ return { success: false, error: 'File not found' };
112
+ }
113
+
114
+ const content = await fs.readFile(fullPath, 'utf8');
115
+ const lines = content.split('\n');
116
+ const searchLines = change.search.split('\n');
117
+
118
+ console.log(chalk.gray(` - Search block: ${searchLines.length} lines`));
119
+ console.log(chalk.gray(` - File total: ${lines.length} lines`));
120
+
121
+ // Extract key identifiers from search text (function names, variable names, strings)
122
+ const searchIdentifiers = extractIdentifiers(change.search);
123
+
124
+ // Try multiple window sizes (±5 lines) to account for LLM not including enough context
125
+ const windowSizes = [0, 2, 5, 10, 15];
126
+
127
+ for (const windowSize of windowSizes) {
128
+ // Try to find the search block with fuzzy matching
129
+ let foundIndex = -1;
130
+
131
+ // First try exact match
132
+ const exactMatch = content.indexOf(change.search);
133
+ if (exactMatch !== -1) {
134
+ foundIndex = exactMatch;
135
+ } else {
136
+ // Try fuzzy matching by looking for patterns
137
+ const searchPattern = extractPattern(change.search);
138
+
139
+ for (let i = 0; i <= lines.length - searchLines.length; i++) {
140
+ const window = lines.slice(Math.max(0, i - windowSize),
141
+ Math.min(lines.length, i + searchLines.length + windowSize));
142
+ const windowContent = window.join('\n');
143
+ const windowPattern = extractPattern(windowContent);
144
+
145
+ // Check if patterns match and key identifiers are present
146
+ if (windowPattern.includes(searchPattern) || searchPattern.includes(windowPattern)) {
147
+ const windowIdentifiers = extractIdentifiers(windowContent);
148
+ const commonIdentifiers = searchIdentifiers.filter(id => windowIdentifiers.includes(id));
149
+
150
+ // If we have enough common identifiers, consider it a match
151
+ if (commonIdentifiers.length >= Math.min(3, searchIdentifiers.length)) {
152
+ // Find the actual search block within this window
153
+ const windowStart = Math.max(0, i - windowSize);
154
+ const windowText = lines.slice(windowStart,
155
+ Math.min(lines.length, i + searchLines.length + windowSize)).join('\n');
156
+
157
+ // Try to find the best match within the window
158
+ const bestMatch = findBestMatch(change.search, windowText);
159
+ if (bestMatch.similarity > 0.7) { // 70% similarity threshold
160
+ foundIndex = windowStart + bestMatch.offset;
161
+ break;
162
+ }
163
+ }
164
+ }
165
+ }
166
+ }
167
+
168
+ if (foundIndex !== -1) {
169
+ // Calculate line boundaries
170
+ const beforeContent = content.substring(0, foundIndex);
171
+ const afterContent = content.substring(foundIndex);
172
+ const afterLines = afterContent.split('\n');
173
+
174
+ // Find the end of the block to replace
175
+ let endLineIndex = 0;
176
+ let currentLineContent = '';
177
+
178
+ for (let i = 0; i < afterLines.length; i++) {
179
+ currentLineContent += (i > 0 ? '\n' : '') + afterLines[i];
180
+
181
+ // Check if we've found a block that matches our search
182
+ const testContent = afterLines.slice(0, i + 1).join('\n');
183
+ const similarity = calculateSimilarity(change.search, testContent);
184
+
185
+ if (similarity > 0.8 || i >= searchLines.length + 5) { // Allow some extra lines
186
+ endLineIndex = i + 1;
187
+ break;
188
+ }
189
+ }
190
+
191
+ // Replace the content
192
+ const newContent = beforeContent + change.replace +
193
+ afterLines.slice(endLineIndex).join('\n');
194
+
195
+ await fs.writeFile(fullPath, newContent);
196
+
197
+ console.log(chalk.green(` ✅ Applied change to ${change.file}`));
198
+ return { success: true };
199
+ }
200
+ }
201
+
202
+ console.log(chalk.yellow(` ⚠️ Could not find matching block in ${change.file}`));
203
+ return { success: false, error: 'Could not find matching block' };
204
+
205
+ } catch (error) {
206
+ console.error(chalk.red(` ❌ Error applying change to ${change.file}:`), error.message);
207
+ return { success: false, error: error.message };
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Find best match within a text block
213
+ */
214
+ function findBestMatch(searchText, textBlock) {
215
+ const searchLines = searchText.split('\n');
216
+ const textLines = textBlock.split('\n');
217
+
218
+ let bestMatch = { similarity: 0, offset: 0 };
219
+
220
+ for (let offset = 0; offset <= textLines.length - searchLines.length; offset++) {
221
+ const testBlock = textLines.slice(offset, offset + searchLines.length).join('\n');
222
+ const similarity = calculateSimilarity(searchText, testBlock);
223
+
224
+ if (similarity > bestMatch.similarity) {
225
+ bestMatch = { similarity, offset };
226
+ }
227
+ }
228
+
229
+ return bestMatch;
230
+ }
231
+
232
+ /**
233
+ * Calculate similarity between two strings
234
+ */
235
+ function calculateSimilarity(str1, str2) {
236
+ const longer = str1.length > str2.length ? str1 : str2;
237
+ const shorter = str1.length > str2.length ? str2 : str1;
238
+
239
+ if (longer.length === 0) return 1.0;
240
+
241
+ const editDistance = levenshteinDistance(longer, shorter);
242
+ return (longer.length - editDistance) / longer.length;
243
+ }
244
+
245
+ /**
246
+ * Calculate Levenshtein distance between two strings
247
+ */
248
+ function levenshteinDistance(str1, str2) {
249
+ const matrix = [];
250
+
251
+ for (let i = 0; i <= str2.length; i++) {
252
+ matrix[i] = [i];
253
+ }
254
+
255
+ for (let j = 0; j <= str1.length; j++) {
256
+ matrix[0][j] = j;
257
+ }
258
+
259
+ for (let i = 1; i <= str2.length; i++) {
260
+ for (let j = 1; j <= str1.length; j++) {
261
+ if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
262
+ matrix[i][j] = matrix[i - 1][j - 1];
263
+ } else {
264
+ matrix[i][j] = Math.min(
265
+ matrix[i - 1][j - 1] + 1,
266
+ matrix[i][j - 1] + 1,
267
+ matrix[i - 1][j] + 1
268
+ );
269
+ }
270
+ }
271
+ }
272
+
273
+ return matrix[str2.length][str1.length];
274
+ }
275
+
276
+ module.exports = {
277
+ parseSearchReplaceBlocks,
278
+ normalizeWhitespace,
279
+ extractIdentifiers,
280
+ extractPattern,
281
+ applyFileChange
282
+ };