@gitsense/gsc-utils 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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/dist/gitsense-chat-utils.cjs.js +10977 -0
  3. package/dist/gitsense-chat-utils.esm.js +10975 -0
  4. package/dist/gsc-utils.cjs.js +11043 -0
  5. package/dist/gsc-utils.esm.js +11041 -0
  6. package/package.json +37 -0
  7. package/src/AnalysisBlockUtils.js +151 -0
  8. package/src/ChatUtils.js +126 -0
  9. package/src/CodeBlockUtils/blockExtractor.js +277 -0
  10. package/src/CodeBlockUtils/blockProcessor.js +559 -0
  11. package/src/CodeBlockUtils/blockProcessor.js.rej +8 -0
  12. package/src/CodeBlockUtils/constants.js +62 -0
  13. package/src/CodeBlockUtils/continuationUtils.js +191 -0
  14. package/src/CodeBlockUtils/headerParser.js +175 -0
  15. package/src/CodeBlockUtils/headerUtils.js +236 -0
  16. package/src/CodeBlockUtils/index.js +83 -0
  17. package/src/CodeBlockUtils/lineNumberFormatter.js +117 -0
  18. package/src/CodeBlockUtils/markerRemover.js +89 -0
  19. package/src/CodeBlockUtils/patchIntegration.js +38 -0
  20. package/src/CodeBlockUtils/relationshipUtils.js +159 -0
  21. package/src/CodeBlockUtils/updateCodeBlock.js +372 -0
  22. package/src/CodeBlockUtils/uuidUtils.js +48 -0
  23. package/src/ContextUtils.js +180 -0
  24. package/src/GSToolBlockUtils.js +108 -0
  25. package/src/GitSenseChatUtils.js +386 -0
  26. package/src/JsonUtils.js +101 -0
  27. package/src/LLMUtils.js +31 -0
  28. package/src/MessageUtils.js +460 -0
  29. package/src/PatchUtils/constants.js +72 -0
  30. package/src/PatchUtils/diagnosticReporter.js +213 -0
  31. package/src/PatchUtils/enhancedPatchProcessor.js +390 -0
  32. package/src/PatchUtils/fuzzyMatcher.js +252 -0
  33. package/src/PatchUtils/hunkCorrector.js +204 -0
  34. package/src/PatchUtils/hunkValidator.js +305 -0
  35. package/src/PatchUtils/index.js +135 -0
  36. package/src/PatchUtils/patchExtractor.js +175 -0
  37. package/src/PatchUtils/patchHeaderFormatter.js +143 -0
  38. package/src/PatchUtils/patchParser.js +289 -0
  39. package/src/PatchUtils/patchProcessor.js +389 -0
  40. package/src/PatchUtils/patchVerifier/constants.js +23 -0
  41. package/src/PatchUtils/patchVerifier/detectAndFixOverlappingHunks.js +281 -0
  42. package/src/PatchUtils/patchVerifier/detectAndFixRedundantChanges.js +404 -0
  43. package/src/PatchUtils/patchVerifier/formatAndAddLineNumbers.js +165 -0
  44. package/src/PatchUtils/patchVerifier/index.js +25 -0
  45. package/src/PatchUtils/patchVerifier/verifyAndCorrectHunkHeaders.js +202 -0
  46. package/src/PatchUtils/patchVerifier/verifyAndCorrectLineNumbers.js +254 -0
  47. package/src/SharedUtils/timestampUtils.js +41 -0
  48. package/src/SharedUtils/versionUtils.js +58 -0
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Component: PatchUtils Fuzzy Matcher
3
+ * Block-UUID: 12e76392-f0ac-4c9d-a5be-c98401487352
4
+ * Parent-UUID: 7679fc85-9213-4637-8d41-9bb5c551c62d
5
+ * Version: 1.1.0
6
+ * Description: Implements fuzzy matching algorithms to find the best location for patch hunks.
7
+ * Language: JavaScript
8
+ * Created-at: 2025-05-14T18:09:37.976Z
9
+ * Authors: Claude 3.7 Sonnet (v1.0.0), Gemini 2.5 Flash (v1.1.0)
10
+ */
11
+
12
+
13
+ /**
14
+ * Calculates the longest common subsequence length between two strings
15
+ * @param {string} a - First string
16
+ * @param {string} b - Second string
17
+ * @returns {number} Length of the longest common subsequence
18
+ * @private
19
+ */
20
+ function _lcsLength(a, b) {
21
+ const m = a.length;
22
+ const n = b.length;
23
+ const dp = Array(m + 1).fill().map(() => Array(n + 1).fill(0));
24
+
25
+ for (let i = 1; i <= m; i++) {
26
+ for (let j = 1; j <= n; j++) {
27
+ if (a[i - 1] === b[j - 1]) {
28
+ dp[i][j] = dp[i - 1][j - 1] + 1;
29
+ } else {
30
+ dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
31
+ }
32
+ }
33
+ }
34
+
35
+ return dp[m][n];
36
+ }
37
+
38
+ /**
39
+ * Calculates similarity score between two strings using LCS
40
+ * @param {string} a - First string
41
+ * @param {string} b - Second string
42
+ * @returns {number} Similarity score between 0 and 1
43
+ * @private
44
+ */
45
+ function _calculateSimilarity(a, b) {
46
+ if (!a.length || !b.length) return 0;
47
+ const lcs = _lcsLength(a, b);
48
+ return lcs / Math.max(a.length, b.length);
49
+ }
50
+
51
+ /**
52
+ * Calculates similarity score between two blocks of text
53
+ * @param {string[]} blockA - First block of text (array of lines)
54
+ * @param {string[]} blockB - Second block of text (array of lines)
55
+ * @returns {number} Similarity score between 0 and 1
56
+ * @private
57
+ */
58
+ function _calculateBlockSimilarity(blockA, blockB) {
59
+ if (blockA.length !== blockB.length) {
60
+ // If blocks have different number of lines, calculate average similarity
61
+ let totalSimilarity = 0;
62
+ const minLength = Math.min(blockA.length, blockB.length);
63
+
64
+ // Compare available lines
65
+ for (let i = 0; i < minLength; i++) {
66
+ totalSimilarity += _calculateSimilarity(blockA[i], blockB[i]);
67
+ }
68
+
69
+ // Penalize for length difference
70
+ const lengthPenalty = minLength / Math.max(blockA.length, blockB.length);
71
+ return (totalSimilarity / blockA.length) * lengthPenalty;
72
+ }
73
+
74
+ // If blocks have same number of lines, calculate average line similarity
75
+ let totalSimilarity = 0;
76
+ for (let i = 0; i < blockA.length; i++) {
77
+ totalSimilarity += _calculateSimilarity(blockA[i], blockB[i]);
78
+ }
79
+
80
+ return totalSimilarity / blockA.length;
81
+ }
82
+
83
+ /**
84
+ * Finds the best match for context lines within source code
85
+ * @param {string[]} contextLinesForMatching - Array of context lines (including removed lines)
86
+ * @param {string} sourceCode - Full source code to search within
87
+ * @param {object} options - Optional parameters
88
+ * @param {number} options.threshold - Minimum similarity threshold (default: 0.7)
89
+ * @param {number} options.maxMatches - Maximum number of matches to return (default: 3)
90
+ * @returns {object} Best match information with position and confidence
91
+ */
92
+ function findBestContextMatch(contextLinesForMatching, sourceCode, options = {}) {
93
+ const { threshold = 0.7, maxMatches = 3 } = options;
94
+
95
+ // If no context lines, return no match
96
+ if (!contextLinesForMatching.length) {
97
+ return {
98
+ found: false,
99
+ reason: "No context lines provided for matching",
100
+ matches: []
101
+ };
102
+ }
103
+
104
+ const sourceLines = sourceCode.split('\n');
105
+ const matches = [];
106
+
107
+ // For each possible starting position in the source
108
+ for (let i = 0; i <= sourceLines.length - contextLinesForMatching.length; i++) {
109
+ const sourceBlock = sourceLines.slice(i, i + contextLinesForMatching.length);
110
+ const similarity = _calculateBlockSimilarity(
111
+ contextLinesForMatching.map(line => line.trim()),
112
+ sourceBlock.map(line => line.trim())
113
+ );
114
+
115
+ if (similarity >= threshold) {
116
+ matches.push({
117
+ position: i + 1, // Convert to 1-based line numbers
118
+ confidence: similarity,
119
+ sourceBlock
120
+ });
121
+ }
122
+ }
123
+
124
+ // Sort matches by confidence (descending)
125
+ matches.sort((a, b) => b.confidence - a.confidence);
126
+
127
+ // Limit number of matches
128
+ const topMatches = matches.slice(0, maxMatches);
129
+
130
+ if (topMatches.length === 0) {
131
+ return {
132
+ found: false,
133
+ reason: "No matches found above threshold",
134
+ threshold,
135
+ matches: []
136
+ };
137
+ }
138
+
139
+ return {
140
+ found: true,
141
+ bestMatch: topMatches[0],
142
+ matches: topMatches
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Finds exact matches for a single context line
148
+ * @param {string} contextLine - A single context line to find
149
+ * @param {string} sourceCode - Full source code to search within
150
+ * @returns {number[]} Array of line numbers (1-based) where the context line appears
151
+ */
152
+ function findExactLineMatches(contextLine, sourceCode) {
153
+ const sourceLines = sourceCode.split('\n');
154
+ const matches = [];
155
+
156
+ const trimmedContext = contextLine.trim();
157
+
158
+ for (let i = 0; i < sourceLines.length; i++) {
159
+ if (sourceLines[i].trim() === trimmedContext) {
160
+ matches.push(i + 1); // Convert to 1-based line numbers
161
+ }
162
+ }
163
+
164
+ return matches;
165
+ }
166
+
167
+ /**
168
+ * Finds the best match using a sliding window approach
169
+ * @param {string[]} contextLinesForMatching - Array of context lines (including removed lines)
170
+ * @param {string} sourceCode - Full source code
171
+ * @param {number} windowSize - Size of the sliding window
172
+ * @returns {object} Best match information
173
+ */
174
+ function findBestMatchWithSlidingWindow(contextLinesForMatching, sourceCode, windowSize = 3) {
175
+ if (contextLinesForMatching.length < windowSize) {
176
+ // If we have fewer context lines than the window size, use regular matching
177
+ return findBestContextMatch(contextLinesForMatching, sourceCode);
178
+ }
179
+
180
+ const sourceLines = sourceCode.split('\n');
181
+ const numWindows = contextLinesForMatching.length - windowSize + 1;
182
+ const candidatePositions = [];
183
+
184
+ // For each window in the context lines
185
+ for (let windowStart = 0; windowStart < numWindows; windowStart++) {
186
+ const windowLines = contextLinesForMatching.slice(windowStart, windowStart + windowSize);
187
+
188
+ // Find matches for this window
189
+ const windowMatches = findBestContextMatch(windowLines, sourceCode, { threshold: 0.9 });
190
+
191
+ if (windowMatches.found) {
192
+ // For each match of this window
193
+ for (const match of windowMatches.matches) {
194
+ // Calculate the position where the full context would start
195
+ const fullContextStart = match.position - windowStart;
196
+
197
+ // Only consider valid positions
198
+ if (fullContextStart >= 1 && fullContextStart + contextLinesForMatching.length <= sourceLines.length + 1) {
199
+ candidatePositions.push({
200
+ position: fullContextStart,
201
+ windowConfidence: match.confidence,
202
+ windowStart
203
+ });
204
+ }
205
+ }
206
+ }
207
+ }
208
+
209
+ // If no candidate positions found
210
+ if (candidatePositions.length === 0) {
211
+ return {
212
+ found: false,
213
+ reason: "No matching windows found",
214
+ matches: []
215
+ };
216
+ }
217
+
218
+ // Evaluate each candidate position with the full context
219
+ const evaluatedPositions = candidatePositions.map(candidate => {
220
+ const sourceBlock = sourceLines.slice(
221
+ candidate.position - 1,
222
+ candidate.position - 1 + contextLinesForMatching.length
223
+ );
224
+
225
+ const fullSimilarity = _calculateBlockSimilarity(
226
+ contextLinesForMatching.map(line => line.trim()),
227
+ sourceBlock.map(line => line.trim())
228
+ );
229
+
230
+ return {
231
+ position: candidate.position,
232
+ confidence: fullSimilarity,
233
+ sourceBlock,
234
+ windowConfidence: candidate.windowConfidence
235
+ };
236
+ });
237
+
238
+ // Sort by confidence
239
+ evaluatedPositions.sort((a, b) => b.confidence - a.confidence);
240
+
241
+ return {
242
+ found: evaluatedPositions.length > 0,
243
+ bestMatch: evaluatedPositions[0] || null,
244
+ matches: evaluatedPositions.slice(0, 3)
245
+ };
246
+ }
247
+
248
+ module.exports = {
249
+ findBestContextMatch,
250
+ findExactLineMatches,
251
+ findBestMatchWithSlidingWindow
252
+ };
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Component: PatchUtils Hunk Corrector
3
+ * Block-UUID: f0c9d9fe-facc-4d04-a246-d24aab8de3d8
4
+ * Parent-UUID: N/A
5
+ * Version: 1.0.0
6
+ * Description: Generates corrected hunks based on validation results and fuzzy matching.
7
+ * Language: JavaScript
8
+ * Created-at: 2025-05-14T16:45:00.000Z
9
+ * Authors: Claude 3.7 Sonnet (v1.0.0)
10
+ */
11
+
12
+
13
+ /**
14
+ * Recalculates a hunk header based on the matched position and content
15
+ * @param {number} matchPosition - The 1-based line number where the hunk should start
16
+ * @param {string[]} contextLines - Context lines in the hunk
17
+ * @param {string[]} addedLines - Lines being added
18
+ * @param {string[]} removedLines - Lines being removed
19
+ * @returns {string} Corrected hunk header
20
+ */
21
+ function recalculateHunkHeader(matchPosition, contextLines, addedLines, removedLines) {
22
+ // Calculate old count (context + removed)
23
+ const oldCount = contextLines.length + removedLines.length;
24
+
25
+ // Calculate new count (context + added)
26
+ const newCount = contextLines.length + addedLines.length;
27
+
28
+ // Format the header parts
29
+ const oldPart = oldCount === 1 ? `${matchPosition}` : `${matchPosition},${oldCount}`;
30
+ const newPart = newCount === 1 ? `${matchPosition}` : `${matchPosition},${newCount}`;
31
+
32
+ return `@@ -${oldPart} +${newPart} @@`;
33
+ }
34
+
35
+ /**
36
+ * Reconstructs a hunk with the corrected header
37
+ * @param {string} header - The corrected hunk header
38
+ * @param {string[]} contextLines - Context lines in the hunk
39
+ * @param {string[]} addedLines - Lines being added
40
+ * @param {string[]} removedLines - Lines being removed
41
+ * @returns {string} Reconstructed hunk
42
+ */
43
+ function reconstructHunk(header, contextLines, addedLines, removedLines) {
44
+ const lines = [header];
45
+
46
+ // A simple approach would be to add all context, then removals, then additions
47
+ // But that's not how patches typically look. Instead, we need to interleave them.
48
+
49
+ // This is a simplified approach - a real implementation would need to preserve
50
+ // the original structure of context/added/removed lines
51
+
52
+ // Add context and removed lines first (interleaved)
53
+ let contextIndex = 0;
54
+ let removedIndex = 0;
55
+
56
+ while (contextIndex < contextLines.length || removedIndex < removedLines.length) {
57
+ // Add a context line
58
+ if (contextIndex < contextLines.length) {
59
+ lines.push(` ${contextLines[contextIndex]}`);
60
+ contextIndex++;
61
+ }
62
+
63
+ // Add a removed line
64
+ if (removedIndex < removedLines.length) {
65
+ lines.push(`-${removedLines[removedIndex]}`);
66
+ removedIndex++;
67
+ }
68
+ }
69
+
70
+ // Add all addition lines at the end
71
+ for (const line of addedLines) {
72
+ lines.push(`+${line}`);
73
+ }
74
+
75
+ return lines.join('\n');
76
+ }
77
+
78
+ /**
79
+ * Generates a corrected hunk based on fuzzy matching results
80
+ * @param {object} originalHunk - The original parsed hunk
81
+ * @param {object} matchResult - Result from fuzzy matching
82
+ * @returns {object} Corrected hunk information
83
+ */
84
+ function generateCorrectedHunk(originalHunk, matchResult) {
85
+ if (!matchResult.found || !matchResult.bestMatch) {
86
+ return {
87
+ success: false,
88
+ reason: matchResult.reason || "No suitable match found",
89
+ originalHunk
90
+ };
91
+ }
92
+
93
+ const { contextLines, addedLines, removedLines } = originalHunk;
94
+ const matchPosition = matchResult.bestMatch.position;
95
+
96
+ // Recalculate the header
97
+ const correctedHeader = recalculateHunkHeader(
98
+ matchPosition,
99
+ contextLines,
100
+ addedLines,
101
+ removedLines
102
+ );
103
+
104
+ // Reconstruct the hunk
105
+ const correctedHunkText = reconstructHunk(
106
+ correctedHeader,
107
+ contextLines,
108
+ addedLines,
109
+ removedLines
110
+ );
111
+
112
+ return {
113
+ success: true,
114
+ correctedHunkText,
115
+ correctedHeader,
116
+ matchPosition,
117
+ confidence: matchResult.bestMatch.confidence,
118
+ originalHunk
119
+ };
120
+ }
121
+
122
+ /**
123
+ * Preserves the original structure of a hunk while updating its header
124
+ * @param {string} originalHunkText - The original hunk text
125
+ * @param {string} correctedHeader - The corrected header
126
+ * @returns {string} Hunk with preserved structure but updated header
127
+ */
128
+ function preserveHunkStructure(originalHunkText, correctedHeader) {
129
+ const lines = originalHunkText.split('\n');
130
+
131
+ // Replace just the header line
132
+ if (lines.length > 0 && lines[0].startsWith('@@ ')) {
133
+ lines[0] = correctedHeader;
134
+ }
135
+
136
+ return lines.join('\n');
137
+ }
138
+
139
+ /**
140
+ * Generates a corrected version of a problematic hunk
141
+ * @param {object} hunkValidation - Validation result for the hunk
142
+ * @param {object} matchResult - Result from fuzzy matching
143
+ * @returns {object} Corrected hunk with explanation
144
+ */
145
+ function generateHunkCorrection(hunkValidation, matchResult) {
146
+ if (!hunkValidation || !matchResult) {
147
+ return {
148
+ success: false,
149
+ reason: "Missing validation or match result"
150
+ };
151
+ }
152
+
153
+ if (hunkValidation.valid) {
154
+ return {
155
+ success: true,
156
+ message: "Hunk is already valid",
157
+ hunkText: hunkValidation.hunk.originalLines.join('\n'),
158
+ needsCorrection: false
159
+ };
160
+ }
161
+
162
+ if (!matchResult.found) {
163
+ return {
164
+ success: false,
165
+ reason: matchResult.reason || "No suitable match found",
166
+ diagnostics: {
167
+ originalHeader: hunkValidation.hunk.header,
168
+ contextLines: hunkValidation.hunk.contextLines,
169
+ matchAttempts: matchResult.matches || []
170
+ }
171
+ };
172
+ }
173
+
174
+ // Generate corrected hunk
175
+ const correctedHeader = recalculateHunkHeader(
176
+ matchResult.bestMatch.position,
177
+ hunkValidation.hunk.contextLines,
178
+ hunkValidation.hunk.addedLines,
179
+ hunkValidation.hunk.removedLines
180
+ );
181
+
182
+ // Preserve the original structure but update the header
183
+ const originalHunkText = [hunkValidation.hunk.header, ...hunkValidation.hunk.originalLines].join('\n');
184
+ const correctedHunkText = preserveHunkStructure(originalHunkText, correctedHeader);
185
+
186
+ return {
187
+ success: true,
188
+ correctedHunkText,
189
+ correctedHeader,
190
+ originalHeader: hunkValidation.hunk.header,
191
+ matchPosition: matchResult.bestMatch.position,
192
+ confidence: matchResult.bestMatch.confidence,
193
+ needsCorrection: true,
194
+ explanation: `Hunk header corrected from "${hunkValidation.hunk.header}" to "${correctedHeader}" based on fuzzy matching with ${(matchResult.bestMatch.confidence * 100).toFixed(1)}% confidence.`
195
+ };
196
+ }
197
+
198
+ module.exports = {
199
+ recalculateHunkHeader,
200
+ reconstructHunk,
201
+ generateCorrectedHunk,
202
+ preserveHunkStructure,
203
+ generateHunkCorrection
204
+ };