cloving 0.3.4 → 0.3.5
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.
- package/dist/cloving_gpt/adapters/claude.js +7 -5
- package/dist/cloving_gpt/adapters/gemini.js +14 -9
- package/dist/cloving_gpt/adapters/mistral.js +9 -7
- package/dist/cloving_gpt/adapters/ollama.js +7 -7
- package/dist/cloving_gpt/adapters/openai.js +9 -7
- package/dist/cloving_gpt/index.js +12 -7
- package/dist/commands/config.js +7 -7
- package/dist/commands/generate/context.js +10 -3
- package/dist/commands/generate/unit_tests.js +3 -261
- package/dist/commands/init.js +6 -6
- package/dist/commands/tokens.js +12 -4
- package/dist/index.js +3 -6
- package/dist/managers/ChatManager.js +77 -33
- package/dist/managers/CodeManager.js +19 -5
- package/dist/managers/CommitManager.js +6 -3
- package/dist/managers/ProxyManager.js +22 -7
- package/dist/managers/ReviewManager.js +26 -10
- package/dist/managers/ShellManager.js +5 -2
- package/dist/managers/UnitTestManager.js +243 -0
- package/dist/package.json +13 -8
- package/dist/utils/command_utils.js +86 -34
- package/dist/utils/config_utils.js +15 -5
- package/dist/utils/git_utils.js +7 -4
- package/dist/utils/string_utils.js +119 -12
- package/package.json +13 -8
|
@@ -68,7 +68,20 @@ const extractJsonMetadata = (response) => {
|
|
|
68
68
|
return jsonString;
|
|
69
69
|
};
|
|
70
70
|
exports.extractJsonMetadata = extractJsonMetadata;
|
|
71
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Extracts markdown content from a given response string.
|
|
73
|
+
*
|
|
74
|
+
* This function attempts to extract markdown content from the input string,
|
|
75
|
+
* focusing on content within code blocks and removing any surrounding metadata.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} response - The input string containing markdown and possibly other content.
|
|
78
|
+
* @returns {string} The extracted markdown content.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* const response = '```markdown\n# Title\nContent\n```\nOther text';
|
|
82
|
+
* const markdown = extractMarkdown(response);
|
|
83
|
+
* console.log(markdown); // Outputs: "# Title\nContent"
|
|
84
|
+
*/
|
|
72
85
|
const extractMarkdown = (response) => {
|
|
73
86
|
let markdownString = response;
|
|
74
87
|
// Regular expression to match any ```<word> block
|
|
@@ -91,12 +104,28 @@ const extractMarkdown = (response) => {
|
|
|
91
104
|
return markdownString;
|
|
92
105
|
};
|
|
93
106
|
exports.extractMarkdown = extractMarkdown;
|
|
107
|
+
/**
|
|
108
|
+
* Parses markdown instructions into separate blocks.
|
|
109
|
+
*
|
|
110
|
+
* This function takes a string input containing markdown instructions and
|
|
111
|
+
* separates it into an array of strings, where each string represents a
|
|
112
|
+
* distinct block of content (either a code block or a text block).
|
|
113
|
+
*
|
|
114
|
+
* @param {string} input - The input string containing markdown instructions.
|
|
115
|
+
* @returns {string[]} An array of strings, each representing a distinct block of content.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* const input = "# Title\n```js\nconst x = 1;\n```\nSome text";
|
|
119
|
+
* const blocks = parseMarkdownInstructions(input);
|
|
120
|
+
* console.log(blocks);
|
|
121
|
+
* // Outputs: ["# Title", "```js\nconst x = 1;\n```", "Some text"]
|
|
122
|
+
*/
|
|
94
123
|
const parseMarkdownInstructions = (input) => {
|
|
95
124
|
const lines = input.split('\n');
|
|
96
125
|
const result = [];
|
|
97
126
|
let buffer = [];
|
|
98
127
|
let inCodeBlock = false;
|
|
99
|
-
lines.forEach(line => {
|
|
128
|
+
lines.forEach((line) => {
|
|
100
129
|
if (line.trim().startsWith('```')) {
|
|
101
130
|
if (inCodeBlock) {
|
|
102
131
|
// End of code block
|
|
@@ -126,6 +155,18 @@ const parseMarkdownInstructions = (input) => {
|
|
|
126
155
|
return result;
|
|
127
156
|
};
|
|
128
157
|
exports.parseMarkdownInstructions = parseMarkdownInstructions;
|
|
158
|
+
/**
|
|
159
|
+
* Finds the indices of a CURRENT/NEW block within the input string.
|
|
160
|
+
*
|
|
161
|
+
* This function searches for the start, divider, and end markers of a CURRENT/NEW block
|
|
162
|
+
* in the input string, starting from the given index. It returns the indices of these
|
|
163
|
+
* markers if a complete block is found.
|
|
164
|
+
*
|
|
165
|
+
* @param {string} input - The input string to search for block indices.
|
|
166
|
+
* @param {number} startIndex - The index in the input string to start searching from.
|
|
167
|
+
* @returns {BlockIndices | null} An object containing the indices of the block components,
|
|
168
|
+
* or null if a complete block is not found.
|
|
169
|
+
*/
|
|
129
170
|
const findBlockIndices = (input, startIndex) => {
|
|
130
171
|
const blockStart = input.indexOf(BLOCK_START, startIndex);
|
|
131
172
|
if (blockStart === -1)
|
|
@@ -142,10 +183,20 @@ const findBlockIndices = (input, startIndex) => {
|
|
|
142
183
|
start: blockStart,
|
|
143
184
|
filePathEnd,
|
|
144
185
|
divider: dividerIndex,
|
|
145
|
-
end: blockEnd
|
|
186
|
+
end: blockEnd,
|
|
146
187
|
};
|
|
147
188
|
};
|
|
148
189
|
exports.findBlockIndices = findBlockIndices;
|
|
190
|
+
/**
|
|
191
|
+
* Extracts the content of a CURRENT/NEW block using the provided indices.
|
|
192
|
+
*
|
|
193
|
+
* This function takes the input string and a set of block indices, and extracts
|
|
194
|
+
* the file path, current content, and new content from the block.
|
|
195
|
+
*
|
|
196
|
+
* @param {string} input - The input string containing the CURRENT/NEW block.
|
|
197
|
+
* @param {BlockIndices} indices - An object containing the indices of the block components.
|
|
198
|
+
* @returns {CurrentNewBlock} An object containing the extracted file path, current content, and new content.
|
|
199
|
+
*/
|
|
149
200
|
const extractBlock = (input, indices) => {
|
|
150
201
|
const filePath = input.slice(indices.start + BLOCK_START.length, indices.filePathEnd).trim();
|
|
151
202
|
const currentContent = input.slice(indices.filePathEnd + 1, indices.divider).trim();
|
|
@@ -247,8 +298,8 @@ const updateFileContent = (currentContent, block) => {
|
|
|
247
298
|
return block.newContent;
|
|
248
299
|
}
|
|
249
300
|
// 2) split currentContent and block.currentContent into new lines and trim() all the lines, then put back the new lines
|
|
250
|
-
const currentLines = currentContent.split('\n').map(line => line.trim());
|
|
251
|
-
const blockLines = block.currentContent.split('\n').map(line => line.trim());
|
|
301
|
+
const currentLines = currentContent.split('\n').map((line) => line.trim());
|
|
302
|
+
const blockLines = block.currentContent.split('\n').map((line) => line.trim());
|
|
252
303
|
// 3) see if the trimmed block.currentContent matches a section of code in the trimmed currentContent
|
|
253
304
|
const blockContent = blockLines.join('\n');
|
|
254
305
|
const currentContentTrimmed = currentLines.join('\n');
|
|
@@ -259,11 +310,16 @@ const updateFileContent = (currentContent, block) => {
|
|
|
259
310
|
}
|
|
260
311
|
// 5) compare the first line of block.currentContent with the matchingLine line of currentContent, putting any preceeding spaces that are in currentContent but not in block.currentContent into a variable named preceedingSpaces
|
|
261
312
|
const firstLineIndex = currentContent.indexOf(blockLines[0]);
|
|
262
|
-
const precedingSpaces = currentContent
|
|
313
|
+
const precedingSpaces = currentContent
|
|
314
|
+
.substring(firstLineIndex, firstLineIndex + currentLines[0].length)
|
|
315
|
+
.match(/^\s*/);
|
|
263
316
|
// 6) add preceedingSpaces to the start of each line in both block.currentContent and block.newContent
|
|
264
317
|
if (precedingSpaces) {
|
|
265
318
|
const precedingSpacesValue = precedingSpaces[0];
|
|
266
|
-
const indentedNewContent = block.newContent
|
|
319
|
+
const indentedNewContent = block.newContent
|
|
320
|
+
.split('\n')
|
|
321
|
+
.map((line) => precedingSpacesValue + line)
|
|
322
|
+
.join('\n');
|
|
267
323
|
// 7) finally, do a simple currentContent.replace(block.currentContent, block.newContent) with the modified (added preceeding spaces to match) variables
|
|
268
324
|
return currentContent.replace(block.currentContent, indentedNewContent);
|
|
269
325
|
}
|
|
@@ -272,6 +328,42 @@ const updateFileContent = (currentContent, block) => {
|
|
|
272
328
|
}
|
|
273
329
|
};
|
|
274
330
|
exports.updateFileContent = updateFileContent;
|
|
331
|
+
/**
|
|
332
|
+
* Analyzes the content of a block against the file content to find matching lines.
|
|
333
|
+
*
|
|
334
|
+
* This function compares the trimmed content of a block with the trimmed content of a file
|
|
335
|
+
* to identify all occurrences of the block content within the file.
|
|
336
|
+
*
|
|
337
|
+
* @param {CurrentNewBlock} block - The block containing the content to be searched for.
|
|
338
|
+
* @param {string} fileContent - The content of the file to be analyzed.
|
|
339
|
+
* @returns {Promise<{ matchingLines: number[]; currentContentTrimmed: string }>} A promise that resolves to an object containing:
|
|
340
|
+
* - matchingLines: An array of line numbers where the block content matches in the file.
|
|
341
|
+
* - currentContentTrimmed: The trimmed content of the file.
|
|
342
|
+
*/
|
|
343
|
+
const analyzeBlockContent = async (block, fileContent) => {
|
|
344
|
+
const currentLines = fileContent.split('\n').map((line) => line.trim());
|
|
345
|
+
const blockLines = block.currentContent.split('\n').map((line) => line.trim());
|
|
346
|
+
const blockContent = blockLines.join('\n');
|
|
347
|
+
const currentContentTrimmed = currentLines.join('\n');
|
|
348
|
+
const matchingLines = [];
|
|
349
|
+
let matchingLine = -1;
|
|
350
|
+
while ((matchingLine = currentContentTrimmed.indexOf(blockContent, matchingLine + 1)) !== -1) {
|
|
351
|
+
matchingLines.push(matchingLine);
|
|
352
|
+
}
|
|
353
|
+
return { matchingLines, currentContentTrimmed };
|
|
354
|
+
};
|
|
355
|
+
/**
|
|
356
|
+
* Processes a single block by applying its changes to the corresponding file.
|
|
357
|
+
*
|
|
358
|
+
* This function handles the following scenarios:
|
|
359
|
+
* 1. Creating a new file if it doesn't exist.
|
|
360
|
+
* 2. Updating an existing file if the block's current content matches exactly once.
|
|
361
|
+
* 3. Logging errors if the current content is not found or matches multiple times.
|
|
362
|
+
*
|
|
363
|
+
* @param {CurrentNewBlock} block - The block containing the changes to be applied.
|
|
364
|
+
* @param {number} index - The index of the block being processed (used for logging).
|
|
365
|
+
* @returns {Promise<void>} A promise that resolves when the block has been processed.
|
|
366
|
+
*/
|
|
275
367
|
const processBlock = async (block, index) => {
|
|
276
368
|
const filePath = path_1.default.resolve(block.filePath);
|
|
277
369
|
let fileContent = await readFileContent(filePath);
|
|
@@ -280,8 +372,13 @@ const processBlock = async (block, index) => {
|
|
|
280
372
|
console.log(`[${index + 1}] ${colors_1.default.green.bold(block.filePath)} has been ${colors_1.default.green.bold('created')}`);
|
|
281
373
|
return;
|
|
282
374
|
}
|
|
283
|
-
|
|
284
|
-
|
|
375
|
+
const { matchingLines } = await analyzeBlockContent(block, fileContent);
|
|
376
|
+
if (matchingLines.length === 0) {
|
|
377
|
+
console.log(`[${index + 1}] ${colors_1.default.red.bold(block.filePath)} ${colors_1.default.red.bold('ERROR: Current content not found to replace in the file')}`);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
else if (matchingLines.length > 1) {
|
|
381
|
+
console.log(`[${index + 1}] ${colors_1.default.red.bold(block.filePath)} ${colors_1.default.red.bold(`ERROR: Current content matches multiple (${matchingLines.length}) parts in the file`)}`);
|
|
285
382
|
return;
|
|
286
383
|
}
|
|
287
384
|
const updatedContent = (0, exports.updateFileContent)(fileContent, block);
|
|
@@ -352,9 +449,19 @@ const checkBlocksApplicability = async (blocks) => {
|
|
|
352
449
|
if (fileContent === '') {
|
|
353
450
|
summary.push(`[${index + 1}] ${colors_1.default.green.bold(block.filePath)} can be ${colors_1.default.green.bold('created')}`);
|
|
354
451
|
}
|
|
355
|
-
else if (block.currentContent.trim() !== ''
|
|
356
|
-
|
|
357
|
-
|
|
452
|
+
else if (block.currentContent.trim() !== '') {
|
|
453
|
+
const { matchingLines } = await analyzeBlockContent(block, fileContent);
|
|
454
|
+
if (matchingLines.length === 0) {
|
|
455
|
+
allApplicable = false;
|
|
456
|
+
summary.push(`[${index + 1}] ${colors_1.default.red.bold(block.filePath)} ${colors_1.default.red.bold('cannot be applied: Current content not found')}`);
|
|
457
|
+
}
|
|
458
|
+
else if (matchingLines.length > 1) {
|
|
459
|
+
allApplicable = false;
|
|
460
|
+
summary.push(`[${index + 1}] ${colors_1.default.red.bold(block.filePath)} ${colors_1.default.red.bold(`cannot be applied: Current content matches multiple (${matchingLines.length}) parts in the file`)}`);
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
summary.push(`[${index + 1}] ${colors_1.default.green.bold(block.filePath)} can be ${colors_1.default.yellow.bold('updated')}`);
|
|
464
|
+
}
|
|
358
465
|
}
|
|
359
466
|
else {
|
|
360
467
|
summary.push(`[${index + 1}] ${colors_1.default.green.bold(block.filePath)} can be ${colors_1.default.yellow.bold('updated')}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloving",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"packageManager": "yarn@1.22.22",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"watch": "tsc -w",
|
|
11
11
|
"check": "bash -c 'npx tsc --noEmit --skipLibCheck'",
|
|
12
12
|
"test": "bash -c jest",
|
|
13
|
-
"prepare": "husky"
|
|
13
|
+
"prepare": "husky",
|
|
14
|
+
"format": "prettier --write \"src/**/*.ts\""
|
|
14
15
|
},
|
|
15
16
|
"bin": "./dist/index.js",
|
|
16
17
|
"files": [
|
|
@@ -23,7 +24,8 @@
|
|
|
23
24
|
"description": "",
|
|
24
25
|
"lint-staged": {
|
|
25
26
|
"*.ts": [
|
|
26
|
-
"yarn run check"
|
|
27
|
+
"yarn run check",
|
|
28
|
+
"prettier --write"
|
|
27
29
|
]
|
|
28
30
|
},
|
|
29
31
|
"lint-prepush": {
|
|
@@ -31,7 +33,8 @@
|
|
|
31
33
|
"tasks": {
|
|
32
34
|
"*.ts": [
|
|
33
35
|
"yarn run check",
|
|
34
|
-
"yarn run test"
|
|
36
|
+
"yarn run test",
|
|
37
|
+
"yarn run format"
|
|
35
38
|
]
|
|
36
39
|
}
|
|
37
40
|
},
|
|
@@ -42,14 +45,14 @@
|
|
|
42
45
|
"colors": "^1.4.0",
|
|
43
46
|
"commander": "^12.1.0",
|
|
44
47
|
"copy-paste": "^1.5.3",
|
|
45
|
-
"eslint": "^
|
|
48
|
+
"eslint": "^8.57.0",
|
|
46
49
|
"eslint-config-prettier": "^9.1.0",
|
|
47
50
|
"eslint-config-raine": "^0.5.0",
|
|
48
51
|
"eslint-config-standard": "^17.1.0",
|
|
49
52
|
"eslint-plugin-import": "^2.29.1",
|
|
50
53
|
"eslint-plugin-jsdoc": "^50.1.0",
|
|
51
|
-
"eslint-plugin-n": "^
|
|
52
|
-
"eslint-plugin-promise": "^
|
|
54
|
+
"eslint-plugin-n": "^16.6.2",
|
|
55
|
+
"eslint-plugin-promise": "^6.6.0",
|
|
53
56
|
"express": "^4.19.2",
|
|
54
57
|
"ignore": "^5.3.2",
|
|
55
58
|
"ini": "^4.1.3",
|
|
@@ -70,12 +73,14 @@
|
|
|
70
73
|
"jest": "^29.7.0",
|
|
71
74
|
"lint-prepush": "^2.2.2",
|
|
72
75
|
"lint-staged": "^15.2.9",
|
|
76
|
+
"prettier": "^3.2.5",
|
|
73
77
|
"ts-jest": "^29.2.4",
|
|
74
78
|
"typescript": "^5.5.4"
|
|
75
79
|
},
|
|
76
80
|
"resolutions": {
|
|
77
81
|
"eslint": "8.57.0",
|
|
78
82
|
"eslint-plugin-n": "16.6.2",
|
|
79
|
-
"eslint-plugin-import": "2.29.1"
|
|
83
|
+
"eslint-plugin-import": "2.29.1",
|
|
84
|
+
"eslint-plugin-promise": "6.6.0"
|
|
80
85
|
}
|
|
81
86
|
}
|