@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,202 @@
1
+ /**
2
+ * Component: PatchUtils Verify and Correct Hunk Headers
3
+ * Block-UUID: 61c74798-bddb-47e1-be48-4a102659933c
4
+ * Parent-UUID: 2308ed72-91ff-48ba-bc80-310c23c01ff1
5
+ * Version: 1.0.0
6
+ * Description: Verifies and corrects the hunk headers within a patch based on the line number prefixes in the content lines.
7
+ * Language: JavaScript
8
+ * Created-at: 2025-05-14T03:47:25.525Z
9
+ * Authors: Gemini 2.5 Flash Thinking (v1.0.0)
10
+ */
11
+
12
+
13
+ const { CONTENT_LINE_REGEX, HUNK_HEADER_REGEX } = require('./constants');
14
+
15
+
16
+ /**
17
+ * Represents an error found during patch verification.
18
+ * @typedef {object} VerificationError
19
+ * @property {number} [originalLineNumber] - The line number reported in the patch file (for line errors).
20
+ * @property {number} [hunkIndex] - The index (0-based) of the hunk where the error occurred (for header errors).
21
+ * @property {string} lineContent - The full content of the problematic line or header in the patch.
22
+ * @property {string} message - Description of the error.
23
+ * @property {number} [searchStart] - The starting line number (1-based) of the search window in the source.
24
+ * @property {number} [searchEnd] - The ending line number (1-based) of the search window in the source.
25
+ * @property {string} [errorType] - Type of error (e.g., 'redundant-change', 'line-mismatch', 'header-mismatch').
26
+ * @property {string[]} [suggestedFix] - Suggested lines to replace the problematic section.
27
+ */
28
+
29
+
30
+ /**
31
+ * Verifies and corrects the hunk headers (`@@ -old,count +new,count @@`) within a patch
32
+ * based on the line number prefixes (`NNN:`) found in the hunk's content lines.
33
+ * Assumes the NNN: prefixes on content lines are already correct (e.g., after running verifyAndCorrectLineNumbers).
34
+ *
35
+ * @param {string} patchText - The full text of the patch (ideally, already processed by verifyAndCorrectLineNumbers).
36
+ * @returns {{
37
+ * correctedPatchText: string,
38
+ * correctionsMade: boolean,
39
+ * errors: VerificationError[]
40
+ * }} - An object containing the potentially corrected patch text, a flag indicating if corrections were made, and an array of errors encountered.
41
+ * @throws {Error} If patchText is not a string.
42
+ */
43
+ function verifyAndCorrectHunkHeaders(patchText) {
44
+ if (typeof patchText !== 'string') {
45
+ throw new Error("Invalid input: patchText must be a string.");
46
+ }
47
+
48
+ const lines = patchText.split('\n');
49
+ const outputLines = [];
50
+ const errors = [];
51
+ let correctionsMade = false;
52
+ let currentHunkIndex = -1; // Track which hunk we are in
53
+ let currentHunkLines = [];
54
+ let processingHunk = false;
55
+
56
+ for (let i = 0; i < lines.length; i++) {
57
+ const line = lines[i];
58
+
59
+ if (line.startsWith('@@ ')) {
60
+ // End of previous hunk (if any), process it
61
+ if (processingHunk) {
62
+ const result = processHunk(outputLines[outputLines.length - 1], currentHunkLines, currentHunkIndex);
63
+ outputLines[outputLines.length - 1] = result.correctedHeader; // Replace header in output
64
+ outputLines.push(...currentHunkLines); // Add content lines
65
+ errors.push(...result.hunkErrors);
66
+ if (result.corrected) correctionsMade = true;
67
+ }
68
+
69
+ // Start of new hunk
70
+ processingHunk = true;
71
+ currentHunkIndex++;
72
+ currentHunkLines = [];
73
+ outputLines.push(line); // Add header temporarily, will be replaced after processing
74
+
75
+ } else if (processingHunk) {
76
+ // Line is part of the current hunk's content
77
+ // Stop collecting if we hit the end marker
78
+ if (line === '# --- PATCH END MARKER ---') {
79
+ processingHunk = false; // Stop processing before the marker
80
+ // Process the last hunk before adding the marker
81
+ const result = processHunk(outputLines[outputLines.length - 1], currentHunkLines, currentHunkIndex);
82
+ outputLines[outputLines.length - 1] = result.correctedHeader;
83
+ outputLines.push(...currentHunkLines);
84
+ errors.push(...result.hunkErrors);
85
+ if (result.corrected) correctionsMade = true;
86
+ outputLines.push(line); // Now add the end marker
87
+ } else {
88
+ currentHunkLines.push(line);
89
+ }
90
+ } else {
91
+ // Line is outside a hunk (metadata, markers, etc.)
92
+ outputLines.push(line);
93
+ }
94
+ }
95
+
96
+ // Process the very last hunk if the patch didn't end with a marker
97
+ if (processingHunk) {
98
+ const result = processHunk(outputLines[outputLines.length - 1], currentHunkLines, currentHunkIndex);
99
+ outputLines[outputLines.length - 1] = result.correctedHeader;
100
+ outputLines.push(...currentHunkLines);
101
+ errors.push(...result.hunkErrors);
102
+ if (result.corrected) correctionsMade = true;
103
+ }
104
+
105
+ return {
106
+ correctedPatchText: outputLines.join('\n'),
107
+ correctionsMade: correctionsMade,
108
+ errors: errors
109
+ };
110
+ }
111
+
112
+ /**
113
+ * Helper function to process a single hunk's header and content.
114
+ * @param {string} headerLine - The original hunk header line (@@ ... @@).
115
+ * @param {string[]} contentLines - Array of lines within the hunk (' ', '+', '-').
116
+ * @param {number} hunkIndex - The 0-based index of this hunk in the patch.
117
+ * @returns {{correctedHeader: string, corrected: boolean, hunkErrors: VerificationError[]}}
118
+ * @private
119
+ */
120
+ function processHunk(headerLine, contentLines, hunkIndex) {
121
+ const hunkErrors = [];
122
+ let corrected = false;
123
+
124
+ // Parse the original header
125
+ const headerMatch = headerLine.match(HUNK_HEADER_REGEX);
126
+ if (!headerMatch) {
127
+ hunkErrors.push({ hunkIndex, lineContent: headerLine, message: "Failed to parse original hunk header." });
128
+ return { correctedHeader: headerLine, corrected, hunkErrors }; // Return original if unparseable
129
+ }
130
+
131
+ // If there are no content lines, keep the original header
132
+ if (contentLines.length === 0) {
133
+ return { correctedHeader: headerLine, corrected: false, hunkErrors };
134
+ }
135
+
136
+ // Track line numbers and counts
137
+ let firstOldLineNumber = null;
138
+ let lastOldLineNumber = null;
139
+ let firstNewLineNumber = null;
140
+ let lastNewLineNumber = null;
141
+ let additionCount = 0;
142
+ let deletionCount = 0;
143
+
144
+ // First pass: find the line number ranges
145
+ for (const line of contentLines) {
146
+ const match = line.match(CONTENT_LINE_REGEX);
147
+
148
+ if (!match) {
149
+ continue;
150
+ }
151
+
152
+ const lineNum = parseInt(match[2], 10);
153
+ const prefix = match[1]; // ' ', '-', or '+'
154
+
155
+ if (prefix === ' ' || prefix === '-') {
156
+ // Update old file line numbers
157
+ if (firstOldLineNumber === null) firstOldLineNumber = lineNum;
158
+ lastOldLineNumber = lineNum;
159
+ }
160
+
161
+ if (prefix === ' ' || prefix === '+') {
162
+ // Update new file line numbers
163
+ if (firstNewLineNumber === null) firstNewLineNumber = lineNum;
164
+ lastNewLineNumber = lineNum;
165
+ }
166
+
167
+ if (prefix === '+') additionCount++;
168
+ if (prefix === '-') deletionCount++;
169
+ }
170
+
171
+
172
+ // If we couldn't determine line numbers from content, use the original header values
173
+ if (firstOldLineNumber === null) {
174
+ firstOldLineNumber = parseInt(headerMatch[1], 10);
175
+ firstNewLineNumber = parseInt(headerMatch[3], 10);
176
+ }
177
+
178
+ // Calculate the spans including all lines in the range
179
+ const oldSpan = lastOldLineNumber - firstOldLineNumber + 1;
180
+ const newSpan = lastNewLineNumber - firstNewLineNumber + 1;
181
+
182
+ // Adjust new span for additions and deletions
183
+ const finalOldSpan = oldSpan;
184
+ const finalNewSpan = oldSpan - deletionCount + additionCount;
185
+
186
+ // Format the new header
187
+ // Note: We always include the count even if it's 1, for consistency
188
+ const oldPart = `${firstOldLineNumber},${finalOldSpan}`;
189
+ const newPart = `${firstNewLineNumber},${finalNewSpan}`;
190
+ const correctedHeader = `@@ -${oldPart} +${newPart} @@`;
191
+
192
+ // Check if the header actually changed
193
+ if (correctedHeader !== headerLine) {
194
+ corrected = true;
195
+ }
196
+
197
+ return { correctedHeader, corrected, hunkErrors };
198
+ }
199
+
200
+ module.exports = {
201
+ verifyAndCorrectHunkHeaders,
202
+ };
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Component: PatchUtils Verify and Correct Line Numbers
3
+ * Block-UUID: 8a9c6d2f-e3b7-4f15-9d0a-b2c1e5f7a8d9
4
+ * Parent-UUID: 647c1aef-0552-4072-810c-59bf72e3fdad
5
+ * Version: 1.2.0
6
+ * Description: Verifies and corrects the NNN: line number prefixes on context and deletion lines within a patch.
7
+ * Language: JavaScript
8
+ * Created-at: 2025-05-14T06:27:12.345Z
9
+ * Authors: Gemini 2.5 Flash Thinking (v1.0.0), Claude 3.7 Sonnet (v1.1.0, v1.2.0)
10
+ */
11
+
12
+
13
+ const { removeLineNumbers } = require('../../CodeBlockUtils/lineNumberFormatter'); // Assuming this utility exists
14
+ const { CONTENT_LINE_REGEX } = require('./constants');
15
+ const { formatAndAddLineNumbers } = require('./formatAndAddLineNumbers'); // Import the helper
16
+
17
+
18
+ /**
19
+ * Represents an error found during patch verification.
20
+ * @typedef {object} VerificationError
21
+ * @property {number} [originalLineNumber] - The line number reported in the patch file (for line errors).
22
+ * @property {number} [hunkIndex] - The index (0-based) of the hunk where the error occurred (for header errors).
23
+ * @property {string} lineContent - The full content of the problematic line or header in the patch.
24
+ * @property {string} message - Description of the error.
25
+ * @property {number} [searchStart] - The starting line number (1-based) of the search window in the source.
26
+ * @property {number} [searchEnd] - The ending line number (1-based) of the search window in the source.
27
+ * @property {string} [errorType] - Type of error (e.g., 'redundant-change', 'line-mismatch', 'header-mismatch').
28
+ * @property {string[]} [suggestedFix] - Suggested lines to replace the problematic section.
29
+ */
30
+
31
+
32
+ /**
33
+ * Verifies and corrects the `NNN:` line number prefixes on context (' ') and deletion ('-') lines
34
+ * within a patch by comparing their content against the original source code within a sliding window.
35
+ *
36
+ * @param {string} patchText - The full text of the patch (including metadata, markers, and NNN: prefixes).
37
+ * @param {string} sourceText - The original source code text (MUST be clean, without NNN: prefixes).
38
+ * @param {number} [windowSize=251] - The total size of the search window (e.g., 5 means +/- 2 lines around the reported number). Must be an odd positive integer.
39
+ * @returns {{
40
+ * correctedPatchText: string,
41
+ * correctionsMade: boolean,
42
+ * errors: VerificationError[]
43
+ * }} - An object containing the potentially corrected patch text, a flag indicating if corrections were made, and an array of errors encountered.
44
+ * @throws {Error} If inputs are invalid (e.g., non-string text, invalid window size).
45
+ */
46
+ function verifyAndCorrectLineNumbers(patchText, sourceText, windowSize = 251) {
47
+ if (typeof patchText !== 'string') {
48
+ throw new Error("Invalid input: patchText must be a string.");
49
+ }
50
+ if (typeof sourceText !== 'string') {
51
+ throw new Error("Invalid input: sourceText must be a string.");
52
+ }
53
+ if (!Number.isInteger(windowSize) || windowSize <= 0 || windowSize % 2 === 0) {
54
+ throw new Error("Invalid input: windowSize must be a positive odd integer (e.g., 3, 5, 7).");
55
+ }
56
+
57
+ // Ensure line numbers are present and correctly formatted before verification
58
+ const formatResult = formatAndAddLineNumbers(patchText, sourceText);
59
+ const patchLines = formatResult.formattedPatchText.split('\n');
60
+ // Ensure source is clean (defensive check, though caller should provide clean source)
61
+ const sourceLines = removeLineNumbers(sourceText).split('\n');
62
+ const outputLines = [];
63
+ const errors = [];
64
+ let correctionsMade = formatResult.correctionsMade; // Inherit corrections made by formatter
65
+ const windowRadius = Math.floor(windowSize / 2);
66
+ const padding = String(sourceText.split('\n').length).length;
67
+
68
+ // Track current position in source and target files
69
+ let currentSourceLineNumber = 0;
70
+ let currentTargetLineNumber = 0;
71
+
72
+ // Track hunk boundaries
73
+ let inHunk = false;
74
+ let hunkSourceStart = 0;
75
+ let hunkTargetStart = 0;
76
+
77
+ // Process each line in the patch
78
+ for (let i = 0; i < patchLines.length; i++) {
79
+ const currentPatchLine = patchLines[i];
80
+
81
+ // Check for hunk headers to reset line tracking
82
+ if (currentPatchLine.startsWith('@@')) {
83
+ inHunk = true;
84
+ outputLines.push(currentPatchLine);
85
+
86
+ // Extract hunk header information to reset line counters
87
+ const hunkMatch = currentPatchLine.match(/@@ -(\d+),\d+ \+(\d+),\d+ @@/);
88
+ if (hunkMatch) {
89
+ hunkSourceStart = parseInt(hunkMatch[1], 10);
90
+ hunkTargetStart = parseInt(hunkMatch[2], 10);
91
+ currentSourceLineNumber = hunkSourceStart;
92
+ currentTargetLineNumber = hunkTargetStart;
93
+ }
94
+ continue;
95
+ }
96
+
97
+ // Skip non-hunk lines (metadata, markers, etc.)
98
+ if (
99
+ !inHunk ||
100
+ currentPatchLine.startsWith('#') || currentPatchLine.startsWith('---') || currentPatchLine.startsWith('+++')
101
+ ) {
102
+ outputLines.push(currentPatchLine);
103
+ continue;
104
+ }
105
+
106
+ const match = currentPatchLine.match(CONTENT_LINE_REGEX);
107
+
108
+ // Process context lines (space-prefixed)
109
+ if (currentPatchLine.startsWith(' ')) {
110
+ if (match) {
111
+ const reportedLineNum = parseInt(match[2], 10);
112
+ const patchLineContent = match[3];
113
+
114
+ // Verify content against source
115
+ let foundMatch = false;
116
+ let bestMatchIndex = -1;
117
+
118
+ // Define search window
119
+ const searchStartIndex = Math.max(0, currentSourceLineNumber - 1 - windowRadius);
120
+ const searchEndIndex = Math.min(sourceLines.length - 1, currentSourceLineNumber - 1 + windowRadius);
121
+
122
+ for (let j = searchStartIndex; j <= searchEndIndex; j++) {
123
+ if (sourceLines[j].trim() === patchLineContent.trim()) {
124
+ bestMatchIndex = j;
125
+ foundMatch = true;
126
+ break;
127
+ }
128
+ }
129
+
130
+ if (foundMatch) {
131
+ // Use the actual line number from the source
132
+ const actualLineNum = bestMatchIndex + 1;
133
+ const correctedLine = ` ${String(actualLineNum).padStart(padding, ' ')}: ${patchLineContent}`;
134
+ outputLines.push(correctedLine);
135
+
136
+ // Update tracking variables
137
+ if (actualLineNum !== reportedLineNum) {
138
+ correctionsMade = true;
139
+ }
140
+
141
+ currentSourceLineNumber = actualLineNum + 1;
142
+ currentTargetLineNumber = actualLineNum + 1;
143
+ } else {
144
+ // Fallback: use current tracking position
145
+ const correctedLine = ` ${String(currentSourceLineNumber).padStart(padding, ' ')}: ${patchLineContent}`;
146
+ outputLines.push(correctedLine);
147
+
148
+ if (currentSourceLineNumber !== reportedLineNum) {
149
+ correctionsMade = true;
150
+ }
151
+
152
+ errors.push({
153
+ originalLineNumber: i + 1,
154
+ lineContent: currentPatchLine,
155
+ message: `Content not found in source window [${searchStartIndex + 1}-${searchEndIndex + 1}]. Using current position ${currentSourceLineNumber}.`,
156
+ searchStart: searchStartIndex + 1,
157
+ searchEnd: searchEndIndex + 1
158
+ });
159
+
160
+ currentSourceLineNumber++;
161
+ currentTargetLineNumber++;
162
+ }
163
+ } else {
164
+ // No line number match, keep original
165
+ outputLines.push(currentPatchLine);
166
+ }
167
+ }
168
+ // Process deletion lines (minus-prefixed)
169
+ else if (currentPatchLine.startsWith('-')) {
170
+ if (match) {
171
+ const reportedLineNum = parseInt(match[2], 10);
172
+ const patchLineContent = match[3];
173
+
174
+ // Verify content against source
175
+ let foundMatch = false;
176
+ let bestMatchIndex = -1;
177
+
178
+ // Define search window
179
+ const searchStartIndex = Math.max(0, currentSourceLineNumber - 1 - windowRadius);
180
+ const searchEndIndex = Math.min(sourceLines.length - 1, currentSourceLineNumber - 1 + windowRadius);
181
+
182
+ for (let j = searchStartIndex; j <= searchEndIndex; j++) {
183
+ if (sourceLines[j].trim() === patchLineContent.trim()) {
184
+ bestMatchIndex = j;
185
+ foundMatch = true;
186
+ break;
187
+ }
188
+ }
189
+
190
+ if (foundMatch) {
191
+ // Use the actual line number from the source
192
+ const actualLineNum = bestMatchIndex + 1;
193
+ const correctedLine = `- ${String(actualLineNum).padStart(padding, ' ')}: ${patchLineContent}`;
194
+ outputLines.push(correctedLine);
195
+
196
+ // Update tracking variables
197
+ if (actualLineNum !== reportedLineNum) {
198
+ correctionsMade = true;
199
+ }
200
+
201
+ currentSourceLineNumber = actualLineNum + 1;
202
+ } else {
203
+ // Fallback: use current tracking position
204
+ const correctedLine = `- ${String(currentSourceLineNumber).padStart(padding, ' ')}: ${patchLineContent}`;
205
+ outputLines.push(correctedLine);
206
+
207
+ if (currentSourceLineNumber !== reportedLineNum) {
208
+ correctionsMade = true;
209
+ }
210
+
211
+ errors.push({
212
+ originalLineNumber: i + 1,
213
+ lineContent: currentPatchLine,
214
+ message: `Content not found in source window [${searchStartIndex + 1}-${searchEndIndex + 1}]. Using current position ${currentSourceLineNumber}.`,
215
+ searchStart: searchStartIndex + 1,
216
+ searchEnd: searchEndIndex + 1
217
+ });
218
+
219
+ currentSourceLineNumber++;
220
+ }
221
+ } else {
222
+ // No line number match, keep original
223
+ outputLines.push(currentPatchLine);
224
+ }
225
+ }
226
+ // Process addition lines (plus-prefixed)
227
+ else if (currentPatchLine.startsWith('+')) {
228
+ if (match) {
229
+ const patchLineContent = match[3];
230
+ // For addition lines, use the current target line number
231
+ const correctedLine = `+ ${String(currentTargetLineNumber).padStart(padding, ' ')}: ${patchLineContent}`;
232
+ outputLines.push(correctedLine);
233
+ currentTargetLineNumber++;
234
+ } else {
235
+ // No line number match, keep original
236
+ outputLines.push(currentPatchLine);
237
+ }
238
+ }
239
+ // Any other lines (should be rare in a valid patch)
240
+ else {
241
+ outputLines.push(currentPatchLine);
242
+ }
243
+ }
244
+
245
+ return {
246
+ correctedPatchText: outputLines.join('\n'),
247
+ correctionsMade: correctionsMade,
248
+ errors: errors
249
+ };
250
+ }
251
+
252
+ module.exports = {
253
+ verifyAndCorrectLineNumbers,
254
+ };
@@ -0,0 +1,41 @@
1
+ i/**
2
+ * Component: SharedUtils Timestamp Utilities
3
+ * Block-UUID: 495fbd8d-a818-4db0-89c4-5311bdfb8537
4
+ * Parent-UUID: N/A
5
+ * Version: 1.0.0
6
+ * Description: Provides utility functions for formatting timestamps. Originally part of PatchUtils.
7
+ * Language: JavaScript
8
+ * Created-at: 2025-04-18T02:59:04.322Z
9
+ * Authors: Gemini 2.5 Pro (v1.0.0)
10
+ */
11
+
12
+
13
+ /**
14
+ * Formats a timestamp in a human-readable format
15
+ * @param {string|Date} timestamp - ISO timestamp or Date object
16
+ * @returns {string} Formatted date string
17
+ */
18
+ function formatTimestamp(timestamp) {
19
+ if (!timestamp) {
20
+ return 'Unknown date';
21
+ }
22
+
23
+ const date = timestamp instanceof Date ? timestamp : new Date(timestamp);
24
+
25
+ if (isNaN(date.getTime())) {
26
+ return 'Invalid date';
27
+ }
28
+
29
+ return date.toLocaleString(undefined, {
30
+ year: 'numeric',
31
+ month: 'short',
32
+ day: 'numeric',
33
+ hour: '2-digit',
34
+ minute: '2-digit',
35
+ second: '2-digit'
36
+ });
37
+ }
38
+
39
+ module.exports = {
40
+ formatTimestamp
41
+ };
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Component: SharedUtils Version Utilities
3
+ * Block-UUID: c18e08cb-1fc1-4d10-963a-eab9e70f6e6e
4
+ * Parent-UUID: N/A
5
+ * Version: 1.0.0
6
+ * Description: Provides utility functions for validating and manipulating semantic version strings. Originally part of PatchUtils.
7
+ * Language: JavaScript
8
+ * Created-at: 2025-04-18T02:59:04.322Z
9
+ * Authors: Gemini 2.5 Pro (v1.0.0)
10
+ */
11
+
12
+
13
+ /**
14
+ * Validates version string format (X.Y.Z)
15
+ * @param {string} version - Version string
16
+ * @returns {boolean} Whether version is valid
17
+ */
18
+ function isValidVersion(version) {
19
+ // Note: This was originally _isValidVersion in PatchUtils
20
+ return /^\d+\.\d+\.\d+$/.test(version);
21
+ }
22
+
23
+ /**
24
+ * Increments a semantic version string
25
+ * @param {string} version - Version in format X.Y.Z
26
+ * @param {string} type - Type of increment: 'major', 'minor', or 'patch'
27
+ * @returns {string} Incremented version
28
+ */
29
+ function incrementVersion(version, type = 'patch') {
30
+ if (!version || !/^\d+\.\d+\.\d+$/.test(version)) {
31
+ throw new Error(`Invalid version format: ${version}`);
32
+ }
33
+
34
+ const parts = version.split('.').map(Number);
35
+
36
+ switch (type.toLowerCase()) {
37
+ case 'major':
38
+ parts[0] += 1;
39
+ parts[1] = 0;
40
+ parts[2] = 0;
41
+ break;
42
+ case 'minor':
43
+ parts[1] += 1;
44
+ parts[2] = 0;
45
+ break;
46
+ case 'patch':
47
+ default:
48
+ parts[2] += 1;
49
+ break;
50
+ }
51
+
52
+ return parts.join('.');
53
+ }
54
+
55
+ module.exports = {
56
+ isValidVersion,
57
+ incrementVersion
58
+ };