minovative-mind-cli 2.5.2 → 2.6.1

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 (41) hide show
  1. package/README.md +29 -26
  2. package/dist/commands/chat.js +2 -2
  3. package/dist/services/agent/inputHandler.d.ts +9 -0
  4. package/dist/services/agent/inputHandler.js +34 -0
  5. package/dist/services/agent/slashCommands.js +105 -29
  6. package/dist/services/agent/syntaxAgent.d.ts +40 -0
  7. package/dist/services/agent/syntaxAgent.js +237 -23
  8. package/dist/services/agent/toolLoop.js +10 -1
  9. package/dist/services/agent/types.d.ts +1 -0
  10. package/dist/services/agent-tools.d.ts +156 -1
  11. package/dist/services/agent-tools.js +259 -67
  12. package/dist/services/agent.d.ts +74 -0
  13. package/dist/services/agent.js +193 -31
  14. package/dist/services/ai.d.ts +5 -0
  15. package/dist/services/ai.js +80 -87
  16. package/dist/services/chatHistoryService.d.ts +11 -0
  17. package/dist/services/chatHistoryService.js +20 -1
  18. package/dist/services/contextAgent.d.ts +1 -1
  19. package/dist/services/contextAgent.js +9 -29
  20. package/dist/services/orchestration/investigationAgent.d.ts +2 -1
  21. package/dist/services/orchestration/investigationAgent.js +7 -2
  22. package/dist/services/orchestration/investigationOrchestrator.d.ts +1 -1
  23. package/dist/services/orchestration/investigationOrchestrator.js +13 -2
  24. package/dist/services/orchestration/orchestrator.js +21 -4
  25. package/dist/services/orchestration/subAgent.d.ts +2 -1
  26. package/dist/services/orchestration/subAgent.js +12 -6
  27. package/dist/utils/analysisRunner.d.ts +27 -4
  28. package/dist/utils/analysisRunner.js +100 -20
  29. package/dist/utils/config.d.ts +2 -0
  30. package/dist/utils/config.js +2 -0
  31. package/dist/utils/fuzzyMatch.d.ts +32 -0
  32. package/dist/utils/fuzzyMatch.js +215 -27
  33. package/dist/utils/localSyntaxValidator.d.ts +2 -2
  34. package/dist/utils/localSyntaxValidator.js +280 -81
  35. package/dist/utils/performanceAuditor.d.ts +2 -7
  36. package/dist/utils/performanceAuditor.js +541 -89
  37. package/dist/utils/projectStorage.js +9 -0
  38. package/dist/utils/systemPrompts.d.ts +3 -2
  39. package/dist/utils/systemPrompts.js +29 -5
  40. package/oclif.manifest.json +2 -2
  41. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import { distance } from 'fastest-levenshtein';
2
+ import { localValidate } from './localSyntaxValidator.js';
2
3
  /**
3
4
  * Trims each line, collapses multiple spaces/tabs to single space,
4
5
  * and normalizes line endings.
@@ -24,32 +25,50 @@ export function levenshteinSimilarity(a, b) {
24
25
  /**
25
26
  * Finds the best match for `searchContent` inside `fileContent`.
26
27
  * Uses a pipeline of strategies: Exact -> Whitespace-normalized -> Levenshtein.
28
+ * Enforces uniqueness and higher confidence thresholds for non-unique search snippets across repetitive files.
27
29
  */
28
30
  export function findBestMatch(fileContent, searchContent) {
29
- if (!fileContent || !searchContent)
31
+ if (fileContent === undefined || searchContent === undefined)
30
32
  return null;
31
- // Strategy 1: Exact Match
32
- const exactIndex = fileContent.indexOf(searchContent);
33
- if (exactIndex !== -1) {
33
+ // Special case: empty search content
34
+ if (searchContent === '') {
34
35
  return {
35
- start: exactIndex,
36
- end: exactIndex + searchContent.length,
36
+ start: 0,
37
+ end: 0,
37
38
  strategy: 'Exact Match',
38
39
  };
39
40
  }
40
- // Strategy 2: Whitespace-normalized Match
41
+ if (!fileContent)
42
+ return null;
43
+ // Strategy 1: Exact Match (with Uniqueness check)
44
+ let firstExactIndex = -1;
45
+ let exactMatchCount = 0;
46
+ let pos = fileContent.indexOf(searchContent);
47
+ while (pos !== -1) {
48
+ exactMatchCount++;
49
+ if (firstExactIndex === -1)
50
+ firstExactIndex = pos;
51
+ pos = fileContent.indexOf(searchContent, pos + 1);
52
+ }
53
+ if (exactMatchCount === 1) {
54
+ return {
55
+ start: firstExactIndex,
56
+ end: firstExactIndex + searchContent.length,
57
+ strategy: 'Exact Match',
58
+ };
59
+ }
60
+ else if (exactMatchCount > 1) {
61
+ // Non-unique exact match across repetitive file content!
62
+ return null;
63
+ }
64
+ // Strategy 2: Whitespace-normalized Match (with Uniqueness check)
41
65
  const normFileLines = fileContent.split(/\r?\n/);
42
- const normSearchContent = normalizeWhitespace(searchContent);
43
- // Fast check: does the normalized file content contain the normalized search content at all?
44
- // Since newlines are preserved by normalizeWhitespace (it only trims lines), we can do this:
45
- const normFileContent = normalizeWhitespace(fileContent);
46
- // To map back to original lines, we try matching line by line.
47
66
  const searchLines = searchContent.split(/\r?\n/);
48
67
  const numSearchLines = searchLines.length;
68
+ const normalizedMatches = [];
49
69
  if (numSearchLines > 0 && numSearchLines <= normFileLines.length) {
50
70
  for (let i = 0; i <= normFileLines.length - numSearchLines; i++) {
51
71
  let isMatch = true;
52
- let charMatchCount = 0;
53
72
  for (let j = 0; j < numSearchLines; j++) {
54
73
  const fileLineNorm = normalizeWhitespace(normFileLines[i + j]);
55
74
  const searchLineNorm = normalizeWhitespace(searchLines[j]);
@@ -64,45 +83,100 @@ export function findBestMatch(fileContent, searchContent) {
64
83
  const endLineOffset = getByteOffsetOfLine(fileContent, i + numSearchLines - 1);
65
84
  const originalEndLine = normFileLines[i + numSearchLines - 1];
66
85
  const end = endLineOffset + originalEndLine.length;
67
- return {
86
+ normalizedMatches.push({
68
87
  start,
69
88
  end,
70
89
  strategy: 'Whitespace-Normalized Match',
71
- };
90
+ });
72
91
  }
73
92
  }
74
93
  }
75
- // Strategy 3: Levenshtein Similarity
76
- // We use a sliding window of size `numSearchLines`, `+ 1`, and `- 1` to handle line hallucinations.
77
- const THRESHOLD = 0.8;
78
- let bestSim = 0;
79
- let bestMatch = null;
94
+ if (normalizedMatches.length === 1) {
95
+ return normalizedMatches[0];
96
+ }
97
+ else if (normalizedMatches.length > 1) {
98
+ // Non-unique whitespace-normalized match across repetitive code blocks!
99
+ return null;
100
+ }
101
+ // Strategy 3: Levenshtein Similarity (with Confidence Threshold & Region Uniqueness)
102
+ // We use higher confidence thresholds and check for competing candidate regions in repetitive files.
103
+ const trimmedSearchLen = searchContent.trim().length;
104
+ // Require higher threshold (0.92) for short search content or single-line snippets, and 0.88 for longer content.
105
+ const confidenceThreshold = (numSearchLines < 3 || trimmedSearchLen < 40) ? 0.92 : 0.88;
106
+ const rawCandidates = [];
80
107
  if (numSearchLines > 0) {
81
108
  for (let i = 0; i < normFileLines.length; i++) {
82
109
  for (const windowSize of [numSearchLines, numSearchLines + 1, numSearchLines - 1]) {
83
110
  if (windowSize <= 0 || i + windowSize > normFileLines.length)
84
111
  continue;
85
112
  const windowLines = normFileLines.slice(i, i + windowSize);
86
- // Normalize both strings before comparing to ignore indentation/whitespace differences
87
113
  const windowStr = normalizeWhitespace(windowLines.join('\n'));
88
114
  const targetStr = normalizeWhitespace(searchLines.join('\n'));
89
115
  const sim = levenshteinSimilarity(windowStr, targetStr);
90
- if (sim >= THRESHOLD && sim > bestSim) {
91
- bestSim = sim;
116
+ if (sim >= confidenceThreshold) {
92
117
  const start = getByteOffsetOfLine(fileContent, i);
93
118
  const endLineOffset = getByteOffsetOfLine(fileContent, i + windowSize - 1);
94
119
  const originalEndLine = normFileLines[i + windowSize - 1];
95
120
  const end = endLineOffset + originalEndLine.length;
96
- bestMatch = {
121
+ rawCandidates.push({
122
+ lineIndex: i,
123
+ windowSize,
124
+ sim,
97
125
  start,
98
126
  end,
99
- strategy: `Levenshtein Match (${(sim * 100).toFixed(1)}%) [Window: ${windowSize}]`,
100
- };
127
+ });
101
128
  }
102
129
  }
103
130
  }
104
131
  }
105
- return bestMatch;
132
+ if (rawCandidates.length === 0)
133
+ return null;
134
+ // Group raw candidate windows into distinct file regions
135
+ // Candidates within `minRegionGap` lines of each other belong to the same region.
136
+ const minRegionGap = Math.max(2, Math.floor(numSearchLines / 2));
137
+ // Sort candidates by lineIndex
138
+ rawCandidates.sort((a, b) => a.lineIndex - b.lineIndex);
139
+ const distinctRegions = [];
140
+ for (const cand of rawCandidates) {
141
+ if (distinctRegions.length === 0) {
142
+ distinctRegions.push(cand);
143
+ }
144
+ else {
145
+ const lastRegion = distinctRegions[distinctRegions.length - 1];
146
+ if (Math.abs(cand.lineIndex - lastRegion.lineIndex) <= minRegionGap) {
147
+ // Same region: keep the candidate with higher similarity score
148
+ if (cand.sim > lastRegion.sim) {
149
+ distinctRegions[distinctRegions.length - 1] = cand;
150
+ }
151
+ }
152
+ else {
153
+ distinctRegions.push(cand);
154
+ }
155
+ }
156
+ }
157
+ // Sort distinct regions by similarity score descending
158
+ distinctRegions.sort((a, b) => b.sim - a.sim);
159
+ if (distinctRegions.length === 1) {
160
+ const best = distinctRegions[0];
161
+ return {
162
+ start: best.start,
163
+ end: best.end,
164
+ strategy: `Levenshtein Match (${(best.sim * 100).toFixed(1)}%) [Window: ${best.windowSize}]`,
165
+ };
166
+ }
167
+ // Multiple distinct regions matched with high similarity across repetitive files.
168
+ // Check if top match significantly outperforms second best match.
169
+ const topCandidate = distinctRegions[0];
170
+ const secondCandidate = distinctRegions[1];
171
+ if (topCandidate.sim - secondCandidate.sim >= 0.08 && secondCandidate.sim < 0.85) {
172
+ return {
173
+ start: topCandidate.start,
174
+ end: topCandidate.end,
175
+ strategy: `Levenshtein Match (${(topCandidate.sim * 100).toFixed(1)}%) [Window: ${topCandidate.windowSize}]`,
176
+ };
177
+ }
178
+ // Search snippet is non-unique / ambiguous across repetitive code regions in the file.
179
+ return null;
106
180
  }
107
181
  export function applyMatch(fileContent, match, replaceContent) {
108
182
  return fileContent.slice(0, match.start) + replaceContent + fileContent.slice(match.end);
@@ -119,3 +193,117 @@ function getByteOffsetOfLine(text, lineIndex) {
119
193
  }
120
194
  return offset;
121
195
  }
196
+ /**
197
+ * Parses differential patch block string into structured PatchBlock objects.
198
+ * Supports blocks formatted as:
199
+ * <<<<< SEARCH
200
+ * search content
201
+ * =====
202
+ * replace content
203
+ * >>>>> REPLACE
204
+ */
205
+ export function parsePatchBlocks(patchText) {
206
+ if (!patchText || typeof patchText !== 'string')
207
+ return [];
208
+ const blocks = [];
209
+ const lines = patchText.split(/\r?\n/);
210
+ let state = 'IDLE';
211
+ let currentSearchLines = [];
212
+ let currentReplaceLines = [];
213
+ const searchMarkerRegex = /^<{5,}\s*SEARCH\b/i;
214
+ const dividerMarkerRegex = /^={5,}\s*$/;
215
+ const replaceMarkerRegex = /^>{5,}\s*REPLACE\b/i;
216
+ for (let i = 0; i < lines.length; i++) {
217
+ const line = lines[i];
218
+ if (state === 'IDLE') {
219
+ if (searchMarkerRegex.test(line)) {
220
+ state = 'SEARCH';
221
+ currentSearchLines = [];
222
+ currentReplaceLines = [];
223
+ }
224
+ }
225
+ else if (state === 'SEARCH') {
226
+ if (dividerMarkerRegex.test(line)) {
227
+ state = 'REPLACE';
228
+ }
229
+ else if (searchMarkerRegex.test(line)) {
230
+ currentSearchLines = [];
231
+ }
232
+ else {
233
+ currentSearchLines.push(line);
234
+ }
235
+ }
236
+ else if (state === 'REPLACE') {
237
+ if (replaceMarkerRegex.test(line)) {
238
+ blocks.push({
239
+ search: currentSearchLines.join('\n'),
240
+ replace: currentReplaceLines.join('\n'),
241
+ });
242
+ state = 'IDLE';
243
+ currentSearchLines = [];
244
+ currentReplaceLines = [];
245
+ }
246
+ else if (searchMarkerRegex.test(line)) {
247
+ state = 'SEARCH';
248
+ currentSearchLines = [];
249
+ currentReplaceLines = [];
250
+ }
251
+ else {
252
+ currentReplaceLines.push(line);
253
+ }
254
+ }
255
+ }
256
+ return blocks;
257
+ }
258
+ /**
259
+ * Applies a sequence of search/replace patch blocks to file content
260
+ * using fuzzy matching, and validates local syntax on the result.
261
+ */
262
+ export function applyPatch(fileContent, patchTextOrBlocks, filePath, options) {
263
+ const blocks = typeof patchTextOrBlocks === 'string'
264
+ ? parsePatchBlocks(patchTextOrBlocks)
265
+ : patchTextOrBlocks;
266
+ if (!blocks || blocks.length === 0) {
267
+ return {
268
+ success: false,
269
+ content: fileContent,
270
+ appliedBlocks: 0,
271
+ failedBlocks: 0,
272
+ errors: ['No patch blocks found in input'],
273
+ };
274
+ }
275
+ let currentContent = fileContent;
276
+ let appliedBlocks = 0;
277
+ let failedBlocks = 0;
278
+ const errors = [];
279
+ for (let i = 0; i < blocks.length; i++) {
280
+ const block = blocks[i];
281
+ const match = findBestMatch(currentContent, block.search);
282
+ if (match) {
283
+ currentContent = applyMatch(currentContent, match, block.replace);
284
+ appliedBlocks++;
285
+ }
286
+ else {
287
+ failedBlocks++;
288
+ const preview = block.search.length > 80 ? block.search.slice(0, 80) + '...' : block.search;
289
+ errors.push(`Failed to locate match for search block ${i + 1}: "${preview}"`);
290
+ }
291
+ }
292
+ let syntaxValidation;
293
+ const shouldValidate = options?.validateSyntax !== false;
294
+ if (shouldValidate && filePath) {
295
+ syntaxValidation = localValidate(filePath, currentContent);
296
+ if (!syntaxValidation.isValid) {
297
+ errors.push(`Syntax validation failed: ${syntaxValidation.error}`);
298
+ }
299
+ }
300
+ const success = failedBlocks === 0 && appliedBlocks > 0 && (!syntaxValidation || syntaxValidation.isValid);
301
+ return {
302
+ success,
303
+ content: currentContent,
304
+ appliedBlocks,
305
+ failedBlocks,
306
+ errors,
307
+ syntaxValidation,
308
+ };
309
+ }
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Performs fast, local syntax validation using regex-based checks.
3
- * This is intended as a first-pass filter before calling more expensive AI-based validation.
2
+ * Performs fast, local syntax validation using bracket-matching state machine and native parsers.
3
+ * Acts as a first-pass filter before AI-based validation.
4
4
  */
5
5
  export interface ValidationResult {
6
6
  isValid: boolean;